WebDriver BiDi

Editor’s Draft,

More details about this document
This version:
https://w3c.github.io/webdriver-bidi/
Latest published version:
https://www.w3.org/TR/webdriver-bidi/
Implementation Report:
https://wpt.fyi/results/webdriver/tests/bidi
Test Suite:
https://github.com/web-platform-tests/wpt/tree/master/webdriver/tests/bidi
Feedback:
GitHub
Inline In Spec
Editors:
James Graham (Mozilla)
Alex Rudenko (Google)
Maksim Sadym (Google)
Channel:
#webdriver on irc.w3.org
Wiki:
W3C WebDriver Wiki

Abstract

This document defines the BiDirectional WebDriver Protocol, a mechanism for remote control of user agents.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C standards and drafts index.

GitHub Issues are preferred for discussion of this specification. Alternatively, you can send comments to the Browser Testing and Tools Working Group’s mailing list, public-browser-tools-testing@w3.org (archives).

This document was published by the Browser Testing and Tools Working Group as an Editor’s Draft.

Publication as an Editor’s Draft does not imply endorsement by W3C and its Members.

This is a draft document and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to cite this document as other than a work in progress.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent that the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 18 August 2025 W3C Process Document.

1. Introduction

This section is non-normative.

WebDriver defines a protocol for introspection and remote control of user agents. This specification extends WebDriver by introducing bidirectional communication. In place of the strict command/response format of WebDriver, this permits events to stream from the user agent to the controlling software, better matching the evented nature of the browser DOM.

2. Infrastructure

This specification depends on the Infra Standard. [INFRA]

Network protocol messages are defined using CDDL. [RFC8610]

This specification defines a wait queue which is a map.

Surely there’s a better mechanism for doing this "wait for an event" thing.

When an algorithm algorithm running in parallel awaits a set of events events, and resume id:

  1. Pause the execution of algorithm.

  2. Assert: wait queue does not contain resume id.

  3. Set wait queue[resume id] to (events, algorithm).

To resume given name, id and parameters:
  1. If wait queue does not contain id, return.

  2. Let (events, algorithm) be wait queue[id]

  3. For each event in events:

    1. If event equals name:

      1. Remove id from wait queue.

      2. Resume running the steps in algorithm from the point at which they were paused, passing name and parameters as the result of the await.

        Should we have something like microtasks to ensure this runs before any other tasks on the event loop?

A WebDriver configuration is a struct with:

A WebDriver configuration has an associated type which is a type.

The value for a WebDriver configuration is either a value whose type is the associated type for that configuration or unset.

Unset is a value indicating that a specific configuration value has not been set.

Note: this algorithm allows accessing the WebDriver configuration for a given navigable by checking values in navigables, then in user contexts and finally in global. Returns unset if configuration is not set.

To get WebDriver configuration value of WebDriver configuration configuration for navigable navigable:

  1. Let top-level traversable be navigable’s top-level traversable.

  2. If configuration’s navigables contains top-level traversable:

    1. Let navigable configuration value be configuration’s navigables[top-level traversable].

    2. If navigable configuration value is not unset, return navigable configuration value.

  3. Let user context be navigable’s associated user context.

  4. If configuration’s user contexts contains user context:

    1. Let user context configuration value be configuration’s user contexts[user context].

    2. If user context configuration value is not unset, return user context configuration value.

  5. Return configuration’s global.

Note: this is a generic algorithm for storing WebDriver configuration per target, which can be either navigable, user context, or store it globally if the target is null or omitted.

To store WebDriver configuration configuration’s value value in optional target which is a navigable, a user context or null if not provided:

  1. If target is null, set configuration’s global to value.

  2. If target is a user context, set configuration’s user contexts[target] to value.

  3. If target is a navigable, set configuration’s navigables[target] to value.

Note: This generic algorithm stores WebDriver configuration’s value in global, user contexts, or navigables, depending on the presence of "userContexts" and "contexts" in command parameters. These parameters are mutually exclusive. If neither is provided, the configuration is stored globally.

