# How to Execute Raw CDP Commands with ego-browser: A Complete Guide

> Learn to execute raw CDP commands with ego-browser using cdp() and browserCdp() helpers for direct Chromium communication. A complete guide for ego-lite.

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

---

**The ego-browser runtime provides two public helpers—`cdp()` for simple raw commands and `browserCdp()` for session-managed interactions—that allow direct communication with the underlying Chromium instance via the Chrome DevTools Protocol.**

The `citrolabs/ego-lite` repository offers a lightweight browser automation framework that exposes low-level Chrome DevTools Protocol (CDP) access through injected JavaScript helpers. When you need to execute raw CDP commands with ego-browser, you can choose between a lightweight direct caller or a robust wrapper that handles session lifecycles automatically.

## Understanding the Two CDP Helpers

ego-browser exposes distinct helpers for different automation scenarios:

- **`cdp(method, params?, sessionId?)`** – Located in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) (lines 12-24), this function sends raw CDP commands and returns the result object directly. It automatically routes requests through the current page's session and is ideal for one-off queries or domain toggles.

- **`browserCdp(method, params?, sessionId?, timeoutMs?)`** – Implemented in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) (lines 79-96), this higher-level wrapper manages session creation, retries on lost sessions, and respects browser-level commands like `Target.*`. It also honors test-time overrides via `state.cdpOverride`.

Both helpers are injected into the script context that agents execute, making them immediately available inside `runMain` functions without imports.

## Using the `cdp()` Helper for Simple Commands

The `cdp()` function is the fastest way to execute raw CDP commands with ego-browser when you don't need complex session management. It builds the CDP payload, registers a response promise, and forwards the JSON string to the host via `globalThis.ego.sendCDPMessage`.

### Query DOM Properties

```typescript
// Read the page title via the Runtime domain
const result = await cdp('Runtime.evaluate', {
  expression: 'document.title',
  returnByValue: true,
});
console.log('Page title:', result.result?.value);

```

This approach uses the implementation in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts), which updates internal state for network-domain tracking when you enable the Network domain.

### Enable CDP Domains

```typescript
// Turn on network tracking for the current page
await cdp('Network.enable');

// Wait for specific network events using built-in helpers
await waitForBrowserEvent(e => e.method === 'Network.responseReceived');

```

When you call `cdp('Network.enable')`, the helper automatically sets `state.networkDomainEnabled` to true, allowing other runtime components to check network status without redundant CDP calls.

## Robust Session Management with `browserCdp()`

When automating across page navigations or attaching to specific targets, use `browserCdp()` to handle session lifecycles automatically. This helper caches sessions for approximately 2 seconds (`SESSION_TTL_MS`) and detects session loss using the `SESSION_LOST` regex pattern to trigger transparent re-attachment.

### Attach to Specific Targets

```typescript
// Get a session ID for a background page or specific target
const { sessionId } = await cdp('Target.attachToTarget', {
  targetId: 'target-id-from-listTabs',
  flatten: true,
});

// Execute commands in that specific session with automatic retry logic
const jsResult = await browserCdp('Runtime.evaluate', {
  expression: 'navigator.userAgent',
  returnByValue: true,
}, sessionId);

console.log('User agent:', jsResult.result?.value);

```

The `browserCdp()` function ensures a valid session exists by calling `ensureSession()` if no `sessionId` is provided, and it respects a default 15-second timeout (`RESPONSE_TIMEOUT_MS`) before aborting requests.

## Mocking CDP in Unit Tests

When writing tests, you can replace the real CDP transport with a mock by assigning `state.cdpOverride` before calling either helper. This allows simulation of any CDP response without launching a real browser:

```typescript
import { state } from './state.js';

// Override the CDP implementation for testing
state.cdpOverride = async (method, params) => ({
  mock: true,
  method,
  params,
});

const mockResult = await cdp('Page.reload');
console.log(mockResult); // { mock: true, method: 'Page.reload', params: {} }

```

The override check appears at lines 13-15 of [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts), ensuring your mock receives the method name and parameters before any real CDP traffic occurs.

## How CDP Commands Flow Through the Runtime

Understanding the execution flow helps debug issues when you execute raw CDP commands with ego-browser. The process follows four distinct stages:

1. **Session Resolution** – `browserCdp()` calls `ensureSession()` or uses the explicit `sessionId` to obtain a valid CDP session from the underlying Chromium instance.

2. **Payload Creation** – The internal `rawCdp()` function constructs a JSON payload containing an incrementing `id`, the requested `method`, `params`, and optional `sessionId`.

3. **Message Dispatch** – The payload string passes through `globalThis.ego.sendCDPMessage`, which serves as the bridge between the sandboxed Node environment and the embedded Chromium engine.

4. **Response Handling** – A listener (`handleMessage`) matches incoming replies to stored promises using the `id` field. Requests automatically timeout after 15 seconds if no response arrives.

If the target session disappears (such as during page navigation), `browserCdp()` catches the error, re-creates the session, and retries the command transparently.

## Summary

- **Two helpers available**: Use `cdp()` in [`src/cdp-eval.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/cdp-eval.ts) for simple commands and `browserCdp()` in [`src/browser-runtime.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/browser-runtime.ts) for session-managed operations.
- **Default timeouts**: Commands abort after 15 seconds (`RESPONSE_TIMEOUT_MS`) unless specified otherwise in `browserCdp()`.
- **Session caching**: Valid sessions are cached for approximately 2 seconds (`SESSION_TTL_MS`) to reduce overhead.
- **Test mocking**: Assign `state.cdpOverride` to intercept CDP calls during unit testing.
- **Transport mechanism**: All commands ultimately route through `globalThis.ego.sendCDPMessage` to reach the Chromium instance.

## Frequently Asked Questions

### What is the difference between `cdp()` and `browserCdp()`?

The `cdp()` helper provides direct, lightweight access to the Chrome DevTools Protocol without session management overhead, making it ideal for simple queries like `Runtime.evaluate`. The `browserCdp()` helper adds automatic session creation, retry logic for lost sessions, and support for browser-level domains like `Target.*`, making it更适合 for complex automation across page navigations.

### How do I handle sessions when a page navigates or reloads?

Use `browserCdp()` instead of `cdp()`. The helper detects session loss using the `SESSION_LOST` regex pattern and automatically calls `ensureSession()` to re-attach to the target before retrying your command. This ensures your raw CDP commands with ego-browser remain reliable even when the page context changes.

### Can I mock CDP responses for testing without launching Chromium?

Yes. Import `state` from [`src/state.ts`](https://github.com/citrolabs/ego-lite/blob/main/src/state.ts) and assign an async function to `state.cdpOverride`. This function receives the `method` and `params` arguments and can return any mock response object. Both `cdp()` and `browserCdp()` check this override at the start of execution, allowing complete testing isolation.

### What happens if a CDP command takes longer than expected to respond?

The runtime enforces a 15-second timeout (`RESPONSE_TIMEOUT_MS`) by default. After this period, the promise rejects with a timeout error. You can customize this behavior by using `browserCdp()` and passing a custom `timeoutMs` parameter in milliseconds as the fourth argument.