Skip to main content

cdpwave

Chrome DevTools Protocol for Python — direct, typed, async

60 CDP domains · 692 typed methods · zero Node.js · zero ChromeDriver · zero browser downloads


CI PyPI Python License: MIT Docs

Chrome DevTools Protocol for Python — direct, typed, async. cdpwave talks to Chrome over a raw WebSocket. No Node.js, no ChromeDriver, no browser downloads. Just pure Python with full type hints and async-first design.

Quick demo

30 seconds to your first CDP call.

pip install cdpwave
import asyncio
from cdpwave import CDPClient

async def main() -> None:
    async with await CDPClient.launch(headless=True) as client:
        session = await client.new_page("https://example.com")
        result = await session.runtime.evaluate("document.title", return_by_value=True)
        print(result["result"]["value"])  # "Example Domain"
        await session.close()

asyncio.run(main())

Why cdpwave?

cdpwave is a low-level Chrome DevTools Protocol client for Python. It talks directly to Chrome's WebSocket endpoint — no Node.js, no ChromeDriver, no Selenium, no Playwright. Just pure async Python with full type hints.

It is the CDP backend that powers wavexis and wavexis-mcp.

Key features

  • Broad CDP coverage — 60 domains with 692 typed methods (~95% of the protocol), plus session.send() for the rest
  • Direct WebSocket — single connection to Chrome's DevTools Protocol, no intermediate layers
  • Fully typed — mypy --strict across the entire codebase, IDE autocomplete everywhere
  • Async-first — built on asyncio, no threading, no blocking calls
  • Browser detection — finds Chrome, Edge, Brave, or Chromium already on your system
  • Flatten sessions — one WebSocket for all tabs via Target.attachToTarget + sessionId
  • Escape hatch — session.send("Any.CDPMethod", params) for any uncovered command
  • HTTP discovery — typed access to /json/version and /json/list endpoints
  • WebSocket keepalive — automatic ping/pong with configurable interval and timeout
  • Direct WebSocket URL — CDPClient.connect(ws_url=...) bypasses HTTP discovery
  • Event helpers — session.wait_for_event() and session.on() for async event handling
  • Multi-tab sessions — client.sessions property tracks all active sessions
  • 1437 integration tests — against a real Chromium browser covering all domains

How it works

Your Python code
  → CDPClient.launch() finds and starts Chrome
    → Connects to Chrome's WebSocket endpoint
      → session.runtime.evaluate("document.title")
        → Chrome executes and returns the result
      ← Typed response parsed into Python dicts
    ← Session stays open for chained calls
  ← Browser closes on context exit

Core concepts

  • CDPClient — The top-level client. Launches Chrome, manages the WebSocket connection, and creates sessions.
  • Session — A CDP target (tab, page, worker). Each session has its own method namespace (session.runtime, session.page, session.network, etc.).
  • Domain — A CDP namespace like Runtime, Page, Network, DOM. Each domain has typed methods with full parameter validation.
  • Escape hatch — session.send("Any.CDPMethod", params) lets you call any CDP method, even if cdpwave doesn't have a typed wrapper for it.

Requirements

  • Python 3.11 or higher
  • A Chromium-based browser (Chrome, Edge, Brave, or Chromium) already installed
  • No Node.js, no ChromeDriver, no separate Chromium download

Install

pip install cdpwave

Quick start

import asyncio
from cdpwave import CDPClient

async def main() -> None:
    # Launch Chrome and connect
    async with await CDPClient.launch(headless=True) as client:
        # Open a new tab
        session = await client.new_page("https://example.com")

        # Evaluate JavaScript
        result = await session.runtime.evaluate(
            "document.title", return_by_value=True
        )
        print(result["result"]["value"])  # "Example Domain"

        # Take a screenshot (data is base64-encoded PNG)
        screenshot = await session.page.capture_screenshot()
        with open("screenshot.png", "wb") as f:
            import base64
            f.write(base64.b64decode(screenshot["data"]))

        # Navigate somewhere else
        await session.page.navigate("https://example.org")
        await session.close()

asyncio.run(main())

Connect to an existing browser

from cdpwave import CDPClient

# Connect to a browser already running with --remote-debugging-port
async with await CDPClient.connect(host="localhost", port=9222) as client:
    session = await client.new_page("https://example.com")
    # ...

Multi-tab sessions

async with await CDPClient.launch() as client:
    tab1 = await client.new_page("https://example.com")
    tab2 = await client.new_page("https://example.org")

    # Work on both tabs independently
    title1 = await tab1.runtime.evaluate("document.title", return_by_value=True)
    title2 = await tab2.runtime.evaluate("document.title", return_by_value=True)

    print(title1["result"]["value"])  # "Example Domain"
    print(title2["result"]["value"])  # "IANA-managed domains"

Escape hatch

Call any CDP method, even if cdpwave doesn't have a typed wrapper:

result = await session.send("Performance.getMetrics", {})
print(result["metrics"])

Event handling

async with await CDPClient.launch() as client:
    session = await client.new_page("https://example.com")

    # Listen for console messages (runtime events need the domain enabled)
    await session.runtime.enable()

    def on_console(msg):
        print(f"[console] {msg['args']}")

    session.on("Runtime.consoleAPICalled", on_console)

    # Wait for the page load event
    await session.wait_for_load_state("load")

Documentation

Full documentation at mathiaspaulenko.github.io/cdpwave

Ecosystem

cdpwave is part of the Wave ecosystem — browser automation tools in 100% Python:

Project Description
cdpwave Chrome DevTools Protocol client (this repo)
bidiwave WebDriver BiDi client — cross-browser, W3C standard
wavexis Browser automation CLI — wraps cdpwave + bidiwave
wavexis-mcp MCP server — 220 browser automation tools for LLMs

Contributing

Contributions are welcome! See CONTRIBUTING.md for guidelines on development setup, code style, testing, and pull request process.

Please read our Code of Conduct before participating.

Security

Found a vulnerability? See SECURITY.md for responsible disclosure.

License

MIT

Metadata

Release files for cdpwave 3.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cdpwave 3.3.0
File Size Uploaded
cdpwave-3.3.0.tar.gz 579.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cdpwave 3.3.0
File Interpreter ABI Platform
cdpwave-3.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 763.0 kB

Release files / cdpwave-3.3.0.tar.gz

Download URL cdpwave-3.3.0.tar.gz
Size 579.0 kB
Tags Source
SHA-256 checksum
How to use checksums
57bd8746550b12ea6fac1dc146b76925baf90c260795132188121aefb92fe613
BLAKE2b-256 checksum
How to use checksums
8711e8d7aa31802fa6847ca36460f8d47abc0ad1e1f312d1cc3fd2bf15291fec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / cdpwave-3.3.0-py3-none-any.whl

Download URL cdpwave-3.3.0-py3-none-any.whl
Size 183.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
facbdae7c32e82eb6542090c5412c9065985a22d5491ad2d18a8dda4d7cbd480
BLAKE2b-256 checksum
How to use checksums
8f7bb9322349ff81881de61e55e9b067d7456e1865c204da3dd3ee53d38d2f28
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

3.3.0 This release

2 release files

3.2.1

2 release files

3.2.0

2 release files

3.1.9

2 release files

3.1.8

2 release files

3.1.7

2 release files

3.1.6

2 release files

3.1.5

2 release files

3.1.3

2 release files

3.1.2

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.1

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.0

2 release files

2.3.0

2 release files

2.2.1

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.1

2 release files

2.0.0

2 release files

1.9.0

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page