To store WebDriver configuration WebDriver configuration configuration’s value value for given command parameters:

  1. If command parameters contains "userContexts" and command parameters contains "contexts", return error with error code invalid argument.

  2. Let affected navigables be an empty set.

  3. If command parameters contains "contexts":

    1. Let navigables be the result of trying to get valid top-level traversables by ids with command parameters["contexts"].

    2. For each navigable of navigables:

      1. Append navigable to affected navigables.

      2. Store configuration’s value in navigable.

  4. Otherwise, if command parameters contains "userContexts":

    1. Let user contexts be the result of trying to get valid user contexts with command parameters["userContexts"].

    2. For each user context of user contexts:

      1. For each top-level traversable in the list of all top-level traversables whose associated user context is user context:

        1. Append top-level traversable to affected navigables.

      2. Store configuration’s value in user context.

  5. Otherwise:

    1. For each top-level traversable of all top-level traversables, append top-level traversable to affected navigables.

    2. Store configuration’s value.

  6. Return affected navigables.

3. Protocol

This section defines the basic concepts of the WebDriver BiDi protocol. These terms are distinct from their representation at the transport layer.

The protocol is defined using a CDDL definition. For the convenience of implementers two separate CDDL definitions are defined; the remote end definition which defines the format of messages produced on the local end and consumed on the remote end, and the local end definition which defines the format of messages produced on the remote end and consumed on the local end

3.1. Definition

Should this be an appendix?

This section gives the initial contents of the remote end definition and local end definition. These are augmented by the definition fragments defined in the remainder of the specification.

Remote end definition

Command = {
  id: js-uint,
  CommandData,
  Extensible,
}

CommandData = (
  BrowserCommand //
  BrowsingContextCommand //
  EmulationCommand //
  InputCommand //
  NetworkCommand //
  ScriptCommand //
  SessionCommand //
  StorageCommand //
  WebExtensionCommand
)

EmptyParams = {
   Extensible
}

Local end definition

Message = (
  CommandResponse /
  ErrorResponse /
  Event
)

CommandResponse = {
  type: "success",
  id: js-uint,
  result: ResultData,
  Extensible
}

ErrorResponse = {
  type: "error",
  id: js-uint / null,
  error: ErrorCode,
  message: text,
  ? stacktrace: text,
  Extensible
}

ResultData = (
  BrowserResult /
  BrowsingContextResult /
  EmulationResult /
  InputResult /
  NetworkResult /
  ScriptResult /
  SessionResult /
  StorageResult /
  WebExtensionResult
)

EmptyResult = {
  Extensible
}

Event = {
  type: "event",
  EventData,
  Extensible
}

EventData = (
  BrowsingContextEvent //
  InputEvent //
  LogEvent //
  NetworkEvent //
  ScriptEvent
)

An EmptyResult is a result type with no required fields, used as the return type for commands that don’t produce result data.

Remote end definition and Local end definition

Extensible = (*text => any)

js-int = -9007199254740991..9007199254740991
js-uint = 0..9007199254740991

3.2. Session

WebDriver BiDi extends the session concept from WebDriver.

A session has a BiDi flag, which is false unless otherwise stated.

A BiDi session is a session which has the BiDi flag set to true.

The list of active BiDi sessions is given by:
  1. Let BiDi sessions be a new list.

  2. For each session in active sessions:

    1. If session is a BiDi session append session to BiDi sessions.

  3. Return BiDi sessions.

3.3. Modules

The WebDriver BiDi protocol is organized into modules.

Each module represents a collection of related commands and events pertaining to a certain aspect of the user agent. For example, a module might contain functionality for inspecting and manipulating the DOM, or for script execution.

Each module has a module name which is a string. The command name and event name for commands and events defined in the module start with the module name followed by a period ".".

Modules which contain commands define remote end definition fragments. These provide choices in the CommandData group for the module’s commands, and can also define additional definition properties. They can also define local end definition fragments that provide additional choices in the ResultData group for the results of commands in the module.

Modules which contain events define local end definition fragments that are choices in the Event group for the module’s events.

An implementation may define extension modules. These must have a module name that contains a single colon ":" character. The part before the colon is the prefix; this is typically the same for all extension modules specific to a given implementation and should be unique for a given implementation.

Other specifications may define their own WebDriver-BiDi modules that extend the protocol. Such modules must not have a name which contains a colon (:) character, nor must they define command names, event names, or property names that contain that character.

Authors of external specifications are encouraged to to add new modules rather than extending existing ones. Where it is desired to extend an existing module, it is preferred to integrate the extension directly into the specification containing the original module definition.

3.4. Commands

A command is an asynchronous operation, requested by the local end and run on the remote end, resulting in either a result or an error being returned to the local end. Multiple commands can run at the same time, and commands can potentially be long-running. As a consequence, commands can finish out-of-order.

Each command is defined by:

A command that can run without an active session is a static command. Commands are not static commands unless stated in their definition.

