Browser Setup¶
WebDriver BiDi requires a driver process — a small executable that acts as a proxy between bidiwave and the browser. The driver receives WebSocket connections, translates BiDi commands, and controls the browser.
Each browser has its own driver:
| Browser | Driver | BiDi support |
|---|---|---|
| Chrome | ChromeDriver | Chrome 111+ |
| Edge | EdgeDriver | Edge 111+ |
| Firefox | (none — native BiDi) | Firefox 129+ |
Chrome¶
1. Download ChromeDriver¶
Download the version that matches your Chrome installation from Chrome for Testing.
To check your Chrome version, open chrome://version in the browser.
2. Launch the driver¶
The driver starts listening on port 9515. You can choose any free port — just make sure bidiwave connects to the same one.
3. Connect¶
import asyncio
from bidiwave import BiDiClient
async def main():
async with await BiDiClient.connect("ws://localhost:9515/session") as client:
caps = client.capabilities
print(f"Connected to {caps.browser_name if caps else 'browser'}")
# ... your code ...
asyncio.run(main())
How the handshake works¶
When bidiwave connects to ws://localhost:9515/session:
- ChromeDriver accepts the WebSocket connection.
- bidiwave sends a
session.newcommand with{"webSocketUrl": true}to negotiate capabilities. Some drivers rejectsession.newon an already-established session — bidiwave falls back tosession.statusin that case. - bidiwave starts the receive loop, and
client.capabilitiesis populated with the result.
You don't need to handle this manually — BiDiClient.connect() does it
all.
Edge¶
1. Download EdgeDriver¶
Download from Microsoft Edge Driver. Match the version to your Edge installation.
2. Launch the driver¶
3. Connect¶
async with await BiDiClient.connect("ws://localhost:9516/session") as client:
caps = client.capabilities
print(f"Connected to {caps.browser_name if caps else 'browser'}")
Edge-specific notes¶
Edge is based on Chromium, so its BiDi support is identical to Chrome's.
The same driver bugs that affect ChromeDriver also affect EdgeDriver
(notably the callFunction bug with primitive arguments — see
Script).
Firefox¶
Firefox implements BiDi natively — no driver process needed. You launch Firefox directly with remote debugging enabled:
--headless— run without a visible window (for CI/CD)--remote-debugging-port=9223— enable remote debugging on port 9223--no-remote— start a new Firefox instance instead of connecting to an existing one
Connect¶
async with await BiDiClient.connect("ws://127.0.0.1:9223/session") as client:
caps = client.capabilities
print(f"Connected to {caps.browser_name if caps else 'browser'}")
Firefox advantages¶
Firefox has the most complete BiDi implementation:
- Network interception — fully supported (Chrome/Edge have partial support)
- Input simulation — fully supported including
setFiles - No driver process — simpler setup, fewer moving parts
Headless mode¶
For CI/CD pipelines and automated testing, run the browser in headless mode (no visible window):
Remote drivers¶
You can connect to a driver running on a remote machine (e.g., in a Docker container or a Selenium Grid):
Make sure the driver port is accessible from your machine (firewall, network, etc.).
Capabilities¶
After connecting, client.capabilities tells you what the browser
supports:
async with await BiDiClient.connect(url) as client:
caps = client.capabilities
if caps is not None:
print(f"Browser: {caps.browser_name} {caps.browser_version}")
print(f"Platform: {caps.platform_name}")
print(f"Vendor: {caps.vendor}")
print(f"Browsing: {caps.supports_browsing}")
print(f"Script: {caps.supports_script}")
print(f"Network: {caps.supports_network}")
print(f"Input: {caps.supports_input}")
Capability fields¶
| Field | Type | Description |
|---|---|---|
browser_name |
str |
"chrome", "MicrosoftEdge", "firefox" |
browser_version |
str |
Full version string (e.g., "131.0.6778.87") |
platform_name |
str |
OS platform (e.g., "windows", "linux") |
vendor |
str |
Browser vendor |
supports_browsing |
bool |
Browsing context commands available |
supports_script |
bool |
Script evaluation available |
supports_network |
bool |
Network module available |
supports_input |
bool |
Input simulation available |
client.capabilities is None only if the driver rejects both
session.new and session.status — guard with if caps is not None
before reading fields. The supports_* flags are only populated when
the driver reports browser identification in the session.new result —
they can't distinguish individual feature support within a module.
Capability-aware code¶
Write code that adapts to the browser's capabilities:
caps = client.capabilities
if caps is not None and caps.supports_network:
await client.network.add_intercept(...)
else:
print("Network interception not supported on this browser")
Troubleshooting¶
"Connection refused"¶
The driver isn't running or is on a different port. Check:
- The driver process is running (
tasklist | findstr chromedriveron Windows,ps aux | grep chromedriveron Linux). - The port matches between the driver and your
connect()call. - No firewall is blocking the port.
"Handshake failed"¶
The driver started but the BiDi handshake failed. Common causes:
- Wrong Chrome/ChromeDriver version — the driver version must match the browser version exactly.
- BiDi not enabled — some older drivers don't support BiDi. Make sure you're using Chrome 111+ / Edge 111+ / Firefox 129+.
"Session not found"¶
The WebSocket URL returned by the driver is stale. This can happen if the driver was restarted while bidiwave was connected. Use the reconnection feature (see Configuration).