|
| 1 | +===================================== |
| 2 | +WebDriver BiDi with Selenium (Python) |
| 3 | +===================================== |
| 4 | + |
| 5 | +.. contents:: Table of Contents |
| 6 | + :depth: 2 |
| 7 | + :local: |
| 8 | + |
| 9 | +.. note:: |
| 10 | + The BiDi API in Selenium is still evolving. Method names and modules can |
| 11 | + change between releases. Always verify examples against the docs for your |
| 12 | + installed version (``pip show selenium``). |
| 13 | + |
| 14 | + |
| 15 | +1. What is WebDriver BiDi? |
| 16 | +========================== |
| 17 | + |
| 18 | +Classic WebDriver is **request/response**: your script sends a command over |
| 19 | +HTTP, the browser replies, and nothing happens in between. The browser cannot |
| 20 | +tell your script that something changed. |
| 21 | + |
| 22 | +**WebDriver BiDi** (Bidirectional) keeps a **WebSocket** open between the test |
| 23 | +and the browser. Both sides can send messages at any time, so the browser can |
| 24 | +*push events* to your script (console logs, network traffic, navigation, ...). |
| 25 | + |
| 26 | +It is a W3C standard being developed with the major browser vendors, and it is |
| 27 | +meant to replace the Chromium-only Chrome DevTools Protocol (CDP) for these use |
| 28 | +cases. |
| 29 | + |
| 30 | + |
| 31 | +2. Classic vs CDP vs BiDi |
| 32 | +========================= |
| 33 | + |
| 34 | +.. list-table:: |
| 35 | + :header-rows: 1 |
| 36 | + :widths: 20 25 25 30 |
| 37 | + |
| 38 | + * - Feature |
| 39 | + - WebDriver Classic |
| 40 | + - CDP |
| 41 | + - WebDriver BiDi |
| 42 | + * - Transport |
| 43 | + - HTTP request/response |
| 44 | + - WebSocket |
| 45 | + - WebSocket |
| 46 | + * - Direction |
| 47 | + - One way at a time |
| 48 | + - Two way |
| 49 | + - Two way |
| 50 | + * - Browser support |
| 51 | + - All major browsers |
| 52 | + - Chromium only |
| 53 | + - Cross-browser (growing) |
| 54 | + * - Standardised |
| 55 | + - Yes (W3C) |
| 56 | + - No (Chrome protocol) |
| 57 | + - Yes (W3C, in progress) |
| 58 | + * - Stability |
| 59 | + - Stable |
| 60 | + - Can change between versions |
| 61 | + - Standardised, still evolving |
| 62 | + |
| 63 | + |
| 64 | +3. Architecture |
| 65 | +=============== |
| 66 | + |
| 67 | +.. code-block:: text |
| 68 | +
|
| 69 | + Test script (Python) |
| 70 | + | |
| 71 | + | Selenium client (driver.script, driver.network, ...) |
| 72 | + v |
| 73 | + WebSocket <-----------------------------> Browser driver |
| 74 | + (webSocketUrl) commands + events (chromedriver / geckodriver) |
| 75 | + | |
| 76 | + v |
| 77 | + Browser |
| 78 | +
|
| 79 | +Key ideas: |
| 80 | + |
| 81 | +* **Commands** go from the script to the browser and get a response with a |
| 82 | + matching ``id``. |
| 83 | +* **Events** are pushed from the browser. You must **subscribe** first. |
| 84 | +* Functionality is grouped into **modules** (also called domains). |
| 85 | + |
| 86 | +Raw protocol messages look like this: |
| 87 | + |
| 88 | +.. code-block:: json |
| 89 | +
|
| 90 | + {"id": 1, "method": "session.subscribe", |
| 91 | + "params": {"events": ["log.entryAdded"]}} |
| 92 | +
|
| 93 | + {"type": "event", "method": "log.entryAdded", |
| 94 | + "params": {"level": "info", "text": "hello"}} |
| 95 | +
|
| 96 | +
|
| 97 | +4. Enabling BiDi in Python |
| 98 | +========================== |
| 99 | + |
| 100 | +BiDi is enabled through the ``webSocketUrl`` capability. In Python, use the |
| 101 | +``enable_bidi`` option: |
| 102 | + |
| 103 | +.. code-block:: python |
| 104 | +
|
| 105 | + from selenium import webdriver |
| 106 | +
|
| 107 | + options = webdriver.ChromeOptions() |
| 108 | + options.enable_bidi = True # sets the webSocketUrl capability |
| 109 | +
|
| 110 | + driver = webdriver.Chrome(options=options) |
| 111 | +
|
| 112 | +Firefox works the same way with ``webdriver.FirefoxOptions()``. |
| 113 | + |
| 114 | +.. tip:: |
| 115 | + If BiDi APIs fail with a connection error, check first that the capability |
| 116 | + is enabled and that your browser, driver, and Selenium versions are up to |
| 117 | + date and compatible. |
| 118 | + |
| 119 | + |
| 120 | +5. BiDi modules |
| 121 | +=============== |
| 122 | + |
| 123 | +The Python bindings expose these modules under |
| 124 | +``selenium.webdriver.common.bidi``: |
| 125 | + |
| 126 | +.. list-table:: |
| 127 | + :header-rows: 1 |
| 128 | + :widths: 25 75 |
| 129 | + |
| 130 | + * - Module |
| 131 | + - Purpose |
| 132 | + * - ``session`` |
| 133 | + - Session status, subscribe and unsubscribe to events |
| 134 | + * - ``browsing_context`` |
| 135 | + - Tabs and windows: create, navigate, reload, close, get the context tree |
| 136 | + * - ``script`` |
| 137 | + - Run JavaScript, listen to console messages and JavaScript errors |
| 138 | + * - ``log`` |
| 139 | + - Log entries (console, JavaScript, other) |
| 140 | + * - ``network`` |
| 141 | + - Observe, intercept, and modify requests and responses |
| 142 | + * - ``storage`` |
| 143 | + - Read and set cookies |
| 144 | + * - ``browser`` |
| 145 | + - Browser-level operations such as user contexts |
| 146 | + * - ``webextension`` |
| 147 | + - Install and uninstall browser extensions |
| 148 | + * - ``cdp``, ``console`` |
| 149 | + - Older CDP-based helpers, kept for backward compatibility |
| 150 | + |
| 151 | + |
| 152 | +6. Examples |
| 153 | +=========== |
| 154 | + |
| 155 | +6.1 Capture console messages |
| 156 | +---------------------------- |
| 157 | + |
| 158 | +.. code-block:: python |
| 159 | +
|
| 160 | + from selenium import webdriver |
| 161 | + from selenium.webdriver.common.by import By |
| 162 | + from selenium.webdriver.support.ui import WebDriverWait |
| 163 | +
|
| 164 | + options = webdriver.ChromeOptions() |
| 165 | + options.enable_bidi = True |
| 166 | + driver = webdriver.Chrome(options=options) |
| 167 | +
|
| 168 | + log_entries = [] |
| 169 | + driver.script.add_console_message_handler(log_entries.append) |
| 170 | +
|
| 171 | + driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html") |
| 172 | + driver.find_element(By.ID, "consoleLog").click() |
| 173 | +
|
| 174 | + WebDriverWait(driver, 5).until(lambda _: len(log_entries) > 0) |
| 175 | + print(log_entries[0].text) |
| 176 | +
|
| 177 | + driver.quit() |
| 178 | +
|
| 179 | +6.2 Capture JavaScript errors |
| 180 | +----------------------------- |
| 181 | + |
| 182 | +.. code-block:: python |
| 183 | +
|
| 184 | + js_errors = [] |
| 185 | + driver.script.add_javascript_error_handler(js_errors.append) |
| 186 | +
|
| 187 | + driver.get("https://www.selenium.dev/selenium/web/bidi/logEntryAdded.html") |
| 188 | + driver.find_element(By.ID, "jsException").click() |
| 189 | +
|
| 190 | + WebDriverWait(driver, 5).until(lambda _: len(js_errors) > 0) |
| 191 | + assert "Error" in js_errors[0].text |
| 192 | +
|
| 193 | +6.3 Network monitoring and interception |
| 194 | +--------------------------------------- |
| 195 | + |
| 196 | +The ``network`` module lets you watch requests and responses, block or mock |
| 197 | +calls, and handle authentication prompts. Its API has changed across recent |
| 198 | +releases, so read the current Selenium Python API docs before writing code. |
| 199 | + |
| 200 | +.. warning:: |
| 201 | + Old tutorials use ``driver.bidi_connection()`` together with ``trio`` and |
| 202 | + CDP wrappers. Prefer the newer ``driver.script`` / ``driver.network`` style |
| 203 | + APIs for new work. |
| 204 | + |
| 205 | + |
| 206 | +7. Common use cases |
| 207 | +=================== |
| 208 | + |
| 209 | +* Fail a test when the page logs a JavaScript error |
| 210 | +* Assert on console output during a flow |
| 211 | +* Wait for network activity to finish instead of using ``time.sleep`` |
| 212 | +* Mock or block API calls (offline mode, error responses, slow responses) |
| 213 | +* Capture request and response data for debugging or performance checks |
| 214 | +* Handle basic authentication without popups |
| 215 | +* Manage cookies and multiple tabs more precisely |
| 216 | + |
| 217 | + |
| 218 | +8. Best practices and pitfalls |
| 219 | +============================== |
| 220 | + |
| 221 | +* **Subscribe before you trigger the action**, or you will miss the event. |
| 222 | +* Events arrive **asynchronously**. Use explicit waits, not ``sleep``. |
| 223 | +* Register handlers once, and remove them when done to avoid duplicate |
| 224 | + callbacks. |
| 225 | +* Keep event handlers fast. Collect data in a list and assert in the test. |
| 226 | +* Pin and test browser, driver, and Selenium versions together. |
| 227 | +* Do not assume every browser supports every BiDi module. Check support and |
| 228 | + add fallbacks or skip markers. |
| 229 | +* For new code, prefer BiDi over ``execute_cdp_cmd`` so tests are not tied to |
| 230 | + Chromium. |
| 231 | + |
| 232 | + |
| 233 | +9. Interview questions |
| 234 | +====================== |
| 235 | + |
| 236 | +**What is WebDriver BiDi and why was it introduced?** |
| 237 | + A W3C standard for two-way communication over a WebSocket. Classic |
| 238 | + WebDriver cannot receive browser events, and CDP works only on Chromium. |
| 239 | + BiDi gives a cross-browser way to stream events and control the browser. |
| 240 | + |
| 241 | +**How is BiDi different from CDP?** |
| 242 | + CDP is a Chrome-specific protocol that can change between versions. BiDi is |
| 243 | + a standardised, cross-browser protocol supported by multiple vendors. |
| 244 | + |
| 245 | +**How do you enable BiDi in Selenium Python?** |
| 246 | + Set ``options.enable_bidi = True`` (the ``webSocketUrl`` capability) before |
| 247 | + creating the driver. |
| 248 | + |
| 249 | +**Give a real use case.** |
| 250 | + Listen for console errors and JavaScript exceptions during a UI test and |
| 251 | + fail the test if any appear, instead of only checking the visible page. |
| 252 | + |
| 253 | +**Does BiDi replace WebDriver Classic?** |
| 254 | + Not immediately. Selenium is migrating its internals toward BiDi while |
| 255 | + keeping backward compatibility, so both coexist. |
| 256 | + |
| 257 | +**What could make BiDi tests flaky?** |
| 258 | + Subscribing too late, using fixed sleeps, duplicated handlers, and version |
| 259 | + mismatches between browser, driver, and Selenium. |
| 260 | + |
| 261 | + |
| 262 | +10. Practice exercises |
| 263 | +====================== |
| 264 | + |
| 265 | +1. Enable BiDi and print every console message from a page of your choice. |
| 266 | +2. Write a pytest fixture that collects JavaScript errors and fails the test if |
| 267 | + the list is not empty at teardown. |
| 268 | +3. Open a second tab using the ``browsing_context`` module and switch between |
| 269 | + tabs. |
| 270 | +4. Add a network handler that logs the URL of every request during a login |
| 271 | + flow. |
| 272 | +5. Run the same BiDi test on Chrome and Firefox and note any differences. |
| 273 | + |
| 274 | + |
| 275 | +11. References |
| 276 | +============== |
| 277 | + |
| 278 | +* Selenium docs, BiDirectional functionality: |
| 279 | + https://www.selenium.dev/documentation/webdriver/bidi/ |
| 280 | +* Selenium Python API docs (``selenium.webdriver.common.bidi``): |
| 281 | + https://www.selenium.dev/selenium/docs/api/py/ |
| 282 | +* W3C WebDriver BiDi specification: |
| 283 | + https://w3c.github.io/webdriver-bidi/ |
0 commit comments