When commands are sent from the local end they have a command id. This is an identifier used by the local end to identify the response from a particular command. From the point of view of the remote end this identifier is opaque and cannot be used internally to identify the command.

Note: This is because the command id is entirely controlled by the local end and isn’t necessarily unique over the course of a session. For example a local end which ignores all responses could use the same command id for each command.

The set of all command names is a set containing all the defined command names, including any belonging to extension modules.

3.5. Errors

WebDriver BiDi extends the set of error codes from WebDriver with the following additional codes:

invalid web extension
Tried to install an invalid web extension.
no such client window
Tried to interact with an unknown client window.
no such handle
Tried to deserialize an unknown RemoteObjectReference.
no such history entry
Tried to havigate to an unknown session history entry.
no such network collector
Tried to remove an unknown collector.
no such intercept
Tried to remove an unknown network intercept.
no such network data
Tried to reference an unknown network data.
no such node
Tried to deserialize an unknown SharedReference.
no such request
Tried to continue an unknown request.
no such screencast
Tried to stop an unknown screencast recording.
no such script
Tried to remove an unknown preload script.
no such storage partition
Tried to access data in a non-existent storage partition.
no such user context
Tried to reference an unknown user context.
no such web extension
Tried to reference an unknown web extension.
unable to close browser
Tried to close the browser, but failed to do so.
unable to set cookie
Tried to create a cookie, but the user agent rejected it.
underspecified storage partition
Tried to interact with data in a storage partition which was not adequately specified.
unable to set file input
Tried to set a file input, but failed to do so.
unavailable network data
Tried to get network data which was not collected or already evicted.
ErrorCode = "invalid argument" /
            "invalid selector" /
            "invalid session id" /
            "invalid web extension" /
            "move target out of bounds" /
            "no such alert" /
            "no such network collector" /
            "no such element" /
            "no such frame" /
            "no such handle" /
            "no such history entry" /
            "no such intercept" /
            "no such network data" /
            "no such node" /
            "no such request" /
            "no such screencast" /
            "no such script" /
            "no such storage partition" /
            "no such user context" /
            "no such web extension" /
            "session not created" /
            "unable to capture screen" /
            "unable to close browser" /
            "unable to set cookie" /
            "unable to set file input" /
            "unavailable network data" /
            "underspecified storage partition" /
            "unknown command" /
            "unknown error" /
            "unsupported operation"

3.6. Events

An event is a notification, sent by the remote end to the local end, signaling that something of interest has occurred on the remote end.

A BiDi session has subscriptions which is a list of subscriptions.

A BiDi session has a known subscription ids which is a set of all subscription ids that have been issued to the local end but which have not yet been unsubscribed.

A subscription is a struct consisting of a subscription id (a string), event names (a set of event names), top-level traversable ids (a set of IDs of top-level traversables) and user context ids (a set of IDs of user contexts).

A subscription subscription is global if subscription’s top-level traversable ids is an empty set and subscription’s user context ids is an empty set.

The set of sessions for which an event is enabled given event name and navigables is:

  1. Let sessions be a new set.

  2. For each session in active BiDi sessions:

    1. If event is enabled with session, event name and navigables, append session to sessions.

  3. Return sessions.

To determine if an event is enabled given session, event name and navigables:

Note: navigables is a set because a shared worker can be associated with multiple contexts.

  1. Let top-level traversables be get top-level traversables with navigables.

  2. For each subscription in session’s subscriptions:

    1. If subscription’s event names do not contains event name, continue.

    2. If subscription is global return true.

    3. If user context ids is not empty:

      1. For each navigable in top-level traversables:

        1. If subscription’s user context ids contains navigable’s associated user context’s user context id, return true.

    4. Otherwise:

      1. Let subscription top-level traversables be get navigables by ids with subscription’s top-level traversable ids.

      2. If the intersection of top-level traversables and subscription top-level traversables is not empty return true.

  3. Return false.

The set of top-level traversables for which an event is enabled given event name and session is:

  1. Let result be a new set.

  2. For each subscription in session’s subscriptions:

    1. If subscription’s event names does not contain event name, continue.

    2. If subscription’s is global:

      1. For each traversable in remote end’s top-level traversables:

        1. Append traversable to result.

      2. Break.

    3. Otherwise, if user context ids is not empty:

      1. For each traversable in remote end’s top-level traversables:

        1. Append traversable to result if subscription’s user context ids contains traversable’s associated user context’s user context id.

    4. Otherwise:

      1. Let top-level traversables be get navigables by ids with subscription’s top-level traversable ids.

      2. Append each item of top-level traversables to result.

  3. Return result.

