len(_cdp_domains), [d['domain'] for d in _cdp_domains[:5]](55, ['Accessibility', 'Animation', 'Audits', 'Autofill', 'BackgroundService'])
(55, ['Accessibility', 'Animation', 'Audits', 'Autofill', 'BackgroundService'])
Search CDP domains and commands by name or description
'Audits.checkFormsIssues: Runs the form issues check for the target page. Found issues are reported\nu'
Connection info from contents p, profile dir d, or the default Chrome profile
Chrome (146+) can expose CDP from your everyday browser: enable Allow remote debugging in chrome://inspect. Chrome then accepts WebSocket connections on the endpoint recorded in its profile’s DevToolsActivePort file – port on the first line, WebSocket path on the second – and asks you to approve each newly connecting client. Only that WebSocket endpoint is served (the /json/* HTTP interface belongs to the debug-instance mechanism described later). cdp_conninfo reads the file, and CDP.connect() uses it by default.
On MacOS, the connection info is stored in ~/Library/Application Support/Google/Chrome/DevToolsActivePort (or ~/Library/Application Support/Chromium/DevToolsActivePort for Chromium).
Chrome DevTools Protocol connection with event support
The transport is pluggable. _send (command frame in, reply frame out) and _dispatch (route an event frame to subscribed queues) are the only two methods that touch the websocket protocol flow, so an alternative transport subclasses CDP, overrides _send and the connection lifecycle, and feeds incoming events to _dispatch. Everything else (domain proxies, helpers, event buffers, Page) is inherited unchanged. solvecdp does exactly this, relaying frames through a solveit server to a Chrome extension.
Connect via Chrome remote debugging HTTP endpoint
remote targets the other mechanism: a separate “debug Chrome” started with a remote debugging port. fastcdp-setup creates a “CDP Chrome” launcher for one on remote’s default port 9223 (not 9222, which a main browser with built-in debugging enabled already holds). Since Chrome 136 the debugging switches are ignored for your everyday profile – a debug instance must point --user-data-dir at a non-standard directory, so that scripts can’t reach your real profile’s (differently-encrypted) cookies. To start one by hand on macOS:
That instance serves the classic HTTP metadata endpoints, and remote reads webSocketDebuggerUrl from /json/version to connect – no approval prompts, since the profile is disposable.
The third connection option: start the user’s installed Chrome ourselves, CDP-ready – no manual setup, no approval popups. launch runs it on a separate profile directory (Chrome requires a non-default one for debugging) with an ephemeral debug port, and quit shuts it down again. One launched instance per profile dir: a second launch on the same dir would just signal the running instance and exit.
We launch headless=True here so running this notebook’s tests doesn’t pop up a browser window; drop it when you want to watch.
Path of the installed Chrome/Chromium binary ($FASTCDP_CHROME overrides)
async def launch(
user_data_dir:NoneType=None, # Profile dir; `~/.cache/fastcdp/profile` if None
headless:bool=False, # Run without a visible window?
debug:bool=None, # Print protocol events?
timeout:int=10, # Seconds to wait for the debug endpoint
reuse:bool=True, # Connect to an instance already running on this profile? (Else raise)
):Launch the installed Chrome CDP-ready on its own profile dir, and connect to it
Quit the browser, wait until it has released its debug port, and close the connection
A second launch on the same profile connects to the running instance instead of failing (reuse=False to make it an error). The reattached handle has no proc, since the process belongs to whoever launched it, and quit works regardless. This is the recovery path when a kernel restart orphans a launched browser.
A quit Chrome leaves DevToolsActivePort behind, so launch trusts the file only after checking its port with is_port_free: a free port proves the file stale, and launch removes it and starts fresh.
The browser’s open page targets
Page targets as attribute-access rows, one line per target
Initialize self. See help(type(self)) for accurate signature.
Initialize self. See help(type(self)) for accurate signature.
Evaluate expr in the page, raising on a JS exception
Attach to target tid
'Example Domain'
Call self as a function.
Call self as a function.
Wait for CSS selector to match an element
Wait for JS expression to be truthy, return its value
Call self as a function.
True
Initialize self. See help(type(self)) for accurate signature.
Initialize self. See help(type(self)) for accurate signature.
Connect via remote debugging and return Page for the active tab
Call self as a function.
remote_page is the quickest start against a debug Chrome: connect and drive whichever tab is focused.
Create a new tab, return Page
True
attach_page is new_page’s counterpart for a tab that already exists: the same Page proxy, bound to the target you name.
Attach to the existing tab tid
Navigate to url and wait for load+idle
Context manager: subscribes before action, waits for load+idle after
Wait until network is idle for idle_ms
Screenshot of the viewport, or the whole scrollable page if full
''
{'nodeId': '2',
'ignored': False,
'role': {'type': 'internalRole', 'value': 'RootWebArea'},
'chromeRole': {'type': 'internalRole', 'value': 144},
'name': {'type': 'computedString',
'value': '',
'sources': [{'type': 'relatedElement', 'attribute': 'aria-labelledby'},
{'type': 'attribute', 'attribute': 'aria-label'},
{'type': 'attribute', 'attribute': 'aria-label', 'superseded': True},
{'type': 'relatedElement', 'nativeSource': 'title'}]},
'properties': [{'name': 'focusable',
'value': {'type': 'booleanOrUndefined', 'value': True}},
{'name': 'focused', 'value': {'type': 'booleanOrUndefined', 'value': True}},
{'name': 'url',
'value': {'type': 'string', 'value': 'https://httpbingo.org/forms/post'}}],
'childIds': ['18'],
'backendDOMNodeId': 2,
'frameId': 'B327EB775230754D1A31282EDBCC91BD'}
Chrome accessibility tree node with compact repr
Chrome accessibility tree node with compact repr
Build AXNode tree from flat CDP accessibility node list
Get accessibility tree for session
focusable=True focused=True url=https://httpbingo.org/forms/post [#2]
focusable=True editable=plaintext settable=True [#6]focusable=True editable=plaintext settable=True [#7]focusable=True editable=plaintext settable=True [#8]focusable=True [#10]focusable=True [#11]focusable=True [#12]focusable=True [#13]focusable=True [#14]focusable=True [#15]focusable=True [#16]focusable=True settable=True [#17]
focusable=True settable=True valuemin=1 valuemax=12 [#53]
focusable=True settable=True valuemax=59 [#55]
focusable=True settable=True valuemin=1 valuemax=2 [#57]
focusable=True hasPopup=menu [#3]focusable=True editable=plaintext settable=True multiline=True [#9]focusable=True [#62]
Find all descendants matching role and/or name substring
Find first descendant matching role and/or name substring
Find first descendant matching role and/or name substring
Click a DOM node
Run code with a DOM node as this
Call function fn on a DOM node, returning the protocol result
{}
Call self as a function.
{'type': 'undefined'}
Click element and wait for load+idle
Helpers for debugging apps: buffered console and network history, auto-handled dialogs, and a few conveniences. CDP only delivers events once the relevant domain is enabled, so each start_* helper enables it and buffers from that moment on.
Console/exception messages buffered since start_console, filtered by regex pattern
Enable and start buffering console messages and uncaught exceptions
start_console begins capture, and console returns everything seen so far; error: entries are uncaught exceptions, with their stack. Messages logged before start_console was called are never seen, so call it right after creating a page.
['log: hello 42',
'warning: watch out',
'error: Error: boom\n at <anonymous>:1:26']
The pattern regex filters entries:
Body of a response seen by start_network, decoded if base64
(status,url,requestId) of responses buffered since start_network, url filtered by regex pattern
Enable and start buffering network responses
requests answers “what did the page load, and with what status?”, and response_body fetches a body by the returned request id (Chrome only keeps bodies while the page is alive).
[(200, 'https://httpbingo.org/forms/post', 'A0012C0BE59AE318F96FC9B3C00B7D1A')]
Auto-respond to JS dialogs from now on, recording (type,message) in dialogs
Without a handler, an unexpected alert or confirm blocks the page – and any eval that triggered it – forever. After handle_dialogs, dialogs are answered as they open: accept=False dismisses them, and text fills prompts.
(True, [('confirm', 'Proceed?')])
Wait for text to appear in (with present=False, disappear from) the page body
Set a <select> element’s value and fire its change event
click on a <select> doesn’t open native dropdowns under CDP, so select_option sets the value directly (firing change so the app reacts). wait_for_text complements wait_for_selector when the interesting change is text, e.g. htmx swaps. And screenshot above takes full=True for the whole scrollable page.
[884, 6070]
True
To finish, exercise the browser we launched at the start end to end, then quit it:
Allow all CDP classes in safepyrun