# How ego-browser Manages Keyboard Input, Form Filling, and the selectOption Function

> Discover how ego-browser simulates keyboard input and form filling using CDP events and directly manipulates DOM properties for selectOption functionality. Learn more now.

- Repository: [CitroLabs/ego-lite](https://github.com/citrolabs/ego-lite)
- Tags: how-to-guide
- Published: 2026-08-07

---

**Ego-browser synthesizes Chrome DevTools Protocol (CDP) keyboard events through its [`keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/keyboard.ts) driver to simulate real user typing, while `selectOption` manipulates DOM properties directly and dispatches native change events.**

Ego-browser, part of the citrolabs/ego-lite repository, provides a robust automation layer for web interaction through synthetic keyboard events. Understanding how ego-browser handles keyboard input, form filling, and the `selectOption` function reveals the precise architecture behind its reliable form automation capabilities. The implementation spans multiple TypeScript modules that coordinate CDP commands with DOM manipulation to replicate authentic user behavior.

## Core Architecture of ego-browser Keyboard Input

The keyboard subsystem delegates high-level commands to low-level CDP events through four interconnected components.

### The Keyboard Driver (src/driver/keyboard.ts)

The [`keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/keyboard.ts) file defines the `KEYS` map (lines 22-41) that translates special keys like Enter (`vk: 13`) and Tab (`vk: 9`) into virtual-key codes, and implements the core event dispatching logic. It maintains a `pressedModifiers` Set (line 100) to track Shift, Control, Alt, and Meta states across keystrokes, enabling complex shortcuts like Control+A.

### Helper Functions (src/helpers.ts)

User-facing methods like `fill`, `press`, and `selectOption` reside in [`src/helpers.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/helpers.ts), providing a simplified API that agents consume directly. These helpers delegate to the keyboard driver while handling selector resolution through `withHandle` and normalizing input parameters before sending commands to the browser.

### CDP Integration Layer (src/browser-runtime.ts)

The driver sends synthetic events via `browserCdp.send('Input.dispatchKeyEvent', ...)`, transforming JavaScript key definitions into browser-native input events that trigger appropriate DOM listeners. This layer bridges the gap between Node.js automation scripts and the Chrome DevTools Protocol.

## How ego-browser Handles Form Filling

The `fill` method implements a three-step workflow to simulate authentic user input, ensuring fields are properly focused and modified before text entry begins.

### The Three-Step Workflow

First, `withHandle(selector)` in [`src/driver/element-ops.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/element-ops.ts) resolves the target element to a JSHandle. Second, if `clearFirst` is enabled in `FillOptions`, the driver sends Control+A followed by Delete to empty the field. Third, `pressSequentially` iterates through each character, dispatching `keydown`, optional `keypress`, and `keyup` events via CDP.

### Typing Delays and Editing Commands

The driver respects `INPUT_EVENT_DELAY_MS` (default 25ms) between keystrokes, configurable through `PressSequentiallyOptions.delay`. The `editingCommandsForKey` function (lines 64-78) detects shortcuts like Ctrl+A and translates them into appropriate DOM editing actions such as `selectAll` and `deleteForward`.

```typescript
// Example: Fill with field clearing and custom delay
await fill('#email', 'user@example.com', { 
  clearFirst: true,
  delay: 50  // 50ms between keystrokes
});

```

## The selectOption Function Implementation

Located in [`src/driver/keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/keyboard.ts), `selectOption` handles `<select>` elements and ARIA comboboxes without simulating individual keystrokes, instead manipulating the DOM directly for reliability.

### Option Matching Logic

The function accepts `SelectOption` values, which can be a string (matching option value), number (treated as index), or an object with `value`, `label`, or `index` properties. It evaluates a script in the page context to match options against `element.options`, supporting multiple selection strategies:

```typescript
type SelectOption =
  | string
  | number
  | { value?: string; label?: string; index?: number };

```

### Change Event Dispatching

After setting `opt.selected = true` for matched options, the function dispatches a bubbling `change` event via `element.dispatchEvent(new Event('change', { bubbles: true }))`. This ensures React, Vue, and vanilla JavaScript listeners trigger correctly, mimicking genuine user interaction with dropdown menus.

```typescript
// Select by label, value, and index simultaneously
await selectOption('#country', [
  { label: 'Canada' },
  { value: 'usa' },
  5  // sixth option in the list
]);

```

## Practical Implementation Examples

### Basic Text Input with Special Keys

```typescript
// Fill and submit a form
await fill('#username', 'alice', { clearFirst: true });
await keyboard.type('#password', 's3cr3t');
await keyboard.press('#password', 'Enter');

```

### Multi-Select Dropdown Handling

When targeting multi-select elements, pass an array to select multiple options at once. The driver sets each option's `selected` property and fires a single change event after processing all selections.

```typescript
// Select multiple colors from a multi-select dropdown
await selectOption('#colors', [
  'red',           // matches option value
  { label: 'Blue' },
  2                // third option by index
]);

```

### Direct Keyboard Control

For complex interactions, access the keyboard driver directly to manage focus and modifiers:

```typescript
// Tab navigation between fields
await keyboard.press('#field1', 'Tab');
await keyboard.type('#field2', 'text with\nnewlines');

```

## Summary

- **ego-browser** synthesizes keyboard events through CDP `Input.dispatchKeyEvent` calls in [`src/driver/keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/keyboard.ts), mapping characters to virtual-key codes with proper modifier tracking
- The **form filling** workflow supports optional field clearing via `FillOptions.clearFirst` and configurable typing delays via `PressSequentiallyOptions`
- **selectOption** manipulates DOM properties directly in [`src/driver/keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/keyboard.ts) and dispatches native `change` events rather than simulating keystrokes, supporting value, label, and index matching strategies
- All keyboard actions resolve selectors through `withHandle` in [`src/driver/element-ops.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/element-ops.ts) before interacting with elements, ensuring stable element references

## Frequently Asked Questions

### How does ego-browser simulate typing delays between keystrokes?

The driver uses `INPUT_EVENT_DELAY_MS` (25ms default) in `pressSequentially`, configurable via `PressSequentiallyOptions.delay`. This creates realistic timing between `keydown`, `keypress`, and `keyup` CDP events sent through `browserCdp.send('Input.dispatchKeyEvent', ...)`.

### Can selectOption handle multi-select elements?

Yes. When passed an array of values, `selectOption` sets `selected = true` on each matching option and fires a single `change` event after all selections are applied, supporting standard HTML multiple-select fields in [`src/driver/keyboard.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/driver/keyboard.ts).

### What CDP command does ego-browser use for keyboard events?

The keyboard driver calls `browserCdp.send('Input.dispatchKeyEvent', ...)` defined in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts), sending structured key event data including virtual key codes, key identifiers, and modifier states to the browser.

### How does the fill command clear existing text?

When `clearFirst: true` is passed in `FillOptions`, the driver sends a Control+A shortcut followed by Delete via `editingCommandsForKey` (lines 64-78), selecting all text and removing it before typing the new value.