bidiwave
WebDriver BiDi for Python — talk to any browser via W3C standard.
Features
- W3C WebDriver BiDi — standard protocol, not proprietary CDP
- Cross-browser — Chrome, Firefox, Edge (Safari when BiDi support lands)
- Async-first — native
async/awaitwithasyncio - Browsing — contexts, navigation, screenshots, viewport control with DPR, element waiting, CSS/XPath locators, PDF printing, dialog handling, download events, history traversal, user prompt management
- Script — evaluate JS, call functions, typed
RemoteValuewithmatchpattern narrowing, preload scripts with channel communication, realm inspection, serialization options, user activation - Input simulation — clicks, keyboard, scroll, drag & drop, file upload, file dialog events
- Network interception — block, modify, mock requests, cache overrides, response body retrieval, authentication handling, extra headers, data collectors, cache behavior control
- Storage — get, set, delete cookies with full attribute support, partition key support, cookie change monitoring
- Emulation — geolocation, locale, screen orientation, timezone, user agent override, network conditions
- Permissions — grant or deny browser permissions without user dialogs
- Preload scripts — inject JS before page load for polyfills or monitoring with user context support
- Web extensions — install and uninstall browser extensions
- CDP bridge — access Chrome DevTools Protocol for browser-specific features
- Event streaming — 27 event types with async handlers and error isolation
- Type-safe — Pydantic v2 models, full type hints,
mypyclean - Spec-compliant — W3C WebDriver BiDi (WD 2025-07-28), full coverage
- Resilient — automatic reconnection with exponential backoff
- Lightweight — no Selenium, no Playwright required
Install
pip install bidiwave
Quick start
import asyncio
from bidiwave import BiDiClient, StringValue
async def main():
async with await BiDiClient.connect("ws://localhost:9515/session") as client:
async with await client.browsing.open("https://example.com") as page:
# Evaluate JS
result = await page.evaluate("document.title")
match result:
case StringValue(value=title):
print(f"Title: {title}")
# Screenshot
screenshot = await page.screenshot()
with open("screenshot.png", "wb") as f:
f.write(screenshot)
asyncio.run(main())
Console log monitoring
async with await BiDiClient.connect(url) as client:
async def on_log(entry):
print(f"[{entry.level}] {entry.text}")
client.on("log.entryAdded", on_log)
await client.session.subscribe(["log.entryAdded"])
async with await client.browsing.open("https://example.com") as page:
await page.evaluate("console.log('hello!')")
await asyncio.sleep(2)
Input simulation
async with await BiDiClient.connect(url) as client:
async with await client.browsing.open("https://example.com") as page:
ctx = page.id
# Click at coordinates
await client.input.click(ctx, x=100, y=200)
# Type text
await client.input.type_text(ctx, "Hello, world!")
# Press Enter
await client.input.press_key(ctx, "Enter")
# Scroll down 500px
await client.input.scroll(ctx, delta_y=500)
# Drag and drop
await client.input.drag_and_drop(ctx, 100, 100, 300, 300)
Network interception
async with await BiDiClient.connect(url) as client:
# Block all requests to ads
intercept = await client.network.add_intercept(
phases=["beforeRequestSent"],
url_patterns=["*ads.example.com*"],
)
async with await client.browsing.open("https://example.com") as page:
await asyncio.sleep(2)
await client.network.remove_intercept(intercept.intercept_id)
Cookies & storage
from bidiwave import Cookie
async with await BiDiClient.connect(url) as client:
async with await client.browsing.open("https://example.com") as page:
# Set a session cookie
await client.storage.set_cookie(
page.id,
cookie=Cookie(
name="session",
value="abc123",
domain="example.com",
http_only=True,
secure=True,
),
)
# Read all cookies
cookies = await client.storage.get_cookies(page.id)
for c in cookies:
print(f"{c.name}={c.value}")
# Delete one cookie
await client.storage.delete_cookie(page.id, "session")
Emulation & permissions
from bidiwave import ViewportSize
async with await BiDiClient.connect(url) as client:
async with await client.browsing.open("https://example.com") as page:
ctx = page.id
# Simulate iPhone viewport
await client.browsing.set_viewport(
ctx,
viewport=ViewportSize(width=375, height=812),
device_pixel_ratio=3.0,
)
# Simulate Tokyo location on 3G
await client.emulation.set_geolocation_override(
coordinates={"latitude": 35.6762, "longitude": 139.6503, "accuracy": 1.0},
contexts=[ctx],
)
await client.emulation.set_network_conditions(
network_conditions={
"offline": False,
"download_throughput": 50000,
"upload_throughput": 25000,
"latency": 400,
},
contexts=[ctx],
)
# Grant geolocation permission
await client.permissions.set_permission(
descriptor={"name": "geolocation"},
state="granted",
contexts=[ctx],
)
Preload scripts
async with await BiDiClient.connect(url) as client:
# Inject a script that runs before every page load
result = await client.preload.add_preload_script(
function_declaration="() => { window.__testMode = true; }",
)
async with await client.browsing.open("https://example.com") as page:
value = await page.evaluate("window.__testMode")
print(f"Test mode: {value}")
await client.preload.remove_preload_script(result.script)
Web extensions
async with await BiDiClient.connect(url) as client:
# Install a browser extension
result = await client.web_extension.install("/path/to/extension.crx")
print(f"Installed: {result}")
# Uninstall when done
await client.web_extension.uninstall(result.extension)
Launch a browser with BiDi
Chrome / Edge
# Chrome — requires ChromeDriver as BiDi proxy
chromedriver --port=9515
# Edge — requires EdgeDriver
msedgedriver --port=9516
Firefox
Firefox implements BiDi natively — no driver needed:
firefox --headless --remote-debugging-port=9223 --no-remote
See Browser Setup for detailed instructions.
Documentation
Full documentation at mathiaspaulenko.github.io/bidiwave
- Installation
- Quick Start
- Browsing
- Script
- Network Interception
- Input Simulation
- Cookies & Storage
- Emulation
- Permissions
- Preload Scripts
- Web Extensions
- CDP
- Events
- Configuration
- Cookbook
- Error Handling
- API Reference
- Protocol Reference
- Changelog
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file bidiwave-1.8.1.tar.gz.
File metadata
- Download URL: bidiwave-1.8.1.tar.gz
- Upload date:
- Size: 132.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf5489e40c633b644e84759d59cb56c0d5d1dbd620c9d34006b957161e0640be
|
|
| MD5 |
87b242c20b8894e1c6a8466ddca3b13a
|
|
| BLAKE2b-256 |
795a9ef5924e298c1a4ba1de556e5908b8c32681a7cd90ef9dc541d0e4754f3e
|
Provenance
The following attestation bundles were made for bidiwave-1.8.1.tar.gz:
Publisher:
release.yml on MathiasPaulenko/bidiwave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bidiwave-1.8.1.tar.gz -
Subject digest:
bf5489e40c633b644e84759d59cb56c0d5d1dbd620c9d34006b957161e0640be - Sigstore transparency entry: 2186031985
- Sigstore integration time:
-
Permalink:
MathiasPaulenko/bidiwave@9e4897d282d5bf2072cb8aed23d55544caf04c04 -
Branch / Tag:
refs/tags/v1.8.1 - Owner: https://github.com/MathiasPaulenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9e4897d282d5bf2072cb8aed23d55544caf04c04 -
Trigger Event:
push
-
Statement type:
File details
Details for the file bidiwave-1.8.1-py3-none-any.whl.
File metadata
- Download URL: bidiwave-1.8.1-py3-none-any.whl
- Upload date:
- Size: 50.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eae9aa8b965ab7ee99e12093c327c6da5fe00e6dea1a5ade9b8628c3065d1c9b
|
|
| MD5 |
593e250bc968ef992c1c5a55d8bef293
|
|
| BLAKE2b-256 |
e54f08758f5178bde64e3c551cd8ff4c63dd8cd7e504cd4fc072878521647dcc
|
Provenance
The following attestation bundles were made for bidiwave-1.8.1-py3-none-any.whl:
Publisher:
release.yml on MathiasPaulenko/bidiwave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
bidiwave-1.8.1-py3-none-any.whl -
Subject digest:
eae9aa8b965ab7ee99e12093c327c6da5fe00e6dea1a5ade9b8628c3065d1c9b - Sigstore transparency entry: 2186032141
- Sigstore integration time:
-
Permalink:
MathiasPaulenko/bidiwave@9e4897d282d5bf2072cb8aed23d55544caf04c04 -
Branch / Tag:
refs/tags/v1.8.1 - Owner: https://github.com/MathiasPaulenko
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9e4897d282d5bf2072cb8aed23d55544caf04c04 -
Trigger Event:
push
-
Statement type: