Skip to content

Latest commit

 

History

79 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

5 stars

Watchers

2 watching

Forks

Releases

Contributors

Languages