Principle:Webdriverio Webdriverio WebDriver Bidi Protocol
| Knowledge Sources | |
|---|---|
| Domains | Bidi_Protocol, WebDriver |
| Last Updated | 2026-02-12 00:00 GMT |
Overview
Enabling bidirectional, real-time communication between a test framework and a browser through a persistent WebSocket connection for event subscription, script evaluation, and network interception.
Description
Traditional WebDriver communication follows a strict request-response model over HTTP, where the test framework sends a command and waits for the browser to return a result. The WebDriver Bidi Protocol introduces a bidirectional channel over WebSocket that allows the browser to push events to the test framework in real time, without polling. This enables capabilities such as subscribing to console log events, intercepting network requests before they are sent, evaluating JavaScript in specific browsing contexts, and receiving DOM mutation notifications. The protocol defines a typed message format with command, result, and event message types, each carrying structured payloads. The framework must manage connection lifecycle, message routing, subscription state, and type-safe deserialization of both local and remote object references.
Usage
This principle applies when tests need to observe or intercept browser behavior that is not accessible through traditional WebDriver commands. It is the right choice for scenarios such as: capturing browser console output in real time, intercepting and modifying network requests (for mocking or authentication injection), listening for page lifecycle events (navigation, load, unload), evaluating scripts in specific frames or workers, and receiving notifications about dialog appearances or download starts. It is essential for modern testing scenarios that require low-latency, event-driven interaction with the browser.
Theoretical Basis
The Bidi protocol is built on a publish-subscribe messaging pattern over a persistent WebSocket connection:
- Connection establishment: During session creation, the browser advertises a WebSocket URL. The framework opens a persistent connection to this endpoint, which remains active for the duration of the session.
- Message framing: All messages are JSON-encoded and follow a discriminated union structure:
- Command messages (client to browser): Carry a unique numeric
id, amethodstring (e.g.,script.evaluate,network.addIntercept), and aparamsobject. - Result messages (browser to client): Echo the command
idand carry either aresultobject or anerrorobject. - Event messages (browser to client): Carry a
methodstring andparamsobject, but noid, representing asynchronous notifications.
- Command messages (client to browser): Carry a unique numeric
- Command correlation: Each outgoing command is assigned a monotonically increasing ID. The framework maintains a map of pending command IDs to promise resolvers, enabling async/await style usage. When a result message arrives, its ID is used to look up and resolve the corresponding promise.
- Event subscription: The framework sends
session.subscribecommands specifying which event categories to receive (e.g.,log.entryAdded,network.beforeRequestSent). The browser then pushes matching events as they occur. Event handlers are registered locally and dispatched based on the event method name.
- Type system: The protocol defines local types (values sent from client to browser, such as script source and arguments) and remote types (values received from browser, such as remote object references with handles for later interaction). This distinction ensures type safety and prevents accidental misuse of browser-internal references.
Pseudocode for Bidi message handling:
function sendCommand(method, params):
id = nextCommandId++
promise = createDeferredPromise()
pendingCommands[id] = promise
websocket.send(serialize({id, method, params}))
return promise
function onMessage(raw):
message = deserialize(raw)
if message has id:
pendingCommands[message.id].resolve(message.result)
delete pendingCommands[message.id]
else:
eventEmitter.emit(message.method, message.params)