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

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 (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 (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

// 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, which updates internal state for network-domain tracking when you enable the Network domain.

Enable CDP Domains

// 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

// 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:

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, 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 ResolutionbrowserCdp() 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 for simple commands and browserCdp() in 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 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.

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 →