Chrome DevTools Protocol for Python — direct, typed, async
60 CDP domains · 692 typed methods · zero Node.js · zero ChromeDriver · zero browser downloads
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 --strictacross 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/versionand/json/listendpoints - 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()andsession.on()for async event handling - Multi-tab sessions —
client.sessionsproperty 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
- Quickstart — 10-minute tutorial
- Guide — in-depth feature coverage
- Cookbook — common recipes
- API Reference — auto-generated docs
- Migration — from pyppeteer or pychrome
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)
| File | Size | Uploaded | |
|---|---|---|---|
| cdpwave-3.3.0.tar.gz | 579.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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