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:
purecdp.protocol— low-level typed bindings, generated from the official devtools-protocol JSON specs.purecdpcore — sans-I/O protocol engine + asyncio transports (websocket / pipe) + browser launcher.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
Release history Release notifications | RSS feed
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a31f2b30c8187c90ae496653aac41df6b21adfce47639f282c2416d2b63db161
|
|
| MD5 |
5594bf88c3ac7924446fc54d12383844
|
|
| BLAKE2b-256 |
988adb9e789e8b0b6ea3a39007d672d20972764c8089e3970434038f99e426b1
|
Provenance
The following attestation bundles were made for purecdp-0.1.0.tar.gz:
Publisher:
release.yml on h5rdly/purecdp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purecdp-0.1.0.tar.gz -
Subject digest:
a31f2b30c8187c90ae496653aac41df6b21adfce47639f282c2416d2b63db161 - Sigstore transparency entry: 2202332492
- Sigstore integration time:
-
Permalink:
h5rdly/purecdp@b92896b5ef7c34ec659afa96d92d1512ce5e1945 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/h5rdly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b92896b5ef7c34ec659afa96d92d1512ce5e1945 -
Trigger Event:
push
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08b25f256b81a7b72a4522a2eb73f2d3dd4e0492733d16e5d1fe7e462142f67d
|
|
| MD5 |
7587d98597b29db6770caab193ea1a6b
|
|
| BLAKE2b-256 |
4db381eb7e2c32c3db7067b0c37ea485eb5898aba5e75d8df3c33445090b917d
|
Provenance
The following attestation bundles were made for purecdp-0.1.0-py3-none-any.whl:
Publisher:
release.yml on h5rdly/purecdp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
purecdp-0.1.0-py3-none-any.whl -
Subject digest:
08b25f256b81a7b72a4522a2eb73f2d3dd4e0492733d16e5d1fe7e462142f67d - Sigstore transparency entry: 2202332531
- Sigstore integration time:
-
Permalink:
h5rdly/purecdp@b92896b5ef7c34ec659afa96d92d1512ce5e1945 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/h5rdly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b92896b5ef7c34ec659afa96d92d1512ce5e1945 -
Trigger Event:
push
-
Statement type: