Scrapeless Wiki

How Does Chrome DevTools Protocol Work?

Informational P2 how does chrome devtools protocol work
Overlaps a live page. A published page already targets this keyword: https://www.scrapeless.com/en/blog/what-is-chrome-devtools-protocol. Publishing both risks cannibalisation β€” decide before this goes live.

Learn how CDP works: the WebSocket transport, domains and commands, targets and sessions, and how Playwright and Puppeteer build on it.

The Chrome DevTools Protocol is how Chrome exposes itself to be inspected and controlled. DevTools itself is just a client of it β€” which is why anything you can do in DevTools, a script can do too.

1. The Transport

Start Chrome with a debugging port:

chrome --remote-debugging-port=9222

An HTTP endpoint then lists what is available:

GET http://localhost:9222/json/version
GET http://localhost:9222/json/list

These return a webSocketDebuggerUrl. Connect to it, and all further communication is JSON messages over that WebSocket.

The HTTP part exists only for discovery. Everything real happens on the socket.

2. Commands and Events

Messages follow JSON-RPC conventions.

A command carries an id, a method and parameters:

{"id": 1, "method": "Page.navigate", "params": {"url": "https://example.com"}}

Its response matches the id:

{"id": 1, "result": {"frameId": "ABC123"}}

Events are unsolicited and have no id:

{"method": "Network.requestWillBeSent", "params": {"request": {...}}}

Events must be enabled per domain before they arrive:

{"id": 2, "method": "Network.enable"}

Forgetting to enable a domain is the most common first-time CDP mistake β€” commands work, and the events you are waiting for never come.

3. Domains

The protocol is organised into namespaces:

Domain Covers
Page Navigation, lifecycle, screenshots, PDF
Network Requests, responses, interception, cookies
Runtime JavaScript evaluation, console
DOM Document tree, node queries
Input Synthetic mouse, keyboard, touch
Emulation Device metrics, geolocation, timezone
Target Tabs, frames, workers
Fetch Request interception and modification
Tracing Performance traces

There are dozens more, some stable and some explicitly experimental.

4. Targets and Sessions

A browser is not one thing to control. Each tab, iframe, service worker and extension is a target.

{"id": 3, "method": "Target.attachToTarget",
 "params": {"targetId": "TAB1", "flatten": true}}

The response carries a sessionId. Subsequent messages for that target must include it:

{"id": 4, "sessionId": "S1", "method": "Page.navigate", "params": {...}}

With flatten: true, all sessions share one WebSocket and are distinguished by sessionId. Without it, sessions nest inside wrapper messages, which is harder to work with.

This matters for anyone writing a client from scratch: one CDP WebSocket URL is one browser, and reaching multiple tabs means attaching to each target and stamping every message with the right session.

5. What Libraries Add

Raw CDP is verbose and stateful. Playwright and Puppeteer wrap it with:

  • Method ergonomics. page.click(selector) expands into resolving the node, computing its box, checking visibility, and dispatching input events at coordinates.
  • Waiting. Correlating events to know when a navigation or a request has genuinely finished.
  • Session management. Attaching to targets and routing messages without you tracking ids.
  • Cross-browser abstraction. Playwright maps the same API onto Firefox and WebKit protocols.

Both keep an escape hatch:

const client = await page.createCDPSession();
await client.send('Network.emulateNetworkConditions', {...});

Use it when the wrapper does not expose something. It is a normal thing to do, not a hack.

6. Practical Notes

  • Remote CDP is not a proxy target. Connecting to a remote browser gives you that browser; per-tab URLs and connection-level tricks that work locally often do not translate.
  • Some domains are experimental and change between Chrome versions without deprecation.
  • Emulation is not always what it seems. Emulation.setDeviceMetricsOverride changes the rendering viewport, which is not the same as the real window size β€” screenshot dimensions and DOM measurements can disagree.
  • Enable before you listen, or events will silently never fire.
  • The connection is the session. Losing the WebSocket loses everything attached to it.

7. Real-World Examples

  • A rendering service driving Page.printToPDF directly, without a wrapper library.
  • Request blocking via Fetch.enable with URL patterns, cutting page weight substantially.
  • A custom client stamping sessionId on every message after attachToTarget with flatten.
  • A debugging session dropping to createCDPSession for a protocol call the library did not wrap.
  • A viewport bug where a requested width never applied over remote CDP, while an explicit screenshot clip did.

8. Summary

CDP exposes Chrome over a WebSocket carrying JSON-RPC messages: commands with ids, matching responses, and events that must be enabled per domain first. Functionality is grouped into domains covering pages, network, runtime, DOM, input, emulation and targets.

Everything controllable is a target β€” tabs, frames, workers β€” reached by attaching and then stamping each message with the returned sessionId, with flatten: true keeping it all on one connection.

Playwright and Puppeteer wrap this with ergonomics, waiting logic and session management, while leaving direct protocol access available for anything they do not cover. That escape hatch is where most non-trivial browser automation eventually goes.