Skip to main content

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?

  • 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, and client.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

cdpify-0.3.0.tar.gz (236.7 kB view details)

Uploaded Source

Built Distribution

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

cdpify-0.3.0-py3-none-any.whl (381.6 kB view details)

Uploaded Python 3

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

Hashes for cdpify-0.3.0.tar.gz
Algorithm Hash digest
SHA256 07e13cf2df88e83732709406d43316c2bf24fa2c2a54e85469b77678740030f7
MD5 1136cf006335ce75aa6b2e0e7ab8450a
BLAKE2b-256 d2486a24fe040584d029ee92af506be1d229d4ddc0a2770ca847c838aa027482

See more details on using hashes here.

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

Hashes for cdpify-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7d8aa7c58e136888ce9b041cdb57349f53f6d8ef4f9f6be8dd81007fa1e5e5c8
MD5 f766ef1dc3ad0cc2422915a6e5b99bf5
BLAKE2b-256 0d06318eafe17f4320cd28a24d54063eea6a4455924831de94b3bd08e352448d

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.11

2 files

0.2.10

2 files

0.2.9

2 files

0.2.8

2 files

0.2.7

2 files

0.2.6

2 files

0.2.5

2 files

0.2.4

2 files

0.2.3

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 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