Chapter 18Lesson 01~205 minutes

WebDriver BiDi: Events, Network, Logs, Script, and Bidirectional Control: Core Concepts and Mental Model

Classic WebDriver is excellent at issuing a command and receiving its result. It is less natural when the browser needs to tell the test runner that something happened asynchronously. WebDriver BiDi adds that event/control channel. This lesson builds the protocol and ownership model before learners subscribe to anything.

WebDriver BiDiW3C protocolEventsSubscriptionsSelenium 4.47

Learning objectives

  • Distinguish classic WebDriver request/response traffic from the BiDi WebSocket event channel.
  • Define browsing contexts, event domains, subscriptions, callbacks, and handler lifetime.
  • Inspect negotiated BiDi capability and session state before using event APIs.
  • Prefer documented high-level Selenium BiDi APIs over low-level/internal transport classes.
  • Explain why CDP is Chromium-specific and temporary rather than a cross-browser BiDi substitute.

1. Why classic WebDriver needs a companion channel

A command such as find_element() or click() has a clear request and response. Console messages, network requests, JavaScript exceptions, prompt events, and navigation events arrive whenever the browser produces them. Polling repeatedly can miss short-lived events, adds latency, and couples tests to timing guesses. BiDi gives the browser a standards-based path to push subscribed events back to the client.

Mental boundary

BiDi complements classic WebDriver. It does not replace locators, ordinary interactions, waits, session ownership, or assertions.

2. Mental model: classic commands plus bidirectional events

Classic WebDriver and BiDi coexist inside one browser automation session

The following diagram visualizes the relationships described in Mental model: classic commands plus bidirectional events. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.

flowchart TD
 T[Test runner] -->|Classic command| D[WebDriver / Grid]
 D -->|Command response| T
 D <-->|BiDi WebSocket| B[Browser]
 B -->|log / network / script events| T
 T -->|subscribe / unsubscribe| B
 B --> A[AUT]
 A -->|console / fetch / navigation| B
 T --> E[Correlated evidence]

The classic path remains synchronous request/response. When BiDi is enabled, the negotiated session can also expose a WebSocket URL. Selenium layers user-facing domain objects such as driver.script, driver.network, and driver.browsing_context on that channel. A subscription tells the browser which event family matters. A callback receives matching events until the handler is removed or the session ends.

3. Objects and state stores

The following table organizes the key choices and evidence for Objects and state stores. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Object Meaning State it owns Failure if confused
WebDriver session One negotiated automation session session ID, capabilities, windows, cookies Opening a second driver is a second session, not another BiDi channel for the first
Classic window handle Identifier used by classic top-level context commands current classic browsing target Treating every handle as an arbitrary BiDi context ID hides semantic boundaries
BiDi browsing context W3C BiDi target such as a top-level context or frame context tree and context-scoped events Subscribing to the wrong context yields missing or irrelevant events
Subscription / handler ID Lifecycle token returned by a high-level API which callback is active Leak causes duplicate callbacks and cross-test contamination
Domain Related commands/events such as log, network, script event schemas and controls Assuming every binding/browser implements identical domains
WebSocket URL Negotiated endpoint for BiDi transport transport route for this session Copying or exposing remote endpoints can leak operational access
Grid Optional remote session router/node system session placement and WebSocket proxying Network reachability can fail even when classic HTTP commands work

4. Read-only preflight before enabling listeners

The following example makes the Read-only preflight before enabling listeners behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

import selenium
from selenium import webdriver

options = webdriver.ChromeOptions()
options.enable_bidi = True

driver = webdriver.Chrome(options=options)
try:
    caps = driver.capabilities
    print("selenium", selenium.__version__)
    print("session", driver.session_id)
    print("browser", caps.get("browserName"), caps.get("browserVersion"))
    print("platform", caps.get("platformName"))
    print("webSocketUrl", caps.get("webSocketUrl"))
    print("classic_handle", driver.current_window_handle)
finally:
    driver.quit()

options.enable_bidi = True asks Selenium to negotiate BiDi. The returned webSocketUrl capability proves whether that session actually exposes the transport. Record browser/binding versions and the negotiated value rather than assuming support from the browser name alone.

5. Events and command domains

BiDi is organized into domains. Logging use cases are surfaced by Selenium through the script namespace, network interception/observation through network, and browsing-context control through browsing_context. The protocol itself contains lower-level commands and event types, but Selenium explicitly encourages high-level use-case APIs where available.

Domain Typical use State mutation? Evidence risk
script / log console messages, JS errors, script operations handlers mostly observe; script commands can mutate page state console may contain user data/tokens
network request/response observation, auth, interception observation can be passive; interception mutates request flow headers/bodies can be highly sensitive
browsing context create/navigate/capture/observe context lifecycle yes for navigation/create/close context metadata and URLs
input low-level BiDi input commands yes can change AUT state
browser browser/user-context operations yes profile-like isolation and policy state

