CDP, WebDriver BiDi, and the Limits of Portability

Puppeteer presents one high-level interface, but Chrome and Firefox do not give that interface the same protocol underneath. This chapter focuses on the decision that follows: when should a task use Chrome DevTools Protocol (CDP), when should it use WebDriver BiDi, and what does “portable” mean when the feature surface is not identical? The answer helps readers choose a browser and protocol from actual requirements instead of treating a shared method name as a guarantee of shared behavior.

The difference is easy to miss in a short script. Launch a browser, create a page, navigate, locate an element, and evaluate JavaScript: those operations can look like one portable path. Add accessibility inspection, tracing, specialized emulation, coverage, a particular network operation, or a browser-specific target feature, and the protocol becomes part of the design. Puppeteer’s abstraction reduces the amount of protocol code the caller writes; it does not eliminate the protocol’s capabilities and limits.

One API, two protocol paths

WebDriver BiDi is a cross-browser automation protocol designed to combine the broad browser reach of WebDriver with bidirectional, lower-level control associated with CDP. Puppeteer supports it for both Chrome and Firefox. The defaults reflect the current state of the implementations: Firefox is launched with WebDriver BiDi by default, while Chrome continues to use CDP by default because the complete Puppeteer feature set is not yet available over BiDi. Chrome can still be launched with BiDi explicitly.

That default is a compatibility choice, not a judgment that one protocol is universally better. Puppeteer’s stated goals include using both protocols as reference implementations, growing cross-browser testing, and exposing new browser capabilities. The FAQ also says that CDP support will continue so existing Chrome automation is not broken and Chrome-specific capabilities that are not standardized by WebDriver BiDi remain available. The coexistence is therefore structural: a common page model can serve different protocol contracts.

Puppeteer’s connection path makes the abstraction concrete. When the caller requests WebDriver BiDi, Puppeteer first tries to establish a native BiDi session by sending a session-status request. If the endpoint responds as a BiDi server, Puppeteer uses that connection directly. If the probe fails with a protocol error, the captured implementation can reuse the same WebSocket transport to create a CDP connection and run a BiDi mapping layer over it for Chrome. This fallback preserves a BiDi-facing API while the browser is controlled through CDP underneath.

The fallback is not a universal bridge. The implementation checks the browser identity and rejects Firefox in BiDi-over-CDP mode. More broadly, a mapping layer can translate commands and events; it cannot manufacture a browser capability that the underlying protocol or browser does not expose. That is why the protocol decision remains visible even when the application only sees Puppeteer objects.

The transport path also affects operational expectations. A pure BiDi session and a BiDi-over-CDP session may expose similar high-level behavior, but their supported features, error details, and browser-specific edges can differ. A script should record the browser and protocol assumptions that matter to its result. Otherwise, a migration from Chrome/CDP to Firefox/BiDi may preserve syntax while silently changing the evidence the test can collect.

Compare requirements, not feature counts

A useful comparison starts with the task rather than with a long protocol inventory.