How the `cdp()` Function Executes Chrome DevTools Protocol Commands in ego-lite
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, lines 13-15, the function inspects state.cdpOverride:
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:
return send({ method, params, session_id: sessionId });
The send() function, defined in package/ego-browser/src/state.ts lines 42-44, is a thin wrapper around state.send:
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 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.orBrowser.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:
- Builds the JSON-RPC payload with a unique message ID
- Registers a pending promise keyed by that ID
- Sends via
globalThis.ego.sendCDPMessage - 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:
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:
await cdp('Network.enable');
Testing with Overrides
Mock CDP responses for isolated unit testing:
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:
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 |
Exposes cdp() helper and high-level evaluate() API |
package/ego-browser/src/state.ts |
Mutable runtime state, default send implementation, test overrides |
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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →