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.

By The Testing AcademyUpdated July 11, 202610 min read
All field guides
In this guide12 sections
  1. Inventory every CDP dependency by intent
  2. Move protocol-independent work back to WebDriver
  3. Map semantics instead of protocol names
  4. Request and validate the BiDi WebSocket
  5. Replace domain enabling with explicit subscriptions
  6. Normalize payloads behind a harness contract
  7. Build contract fixtures for parity testing
  8. Roll out without duplicating side effects
  9. Verify every supported browser and Grid route
  10. Diagnose migration failures by boundary
  11. Migration completion checklist
  12. 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.

  1. 01 / inventory hooks

    Inventory CDP Hooks

    Record intent, command or event, payload use, browser scope, and owner.

  2. 02 / map bidi domains

    Map BiDi Domains

    Choose supported standard commands and events by semantic outcome.

  3. 03 / enable websocket

    Enable WebSocket

    Request webSocketUrl and verify the remote session returns an endpoint.

  4. 04 / replace handlers

    Replace Handlers

    Normalize BiDi payloads behind a stable harness-owned interface.

  5. 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 intentExisting mechanismMigration targetDecision
Record request startCDP network eventBiDi network.beforeRequestSentNormalize URL, method, context, request id
Detect response headersCDP response eventBiDi network.responseStartedPreserve duplicate-header semantics
Observe completed responseCDP loading eventBiDi network.responseCompletedDefine completion contract explicitly
Set an ordinary cookieCDP commandSelenium cookie APIRemove protocol dependency
Use unsupported browser emulationCDP commandScoped CDP adapterKeep 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.

JSON
{
  "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.

JSON
{
  "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.

TypeScript
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.

Code PROMODE / 10% offJoin the batch

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 11, 2026 / Reviewed July 11, 2026

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.

  1. 01
    Selenium documentation

    Selenium Project

    Canonical WebDriver, Grid, waits, element, and browser automation guidance.

  2. 02
    WebDriver 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.