Chrome DevTools Protocol · Lesson 2

Domains & Events

Enable a domain, watch its events stream in — the request lifecycle and a console call, from a live trace.

In Lesson 1 you learned the three message shapes and sent your first command. Every command's name had two parts — Browser.getVersion, Page.navigate. That first part is a Domain, and organizing the whole protocol. This lesson makes domains concrete: you'll turn two of them on and watch real events pour out.

The one-sentence mental model

A Domain is a namespace of related commands and events — and each domain's events are silent until you send its Domain.enable command. Commands are pull; events are push you opt into.docs

1 · The domains you'll actually use

CDP has dozens of domains, but a handful cover almost everything:docs

PageNavigation, lifecycle events, screenshots, frames
RuntimeEvaluate JS in the page; console API calls
DOMRead / query / modify the DOM tree by nodeId
NetworkWatch requests & responses; read response bodies
InputSynthesize keyboard / mouse / touch
TargetDiscover & attach to targets; sessions (Lesson 3)

2 · Runtime — run JS, and hear the page talk back

You met Runtime.evaluate in Lesson 1's exercise. But Runtime also emits events — once enabled. Enable it, then run a console.log, and the page's console call comes back to you as an event. This is the real exchange:

# we send: { "id": 1, "method": "Runtime.enable" } { "id": 2, "method": "Runtime.evaluate", "params": { "expression": "console.log('hello from CDP', 42)" } } # the browser pushes back an EVENT (no id) reporting the console call: { "method": "Runtime.consoleAPICalled", "params": { "type": "log", "args": [ {"type":"string","value":"hello from CDP"}, {"type":"number","value":42} ] } }

This is how a tool captures a page's console output: not by scraping a screen, but by subscribing to Runtime.consoleAPICalled. Each argument arrives as a typed RemoteObject — here a string and a number.

3 · Network — the request lifecycle on the wire

Now the domain that makes CDP a debugging superpower. Send Network.enable, then navigate, and every request narrates itself through a fixed lifecycle. This is the real stream for loading example.com, filtered to the main document:

# enable, then navigate: { "id": 1, "method": "Network.enable" } { "id": 3, "method": "Page.navigate", "params": { "url": "https://example.com/" } } # the request tells its whole story as events (all id-less): Network.requestWillBeSent requestId=6D2A71… GET https://example.com/ Network.responseReceived status=200 mimeType=text/html Network.dataReceived (body bytes arriving) Network.loadingFinished requestId=6D2A71… ← this request is done
The four beats, and the requestId that ties them

Every request walks the same path: requestWillBeSent → responseReceived → dataReceived (0+ times) → loadingFinished.docs All four carry the same requestId — that's how you correlate them across the many requests interleaving on one page load.

Once you have a request's requestId, you can ask for its body — a command whose result (not an event) carries the bytes. Real capture, main document:

# command — feed it the requestId from the event above: { "id": 99, "method": "Network.getResponseBody", "params": { "requestId": "6D2A71…" } } # result: { "id": 99, "result": { "base64Encoded": false, "body": "<!doctype html><html lang=\"en\"><head><title>Example Domain</title>…" } }
Still noisy — filter by more than the domain

Just like /json/list in Lesson 1, the event stream is cluttered: a real capture also shows chrome-extension://… requests from bundled extensions, and …ExtraInfo variants of the events. Filter by the url (and often by type: Document, Script, XHR…) to find the request you care about.

🔧 Try it yourself (≈ 5 minutes)

The driver script grew two verbs for this lesson. From the cdp/ directory:

# start a throwaway Chrome bash assets/cdp-ws.sh up # Network domain: enable + navigate, printed as a request lifecycle (>> request, << response) bash assets/cdp-ws.sh net https://example.com # Runtime domain: run a console.log and catch it as a consoleAPICalled event bash assets/cdp-ws.sh console 'console.log("hello from CDP", 42)' bash assets/cdp-ws.sh down

In the net output, pick one request and trace it from >> to <<. What was its status and MIME type? Bring me anything odd.

Check yourself

Which Network event tells you a request's bytes have fully arrived?
You navigated but saw no Network.* events at all. What was missing?
To fetch a response body, which value from the event does getResponseBody need?
Primary source — read this next

The official Network domain reference and Runtime domain reference. Skim the event lists — you've now watched the core ones fire live, so the docs read as a map of a place you've already been, not a wall of names.