To obtain a set of event names given a name:
  1. Let events be an empty set.

  2. If name contains a U+002E (period):

    1. If name is the event name for an event, append name to events and return success with data events.

    2. Return an error with error code invalid argument

  3. Otherwise name is interpreted as representing all the events in a module. If name is not a module name return an error with error code invalid argument.

  4. Append the event name for each event in the module with name name to events.

  5. Return success with data events.

4. Transport

Message transport is provided using the WebSocket protocol. [RFC6455]

Note: In the terms of the WebSocket protocol, the local end is the client and the remote end is the server / remote host.

Note: The encoding of commands and events as messages is similar to JSON-RPC, but this specification does not normatively reference it. [JSON-RPC] The normative requirements on remote ends are instead given as a precise processing model, while no normative requirements are given for local ends.

A WebSocket listener is a network endpoint that is able to accept incoming WebSocket connections.

A WebSocket listener has a host, a port, a secure flag, and a list of WebSocket resources.

When a WebSocket listener listener is created, a remote end must start to listen for WebSocket connections on the host and port given by listener’s host and port. If listener’s secure flag is set, then connections established from listener must be TLS encrypted.

A remote end has a set of WebSocket listeners active listeners, which is initially empty.

A remote end has a set of WebSocket connections not associated with a session, which is initially empty.

A WebSocket connection is a network connection that follows the requirements of the WebSocket protocol

A BiDi session has a set of session WebSocket connections whose elements are WebSocket connections. This is initially empty.

A BiDi session session is associated with connection connection if session’s session WebSocket connections contains connection.

Note: Each WebSocket connection is associated with at most one BiDi session.

When a client establishes a WebSocket connection connection by connecting to one of the set of active listeners listener, the implementation must proceed according to the WebSocket server-side requirements, with the following steps run when deciding whether to accept the incoming connection:

  1. Let resource name be the resource name from reading the client’s opening handshake. If resource name is not in listener’s list of WebSocket resources, then stop running these steps and act as if the requested service is not available.

  2. If resource name is the byte string "/session", and the implementation supports BiDi-only sessions:

    1. Run any other implementation-defined steps to decide if the connection should be accepted, and if it is not stop running these steps and act as if the requested service is not available.

    2. Add the connection to WebSocket connections not associated with a session.

    3. Return.

  3. Get a session ID for a WebSocket resource with resource name and let session id be that value. If session id is null then stop running these steps and act as if the requested service is not available.

  4. If there is a session in the list of active sessions with session id as its session ID then let session be that session. Otherwise stop running these steps and act as if the requested service is not available.

  5. Run any other implementation-defined steps to decide if the connection should be accepted, and if it is not stop running these steps and act as if the requested service is not available.

  6. Otherwise append connection to session’s session WebSocket connections, and proceed with the WebSocket server-side requirements when a server chooses to accept an incoming connection.

Do we support > 1 connection for a single session?

When a WebSocket message has been received for a WebSocket connection connection with type type and data data, a remote end must handle an incoming message given connection, type and data.

When the WebSocket closing handshake is started or when the WebSocket connection is closed for a WebSocket connection connection, a remote end must handle a connection closing given connection.

Note: Both conditions are needed because it is possible for a WebSocket connection to be closed without a closing handshake.

To construct a WebSocket resource name given a session session:

  1. If session is null, return "/session"

  2. Return the result of concatenating the string "/session/" with session’s session ID.

To construct a WebSocket URL given a WebSocket listener listener and session session:

  1. Let resource name be the result of construct a WebSocket resource name with session.

  2. Return a WebSocket URI constructed with host set to listener’s host, port set to listener’s port, path set to resource name, following the wss-URI construct if listener’s secure flag is set and the ws-URL construct otherwise.

To get a session ID for a WebSocket resource given resource name:

  1. If resource name doesn’t begin with the byte string "/session/", return null.

  2. Let session id be the bytes in resource name following the "/session/" prefix.

  3. If session id is not the string representation of a UUID, return null.

  4. Return session id.

To start listening for a WebSocket connection given a session session:
  1. If there is an existing WebSocket listener in active listeners which the remote end would like to reuse, let listener be that listener. Otherwise let listener be a new WebSocket listener with implementation-defined host, port, secure flag, and an empty list of WebSocket resources.

  2. Let resource name be the result of construct a WebSocket resource name with session.

  3. Append resource name to the list of WebSocket resources for listener.

  4. Append listener to the remote end’s active listeners.

  5. Return listener.

