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.Ctrl
  • KeyModifiers.Alt
  • KeyModifiers.Shift
  • KeyModifiers.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 Shift flag 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_event commands via the KeyEvents class in zendriver/core/keys.py.
  • Use Element.send_keys() for all keyboard input; it accepts strings, SpecialKeys enums, or pre-built payload lists.
  • Combine modifiers with the bitwise OR operator (|) on KeyModifiers values like KeyModifiers.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) and zendriver/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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →