If you are viewing this on GitHub, the source was moved to Chromium. Please contribute there and file issues on
crbug.com.
WebDriver BiDi for Chromium 
This is an implementation of the WebDriver BiDi protocol with some extensions (BiDi+) for Chromium, implemented as a JavaScript layer translating between BiDi and CDP, running inside a Chrome tab.
Current status can be checked at WPT WebDriver BiDi status.
Performance Benchmarks
The project continuously monitors the performance and overhead of the WebDriver BiDi implementation.
- Dashboard: Chromium-BiDi Performance Benchmarks
- Details: Refer to docs/benchmark.md for detailed information about the benchmarking infrastructure, methodology, and statistical analysis.
Note that performance data can be sensitive to CI environment fluctuations, especially on macOS.
BiDi+
"BiDi+" is an extension of the WebDriver BiDi protocol. In addition to WebDriver BiDi it has:
Command goog:cdp.sendCommand
CdpSendCommandCommand = {
method: "goog:cdp.sendCommand",
params: CdpSendCommandParameters,
}
CdpSendCommandParameters = {
method: text,
params: any,
session?: text,
}
CdpSendCommandResult = {
result: any,
session: text,
}
The command runs the described CDP command and returns the result.
Command goog:cdp.getSession
CdpGetSessionCommand = {
method: "goog:cdp.getSession",
params: CdpGetSessionParameters,
}
CdpGetSessionParameters = {
context: BrowsingContext,
}
CdpGetSessionResult = {
session: text,
}
The command returns the default CDP session for the selected browsing context.
Command goog:cdp.resolveRealm
CdpResolveRealmCommand = {
method: "goog:cdp.resolveRealm",
params: CdpResolveRealmParameters,
}
CdpResolveRealmParameters = {
realm: Script.Realm,
}
CdpResolveRealmResult = {
executionContextId: text,
}
The command returns resolves a BiDi realm to its CDP execution context ID.
Events goog:cdp
CdpEventReceivedEvent = {
method: "goog:cdp.<CDP Event Name>",
params: CdpEventReceivedParameters,
}
CdpEventReceivedParameters = {
event: text,
params: any,
session: text,
}
The event contains a CDP event.
Field goog:channel
Each command can be extended with a goog:channel:
Command = {
id: js-uint,
"goog:channel"?: text,
CommandData,
Extensible,
}
If provided and non-empty string, the very same goog:channel is added to the response:
CommandResponse = {
id: js-uint,
"goog:channel"?: text,
result: ResultData,
Extensible,
}
ErrorResponse = {
id: js-uint / null,
"goog:channel"?: text,
error: ErrorCode,
message: text,
?stacktrace: text,
Extensible
}
When client uses
commands session.subscribe
and session.unsubscribe
with goog:channel, the subscriptions are handled per channel, and the corresponding
goog:channel filed is added to the event message:
Event = {
"goog:channel"?: text,
EventData,
Extensible,
}
Dev Setup
All commands below are intended to be run from the third_party/chromium-bidi directory unless specified otherwise.
Build Setup
The project uses Chromium build toolchains (gn and ninja/autoninja) for compiling TypeScript and bundling.
- Fetch the toolchains and sync dependencies using
gclient(requiresdepot_toolsinstalled and in your PATH):gclient sync - Generate the Ninja build configuration:
gn gen --root=../.. ../../out/Default - Build the project:
To build the test runner targets as well:autoninja -C ../../out/Default third_party/chromium-bidi:defaultautoninja -C ../../out/Default third_party/chromium-bidi:webdriver_bidi_unittests third_party/chromium-bidi:webdriver_bidi_e2e_tests
Code Formatting & Linting
We use a suite of tools to format and lint the codebase:
- keep-sorted to automatically sort lists, imports, and keys.
- ESLint to lint JavaScript and TypeScript files.
- Prettier to format JavaScript, TypeScript, JSON, and Markdown files.
- Ruff to lint and format Python files.
Running Presubmit Checks
Presubmit checks run automatically before upload/commit and can be executed manually via:
git cl presubmit
Auto-formatting Code
git cl formatformats Python and C++ files, but does not format TypeScript/JavaScript files. Use Prettier and ESLint for TypeScript and JavaScript code.
To auto-format and lint files:
- JavaScript / TypeScript / JSON / Markdown (Prettier):
./tools/node.py node_modules/prettier/bin/prettier.cjs --cache --write . - JavaScript / TypeScript (ESLint auto-fix):
./tools/node.py node_modules/eslint/bin/eslint.js --cache --fix . - Python (Ruff / git cl format):
(or viaruff check --fix . && ruff format .git cl format --python) - keep-sorted:
(or in git:find src tests docs examples -type f | xargs keep-sorted --mode=fixgit ls-files | xargs keep-sorted --mode=fix)
Starting WebDriver BiDi Server
ChromeDriver is used as the WebDriver BiDi server.
First, build the targets:
autoninja -C ../../out/Default third_party/chromium-bidi:default chrome/test/chromedriver:chromedriver_server
Run the server:
./tools/run_bidi_server.py --gen-dir ../../out/Default/gen/third_party/chromium-bidi
By default, the server runs on port 8080. Use the PORT= environment variable or --port= argument to run it on another port:
PORT=8081 ./tools/run_bidi_server.py --gen-dir ../../out/Default/gen/third_party/chromium-bidi
./tools/run_bidi_server.py --gen-dir ../../out/Default/gen/third_party/chromium-bidi --port=8081
Running
Testing in Chromium uses GN script_test targets:
third_party/chromium-bidi:webdriver_bidi_unitteststhird_party/chromium-bidi:webdriver_bidi_e2e_tests
When built, these targets produce executable runner wrappers in the build directory (out/Default/bin/).
Unit tests
First, build the unit test target:
autoninja -C ../../out/Default third_party/chromium-bidi:webdriver_bidi_unittests
Run all unit tests:
../../out/Default/bin/run_webdriver_bidi_unittests
Filter unit tests by test name:
../../out/Default/bin/run_webdriver_bidi_unittests -- --test-name-pattern="<test_name>"
Filter unit tests by file path:
../../out/Default/bin/run_webdriver_bidi_unittests -- --test-path-pattern="<path_pattern>"
Filter unit tests using ResultDB / Chromium test filter (--test-filter / --isolated-script-test-filter / --gtest_filter):
../../out/Default/bin/run_webdriver_bidi_unittests --test-filter=':chromium-bidi!mocha:src/utils/:assert.test.ts#assert:should not throw an error when the predicate is truthy'
Multiple tests can be separated with :: or ::
../../out/Default/bin/run_webdriver_bidi_unittests --test-filter=':chromium-bidi!mocha:src/utils/:assert.test.ts#assert:should not throw an error when the predicate is truthy:::chromium-bidi!mocha:src/utils/:DefaultMap.test.ts#DefaultMap:sets and gets properly'
Or using filter files (--test-filter-file / --isolated-script-test-filter-file):
../../out/Default/bin/run_webdriver_bidi_unittests --test-filter-file=path/to/filter_file.txt
When running in
zshor other shells that interpret!,#, or[/], make sure to wrap the filter argument in single quotes'...'to prevent history expansion (event not found) or glob expansion (no matches found).When running from the Chromium repository root (
src/), useout/Default/bin/run_webdriver_bidi_unittests.
E2E tests
The e2e tests serve the following purposes:
- Brief checks of the scenarios (the detailed check is done in WPT)
- Test Chromium-specific behavior nuances
- Add a simple setup for engaging the specific command
The E2E tests are written using Python (pytest), in order to more-or-less align with the web-platform-tests.
Python dependencies are managed automatically via vpython3 (part of depot_tools).
Running
First, build the e2e test target:
autoninja -C ../../out/Default third_party/chromium-bidi:webdriver_bidi_e2e_tests
The E2E tests automatically start and connect to the BiDi server.
Run all E2E tests:
../../out/Default/bin/run_webdriver_bidi_e2e_tests
Filter E2E tests using ResultDB / Chromium test filter (--test-filter / --isolated-script-test-filter / --gtest_filter):
../../out/Default/bin/run_webdriver_bidi_e2e_tests --test-filter=':chromium-bidi!pytest:tests/bluetooth/:test_characteristic_emulation.py#test_bluetooth_add_same_characteristic_uuid_twice'
Legacy pytest node IDs, wildcards (*), and multiple :: or : separated test IDs are also supported:
../../out/Default/bin/run_webdriver_bidi_e2e_tests --isolated-script-test-filter='tests/bluetooth/test_characteristic_emulation.py::test_bluetooth_add_same_characteristic_uuid_twice::tests/browser/test_create_user_context.py::test_browser_create_user_context_proxy[True]'
Or using filter files (--test-filter-file / --isolated-script-test-filter-file):
../../out/Default/bin/run_webdriver_bidi_e2e_tests --test-filter-file=path/to/filter_file.txt
When running in
zshor other shells that interpret!,#, or[/], make sure to wrap the filter argument in single quotes'...'to prevent history expansion (event not found) or glob expansion (no matches found).When running from the Chromium repository root (
src/), useout/Default/bin/run_webdriver_bidi_e2e_tests.
Additionally the output is recorded under ./logs/<DATE>.e2e.log, which will contain
both the PyTest logs and in the event of FAILED test all the Chromium-BiDi logs.
If you need to see the logs for all tests run the command with VERBOSE=true:
VERBOSE=true ../../out/Default/bin/run_webdriver_bidi_e2e_tests
Pass a test file path to run only the selected file:
../../out/Default/bin/run_webdriver_bidi_e2e_tests -- tests/<PathOrFile>
Run a specific test using the -k filter:
../../out/Default/bin/run_webdriver_bidi_e2e_tests -- -k <TestName>
Use the PORT environment variable to connect to another port:
PORT=8081 ../../out/Default/bin/run_webdriver_bidi_e2e_tests
Use HEADLESS to run the tests in headless (new or old) or headful modes.
Values: true, old, false, default: true.
HEADLESS=true ../../out/Default/bin/run_webdriver_bidi_e2e_tests
When running from the Chromium repository root (
src/), useout/Default/bin/run_webdriver_bidi_e2e_tests.
Updating snapshots
../../out/Default/bin/run_webdriver_bidi_e2e_tests -- --snapshot-update true
See https://github.com/tophat/syrupy for more information.
Local http server
E2E tests use local http
server pytest-httpserver, which is run
automatically with the tests. However,
sometimes it is useful to run the http server outside the test
case, for example for manual debugging. This can be done by running:
vpython3 tools/run_local_http_server.py
Examples
Refer to examples/README.md.
WPT (Web Platform Tests)
WPT tests for WebDriver BiDi are located in Chromium under third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/.
First, build the WPT target:
autoninja -C ../../out/Default headless_shell_wpt
To run all BiDi WPT tests in Chromium:
../../third_party/blink/tools/run_wpt_tests.py -t Default --no-manifest-update external/wpt/webdriver/tests/bidi/
To run a specific test:
../../third_party/blink/tools/run_wpt_tests.py -t Default --no-manifest-update external/wpt/webdriver/tests/bidi/session/status/status.py
How does it work?
The architecture is described in the WebDriver BiDi in Chrome Context implementation plan .
There are 2 main components:
- ChromeDriver (C++ server), which hosts the WebDriver BiDi WebSocket and HTTP endpoints and manages browser instances.
- Front-end BiDi Mapper in
src/bidiMapper(bundled intomapperTab.js). It runs inside the browser, receives BiDi commands from ChromeDriver, and translates them to CDP commands.
Contributing
The chromium-bidi source code lives in the Chromium repository under third_party/chromium-bidi.
Contributions should follow the Chromium Contributing Guide.
Issues are tracked in the Chromium issue tracker (Chromium > Platform > Browser Automation > ChromeDriver). If you find a bug, please file an issue on crbug.com.
The BiDi commands are processed in src/bidiMapper/CommandProcessor.ts. To add a
new command, add it to _processCommand, write and call the module processor for it.
Updating Node dependencies
This does not work on Cog workspaces.
- Check and bump dependencies:
- Check outdated:
npm outdated - Bulk upgrade
package.jsonto latest:npx npm-check-updates -u && npm install --ignore-scripts - Upgrade specific package:
npm install --ignore-scripts <package>@latest - Or update within semver ranges:
npm update --ignore-scripts
- Check outdated:
- Build and run tests to ensure dependencies work properly:
autoninja -C ../../out/Default third_party/chromium-bidi:default third_party/chromium-bidi:webdriver_bidi_unittests third_party/chromium-bidi:webdriver_bidi_e2e_tests ../../out/Default/bin/run_webdriver_bidi_unittests ../../out/Default/bin/run_webdriver_bidi_e2e_tests - If production dependencies (in
dependenciesofpackage.json) were added or updated, updateREADME.chromiumand third-party license notices:./tools/append_notices.py - Upload the filtered
node_modulesto Google Cloud Storage and updateDEPSfiles (DEPSand Chromium's root../../DEPS):./tools/update_node_modules.py --force - Upload a CL with
package.json,package-lock.json,DEPS,../../DEPS, and any updatedREADME.chromium/licenses/viagit cl uploadand submit for review.
Publish new npm release
TODO(crbug.com/540164671): describe the process.
Syncing from Chromium to GitHub
Chromium (third_party/chromium-bidi) is the source of truth. Every commit on main that touches this directory is mirrored automatically to GoogleChromeLabs/chromium-bidi by Copybara: one GitHub commit per Chromium commit, carrying a GitOrigin-RevId trailer. Chromium-specific files (README.chromium, OWNERS, DIR_METADATA, licenses/, copy.bara.sky) are not exported.
The workflow is defined in third_party/chromium-bidi/copy.bara.sky. The automated run is registered in Copybara-as-a-Service from an internal copy of that file (Googlers: the location is documented in its header). When changing origin_files or transformations, update both copies. Sync failures are emailed to browser-automation@google.com.
Running Copybara manually (fallback)
Only needed if the automated sync is paused or broken.
Copybara processes pending Chromium commits one by one, and the first run on a machine also has to fetch Chromium, so expect tens of minutes. If a single git step exceeds the default 15-minute timeout, pass
--repo-timeout=30m.
Prerequisites (Googlers):
- Set up the
copybaraCLI alias (see go/copybara-setup). - Create a GitHub token for
GoogleChromeLabs/chromium-bidi(fine-grained:ContentsandWorkflowsset to Read and write; classic:repoandworkflowscopes) and store it where Copybara's git looks up HTTPS credentials,~/.googlekeys/copybara_git_credentials(one line:https://<github-user>:<token>@github.com). - Ask a repository admin to add your GitHub account to the bypass list of the
mainbranch ruleset; direct pushes tomainare rejected otherwise.
- Set up the
Launch directory: Run Copybara from the root of the Chromium repository (
src/):cd /path/to/chromium/srcSync:
copybara third_party/chromium-bidi/copy.bara.sky default --dry-run # verify first copybara third_party/chromium-bidi/copy.bara.sky default # push to GitHub
Update CDDL types
Prerequisites
- cddlconv: We use cddlconv to generate our WebDriverBiDi types.
- Install Rust.
- Run
cargo install cddlconv@0.2.0
- parse5: parse5 is required by the
webdriver-bidispecification repository to extract CDDL definitions from specifications.- Run
npm install -g parse5
- Run
Run the following steps from the third_party/chromium-bidi directory:
- (Optional) If you want to add a new specification, add it to the
tools/update_bidi_types.pyscript. - Run the
tools/update_bidi_types.pyscript. - Build the project (
autoninja -C ../../out/Default third_party/chromium-bidi:default). If a new WebDriver BiDi command was added, compilation will fail withSwitch is not exhaustive. Cases not matched .... - Add the new BiDi command to
CommandProcessor.#processCommandinsrc/bidiMapper/CommandProcessor.ts. For now, just have it throw an UnknownErrorException.
case '{NEW_COMMAND_NAME}':
throw new UnknownErrorException(
`Method ${command.method} is not implemented.`,
);
- Upload a CL and have it reviewed and landed via Gerrit.
Adding new command
Want to add a shiny new command to WebDriver BiDi for Chromium? Here's the playbook:
Prerequisites
Specification
The WebDriver BiDi module, command, or event must be specified either in the WebDriver BiDi specification or as an extension in a separate specification (e.g., the Permissions specification). The specification should include the command's type definitions in valid CDDL format.
WPT wdspec tests
You'll need tests to prove your command works as expected. These tests should be written using WPT wdspec and submitted to Chromium under third_party/blink/web_tests/external/wpt/webdriver/tests/bidi/ along with the spec itself.
CDP implementation
Make sure Chromium already has the CDP methods your command will rely on.
Update CDDL types
Follow the steps in Update CDDL types to update the protocol types before implementing the command.
Implement the new command
CommandProcessor.#processCommand in src/bidiMapper/CommandProcessor.ts handles parsing parameters and running your command.
(only if the new command has non-empty parameters) parse command parameters
If your command has parameters, update the BidiCommandParameterParser interface in src/bidiMapper/BidiParser.ts and implement the parsing logic in src/bidiMapper/BidiNoOpParser.ts, src/bidiTab/BidiParser.ts, and src/protocol-parser/protocol-parser.ts.
Implement the new command
Write the core logic for your command in the appropriate domain processor.
Call the module processor's method
Call your new module processor method from CommandProcessor.#processCommand, passing in the parsed parameters.
Add e2e tests
Write end-to-end tests for your command, including the happy path and any edge cases that might trip things up. Focus on testing the code in the mapper.
Build the E2E test target and run your test:
autoninja -C ../../out/Default third_party/chromium-bidi:webdriver_bidi_e2e_tests
../../out/Default/bin/run_webdriver_bidi_e2e_tests -- -k <TestName>
Update WPT expectations
If WPT expectations or baselines need to be updated, use Chromium's standard tooling (e.g. third_party/blink/tools/blink_tool.py rebaseline-cl or update test expectations in third_party/blink/web_tests/).
Submit for review
Upload your change list via git cl upload and submit it for review.