6. Subscription lifecycle is resource lifecycle

An event handler is not a free-standing callback. It is session-scoped state. Add it before the triggering action, preserve the returned handler/subscription identifier, and remove it in cleanup. If a fixture reuses a browser session, an unremoved handler can fire in later tests and create duplicate evidence.

events = []
handler_id = driver.script.add_console_message_handler(events.append)
try:
    # trigger only after the handler exists
    pass
finally:
    driver.script.remove_console_message_handler(handler_id)

7. Browsing context is not merely “the current tab”

BiDi event APIs can scope subscriptions to browsing contexts. A context identifier has protocol meaning: it identifies a context in the BiDi context tree. Classic WebDriver window handles identify top-level contexts for classic commands. Current Selenium may bridge these concepts in convenient APIs, but production code should still label the identifier type explicitly instead of passing opaque strings between layers and hoping they are interchangeable.

8. High-level APIs versus low-level/internal classes

Version-sensitive boundary

The low-level BiDi protocol surface evolves quickly. Selenium documentation states that low-level components remain accessible but are not the recommended user-facing layer. Pin the binding version and prefer stable high-level methods such as driver.script.add_console_message_handler().

The following table organizes the key choices and evidence for High-level APIs versus low-level/internal classes. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.

Choice Benefit Cost Recommended use
High-level Selenium API portable intent, simpler lifecycle may not expose newest protocol feature immediately default
Low-level protocol class earlier access to new domain detail beta/internal churn, schema coupling small isolated adapter only when necessary
Classic polling widely understood, no BiDi dependency can miss events or add delay state conditions that are naturally queryable
CDP deep Chromium-specific control not cross-browser; temporary Selenium bridge only explicitly Chromium-specific requirements

9. BiDi versus CDP

Chrome DevTools Protocol is a Chromium protocol. Selenium has historically exposed CDP to bridge gaps while WebDriver BiDi matured. Current Selenium documentation describes CDP support as temporary, and Selenium 4.47 blocks Firefox CDP access in Python. A test that calls Chromium CDP and then labels itself “cross-browser BiDi” is architecturally incorrect.

10. Trust boundaries and privacy

BiDi can reveal rich telemetry. Console messages may contain tokens; network handlers can see URLs, headers, cookies, and possibly bodies; remote WebSocket endpoints are session infrastructure. Use synthetic data in training, redact before persistence, minimize subscriptions, and never expose public Grid/BiDi endpoints. Event richness increases privacy responsibility.

11. DevOps connection: observability with version discipline

BiDi can turn opaque CI failures into event-rich incidents, but only if the pipeline records the exact Selenium binding, browser version, returned capabilities, selected domains, handler lifecycle, and any fallback used. A green test produced after silently disabling unsupported evidence is not equivalent to a fully observed run.

12. Lesson summary

  • Classic WebDriver issues commands; BiDi adds asynchronous browser-to-client events and bidirectional control.
  • Enablement is negotiated per session and should be proven by returned capability state.
  • Subscriptions are resources that must be created before the trigger and removed deterministically.
  • Browsing-context identifiers, classic handles, domains, and WebSocket endpoints are distinct concepts.
  • Prefer high-level Selenium BiDi APIs; isolate low-level version-sensitive code.
  • CDP is browser-specific and temporary, not cross-browser BiDi.

Knowledge check

What proves that a newly created session actually negotiated BiDi?

Why must a handler be installed before the action that emits the event?

Is a classic window handle conceptually identical to every BiDi browsing-context ID?

Why is CDP not a valid cross-browser fallback for Firefox in this chapter?

What is the cleanup responsibility for an event subscription?

Next lesson

WebDriver BiDi: Events, Network, Logs, Script, and Bidirectional Control: Guided Hands-On Workflow

Continue with WebDriver BiDi: Events, Network, Logs, Script, and Bidirectional Control: Guided Hands-On Workflow. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against Selenium primary documentation on 2026-08-28. Mandatory examples pin Selenium Python 4.47.0 and Python 3.10+, request BiDi through options.enable_bidi = True, prefer documented high-level driver.script/driver.network APIs, use Selenium Manager for normal local driver resolution, and target only the loopback synthetic AUT. BiDi domain parity is explicitly treated as evolving. Low-level/internal classes are discussed as an adapter-only escape hatch, not the default. CDP is labeled Chromium-specific/temporary and is not used as a cross-browser fallback. Paid clouds, enterprise identity/proxies, managed Kubernetes, and public Grid endpoints are not required.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.