WebDriver BiDi Events for Stable Browser Automation
Understand WebDriver BiDi sessions, events, transport, and bounded assertions for maintainable browser automation.
Want the structured docs for Getting Started?
This article lives in the editorial library. For step-by-step setup, reference material, and ongoing updates, jump into the docs section.
WebDriver BiDi gives browser automation a standard channel in both directions. A client can send a command while the browser can publish an event, so a test can observe navigation, log, network, and browsing-context changes without treating every transition as a guessed delay. The protocol is useful when the test needs a precise observation boundary, but an event is still evidence from the browser rather than proof that an application or service accepted an operation.
The practical design is to treat a BiDi run as a small system with a negotiated session, an owned transport, explicit subscriptions, and user-visible assertions. These boundaries make failure handling easier to classify. The Playwright getting-started guide gives related context for browser lifecycle decisions, while browser interaction validation covers evidence that sits beyond a protocol event.
The distinction matters for ordinary maintenance. A test can observe a document navigation, a console message, or a request response while the application is still deciding what to show. A driver can return a successful command response even when a later component rejects the submitted data. By naming the browser observation and the application outcome separately, a team can change a page implementation without weakening the assertion that protects the user journey. The same separation helps reviewers decide whether a failure belongs to the page, the driver, the transport, or the service behind the page.
What WebDriver BiDi changes
Classic WebDriver is request oriented: the client sends a command and receives a response. That model remains valuable for actions and assertions, but it can be awkward when a test needs to learn that a browsing context was created, a log entry appeared, or a network activity changed while another command was in flight. BiDi adds a long-lived, bidirectional connection so the browser can send an event independently of a new client request. The W3C WebDriver BiDi specification defines the protocol vocabulary and the relationship between commands, events, and browsing contexts.
An event does not replace an assertion. A log.entryAdded notification can tell a test that the browser observed a console entry, but it does not say that a server stored a record or that a user-visible workflow succeeded. A network event can identify a request or response category, yet the page may still render an error state. Pair each event with the smallest assertion that represents the intended outcome, and keep the event as supporting evidence rather than a shortcut to business success.
BiDi also separates observation from implementation detail. The client subscribes to named event kinds and can scope a subscription to a browsing context. This is clearer than collecting every message from a driver and searching a large log later. Scope does not create a privacy boundary by itself: an authorized test still needs a data policy for URLs, headers, console arguments, and page content. Capture the metadata needed to diagnose the scenario and avoid retaining secrets merely because an event carries them.
The protocol is evolving, and browser or driver support can differ by command and event. A session that connects successfully may still reject a command, omit an optional field, or close when a feature is unavailable. Record the negotiated capabilities and the implementation versions with the run. A compatibility check should classify unsupported protocol behavior separately from an application failure, so a missing event does not become a misleading timeout.
Negotiate a session you can own
Session creation is the point where a client declares what it expects and receives the identifiers it must use later. Keep the session configuration close to the test fixture: browser preferences, proxy or profile inputs, logging policy, and the requested capabilities should be reviewable as one unit. A capability request is not a promise that the browser will implement every requested feature. Read the response, preserve the accepted values, and make later decisions from that result rather than from the original wish list.
Browsing contexts are another ownership boundary. A top-level tab, a child frame, and a newly opened window can each have a context identifier, and events may include that identifier. Store the identifiers when the fixture creates or discovers them. When a test has several contexts, route an event to the case that owns its context and ignore unrelated notifications. This avoids a race in which a background tab satisfies a condition intended for the active page.
The fixture should define when a session is ready and when it is no longer usable. Readiness can include a successful protocol handshake, a known browsing context, and a subscription acknowledgement where the implementation exposes one. Shutdown should stop new actions, close or detach event listeners, request the supported session-ending operation, and then close the transport. Cleanup in a finally path matters because an event callback can fail independently of the command that started it.
Keep session setup deterministic for repeatable tests. Give each worker an owned profile and artifact location, and record the browser, driver, and protocol versions. Do not reuse an unknown session left by a previous run. The Selenium BiDirectional documentation describes the client-facing direction of this model, while the application team still owns its test data, server cleanup, and retention decisions.
Subscribe to events without losing context
Subscriptions should be as narrow as the question being answered. If a test waits for a page error, subscribe to the relevant log event and filter it by the context under test. If it needs a navigation milestone, subscribe to browsing-context events and retain the context identifier, URL category, and timestamp that the test policy allows. A narrow subscription reduces callback work and makes a failure record explainable. It also limits accidental capture of activity from other pages in the same session.
An event handler should be side-effect free whenever possible. It can append a bounded record to an in-memory queue, resolve a wait for a named condition, or update a state machine. It should not click a control, submit a form, mutate application storage, or start a second session while processing a notification. Those actions can make one browser event cause multiple user actions and can create re-entrant races that are hard to reproduce.
Event order is meaningful only within the guarantees stated by the protocol and the implementation. A request event may arrive before a page has displayed its result, and a log event may be delivered after the action that caused it has returned. Use event data to mark an observation, then wait for the user-facing state that ends the journey. If several outcomes are valid, define the alternatives explicitly, classify the first named outcome, and assert the corresponding result rather than accepting an arbitrary message.
Late events are normal during teardown. Mark the session as closing before releasing listeners, and have callbacks discard messages that arrive after the test has reached its terminal state. A bounded queue should report overflow as a diagnostic condition instead of silently dropping the oldest record. When a subscription is rejected, record the rejected event name and continue only if the test has an approved fallback; otherwise fail with an unsupported-capability category.
Treat the transport as a lifecycle
The BiDi transport is a long-lived connection, not a series of independent HTTP requests. It carries commands, responses, and unsolicited events, so the client must correlate a command response with its request identifier while dispatching events to their subscribers. Keep that correlation inside the protocol client or library. Test code should receive a named result or event record, not parse raw frames in every test case.
Connection loss can occur while a command is pending or while the browser is emitting events. Distinguish a clean session close, a transport error, a browser crash, and an application timeout in the final diagnostic. A reconnect is not automatically safe: repeating a command may duplicate a submission or create a second window. Retry only an idempotent setup operation under an explicit policy, and create a new owned session when the old session can no longer prove its state.
Backpressure deserves an operational policy. A page that emits many console entries or network events can fill an unbounded queue and obscure the event that matters. Filter at subscription time when possible, cap retained records, and include a dropped-record indicator in the result. Redact values before they reach durable artifacts, especially for URLs with query data, request headers, console arguments, and page snippets. The browser protocol makes data available; it does not decide what a team may retain.
Transport timeouts should identify which layer stopped making progress. A command response timeout, an event wait timeout, and a page readiness timeout mean different things. Include the command or event name, context identifier when allowed, connection state, and last observed category in the failure. Do not turn every disconnect into a generic page timeout, because the recovery path for a driver failure differs from the recovery path for an application that never rendered its result.
Build stable event-driven assertions
Begin a test with a declared outcome and a small state model. For an authorized checkout journey, the model might include a synthetic cart, a submitted handoff, a visible order status, and a bounded failure record. BiDi can observe a navigation or response along the path, but the stable assertion is the status that a user can read. Naming the outcome first prevents the test from becoming a collection of convenient protocol signals.
Use one wait per transition and give it a semantic name. A wait for contextCreated should end when the expected context appears; a separate wait for a confirmation heading should end when the page exposes that result. Keep predicates read-only and make their deadlines part of environment configuration. When a deadline changes, preserve the condition name in the report so a slower approved environment does not hide a changed application contract.
Failure artifacts should preserve the first missing condition. Record the accepted capabilities, action name, event category, context ownership, browser and driver versions, and a short redacted observation. A screenshot or HTML fragment may be appropriate for an approved synthetic fixture, but an authenticated page dump is not required simply because BiDi can expose page-related events. The Puppeteer BrowserContext lifecycle guide offers a related model for keeping browser state and cleanup ownership explicit.
Exercise negative paths deliberately. Withhold a readiness signal in a synthetic fixture and confirm that the test reports a bounded timeout naming that signal. Simulate a rejected subscription or a closed transport and verify that the result is classified as an environment or capability problem, not as a successful user journey. This rehearsal tests the diagnostic surface without collecting private data or relying on a production account.
Use BotBrowser with clear boundaries
BotBrowser can provide controlled isolated browser contexts and repeatable profile inputs for authorized automation runs that observe WebDriver BiDi behavior. That capability helps a team compare the same session inputs across runs and keep profile ownership explicit while it evaluates event delivery. BotBrowser does not implement the WebDriver BiDi protocol, add unsupported commands or events, or guarantee driver, browser, transport, and application compatibility. The protocol implementation, event support, and server-side outcome remain responsibilities of the selected browser stack and application.
Isolation is useful for keeping synthetic storage and session state separate, but it does not make a test authorized by itself. Define which pages, data, and event fields the run may access. Keep profile directories and artifacts owned by the worker, close the session through its supported lifecycle, and remove only data that the test policy permits. A controlled starting context improves repeatability; it does not guarantee that an event arrives, that a service accepts a request, or that a browser release preserves an optional feature.
When a run uses BotBrowser alongside a BiDi-capable driver, record the actual driver and browser combination and the accepted session capabilities. If a command or event is missing, report the compatibility boundary with the implementation that rejected it. Do not attribute a protocol limitation to profile isolation, and do not treat a repeatable profile as evidence that an application completed its server transaction. These distinctions keep product validation separate from protocol conformance.
A review checklist for BiDi teams
Before a run, confirm that the session owner, profile input, transport, requested capabilities, and event subscriptions are named. Choose the browsing context that owns each assertion and define what data may enter logs. Make the user-visible completion state explicit, then list any protocol events that support that assertion. This preparation turns a connection into a reviewable test fixture rather than an opaque background service.
During a run, correlate every command response with its request and route every event through a context-aware handler. Keep handlers bounded and side-effect free. Classify unsupported commands, disconnects, queue overflow, and page timeouts separately. If a retry is allowed, state why it is safe and create a new owned session when the prior state cannot be proved. Never let an event callback silently repeat a user action.
After a run, close listeners and the session even when an assertion fails. Retain only the approved synthetic evidence, including the first missing condition and the accepted capability set. Compare results by outcome and compatibility tuple rather than by incidental event timing. When a browser or driver changes, rerun the capability and event checks before changing page wait budgets.
The durable test contract is simple: BiDi carries commands and observations, the fixture owns session and transport lifecycle, and the application defines what completion means. With those responsibilities separated, an event can make a failure more precise without being mistaken for a business guarantee. That is the basis for stable automation across ordinary browser, driver, and application changes.
Teams can make the contract visible in code review by keeping a small record beside each journey. Name the command that starts a transition, the event that helps observe it, the context that owns the observation, and the assertion that a user would recognize. State the approved fallback when a browser does not expose the requested event. This record does not need raw protocol frames. It needs enough context for another maintainer to tell whether a changed result reflects a page regression, an unsupported capability, or a lost connection. Keeping that decision close to the test also prevents a future timeout increase from quietly changing the meaning of completion.
The record should include the cleanup obligation as well. A test that opens a child context should say who closes it; a subscription should say when it is removed; and a retained artifact should name its synthetic data boundary. These details are small, but they prevent late notifications from being attributed to a new test and stop a failed run from leaving a profile that changes the next run. BiDi supplies the mechanism for observation, while the fixture and application owners supply the policy that makes the observation safe to use.
Sources
Related Articles
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.