How to Send Keyboard Events and Key Combinations with Zendriver
Zendriver converts high-level Python calls into Chrome DevTools Protocol (CDP) keyboard commands using the KeyEvents class and Element.send_keys() method.
Zendriver is a Python driver for Chrome DevTools Protocol (CDP) that provides granular control over browser automation. Sending keyboard events and key combinations with Zendriver requires understanding three core abstractions defined in zendriver/core/keys.py: the modifier bitmask enum, the special keys enum, and the payload generator class. These components translate Python strings and tuples into CDP-compatible dispatch_key_event payloads.
Understanding the Keyboard Architecture
Zendriver's keyboard system is built around three interconnected components that handle the translation from Python objects to CDP commands.
KeyModifiers Enum
The KeyModifiers class is an IntEnum located at lines 8-21 of zendriver/core/keys.py. It represents modifier keys as bitmasks:
KeyModifiers.CtrlKeyModifiers.AltKeyModifiers.ShiftKeyModifiers.Meta
You combine modifiers using the bitwise OR operator (|):
from zendriver import KeyModifiers
ctrl_shift = KeyModifiers.Ctrl | KeyModifiers.Shift
SpecialKeys Enum
SpecialKeys (lines 23-41) defines non-printable keys such as Enter, Escape, and Arrow keys. Each enum entry stores the DOM key name and its corresponding virtual-key code. Available options include SpecialKeys.ENTER, SpecialKeys.ESCAPE, SpecialKeys.ARROW_DOWN, and SpecialKeys.TAB.
KeyEvents Class
The KeyEvents class (lines 45-95) serves as the central translator. It converts key descriptions into lists of CDP payload dictionaries expected by cdp.input_.dispatch_key_event. The class handles:
- Key code lookup for printable and non-printable keys
- Shifted character normalization (automatically adding
Shiftflag for uppercase letters) - Modifier bitmask validation
Sending Basic Keyboard Input
The Element.send_keys() method in zendriver/core/element.py (lines 38-60) is the primary entry point for dispatching keyboard events.
Sending Plain Text
For standard text input, pass a string directly to send_keys():
await element.send_keys("Hello, world!")
Internally, this calls KeyEvents.from_text("Hello, world!", KeyPressEvent.CHAR), which generates a series of char events for each character in the string.
Sending Special Keys
To press non-printable keys like Enter or Escape, pass a SpecialKeys enum value:
from zendriver import SpecialKeys
await element.send_keys(SpecialKeys.ENTER)
await element.send_keys(SpecialKeys.ESCAPE)
The method wraps the enum in a KeyEvents instance and emits it as a DOWN_AND_UP sequence, triggering both key press and release events automatically.
Handling Modifier Combinations
To send keyboard shortcuts like Ctrl+A or Ctrl+V, use a tuple containing the key and a KeyModifiers bitmask.
Single Modifier Shortcuts
Pass a tuple of (key, modifiers) to send_keys():
from zendriver import KeyModifiers
await element.send_keys(("a", KeyModifiers.Ctrl)) # Select all
await element.send_keys(("v", KeyModifiers.Ctrl)) # Paste
The _normalise_key method (lines 33-40 in keys.py) automatically handles uppercase letters by adding the Shift flag and returning the non-shifted base key. If you attempt to use an unsupported combination, the library raises a descriptive ValueError.
Complex Modifier Masks
Combine multiple modifiers for shortcuts like Ctrl+Shift+T:
modifiers = KeyModifiers.Ctrl | KeyModifiers.Shift
await element.send_keys(("t", modifiers))
Complex Mixed Input Sequences
For scenarios requiring text, special keys, and modifiers in a single operation, use KeyEvents.from_mixed_input(). This helper accepts a list containing:
- Strings – split into individual character events
- SpecialKeys – emitted as DOWN_AND_UP sequences
- Tuples –
(key, modifiers)pairs for shortcuts
Mixed Sequence Example
from zendriver import SpecialKeys, KeyModifiers, KeyEvents, KeyPressEvent
payload = KeyEvents.from_mixed_input(
[
"Hello ", # Normal text
SpecialKeys.ENTER, # New line
("a", KeyModifiers.Ctrl), # Ctrl+A (select all)
("c", KeyModifiers.Ctrl), # Ctrl+C (copy)
SpecialKeys.ARROW_DOWN, # Navigate down
("v", KeyModifiers.Ctrl), # Ctrl+V (paste)
" – pasted 😊", # Unicode text including emoji
],
ascii_keypress=KeyPressEvent.DOWN_AND_UP # Use down-up for ASCII chars
)
await element.send_keys(payload)
The from_mixed_input() method iterates through the list and delegates to the appropriate handler based on type. Strings route through from_text(), SpecialKeys trigger to_cdp_events(), and tuples instantiate new KeyEvents(key, modifiers) objects. The resulting payload list matches the shape required by cdp.input_.dispatch_key_event, which Element.send_keys() dispatches in a loop.
Summary
- Zendriver converts Python keyboard instructions into CDP
dispatch_key_eventcommands via theKeyEventsclass inzendriver/core/keys.py. - Use
Element.send_keys()for all keyboard input; it accepts strings,SpecialKeysenums, or pre-built payload lists. - Combine modifiers with the bitwise OR operator (
|) onKeyModifiersvalues likeKeyModifiers.Ctrl | KeyModifiers.Shift. - Send complex sequences using
KeyEvents.from_mixed_input(), which handles mixed lists of text, special keys, and modifier tuples. - Reference implementations reside in
zendriver/core/keys.py(payload generation) andzendriver/core/element.py(dispatch logic).
Frequently Asked Questions
How do I send Ctrl+A to select all text in Zendriver?
Pass a tuple containing the lowercase key and the Ctrl modifier to Element.send_keys(): await element.send_keys(("a", KeyModifiers.Ctrl)). The KeyEvents class automatically normalizes the key and sets the correct modifier bitmask in the CDP payload.
What is the difference between KeyPressEvent.CHAR and DOWN_AND_UP?
KeyPressEvent.CHAR generates single char events suitable for typing text into input fields, while KeyPressEvent.DOWN_AND_UP generates separate keyDown and keyUp events required for special keys and shortcuts. The from_text() method defaults to CHAR, whereas SpecialKeys automatically use DOWN_AND_UP.
Can I send emoji and special characters with send_keys?
Yes. Zendriver handles Unicode characters including emoji through KeyEvents.from_text() or from_mixed_input(). The library does not limit character encoding, allowing payloads like "pasted 😊" to pass through to the CDP layer correctly.
Where does the actual CDP dispatch happen?
The actual network dispatch occurs in Element.send_keys() at zendriver/core/element.py lines 55-60, which loops through the payload list and calls await self._tab.send(cdp.input_.dispatch_key_event(**cluster)) for each event cluster generated by the KeyEvents class.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →