PRACTICAL GUIDE / enable WebDriver BiDi Selenium
Enable WebDriver BiDi and Manage Event Subscriptions in Selenium
Enable Selenium WebDriver BiDi, verify WebSocket negotiation, scope event subscriptions, prevent races, and clean handlers up deterministically.
In this guide12 sections
- Negotiate BiDi During Session Creation
- Request and Verify the WebSocket URL
- Keep Classic Commands and BiDi Events Distinct
- Subscribe Before Triggering the Event
- Scope Subscriptions to the Intended Context
- Give Every Subscription One Owner
- Design Handlers for Backpressure and Ordering
- Verify Remote and Grid Support Early
- Treat CDP and BiDi as Different Contracts
- Diagnose Subscription Failures Systematically
- BiDi Subscription Checklist
- Make Event Ownership Part of the Test
What you will learn
- Negotiate BiDi During Session Creation
- Request and Verify the WebSocket URL
- Keep Classic Commands and BiDi Events Distinct
- Subscribe Before Triggering the Event
WebDriver BiDi is not a second test session bolted onto Selenium. It is a negotiated WebSocket channel attached to the same browser session, allowing the browser to push events while Classic WebDriver commands continue to navigate, locate, and interact. Reliable use begins with capability verification and disciplined subscription ownership.
Most BiDi failures blamed on "missing events" are lifecycle defects: the capability was not accepted, the handler was registered after the event, the subscription targeted the wrong browsing context, or a listener from an earlier test remained active. Treat connection and subscription state as test infrastructure with explicit setup, proof, and teardown.
Negotiate BiDi During Session Creation
The official Selenium BiDi guide enables the feature by requesting webSocketUrl in browser options. That capability must be present in the new-session request; enabling it after construction is impossible because the remote end returns the WebSocket endpoint as part of session negotiation.
The WebDriver BiDi specification defines the asynchronous channel and subscription commands. Selenium's binding owns the actual socket connection once the session is established. Your test should use binding-level modules or BiDi listeners instead of inventing message IDs and reconnection behavior.
Animated field map
WebDriver BiDi Session Lifecycle
The session request negotiates a WebSocket endpoint, Selenium connects, the test subscribes before acting, and an asynchronous handler receives the event.
01 / bidi capability
BiDi Capability
Request webSocketUrl in browser options before creating the session.
02 / session response
New Session Response
Verify that the remote end returned a usable WebSocket URL capability.
03 / websocket connection
WebSocket Connection
Let the Selenium binding manage connection state and protocol messages.
04 / event subscription
Event Subscription
Register the event and optional browsing-context scope before the trigger.
05 / async handler
Asynchronous Handler
Queue event data, correlate it to the test, and remove the listener at teardown.
Request and Verify the WebSocket URL
Do not infer BiDi support because the browser launched. Read the returned capability and fail the setup clearly if it is absent or malformed. A remote Grid or vendor can accept the browser session while not providing the requested bidirectional endpoint.
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.net.URI;
import java.util.Set;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
ChromeOptions options = new ChromeOptions();
options.setCapability("webSocketUrl", true);
WebDriver driver = new ChromeDriver(options);
Object negotiated = ((HasCapabilities) driver)
.getCapabilities()
.getCapability("webSocketUrl");
assertNotNull(negotiated, "Remote end did not return webSocketUrl");
URI endpoint = URI.create(negotiated.toString());
assertTrue(Set.of("ws", "wss").contains(endpoint.getScheme()));Do not print the full endpoint into public logs. It is session-scoped connection data and may contain routing information. A setup record can safely state that negotiation succeeded, along with browser name, session ID, and Grid run identity under the project's access controls.
Keep Classic Commands and BiDi Events Distinct
Classic WebDriver follows request and response: the client sends get, click, or findElement and waits for a result. BiDi lets the remote end send subscribed events independently, such as a log entry or network event. They share a browser session but have different timing semantics.
A Classic command completing does not guarantee every related event has already been processed by your callback. Conversely, receiving a network or log event does not mean the page's business transition is complete. Wait for the evidence relevant to the assertion: an expected event when testing instrumentation, or a product state when using events only as diagnostics.
Subscribe Before Triggering the Event
Selenium Java exposes the negotiated channel through HasBiDi. The low-level BiDi object can add a typed listener and returns an ID for removal. The following example scopes log.entryAdded to the current top-level browsing context and uses a blocking queue that is safe for an asynchronous callback.
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.time.Duration;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.bidi.BiDi;
import org.openqa.selenium.bidi.HasBiDi;
import org.openqa.selenium.bidi.log.Log;
import org.openqa.selenium.bidi.log.LogEntry;
BiDi bidi = ((HasBiDi) driver).getBiDi();
BlockingQueue<LogEntry> events = new LinkedBlockingQueue<>();
long listenerId = bidi.addListener(
driver.getWindowHandle(),
Log.entryAdded(),
events::offer);
try {
driver.get("https://app.example.test/diagnostics");
driver.findElement(By.cssSelector("[data-testid='emit-log']")).click();
LogEntry event = events.poll(Duration.ofSeconds(5).toMillis(), TimeUnit.MILLISECONDS);
assertNotNull(event, "Expected log.entryAdded event");
} finally {
bidi.removeListener(listenerId);
}The duration is a local test budget. A queue gives the test a precise handoff and keeps assertions off the connection callback. The callback should never call driver or block waiting for another event; either behavior can deadlock, reorder work, or slow event processing.
Scope Subscriptions to the Intended Context
A global subscription is convenient for one-tab tests but noisy in suites that open authentication popups, previews, or background windows. Scope to a known browsing context when the assertion belongs to one tab. In Selenium Java, top-level window handles are the identifiers used by the BiDi browsing-context APIs.
New tabs receive new handles. A listener scoped only to the original context will not automatically become evidence for a popup case. Capture the new handle, register the needed context-specific listener before triggering events inside it, and remove that listener when the popup workflow ends. Do not silently broaden every subscription to hide context mistakes.
Give Every Subscription One Owner
The component that calls addListener should retain the returned ID and remove it. Higher-level modules such as LogInspector, Network, and Script are AutoCloseable where their Java API supports managed listener state; use try-with-resources for those wrappers. Direct BiDi listeners need an explicit finally block.
Avoid a global clearListeners() in shared infrastructure. It can remove another component's valid handlers and make failures order-dependent. Targeted removal preserves unrelated subscriptions. When a test retries inside the same session, tear down the first attempt's listeners before registering the next set or events will be duplicated.
Design Handlers for Backpressure and Ordering
The WebSocket can deliver events faster than heavy test-side processing can consume them. Keep handlers limited to validation, redaction, and enqueueing. Apply a bounded queue when event volume is not intrinsically limited, and define whether overflow fails the evidence check or records an incomplete artifact.
Use event fields and a local sequence to establish order within the captured stream. Wall-clock timestamps from different systems are useful for correlation but can drift. Do not assume callbacks execute on the test thread or that a normal ArrayList is safe; use a concurrent collection, queue, or explicit synchronization.
Verify Remote and Grid Support Early
For a remote session, the client reaches the Grid over HTTP while the negotiated BiDi connection may require a WebSocket route through the Grid or provider. A successful Classic session does not prove that route works. Run a small startup probe that negotiates webSocketUrl, registers one harmless event, triggers it, and removes the listener.
If negotiation fails, capture the sanitized new-session response and Grid capability configuration. If the URL is present but connection setup fails, inspect proxy upgrades, TLS trust, provider support, and network reachability. If the connection works but no event arrives, inspect event support, subscription scope, and trigger timing rather than changing browser waits first.
Treat CDP and BiDi as Different Contracts
Chrome DevTools Protocol integration and WebDriver BiDi both expose browser events, but their domain types, browser coverage, and lifecycle APIs differ. Do not label a CDP listener as BiDi because it happens to use a WebSocket. Keep adapters behind an explicit interface if a temporary fallback is required, and report which backend produced the evidence.
When migrating, compare behavior by use case: log capture, request observation, authentication, or script instrumentation. A green CDP test does not establish that the corresponding BiDi subscription has the same event shape or scope. Build a small contract test for each adapter before switching the suite.
Diagnose Subscription Failures Systematically
An absent webSocketUrl is a negotiation problem. A connection exception is a transport or routing problem. Events that appear only on the second run usually indicate registration after the trigger. Duplicate events point to leaked listeners. Events from unrelated tabs indicate scope that is too broad. A handler that stalls the suite points to blocking work on the event-processing path.
Record lifecycle transitions such as bidi-negotiated, listener-added, trigger-started, event-received, and listener-removed. These markers make the missing boundary visible without logging the full socket URL or event payload.
BiDi Subscription Checklist
- Request
webSocketUrlbefore creating the WebDriver session. - Validate the returned capability rather than assuming acceptance.
- Let Selenium manage the WebSocket connection and protocol messages.
- Register listeners before navigation or interaction can emit the event.
- Scope events to browsing contexts when unrelated tabs create noise.
- Hand off callback data through thread-safe, bounded structures.
- Keep callbacks free of WebDriver commands and blocking I/O.
- Retain each listener ID and remove only what the component owns.
- Probe the complete path on every remote environment.
- Distinguish negotiation, transport, subscription, and handler failures.
Make Event Ownership Part of the Test
BiDi becomes dependable when the test can prove five transitions: capability requested, endpoint negotiated, connection available, subscription active before the trigger, and listener removed after the assertion. Encode those transitions in infrastructure and keep asynchronous handlers small. Then browser events become precise evidence instead of timing-sensitive background noise.
// 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
How do I enable WebDriver BiDi in Selenium Java?
Set the standard webSocketUrl capability to true in the browser options before session creation. Then verify that the returned session capabilities contain a WebSocket URL and use Selenium's BiDi APIs.
Should my test open the returned WebSocket URL itself?
Normally no. Selenium's binding manages the connection, command IDs, subscriptions, event mapping, and shutdown. Manual socket code is appropriate only for protocol-level tooling that intentionally bypasses the binding.
Why must a BiDi listener be registered before the browser action?
Events are pushed when they occur and are not replayed for a listener added later. Subscribe first, trigger second, and wait for the specific event or product completion signal.
Can a BiDi subscription be limited to one tab?
Many event APIs accept browsing-context identifiers. In Selenium Java, the current window handle is the corresponding top-level browsing context ID, so scope listeners when unrelated tabs would create noise.
What should remove a BiDi subscription?
The code that created the listener should retain its ID or own the module wrapper, then remove or close it in finally or try-with-resources. Quitting the driver ends the session but is too late for per-test isolation in reused fixtures.
RELATED GUIDES
Continue the learning route
GUIDE 01
Capture Console Messages and JavaScript Errors with Selenium BiDi
Capture Selenium BiDi console messages and JavaScript exceptions with scoped listeners, thread-safe evidence, filtering policy, and failure diagnostics.
GUIDE 02
Intercept Requests and Supply Authentication with Selenium BiDi Network
Use Selenium BiDi network intercepts for scoped Basic or Digest authentication, request control, response evidence, cleanup, and safe diagnostics.
GUIDE 03
Selenium BiDi Script Realms and Preload Scripts for Test Instrumentation
Use Selenium BiDi script realms and preload scripts for isolated instrumentation, test-side channels, typed evaluation, cleanup, and failure analysis.
GUIDE 04
Selenium Grid Tutorial: Run Tests Across Browsers
Selenium Grid tutorial explaining architecture, setup, remote WebDriver, browser capabilities, parallel execution, Docker, CI, and debugging tips.