Skip to main content

fastcdp

fastcdp is an async Python client for the Chrome DevTools Protocol (CDP) over WebSocket. Use it to automate your everyday Chrome browser or a separate Chrome instance. It can launch Chrome or connect to a running browser.

Every CDP domain is available as a Python attribute, such as await cdp.page.navigate(url=...). fastcdp loads the full protocol schema from bundled JSON files to generate signatures and docstrings. It discovers Chrome’s debug port automatically.

The Page class provides tab-scoped operations for navigation, content waits, screenshots, and accessibility tree access. The examples below use it to inspect and fill a form.

Installation

Install latest from pypi

$ pip install fastcdp

How to use

from fastcdp import *

Choose one of five connection methods. Read doc(fastcdp.skill) for browser-selection and ownership guidance, then the chosen method’s full docs:

  • cdp = await CDP.launch() starts or reuses Chrome with a separate automation profile. The default profile persists between runs.
  • cdp = await CDP.connect() attaches to an everyday Chrome with remote debugging enabled and the user’s approval.
  • cdp = await CDP.remote() attaches to a dedicated debug browser. fastcdp-setup creates a launcher for this configuration.
  • cdp = await ExtCDP.listen() waits for the fastcdp-chrome extension in the requested browser.
  • async with CDP.testing(headless=True) as cdp: owns a disposable Chrome for Testing and temporary profile. Install it once with fastcdp-setup --install stable; the context closes the browser and removes the profile when finished. It leaves your installed Chrome and CDP Chrome setup untouched.

This walkthrough uses connect:

In Chrome 146 or later, open chrome://inspect/#remote-debugging and enable “Allow remote debugging for this browser instance”:

image.png

The CDP class

Connect to Chrome and approve its permission prompt:

cdp = await CDP.connect()

Use cdp.on() to subscribe to events or cdp.wait_event() to wait for an event. To find commands by name or description, use 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'

Page holds a tab’s session. Its helpers bind sid. Read doc(page) to discover those helpers and protocol domains, then doc(page.goto) or doc(page.DOM.focus) for a selected operation. Connection-wide operations such as creating tabs remain on page.cdp:

page = await cdp.new_page()
await page.goto('https://httpbingo.org/forms/post')

Choose how goto waits for navigation:

  • By default, it waits for the document’s load event.
  • wait='idle' also waits for initial network activity to settle.
  • wait=None skips the navigation wait. Use a subsequent content wait to check application-specific readiness.

wait_for_selector waits for an element. wait_for waits for a JavaScript expression to become truthy and returns its value:

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

Call Page.new() without arguments to create a CDP object and attach it to a new page:

page = await Page.new()
await page.goto('https://httpbingo.org/forms/post')

Use ax_tree to find elements through the accessibility 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

Use find and find_id to locate elements in the tree:

nmid = root.find_id('textbox', 'Customer name')
nmid
4

page.attrs reads an element’s HTML attributes by node id or CSS selector. For example, await page.attrs(nmid) and await page.attrs('[name=custname]') each return a dictionary.

await page.sel_attrs('input', 'name', 'type') returns one dictionary per matching element. Missing requested attributes have the value None. Omit the attribute names to read all attributes.

Interact with elements using CDP methods or these 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'}

Choose the input method according to the interaction you need:

  • click moves the real mouse before pressing and releasing.
  • tap sends a trusted Chrome tap gesture without moving the mouse. Use it when mouse movement is unreliable or hover is undesirable.
  • dom_click calls the element’s JavaScript activation. It does not produce trusted input.

click_and_wait clicks and waits for a top-frame navigation. For another activation method, use expect_navigation around that operation. For in-place UI updates, activate the element 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()

For an LLM using fastcdp through safepyrun, such as in Solveit, register all CDP classes with:

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.

Metadata

Release files for fastcdp 0.0.20

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.20
File Size Uploaded Provenance Actions
fastcdp-0.0.20.tar.gz 294.4 kB Without provenance Details for fastcdp-0.0.20.tar.gz

Built distribution (wheel)

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

Total release size: 590.2 kB

Release files / fastcdp-0.0.20.tar.gz

Download URL fastcdp-0.0.20.tar.gz
Size 294.4 kB
Tags Source
SHA-256 checksum
How to use checksums
729c903b309e2fca99a47322d2c7017bf8f4a6be5a4205ca4efec3f013eea2ee
BLAKE2b-256 checksum
How to use checksums
e354f742cd24f5d65a861ee2716aec893b4c9ca25485d338db37b9194ca7b59e
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.20-py3-none-any.whl

Download URL fastcdp-0.0.20-py3-none-any.whl
Size 295.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
503506916e510365e80660b43920b499629d6ab6ce0e47165e600caa7b9ec496
BLAKE2b-256 checksum
How to use checksums
b68e02e6f1336c1c4a2924226e2a8d569874ff0cbdc9c0ba55cae88806e5dc42
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.15

Security for fastcdp 0.0.20

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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