Skip to main content

Vanilla Python Chrome DevTools Protocol. Optional dependencies for accelerated performance

Project description

purecdp

Pure-Python (stdlib-only) library for the Chrome DevTools Protocol, targeting Python 3.14+.

Three things, layered:

  1. purecdp.protocol — low-level typed bindings, generated from the official devtools-protocol JSON specs.
  2. purecdp core — sans-I/O protocol engine + asyncio transports (websocket / pipe) + browser launcher.
  3. purecdp.testing — opinionated helpers for driving a browser in tests, incl. a pytest plugin.

See Examples.md for task-grouped recipes (basics, testing, stealth, LLM agents, frames), DESIGN.md for architecture and rationale, and ROADMAP.md for work order and status.

Quickstart

import asyncio
import purecdp
from purecdp.protocol import page, runtime

async def main():
    async with await purecdp.launch() as browser:   # finds Chromium/Chrome/Brave
        session = await browser.new_page()          # create target + attach

        await session.execute(page.enable())
        waiter = asyncio.create_task(session.wait_for(page.LoadEventFired))
        await asyncio.sleep(0)  # subscribe before triggering
        await session.execute(page.navigate(url="https://example.com"))
        await waiter

        result, _ = await session.execute(
            runtime.evaluate(expression="document.title", return_by_value=True))
        print(result.value)

asyncio.run(main())

purecdp.launch(pipe=True) uses --remote-debugging-pipe instead of a websocket; purecdp.discovery.get_version(port=9222) reaches a browser someone else started. browser.new_context() gives an isolated cookies/storage context (cheap per-test isolation); connection.set_auto_attach() auto-attaches popups/workers/OOPIFs, resuming paused targets automatically.

Writing tests with purecdp

The stdlib way (purecdp.testing.CDPTestCase — fresh browser, isolated context, and a Page per test):

from purecdp.testing import CDPTestCase, JSError

class MyAppTests(CDPTestCase):
    async def test_stubbed_api(self):
        async def api(request):
            await request.fulfill(body='{"items": []}',
                                  content_type="application/json")
        await self.page.route("*/api/*", api)
        await self.page.goto("https://myapp.example/", wait="idle")
        await self.page.wait_for_selector("#empty-state")
        assert not self.page.js_errors

Page gives you goto (load/idle waits), evaluate (promises awaited, JSError raised), wait_for_selector/wait_for_function, click, query/query_all (held elements, text-filtered, stale-copy-proof), screenshot, always-on page.console / page.js_errors capture, and page.route() request stubbing/rewriting/aborting. For traffic-level assertions there's page.record() (full request/response exchanges, with parse_sse() for streamed bodies) and page.expect_download() (capture a download's bytes without touching disk).

Prefer pytest? The optional plugin (auto-registered when installed, or -p purecdp.testing.pytest_plugin) provides a session-scoped cdp_browser and per-test cdp_page, and runs async def tests itself — no pytest-asyncio required:

async def test_title(cdp_page):
    await cdp_page.goto("data:text/html,<title>hi</title>")
    assert await cdp_page.title() == "hi"

Low-observability / stealth

For automation that shouldn't advertise itself, purecdp layers three opt-in pieces (reduces common detection signals — not a promise of undetectability; CreepJS-class and TLS/HTTP2 fingerprints still identify automation):

import purecdp
from purecdp.testing import Page, apply_stealth

async with await purecdp.launch(stealth=True) as browser:   # AutomationControlled off, headless=new
    session = await browser.new_page()
    page = await Page.create(session, capture=False, track_network=False)  # no Runtime.enable leak
    await apply_stealth(page)                                # fingerprint init-scripts, before goto
    await page.goto("https://example.com")

apply_stealth(page, evasions=[...]) selects a subset; keyword overrides (webgl_vendor, webgl_renderer, languages, vendor, hardware_concurrency) tune the spoofed values.

For behavioral realism (the more durable signal — how input moves, all over trusted Input events):

el = await page.query("#submit")
await el.human_click()             # curved, eased path to a near-center point
await page.human_type("hello@example.com")   # per-key events, human cadence
await page.human_scroll(1200)      # eased wheel steps, not one jump

Since purecdp drives a real browser, its TLS/HTTP2 (JA3/JA4) fingerprint is already authentically Chrome's — no spoofing needed, unlike browserless HTTP scrapers.

Driving with an LLM (agent surface)

page.snapshot() returns a compact, ref-tagged view of the page from the accessibility tree — small and stable enough to hand an LLM, instead of raw HTML or screenshots. Each control gets a session ref (e1, e2, …); password fields are masked.

snap = await page.snapshot()
print(snap)
# - RootWebArea "Login"
#   - textbox "Email" [e1]
#   - textbox "Password" = "••••••" [e2]
#   - button "Sign in" [e3]

await page.act("e1", "fill", text="me@example.com")   # fill/type/select/click/…
await page.act("e3", "click")
el = await page.element_for_ref("e1")                  # escape hatch: full Element API

An optional MCP server exposes this to any Model Context Protocol host — no extra dependency, stdlib JSON-RPC over stdio:

python -m purecdp.mcp            # tools: navigate, snapshot, act, evaluate
python -m purecdp.mcp --stealth  # low-observability launch

Layout

spec/               vendored protocol JSON + PIN (source commit)
src/purecdp/            the library (src layout)
src/purecdp/_generator/ code generator (dev tool, reads spec/, writes src/purecdp/protocol/)
src/purecdp/protocol/   GENERATED bindings — do not edit by hand
tests/              tests and test utilities

Regenerating the bindings

python -m purecdp._generator          # uses spec/ and src/purecdp/protocol/ defaults

Update spec/*.json + spec/PIN from a devtools-protocol checkout first when moving the pin.

Running tests

Stdlib only — no test dependencies:

python -m unittest              # discovery from repo root
python tests/test_protocol.py   # single file works too

python -m pytest also works (nicer output, subtest counts) but is never required.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

purecdp-0.1.0.tar.gz (358.1 kB view details)

Uploaded Source

Built Distribution

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

purecdp-0.1.0-py3-none-any.whl (369.4 kB view details)

Uploaded Python 3

File details

Details for the file purecdp-0.1.0.tar.gz.

File metadata

  • Download URL: purecdp-0.1.0.tar.gz
  • Upload date:
  • Size: 358.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for purecdp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a31f2b30c8187c90ae496653aac41df6b21adfce47639f282c2416d2b63db161
MD5 5594bf88c3ac7924446fc54d12383844
BLAKE2b-256 988adb9e789e8b0b6ea3a39007d672d20972764c8089e3970434038f99e426b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for purecdp-0.1.0.tar.gz:

Publisher: release.yml on h5rdly/purecdp

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

File details

Details for the file purecdp-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: purecdp-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 369.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for purecdp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 08b25f256b81a7b72a4522a2eb73f2d3dd4e0492733d16e5d1fe7e462142f67d
MD5 7587d98597b29db6770caab193ea1a6b
BLAKE2b-256 4db381eb7e2c32c3db7067b0c37ea485eb5898aba5e75d8df3c33445090b917d

See more details on using hashes here.

Provenance

The following attestation bundles were made for purecdp-0.1.0-py3-none-any.whl:

Publisher: release.yml on h5rdly/purecdp

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 Pingdom Monitoring Sentry Error logging StatusPage Status page