Skip to main content

bidiwave

WebDriver BiDi for Python — talk to any browser via W3C standard.

CI PyPI Python License

Features

  • W3C WebDriver BiDi — standard protocol, not proprietary CDP
  • Cross-browser — Chrome, Firefox, Edge (Safari when BiDi support lands)
  • Async-first — native async/await with asyncio
  • Browsing — contexts, navigation, screenshots, viewport control with DPR, element waiting, CSS/XPath locators, PDF printing, dialog handling
  • Script — evaluate JS, call functions, typed RemoteValue with match pattern narrowing, preload scripts with channel communication, realm inspection
  • Input simulation — clicks, keyboard, scroll, drag & drop, file upload
  • Network interception — block, modify, mock requests, cache overrides, response body retrieval, authentication handling
  • Storage — get, set, delete cookies with full attribute support, cookie change monitoring
  • Emulation — geolocation, network conditions, timezone, user agent override
  • Permissions — grant or deny browser permissions without user dialogs
  • Preload scripts — inject JS before page load for polyfills or monitoring
  • CDP bridge — access Chrome DevTools Protocol for browser-specific features
  • Event streaming — 21 event types with async handlers and error isolation
  • Type-safe — Pydantic v2 models, full type hints, mypy clean
  • 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)

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

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

bidiwave-1.8.0.tar.gz (99.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bidiwave-1.8.0-py3-none-any.whl (43.1 kB view details)

Uploaded Python 3

File details

Details for the file bidiwave-1.8.0.tar.gz.

File metadata

  • Download URL: bidiwave-1.8.0.tar.gz
  • Upload date:
  • Size: 99.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bidiwave-1.8.0.tar.gz
Algorithm Hash digest
SHA256 3b8d1607b82352ed8b9a596907ffbc1ae6e4c73984be2db880b2b147472f9fda
MD5 19bb8d6f574460d265acfb92cdbdfd84
BLAKE2b-256 f33a29c0af24c7ab89f85789c87c3bff7c1568ad7f19701b3ac6628b2f68f428

See more details on using hashes here.

Provenance

The following attestation bundles were made for bidiwave-1.8.0.tar.gz:

Publisher: release.yml on MathiasPaulenko/bidiwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file bidiwave-1.8.0-py3-none-any.whl.

File metadata

  • Download URL: bidiwave-1.8.0-py3-none-any.whl
  • Upload date:
  • Size: 43.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for bidiwave-1.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2c6fbb3de2842f91e2874a823db0a51bac70814429692010f9c8f10f2e8bcd6d
MD5 ba1191331d943aa76748a4a7b29f6945
BLAKE2b-256 b4c54e93bff8bf3de59ebea5d579d0f702160634d4024795ab2f95d6c78456ed

See more details on using hashes here.

Provenance

The following attestation bundles were made for bidiwave-1.8.0-py3-none-any.whl:

Publisher: release.yml on MathiasPaulenko/bidiwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page