Note: An intermediary node handling multiple sessions can use one or many WebSocket listeners. WebDriver defines that an endpoint node supports at most one session at a time, so it’s expected to only have a single listener.

Note: For an endpoint node the host in the above steps will typically be "localhost".

To handle an incoming message given a WebSocket connection connection, type type and data data:
  1. If type is not text, send an error response given connection, null, and invalid argument, and finally return.

  2. Assert: data is a scalar value string, because the WebSocket handling errors in UTF-8-encoded data would already have failed the WebSocket connection otherwise.

    Nothing seems to define what status code is used for UTF-8 errors.

  3. If there is a BiDi Session associated with connection connection, let session be that session. Otherwise if connection is in WebSocket connections not associated with a session, let session be null. Otherwise, return.

  4. Let parsed be the result of parsing JSON into Infra values given data. If this throws an exception, then send an error response given connection, null, and invalid argument, and finally return.

  5. If session is not null and not in active sessions then return.

  6. Match parsed against the remote end definition. If this results in a match:

    1. Let matched be the map representing the matched data.

    2. Assert: matched contains "id", "method", and "params".

    3. Let command id be matched["id"].

    4. Let method be matched["method"]

    5. Let command be the command with command name method.

    6. If session is null and command is not a static command, then send an error response given connection, command id, and invalid session id, and return.

    7. Run the following steps in parallel:

      1. Let result be the result of running the remote end steps for command given session and command parameters matched["params"]

      2. If result is an error, then send an error response given connection, command id, and result’s error code, and finally return.

      3. Let value be result’s data.

      4. Assert: value matches the definition for the result type corresponding to the command with command name method.

      5. If method is "session.new", let session be the entry in the list of active sessions whose session ID is equal to the "sessionId" property of value, append connection to session’s session WebSocket connections, and remove connection from the WebSocket connections not associated with a session.

      6. Let response be a new map matching the CommandResponse production in the local end definition with the id field set to command id and the value field set to value.

      7. Let serialized be the result of serialize an infra value to JSON bytes given response.

      8. Send a WebSocket message comprised of serialized over connection.

  7. Otherwise:

    1. Let command id be null.

    2. If parsed is a map and parsed["id"] exists and is an integer greater than or equal to zero, set command id to that integer.

    3. Let error code be invalid argument.

    4. If parsed is a map and parsed["method"] exists and is a string, but parsed["method"] is not in the set of all command names, set error code to unknown command.

    5. Send an error response given connection, command id, and error code.

To get related navigables given an settings object settings:

  1. Let related navigables be an empty set.

  2. If settingsrelevant global object is a Window:

    1. Let navigable be relevant global object’s associated Document’s node navigable.

    2. If navigable is not null, append navigable to related navigables.

  3. Otherwise if the global object specified by settings is a WorkerGlobalScope, for each owner in the global object’s owner set:

    1. Let navigable be null.

    2. If owner is a Document, set navigable to owner’s node navigable.

    3. If navigable is not null, append navigable to related navigables.

  4. Return related navigables.

To get navigables by ids given a list of context ids navigable ids:

  1. Let result be an empty set.

  2. For each navigable id in navigable ids:

    1. Let navigable be the navigable with id navigable id if such navigable exists, and null otherwise.

    2. Append navigable to result if navigable is not null.

  3. Return result.

To get top-level traversables given a list of navigables navigables:

  1. Let result be an empty set.

  2. For each navigable in navigables:

    1. Append navigable’s top-level traversable to result.

  3. Return result.

To get valid navigables by ids given a list of context ids navigable ids:

  1. Let result be an empty set.

  2. For each navigable id in navigable ids:

    1. Let navigable be the result of trying to get a navigable with navigable id.

    2. Append navigable to result.

  3. Return success with data result.

To get valid top-level traversables by ids given a list of context ids navigable ids:

  1. Let result be an empty set.

  2. For each navigable id in navigable ids:

    1. Let navigable be the result of trying to get a navigable with navigable id.

    2. If navigable is not a top-level traversable, return error with error code invalid argument.

    3. Append navigable to result.

  3. Return success with data result.

To emit an event given session, and body:
  1. Assert: body matches the Event production.

  2. Let serialized be the result of serialize an infra value to JSON bytes given body.