PRACTICAL GUIDE / Selenium CDP to BiDi migration
Migrate Selenium CDP Hooks to WebDriver BiDi
Migrate Selenium CDP hooks to WebDriver BiDi with a capability inventory, event adapters, parity tests, cross-browser rollout, and failure diagnostics.
In this guide12 sections
- Inventory every CDP dependency by intent
- Move protocol-independent work back to WebDriver
- Map semantics instead of protocol names
- Request and validate the BiDi WebSocket
- Replace domain enabling with explicit subscriptions
- Normalize payloads behind a harness contract
- Build contract fixtures for parity testing
- Roll out without duplicating side effects
- Verify every supported browser and Grid route
- Diagnose migration failures by boundary
- Migration completion checklist
- Finish with an explicit compatibility boundary
What you will learn
- Inventory every CDP dependency by intent
- Move protocol-independent work back to WebDriver
- Map semantics instead of protocol names
- Request and validate the BiDi WebSocket
A Selenium CDP to BiDi migration is not a namespace replacement. It is a redesign of how a test harness requests bidirectional access, subscribes to browser events, normalizes payloads, and handles uneven implementation support. The safe unit of migration is a test capability such as "observe failed requests," not a line that calls Network.enable.
Start with an evidence-backed inventory. Move ordinary browser automation back to WebDriver APIs, map supported event use cases to WebDriver BiDi, and isolate the remaining CDP dependencies behind explicit compatibility decisions. This produces a migration that can improve cross-browser coverage without pretending the two protocols have identical surfaces.
Inventory every CDP dependency by intent
Search for direct command execution, generated DevTools domain classes, performance-log parsing, debugger addresses, and framework wrappers. Include setup code that enables domains and teardown code that removes listeners. A helper named captureTraffic may hide more version coupling than a visible executeCdpCommand call.
Animated field map
CDP to WebDriver BiDi Migration
Classify browser hooks, select supported BiDi domains, enable the socket, replace adapters, and prove behavior across browsers.
01 / inventory hooks
Inventory CDP Hooks
Record intent, command or event, payload use, browser scope, and owner.
02 / map bidi domains
Map BiDi Domains
Choose supported standard commands and events by semantic outcome.
03 / enable websocket
Enable WebSocket
Request webSocketUrl and verify the remote session returns an endpoint.
04 / replace handlers
Replace Handlers
Normalize BiDi payloads behind a stable harness-owned interface.
05 / verify browsers
Cross-Browser Verification
Run contract fixtures and preserve explicit unsupported cases.
For each item, capture the business reason, CDP domain and event, fields consumed, commands that mutate state, browser restrictions, failure behavior, and tests that exercise it. Delete hooks with no current consumer. A migration is easier when obsolete diagnostics are removed rather than faithfully reimplemented.
Move protocol-independent work back to WebDriver
CDP is often used for work Selenium already models portably. Cookies, windows, screenshots, script execution, timeouts, and browser options should use the standard WebDriver or Selenium API when it satisfies the requirement. The official Selenium CDP guide explicitly describes CDP support as temporary and warns that CDP was not designed as a stable testing API.
This first pass reduces the migration surface without introducing BiDi. For example, a CDP cookie command used only to seed an ordinary cookie should become Selenium cookie management. Device or browser arguments exposed by a typed options class should move there. Keep CDP only when its observable outcome is unavailable through the normal automation contract.
Do not confuse "works in Chrome" with "requires CDP." The inventory must name the missing standard capability. That statement becomes testable and can be revisited as Selenium and browser support changes.
Map semantics instead of protocol names
BiDi network observation uses subscriptions such as network.beforeRequestSent, network.responseStarted, and network.responseCompleted. These are not guaranteed to align field for field with CDP's Network.requestWillBeSent or loading events. Context identity, redirect data, headers, timing, and body access have different definitions and shapes.
Create a small mapping table owned by the framework:
| Harness intent | Existing mechanism | Migration target | Decision |
|---|---|---|---|
| Record request start | CDP network event | BiDi network.beforeRequestSent | Normalize URL, method, context, request id |
| Detect response headers | CDP response event | BiDi network.responseStarted | Preserve duplicate-header semantics |
| Observe completed response | CDP loading event | BiDi network.responseCompleted | Define completion contract explicitly |
| Set an ordinary cookie | CDP command | Selenium cookie API | Remove protocol dependency |
| Use unsupported browser emulation | CDP command | Scoped CDP adapter | Keep with owner and removal condition |
The Selenium WebDriver BiDi documentation groups supported capabilities by W3C and CDP-backed APIs. Treat documentation plus executable browser checks as the source of support truth. Never infer support because an event exists in the specification alone.
Request and validate the BiDi WebSocket
The new-session capability webSocketUrl: true asks a compliant remote end to return its BiDi socket endpoint. With Grid, websocket proxying must also be enabled and reachable through every load balancer or reverse proxy in the route.
{
"capabilities": {
"alwaysMatch": {
"browserName": "firefox",
"webSocketUrl": true,
"se:name": "network-observer-contract"
}
}
}Fail setup clearly if the returned capabilities do not contain a usable WebSocket URL. A null endpoint is an infrastructure or support decision, not a reason to let later event assertions time out. Record the selected browser and returned capabilities so a failed support check can be distinguished from an application failure.
Open one connection per WebDriver session unless the binding documents another lifecycle. The session owns the socket. Teardown must remove test listeners and close protocol resources before or alongside driver.quit, while tolerating a browser that already terminated.
Replace domain enabling with explicit subscriptions
CDP code commonly enables a domain and then registers listeners. BiDi uses session.subscribe with a list of events and optional context scoping. The following command subscribes to a focused network lifecycle rather than enabling an entire loosely consumed domain.
{
"id": 7,
"method": "session.subscribe",
"params": {
"events": [
"network.beforeRequestSent",
"network.responseStarted",
"network.responseCompleted",
"network.fetchError"
]
}
}Wait for the command response before triggering traffic. Route incoming messages by method, and route command responses by id; websocket arrival order does not make every message a response to the latest command. When tests need only one tab, apply a supported context filter after the context exists. Global subscriptions are simpler during early migration but produce more data and require strict test-level filtering.
Unsubscribe or detach local consumers when a test finishes. Session-wide listeners that capture mutable test objects are a common source of duplicate evidence and memory retention in long-running workers.
Normalize payloads behind a harness contract
Do not expose raw BiDi event objects throughout the suite. Define the smallest stable model your assertions require. That model should preserve important semantics, such as repeated headers and nullable navigation relationships, while discarding protocol fields no test uses.
export type ObservedRequest = {
requestId: string;
contextId: string | null;
url: string;
method: string;
navigationId: string | null;
};
type BidiBeforeRequest = {
context: string | null;
navigation: string | null;
request: { request: string; url: string; method: string };
};
export function normalizeBeforeRequest(
event: BidiBeforeRequest,
): ObservedRequest {
return {
requestId: event.request.request,
contextId: event.context,
url: event.request.url,
method: event.request.method,
navigationId: event.navigation,
};
}The adapter is also where privacy rules belong. Redact authorization, cookies, query secrets, and bodies before persistence. Raw traffic can carry credentials even when the test report itself contains only synthetic data.
Build contract fixtures for parity testing
Create a deterministic local page or test service that produces redirects, duplicate headers, successful resources, failed requests, frames, and cache behavior relevant to the suite. Run the same fixture through the old CDP observer and new BiDi observer, then compare the normalized model.
Compare meaning, not event count or raw order. One protocol may expose intermediate details that the other groups differently. Assert that the expected request URL, method, context association, response status, and failure classification are present. If exact ordering matters to a product assertion, define the permitted partial order and explain why.
Version the fixture expectations with the adapter. When a browser implementation changes, a small contract test should identify whether the difference is a protocol bug, an intentional semantic correction, or a normalization defect before hundreds of application tests fail.
Roll out without duplicating side effects
Read-only event collectors can operate in shadow mode for selected jobs. Keep their outputs separate, sample failures, and compare normalized records. Do not let both collectors satisfy the same wait, because whichever callback wins can hide missing events in the other path.
Mutating operations require a single active implementation. Blocking a request, injecting script, changing permissions, or altering network conditions through CDP and BiDi together can produce double application, conflicting state, or meaningless parity results. Select one adapter per session with a feature flag and run separate jobs when comparison is necessary.
Roll out by capability cluster rather than by browser alone. Network observation can move while an unsupported emulation feature remains isolated behind CDP. Publish the remaining inventory so "migration complete" means no unowned direct CDP calls, not necessarily zero justified fallbacks.
Verify every supported browser and Grid route
WebDriver BiDi aims at interoperable automation, but browser implementations and Selenium binding surfaces can progress at different rates. Run the contract suite against each browser and deployment path you claim to support. Local success does not prove that a Grid router, proxy, firewall, or cloud endpoint preserves websocket upgrades and long-lived connections.
Classify outcomes as supported, unsupported with a documented fallback, or defective. Do not skip an entire test silently because the socket is absent. A capability report should state the browser, runtime identity, requested event set, successful subscriptions, and any retained CDP feature.
Cross-browser payload differences belong in adapters only when they preserve the same standard meaning. Browser-name conditionals scattered through assertions recreate the coupling the migration was meant to remove.
Diagnose migration failures by boundary
No webSocketUrl in returned capabilities points to browser, driver, Grid, or provider support. A socket connection failure points to routing, authentication, TLS, or websocket proxying. A subscription error points to an event name, parameter shape, unsupported module, or context scope. Events arriving with failed assertions point to normalization or test semantics.
If event volume doubles, inspect listener registration and teardown before blaming the browser. If an expected request is missing, verify that subscription completed before the trigger, that the context filter includes the target, and that the request was not served before the observer started. If only one browser fails, reduce the case to the contract fixture and preserve the raw sanitized event.
Avoid falling back automatically on every error. A malformed BiDi command should fail loudly; silently switching to CDP would conceal a migration defect. Fallback should occur only for a predeclared unsupported capability and should be visible in run metadata.
Migration completion checklist
- Inventory direct commands, generated APIs, logs, wrappers, and listener cleanup.
- Remove CDP uses already covered by WebDriver or typed browser options.
- Map each remaining hook by test intent and consumed data.
- Request the BiDi websocket and fail clearly when it is unavailable.
- Subscribe before triggering the observed browser behavior.
- Normalize raw events behind a harness-owned semantic model.
- Protect secrets before network or script data reaches artifacts.
- Compare read-only adapters with deterministic contract fixtures.
- Never execute the same mutating operation through both protocols.
- Verify every browser and remote route included in the support claim.
- Keep every CDP fallback narrow, owned, measured, and removable.
Finish with an explicit compatibility boundary
The strongest migration does not hide CDP behind a differently named helper. It leaves ordinary behavior in WebDriver, moves supported bidirectional use cases to standard BiDi subscriptions, and exposes every remaining exception. Once contract tests pass across the promised browser matrix and fallback use is visible, the suite has a stable compatibility boundary that can evolve without another repository-wide protocol rewrite.
// LIVE COURSE / THE TESTING ACADEMY
Playwright Automation Mastery
Go beyond Selenium. Master Playwright with JS/TS in 90 days.
From the instructor behind this guide.
Playwright jobs are growing 8x faster than Selenium. 90 days / 75+ live hrs / Tue-Thu-Sat 7 AM IST.
PRIMARY REFERENCES
Verify the details at the source
QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.
- 01Selenium documentation
Selenium Project
Canonical WebDriver, Grid, waits, element, and browser automation guidance.
- 02WebDriver standard
W3C
The browser automation protocol specification behind WebDriver implementations.
FAQ / QUICK ANSWERS
Questions testers ask
Why should Selenium teams migrate CDP hooks to WebDriver BiDi?
CDP is browser-specific and version-sensitive, while WebDriver BiDi is a standards-based bidirectional protocol intended for interoperable browser automation. Migration reduces direct dependence on Chromium protocol details where supported.
Is every Chrome DevTools Protocol feature available in WebDriver BiDi?
No. Protocol and browser implementations continue to grow, and some CDP use cases have no BiDi equivalent yet. Inventory each hook and keep a narrowly scoped fallback only where verified support is absent.
Should a migration translate CDP method names one for one?
No. Map the test intent and data contract instead. BiDi domains, event payloads, context filters, and subscription lifecycle differ from CDP even when both expose network or script information.
Can CDP and BiDi listeners run together during rollout?
Read-only listeners can run in a controlled comparison phase if duplicate data is isolated. Do not execute the same mutating command through both protocols, because the browser state could change twice.
How should cross-browser parity be proven after migration?
Run contract tests against every supported browser, compare normalized semantic fields rather than raw payloads, and retain unsupported-feature decisions as explicit, reviewed capability records.
RELATED GUIDES
Continue the learning route
GUIDE 01
Track Tabs, Frames, and Navigation with BiDi Browsing Context Events
Track WebDriver BiDi context creation, navigation, frames, and teardown with event correlation, state modeling, diagnostics, and reliable Selenium tests.
GUIDE 02
Designing a Cross-Browser Options Matrix in Selenium WebDriver 4
Design a Selenium browser options matrix that separates W3C capabilities, vendor settings, environments, and verified cross-browser expectations.
GUIDE 03
WebDriver Capability Negotiation with alwaysMatch and firstMatch
Understand WebDriver capability negotiation with alwaysMatch, firstMatch, Selenium RemoteWebDriver, conflict rules, and session evidence.
GUIDE 04
Control DriverService Ports, Logs, and Process Lifecycles in Selenium 4
Configure Selenium DriverService ports and logs, own local driver processes safely, prevent leaks, and preserve useful startup failure evidence.