CDP is the protocol under Playwright, Puppeteer and your agent
A JSON-RPC conversation over a websocket, organised into domains, with commands you send and events the browser emits. Every higher-level library is a wrapper around it.
The shape worth knowing even if you never write it directly
Domains, commands and events
The surface is grouped into domains such as Page, Network, Runtime, DOM and Target. You send commands and receive replies, and separately the browser pushes events. Most confusing behaviour comes from treating a reply as if it were an event.
Domains must be enabled
Several domains emit nothing and silently ignore configuration until you call their enable command. This is the single most common CDP mistake: the call returns successfully, and nothing happens, forever.
Targets and sessions
A browser is a tree of targets, one per tab, worker and out-of-process frame. Attaching gives a session id that scopes subsequent commands, which is why a command that worked on one tab appears to do nothing on another.
It is versioned by the browser, not by a spec
The stable domains are documented and change slowly; the experimental ones can change between Chrome releases without notice. Building on an experimental domain is a decision to track Chrome's release cadence.
CDP against the layers above it
| Level | Reach for it when | |
|---|---|---|
| Raw CDP | The wire itself | You need a domain no library exposes |
| Puppeteer | Thin wrapper, Chromium first | You want CDP with ergonomics |
| Playwright | Thicker, multi-engine | You want auto-waiting and other engines |
| Selenium WebDriver | A W3C standard, not CDP | You need broad, long-lived compatibility |
| WebDriver BiDi | Standardised bidirectional successor | You want CDP-like events, portably |
| An agent framework | A model on top of one of these | The decisions should not be in your code |
As of 2026-08-31 · Competitor details come from each vendor's published pricing page on that date.
Debugging a CDP call that does nothing
In this order. The first two explain most cases.
-
Check you enabled the domain
Call the domain's enable command before configuring it. Several commands are accepted and discarded when the domain is disabled, and the reply gives no hint.
-
Check which session the command went to
A command sent to the browser target does not reach a page target. If nothing seems to apply, the session id is the first thing to print.
-
Check ordering against navigation
Configuration applied after a navigation begins may land in the outgoing document. Anything that must apply to the next page has to be registered before the navigation is issued.
-
Look at the raw traffic
Every library can log the underlying protocol messages. Reading them is usually faster than reasoning about what the wrapper intended to send.
-
Prefer returnByValue for real data
Object previews are lossy by design and truncate deep structures. When you need the actual value rather than something to look at in a debugger, ask for it by value.
Common questions
Is CDP a standard?
No. It is Chromium's own protocol, documented but controlled by the Chromium project. WebDriver BiDi is the W3C effort to standardise the same capabilities across engines, and it is the direction the ecosystem is moving, though CDP remains what most tooling actually speaks today.
Can I use CDP over the network?
Yes, and that is exactly what a hosted browser endpoint is: the websocket is simply somewhere else. The consequence is latency per command, which is why chatty scripts feel slower remotely and batched ones barely change.
Does Firefox support CDP?
Partially, through a compatibility layer, and the coverage is incomplete. If cross-engine support matters, WebDriver BiDi or Playwright's own abstraction is a more durable choice than assuming CDP is portable.
Should I write CDP directly?
Rarely, and only for something the library above you does not expose. The wrappers exist because raw CDP requires you to manage sessions, ordering and enablement by hand, and most bugs in direct CDP code are in exactly those three areas.
A CDP endpoint you did not have to operate
Connect Playwright or Puppeteer to a remote websocket and keep the code you already have.