Skip to main content

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 in chrome://inspect/#remote-debugging; Chrome gives you 60 seconds to approve each new client.
  • cdp = await CDP.remote() — attach to a “debug Chrome” on remote’s default port 9223; fastcdp-setup creates 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”:

image.png

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:

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 sends real pointer events. click_and_wait does the same and requires the click to navigate the top frame, waiting for load by default. For a custom action, wrap it in async with page.expect_navigation():; for in-place UI updates, click normally and wait for the resulting content instead.

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 existing page, 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.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fastcdp 0.0.10
File Size Uploaded
fastcdp-0.0.10.tar.gz 287.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fastcdp 0.0.10
File Interpreter ABI Platform
fastcdp-0.0.10-py3-none-any.whl Python 3 none any Details

Total release size: 578.5 kB

Release files / fastcdp-0.0.10.tar.gz

Download URL fastcdp-0.0.10.tar.gz
Size 287.9 kB
Tags Source
SHA-256 checksum
How to use checksums
cc06930328c79b93e30b449ab03a8222aa83d527b2edfa8af49e2d46c1139eb6
BLAKE2b-256 checksum
How to use checksums
caae99e387cb56eff9cce2eb5b4a0687a93eb72739acbef57840030e21b7f4ad
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.10-py3-none-any.whl

Download URL fastcdp-0.0.10-py3-none-any.whl
Size 290.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
65100149ab0716dbe07e4cfb085424bcc0e950e9118d356f6f7693c8346bed72
BLAKE2b-256 checksum
How to use checksums
8227b8feaee14ae02e1aad8627941784e897e9c1d7a2360cc8a4cc8c8ad5141a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Release history Release notifications | RSS feed

0.0.17

2 release files

This release

0.0.10 This release

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

0.0.1

2 release 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