cdpify
🚀 An async, typed Python client for the Chrome DevTools Protocol.
cdpify turns the Chrome DevTools Protocol (CDP) into a Pythonic, IDE-friendly
API. Commands, results, events, and protocol types are generated from the
official CDP specifications, so you get autocomplete and typed responses
without working with raw JSON messages.
Contents
- Why cdpify?
- Installation
- Quick start
- Supported domains
- Listening for events
- Working with target sessions
- Configuration
- Development
- Resources
Why cdpify?
- Typed by default — generated models for commands, results, events, and shared protocol types
- Complete domain coverage — access all 58 generated CDP domains through
properties such as
client.page,client.network, andclient.runtime - Async throughout — transport-neutral core with an optional WebSocket implementation
- Typed event streams — consume CDP events with async iterators
- Multi-target support — use immutable, concurrency-safe session views
- Low-level protocol access — execute any CDP method through
execute()when needed
Installation
pip install "cdpify[websocket]"
Requires Python 3.12 or newer. The WebSocket extra provides the recommended
default transport used by Client(url). Install cdpify without an extra when
supplying your own Transport implementation.
Install the generator dependencies only when regenerating protocol modules:
pip install "cdpify[generator]"
Quick start
Start Chrome or Chromium with remote debugging enabled, then obtain a page's
webSocketDebuggerUrl from http://localhost:9222/json.
import asyncio
import json
from urllib.request import urlopen
from cdpify import Client
def get_websocket_url() -> str:
with urlopen("http://localhost:9222/json", timeout=5) as response:
return json.load(response)[0]["webSocketDebuggerUrl"]
async def main() -> None:
ws_url = get_websocket_url()
async with Client(ws_url) as client:
await client.page.navigate(url="https://example.com")
result = await client.runtime.evaluate(
expression="document.title",
return_by_value=True,
)
print(result.result.value)
asyncio.run(main())
Domains are available as lazy properties on Client. Parameters use
Python's snake_case; cdpify handles conversion to and from CDP's wire format.
Supported domains
The generated client currently includes all 58 domains from the bundled CDP
specifications. Each domain is available as a lazy property on Client and
CDPSession:
| CDP domain | Python accessor | CDP domain | Python accessor |
|---|---|---|---|
Accessibility |
client.accessibility |
IndexedDB |
client.indexed_db |
Ads |
client.ads |
Input |
client.input |
Animation |
client.animation |
Inspector |
client.inspector |
Audits |
client.audits |
IO |
client.io |
Autofill |
client.autofill |
LayerTree |
client.layer_tree |
BackgroundService |
client.background_service |
Log |
client.log |
BluetoothEmulation |
client.bluetooth_emulation |
Media |
client.media |
Browser |
client.browser |
Memory |
client.memory |
CacheStorage |
client.cache_storage |
Network |
client.network |
Cast |
client.cast |
Overlay |
client.overlay |
Console |
client.console |
Page |
client.page |
CrashReportContext |
client.crash_report_context |
Performance |
client.performance |
CSS |
client.css |
PerformanceTimeline |
client.performance_timeline |
Debugger |
client.debugger |
Preload |
client.preload |
DeviceAccess |
client.device_access |
Profiler |
client.profiler |
DeviceOrientation |
client.device_orientation |
PWA |
client.pwa |
DigitalCredentials |
client.digital_credentials |
Runtime |
client.runtime |
DOM |
client.dom |
Schema |
client.schema |
DOMDebugger |
client.dom_debugger |
Security |
client.security |
DOMSnapshot |
client.dom_snapshot |
ServiceWorker |
client.service_worker |
DOMStorage |
client.dom_storage |
SmartCardEmulation |
client.smart_card_emulation |
Emulation |
client.emulation |
Storage |
client.storage |
EventBreakpoints |
client.event_breakpoints |
SystemInfo |
client.system_info |
Extensions |
client.extensions |
Target |
client.target |
FedCm |
client.fed_cm |
Tethering |
client.tethering |
Fetch |
client.fetch |
Tracing |
client.tracing |
FileSystem |
client.file_system |
WebAudio |
client.web_audio |
HeadlessExperimental |
client.headless_experimental |
WebAuthn |
client.web_authn |
HeapProfiler |
client.heap_profiler |
WebMCP |
client.web_mcp |
Listening for events
Events are exposed as typed async streams:
from cdpify.domains.network.events import NetworkEvent, RequestWillBeSentEvent
await client.network.enable()
async for event in client.listen(
event_name=NetworkEvent.REQUEST_WILL_BE_SENT,
event_type=RequestWillBeSentEvent,
):
print(event.request.method, event.request.url)
client.listen() accepts an optional timeout in seconds and yields only
events from the root connection. A session view applies the same rule to its
bound target:
tab = client.session("session-id")
async for event in tab.listen(
event_name=NetworkEvent.REQUEST_WILL_BE_SENT,
event_type=RequestWillBeSentEvent,
):
# This stream contains events from this session only.
print(event.request.url)
To observe the root connection and all attached sessions together, use
listen_all(). Routing metadata is returned separately from the generated CDP
event model:
async for received in client.listen_all(
event_name=NetworkEvent.REQUEST_WILL_BE_SENT,
event_type=RequestWillBeSentEvent,
):
print(received.session_id, received.value.request.url)
received.session_id is None for a root event. Generated event dataclasses
contain only fields defined by the CDP specification.
Working with target sessions
Attach to a target in flat mode and create an immutable session view with
client.session(). Every generated command on that view is routed to the bound
session:
from cdpify import Client
async with Client(browser_ws_url) as root_client:
attached = await root_client.target.attach_to_target(
target_id="target-id",
flatten=True,
)
tab = root_client.session(attached.session_id)
await tab.page.enable()
await tab.runtime.evaluate(expression="console.log('Hello from CDP')")
There is no mutable "active session". Create one view per attached target and use them safely from concurrent tasks:
import asyncio
tab_a = root_client.session(session_a)
tab_b = root_client.session(session_b)
await asyncio.gather(
tab_a.runtime.evaluate(expression="document.title"),
tab_b.runtime.evaluate(expression="document.title"),
)
Session routing is transport metadata, not a generated command parameter.
Generated methods therefore never accept an additional routing session_id.
A real sessionId declared by the CDP specification remains a normal typed
parameter. For low-level access, root_client.execute() optionally accepts a
session ID, while tab.execute() is always bound and cannot be overridden:
await root_client.execute("Page.enable", session_id=session_a)
await tab.execute("Runtime.evaluate", {"expression": "1 + 1"})
Configuration
client = Client(
url="ws://localhost:9222/devtools/browser/...",
additional_headers={"Authorization": "Bearer token"},
max_frame_size=100 * 1024 * 1024,
default_timeout=30.0,
)
For another transport protocol, implement the exported Transport protocol
and inject it directly:
from cdpify import Client
client = Client(transport=my_transport)
For methods not covered by the generated API, use the low-level escape hatch:
result = await client.execute(
"Runtime.evaluate",
{"expression": "1 + 1", "returnByValue": True},
)
Development
Install the project and its development dependencies with uv:
uv sync --dev
uv run pytest
uv run ruff check . --exclude cdpify/domains
To download the latest protocol definitions and regenerate all domain clients:
uv run python -m cdpify.generator
Generate only selected domains by repeating --domain:
uv run python -m cdpify.generator --domain Page --domain Runtime
Use --spec-dir and --output-dir to override where downloaded specifications
and generated modules are written.
The generated output lives in cdpify/domains/ and should not be edited by
hand. The repository also refreshes the upstream specification automatically
once a week and opens a pull request when generated code changes.
Resources
The code-generation approach was inspired by
cdp-use.
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 cdpify-0.3.0.tar.gz.
File metadata
- Download URL: cdpify-0.3.0.tar.gz
- Upload date:
- Size: 236.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07e13cf2df88e83732709406d43316c2bf24fa2c2a54e85469b77678740030f7
|
|
| MD5 |
1136cf006335ce75aa6b2e0e7ab8450a
|
|
| BLAKE2b-256 |
d2486a24fe040584d029ee92af506be1d229d4ddc0a2770ca847c838aa027482
|
File details
Details for the file cdpify-0.3.0-py3-none-any.whl.
File metadata
- Download URL: cdpify-0.3.0-py3-none-any.whl
- Upload date:
- Size: 381.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d8aa7c58e136888ce9b041cdb57349f53f6d8ef4f9f6be8dd81007fa1e5e5c8
|
|
| MD5 |
f766ef1dc3ad0cc2422915a6e5b99bf5
|
|
| BLAKE2b-256 |
0d06318eafe17f4320cd28a24d54063eea6a4455924831de94b3bd08e352448d
|