# How the `cdp()` Function Executes Chrome DevTools Protocol Commands in ego-lite

> Learn how the cdp() function executes Chrome DevTools Protocol commands via a pipeline delegating to send() and routing through browserCdp() or rawCdp() for flexible command execution.

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

---

**The `cdp()` function executes Chrome DevTools Protocol commands through a layered pipeline that delegates to `send()`, which routes through `browserCdp()` or `rawCdp()` depending on session context, with optional override support for testing.**

The `cdp()` helper serves as the primary entry point for sending raw Chrome DevTools Protocol (CDP) commands from the **ego-browser** runtime in the citrolabs/ego-lite repository. Understanding its execution flow reveals how the library abstracts transport complexity while maintaining flexibility for mocking and session management.

## Override Handling for Testing

Before any real network traffic occurs, `cdp()` checks for a user-supplied override. In [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts), lines 13-15, the function inspects `state.cdpOverride`:

```typescript
if (state.cdpOverride) {
  return state.cdpOverride(method, params);
}

```

This early exit enables complete mocking of CDP commands in unit tests without modifying downstream code.

## Message Sending Through the Transport Layer

When no override exists, `cdp()` delegates to the generic `send()` utility. The call on line 15 of **cdp-eval.ts** packages the command into a structured request:

```typescript
return send({ method, params, session_id: sessionId });

```

The `send()` function, defined in [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) lines 42-44, is a thin wrapper around `state.send`:

```typescript
export function send(req: CDPRequest): Promise<unknown> {
  return state.send(req);
}

```

By default, `state.send` points to `defaultSend` (lines 10-22 of **state.ts**), which ultimately invokes `browserCdp()` from the low-level CDP client.

## Browser-Level Session Handling

The `browserCdp()` function in [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) determines proper routing for each command. Lines 85-94 implement session-selection logic that distinguishes browser-level methods from page-level methods:

- **Browser-level methods** — commands starting with `Target.` or `Browser.` are sent directly
- **Page-level methods** — other commands require an active session

For page-level commands lacking an explicit session ID, `browserCdp()` calls `ensureSession()` (lines 14-38 of **browser-runtime.ts**) to create or reuse a CDP session, automatically attaching to the active tab when necessary.

## Raw CDP Transport and Promise Management

The `rawCdp()` function (lines 38-77 of **browser-runtime.ts**) handles the actual wire protocol:

1. Builds the JSON-RPC payload with a unique message ID
2. Registers a pending promise keyed by that ID
3. Sends via `globalThis.ego.sendCDPMessage`
4. Wires a timeout to prevent indefinite hangs

This promise-based approach allows asynchronous response matching when the browser runtime returns results.

## Result Extraction and Side Effects

After receiving the response, `cdp()` processes results in lines 17-25 of **cdp-eval.ts**:

```typescript
const result = response.result ?? {};
if (method === 'Network.enable' || method === 'Network.disable') {
  state.networkDomainEnabled = method === 'Network.enable';
}
return result;

```

The function returns the `result` field (or empty object if absent) and tracks Network domain state for downstream helpers like `waitForNetworkIdle`.

## Complete Execution Pipeline

The full flow from caller to browser:

```

cdp() → (override check) → send() → state.send (defaultSend)
      → browserCdp() → (ensureSession?) → rawCdp()
      → globalThis.ego.sendCDPMessage → response → result extraction

```

## Practical Code Examples

### Basic CDP Command

Enable the Network domain on the current page without managing session details:

```javascript
await cdp('Network.enable');

```

### Testing with Overrides

Mock CDP responses for isolated unit testing:

```javascript
setOverrides({
  cdpOverride: (method, params) => {
    return { result: { mock: true } };
  },
});

const resp = await cdp('Runtime.evaluate', { expression: '2+2' });
console.log(resp.mock); // → true

```

### Targeted Session Commands

Send commands to specific targets like detached iframes:

```javascript
const sessionId = await cdp('Target.attachToTarget', {
  targetId: 'target-123',
  flatten: true
});

await cdp(
  'Runtime.evaluate',
  { expression: 'document.title' },
  sessionId.sessionId
);

```

## Key Source Files

| File | Purpose |
|------|---------|
| [`package/ego-browser/src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/cdp-eval.ts) | Exposes `cdp()` helper and high-level `evaluate()` API |
| [`package/ego-browser/src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/state.ts) | Mutable runtime state, default `send` implementation, test overrides |
| [`package/ego-browser/src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/package/ego-browser/src/browser-runtime.ts) | Low-level CDP transport, session management, event handling |

## Summary

- **`cdp()`** provides a unified entry point for all Chrome DevTools Protocol commands
- **Override support** enables test mocking without transport dependencies
- **Automatic session management** removes boilerplate for typical page-level commands
- **Promise-based transport** handles asynchronous CDP responses with timeout protection
- **Network domain tracking** integrates with higher-level automation helpers

## Frequently Asked Questions

### What happens if I don't provide a session ID to `cdp()`?

When no session ID is provided and the command is not a browser-level method (not starting with `Target.` or `Browser.`), `browserCdp()` automatically calls `ensureSession()` to attach to the active tab. This default behavior simplifies most automation scenarios where you operate on the current page.

### How can I mock CDP responses in my tests?

Set `state.cdpOverride` via `setOverrides()` with a function that receives the method name and parameters, then returns a custom result object. The override executes immediately and bypasses all real transport logic, making tests fast and deterministic.

### What's the difference between `send()` and `rawCdp()`?

`send()` is the configurable abstraction that routes through `state.send`, enabling injection of custom transport behavior. `rawCdp()` is the concrete implementation that builds JSON-RPC payloads, manages pending promises by message ID, and calls `globalThis.ego.sendCDPMessage`. Most callers never interact with `rawCdp()` directly.

### Does `cdp()` handle CDP errors or timeouts?

`rawCdp()` implements timeout protection for unresponsive commands, and the transport layer translates CDP error responses through the error handling utilities. However, `cdp()` itself focuses on successful result extraction—callers should wrap commands in try/catch blocks when expecting potential CDP failures.