Skip to content

Latest commit

 

History

History
283 lines (210 loc) · 8.65 KB

File metadata and controls

283 lines (210 loc) · 8.65 KB

WebDriver BiDi with Selenium (Python)

Note

The BiDi API in Selenium is still evolving. Method names and modules can change between releases. Always verify examples against the docs for your installed version (pip show selenium).

Classic WebDriver is request/response: your script sends a command over HTTP, the browser replies, and nothing happens in between. The browser cannot tell your script that something changed.

WebDriver BiDi (Bidirectional) keeps a WebSocket open between the test and the browser. Both sides can send messages at any time, so the browser can push events to your script (console logs, network traffic, navigation, ...).

It is a W3C standard being developed with the major browser vendors, and it is meant to replace the Chromium-only Chrome DevTools Protocol (CDP) for these use cases.

Feature WebDriver Classic CDP WebDriver BiDi
Transport HTTP request/response WebSocket WebSocket
Direction One way at a time Two way Two way
Browser support All major browsers Chromium only Cross-browser (growing)
Standardised Yes (W3C) No (Chrome protocol) Yes (W3C, in progress)
Stability Stable Can change between versions Standardised, still evolving
Test script (Python)
     |
     |  Selenium client (driver.script, driver.network, ...)
     v
WebSocket  <----------------------------->  Browser driver
(webSocketUrl)      commands + events       (chromedriver / geckodriver)
                                                  |
                                                  v
                                               Browser

Key ideas:

  • Commands go from the script to the browser and get a response with a matching id.
  • Events are pushed from the browser. You must subscribe first.
  • Functionality is grouped into modules (also called domains).

Raw protocol messages look like this:

{"id": 1, "method": "session.subscribe",
 "params": {"events": ["log.entryAdded"]}}

{"type": "event", "method": "log.entryAdded",
 "params": {"level": "info", "text": "hello"}}

BiDi is enabled through the webSocketUrl capability. In Python, use the enable_bidi option:

from selenium import webdriver

options = webdriver.ChromeOptions()
options.enable_bidi = True          # sets the webSocketUrl capability

driver = webdriver.Chrome(options=options)

Firefox works the same way with webdriver.FirefoxOptions().

Tip

If BiDi APIs fail with a connection error, check first that the capability is enabled and that your browser, driver, and Selenium versions are up to date and compatible.

The Python bindings expose these modules under selenium.webdriver.common.bidi:

Module Purpose
session Session status, subscribe and unsubscribe to events
browsing_context Tabs and windows: create, navigate, reload, close, get the context tree
script Run JavaScript, listen to console messages and JavaScript errors
log Log entries (console, JavaScript, other)
network Observe, intercept, and modify requests and responses
storage Read and set cookies
browser Browser-level operations such as user contexts
webextension Install and uninstall browser extensions
cdp, console Older CDP-based helpers, kept for backward compatibility
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait

options = webdriver.ChromeOptions()
options.enable_bidi = True
driver = webdriver.Chrome(options=options)

log_entries = []
driver.script.add_console_message_handler(log_entries.append)

driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html")
driver.find_element(By.ID, "consoleLog").click()

WebDriverWait(driver, 5).until(lambda _: len(log_entries) > 0)
print(log_entries[0].text)

driver.quit()
js_errors = []
driver.script.add_javascript_error_handler(js_errors.append)

driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html")
driver.find_element(By.ID, "jsException").click()

WebDriverWait(driver, 5).until(lambda _: len(js_errors) > 0)
assert "Error" in js_errors[0].text

The network module lets you watch requests and responses, block or mock calls, and handle authentication prompts. Its API has changed across recent releases, so read the current Selenium Python API docs before writing code.

Warning

Old tutorials use driver.bidi_connection() together with trio and CDP wrappers. Prefer the newer driver.script / driver.network style APIs for new work.

  • Fail a test when the page logs a JavaScript error
  • Assert on console output during a flow
  • Wait for network activity to finish instead of using time.sleep
  • Mock or block API calls (offline mode, error responses, slow responses)
  • Capture request and response data for debugging or performance checks
  • Handle basic authentication without popups
  • Manage cookies and multiple tabs more precisely
  • Subscribe before you trigger the action, or you will miss the event.
  • Events arrive asynchronously. Use explicit waits, not sleep.
  • Register handlers once, and remove them when done to avoid duplicate callbacks.
  • Keep event handlers fast. Collect data in a list and assert in the test.
  • Pin and test browser, driver, and Selenium versions together.
  • Do not assume every browser supports every BiDi module. Check support and add fallbacks or skip markers.
  • For new code, prefer BiDi over execute_cdp_cmd so tests are not tied to Chromium.
What is WebDriver BiDi and why was it introduced?
A W3C standard for two-way communication over a WebSocket. Classic WebDriver cannot receive browser events, and CDP works only on Chromium. BiDi gives a cross-browser way to stream events and control the browser.
How is BiDi different from CDP?
CDP is a Chrome-specific protocol that can change between versions. BiDi is a standardised, cross-browser protocol supported by multiple vendors.
How do you enable BiDi in Selenium Python?
Set options.enable_bidi = True (the webSocketUrl capability) before creating the driver.
Give a real use case.
Listen for console errors and JavaScript exceptions during a UI test and fail the test if any appear, instead of only checking the visible page.
Does BiDi replace WebDriver Classic?
Not immediately. Selenium is migrating its internals toward BiDi while keeping backward compatibility, so both coexist.
What could make BiDi tests flaky?
Subscribing too late, using fixed sleeps, duplicated handlers, and version mismatches between browser, driver, and Selenium.
  1. Enable BiDi and print every console message from a page of your choice.
  2. Write a pytest fixture that collects JavaScript errors and fails the test if the list is not empty at teardown.
  3. Open a second tab using the browsing_context module and switch between tabs.
  4. Add a network handler that logs the URL of every request during a login flow.
  5. Run the same BiDi test on Chrome and Firefox and note any differences.