fastcdp
fastcdp provides an async Python client for the Chrome DevTools Protocol (CDP) over WebSocket. It auto-discovers Chrome’s debug port, loads the full protocol schema from bundled JSON files, and exposes every CDP domain as a Python attribute with auto-generated signatures and docstrings — e.g. await cdp.page.navigate(url=...).
It can drive a Chrome it launches itself, one you started with a debug port, your everyday browser via Chrome’s built-in remote debugging, or — with the companion fastcdp-chrome extension — your everyday browser with no flags or popups at all.
It includes a Page class for tab-scoped operations, event subscription via cdp.on()/cdp.wait_event(), explicit navigation waits (goto, expect_navigation), content waits (wait_for_selector, wait_for), screenshot capture, and accessibility tree access. A cdp_search utility lets you search CDP commands by name or description. For use inside safepyrun sandboxes, cdp_yolo() registers all CDP classes.
Installation
Install latest from pypi
$ pip install fastcdp
How to use
from fastcdp import *
There are four ways to get connected (the fastcdp.skill module doc gives the full decision matrix):
cdp = await CDP.launch()— start a fresh, throwaway instance of your installed Chrome; zero setup.cdp = await CDP.connect()— attach to your everyday Chrome (146+) after enabling Allow remote debugging inchrome://inspect/#remote-debugging; Chrome gives you 60 seconds to approve each new client.cdp = await CDP.remote()— attach to a “debug Chrome” onremote’s default port 9223;fastcdp-setupcreates a launcher for exactly such a browser.cdp = await ExtCDP.listen()— wait for the fastcdp-chrome extension to dial in from your everyday browser: no flags, no popups.
This walkthrough uses connect:
Chrome 146+ has built-in remote debugging support. Navigate to chrome://inspect/#remote-debugging and enable “Allow remote debugging for this browser instance”:
The CDP class
Connect to Chrome (which will pop up a permissions window):
cdp = await CDP.connect()
Every CDP domain is available as an attribute with auto-generated signatures. You can search for commands with cdp_search:
cdp_search('screenshot')
"Emulation.setVisibleSize: Resizes the frame/viewport of the page. Note that this does not affect the frame's container\n(e.g. browser window). Can \nHeadlessExperimental.beginFrame: Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a\nscreenshot from the res\n evt Overlay.screenshotRequested: Fired when user asks to capture screenshot of some area on the page.\nPage.captureScreenshot: Capture page screenshot."
List open pages and attach to one:
ps = await cdp.pages
pg = ps[0]
pg['title']
'8. Database Transactions — PlanetScale'
tid = pg['targetId']
sid = await cdp.attach(tid)
await cdp.eval('document.title', sid)
'8. Database Transactions — PlanetScale'
The Page class wraps a tab with its own session, so you don’t need to pass sid everywhere:
page = await cdp.new_page()
await page.goto('https://httpbingo.org/forms/post')
goto waits for the document’s load event by default. Pass wait='idle' when initial network activity must also settle, or wait=None when the next application-specific content wait is a better definition of ready. You can wait_for any JS expression to become truthy and have its value returned:
await page.wait_for('document.title')
'6. httpbin.org/forms/post'
Take a screenshot of the page:
img = await page.screenshot()
Clean up when done:
await page.close()
await cdp.close()
See CDP docs for full details.
Page.new and Filling forms
Instead of CDP.connect, you can call Page.new with no params to automatically create a CDP object and attach it to a new page:
page = await Page.new()
await page.goto('https://httpbingo.org/forms/post')
For finding elements to interact with, use ax_tree. Pass frame_id= to read a child frame directly:
root = await page.ax_tree()
print(str(root)[:300])
- **RootWebArea** "6. httpbin.org/forms/post" `focusable=True` `focused=True` `url=https://httpbin.org/forms/post` [#2]
- **LabelText** "" [#24]
- **StaticText** "Customer name: " [#64]
- **InlineTextBox** "Customer name: "
- **textbox** "Customer name: " `focusable=True` `editable=p
find and find_id are used to identify elements in the tree:
nmid = root.find_id('textbox', 'Customer name')
nmid
4
You can use regular CDP methods, or one of the provided shortcuts:
await page.fill_text(nmid, 'Jeremy Howard')
await page.click(root.find_id('radio', 'Large'))
await page.js_node_run('this.value = "18:30"', root.find_id('InputTime', 'delivery time'))
{'type': 'undefined'}
click moves the real mouse before pressing and releasing. tap sends a trusted Chrome tap gesture without moving the mouse. dom_click calls the element’s JavaScript activation and does not produce trusted input. Use tap when mouse movement is unreliable or hover is undesirable.
click_and_wait uses click and requires a top-frame navigation. For another activation path, compose it with expect_navigation. For in-place UI updates, activate normally and wait for the resulting content.
await page.click_and_wait(root.find_id('button', 'Submit order'))
When using page.New(), close() also shuts down the CDP websocket.
await page.close()
To allow LLMs like solveit with safepyrun to access fastcdp, use:
cdp_yolo()
Then open a controlled page for it:
page = await Page.new()
Then use a prompt such as:
Try using python to go to
<url>using the existingpage, fill it out, read it to check it’s filled correctly, then submit it, and see what you get back. Don’t use find_id - you can get all the ids at once with ax_tree (don’t truncate the result of it). Don’t add extra waits etc - fastcdp handles it automatically. IDs can change so be sure to use the ax_tree IDs you read.
Release files for fastcdp 0.0.12
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fastcdp-0.0.12.tar.gz | 290.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fastcdp-0.0.12-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 583.5 kB
Release files / fastcdp-0.0.12.tar.gz
| Download URL | fastcdp-0.0.12.tar.gz |
|---|---|
| Size | 290.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
893e9aa05f7dff25454b77c8511c056daaa3728d30c1fd88fb6267a35ca2881b
|
|
BLAKE2b-256 checksum How to use checksums |
9a14a0514933aad1c0778b8f77988ffeb9a90cea1fe7bf5910164b64fce53e04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / fastcdp-0.0.12-py3-none-any.whl
| Download URL | fastcdp-0.0.12-py3-none-any.whl |
|---|---|
| Size | 292.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
96ea96218ae13a388d95e7f5918c5953c604e4b076df49ef35a11656eed2f469
|
|
BLAKE2b-256 checksum How to use checksums |
8413b58dbb77e050010e0e3b6bdfdf452cdca1a473738fcdecaa21d0068c0db7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|