Skip to content
AnswerDotAIPublic

About

Lightweight Chrome Debug Protocol (CDP) client for python

Resources

Stars

6 stars

Watchers

2 watching

Forks

Repository files navigation

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.

About

Lightweight Chrome Debug Protocol (CDP) client for python

Resources

Stars

6 stars

Watchers

2 watching

Forks

Releases

Contributors

Languages