cdp

Chrome DevTools: snapshot, act, sniff, replay

Attach to a running Chrome with cdp_connect(). pg.snapshot() gives an agent a compact a11y map; fill_form / act drive it. Network capture + replay for hidden APIs.


source

cdp_app

def cdp_app(
    url, port:int=9223, user_data_dir:NoneType=None, wait:int=15
):

Open url as an app window – no tabs, no address bar – in the persistent debug Chrome.


source

cdp_ws

def cdp_ws(
    port:int=9223, headless:bool=True, user_data_dir:NoneType=None, extra_flags:NoneType=None
)->str:

Websocket debugger URL of the debug Chrome on port, starting one (headless by default) if needed. For scrapling’s cdp_url=.


source

cdp_connect

async def cdp_connect(
    port:int=9223, user_data_dir:NoneType=None, headless:bool=False, extra_flags:NoneType=None
):

Connect to a debug Chrome on port; if none is running, set one up on a persistent profile and connect.


source

cdp_setup

async def cdp_setup(
    port:int=9223, user_data_dir:NoneType=None, headless:bool=False, timeout:NoneType=None,
    extra_flags:NoneType=None
):

Start a persistent debug Chrome on port with its own profile, ready for CDP.remote


source

CDP.eval

async def eval(
    expr:str, sid:NoneType=None
):

Evaluate expr in the page and return its decoded value.

Upgrades fastcdp’s eval in three ways every caller wants: promises are awaited, objects and arrays come back as Python values instead of None, and a JS exception is raised as JSError rather than silently read as an empty result.


source

JSError

def JSError(
    *args, **kwargs
):

JavaScript raised while evaluating an expression in the page.


source

CDP.open_page

async def open_page(
    url
):

Call self as a function.

cdp = syncy(cdp_connect())
assert cdp.is_open
pgs = syncy(cdp.pages)
if pgs:
    tid = pgs[0]['targetId']
    sid = syncy(cdp.attach(tid))
    pg=Page(cdp, tid, sid)
    root = syncy(pg.ax_tree())
    print(str(root)[:300])
- **RootWebArea** "New Tab" `focusable=True` `url=chrome://new-tab-page/` [#1]
  - **Iframe** "" [#44]
  - **combobox** "Search Google or type a URL" `live=polite` `relevant=additions text` `focusable=True` `editable=plaintext` `settable=True` `hasPopup=listbox` [#24]
  - **button** "Search by voice
page = syncy(cdp.new_page())
syncy(page.goto('https://vedicreader.com/s/'))
rt = syncy(page.ax_tree())
print(str(rt)[:300])
- **RootWebArea** "VedicReader" `focusable=True` `focused=True` `url=https://vedicreader.com/` [#2]
  - **navigation** "" [#128]
    - **link** "Vr." `focusable=True` `url=https://vedicreader.com/` [#129]
      - **heading** "Vr." `level=4` [#130]
        - **StaticText** "Vr." [#262]
          - **
syncy(page.click(rt.find_id('button', 'Test Drive')))

Debug Chrome

cdp_install / keep-alive launch a persistent profile. Cookies survive restarts.

# LIST — is a debug Chrome up, and what's open in it?
import httpx
port = 9223
print('running on 9223?', _debug_running(port))
ver = httpx.get(f'http://127.0.0.1:{port}/json/version').json()
print('browser:', ver['Browser'], '| ws:', ver['webSocketDebuggerUrl'])
for t in httpx.get(f'http://127.0.0.1:{port}/json/list').json():   # open tabs/pages
    print(' ', t['type'], '|', t['title'][:40], '|', t['url'][:50])
print('live debug ports:', [p for p in (9222, 9223, 9224) if _debug_running(p)])
running on 9223? True
browser: Chrome/150.0.7871.129 | ws: ws://127.0.0.1:9223/devtools/browser/bb4a47a3-b032-4afd-b398-83f1de66a3ab
  page | Gayathri Dhyaanam | https://vedicreader.com/
  page | New Tab | chrome://newtab/
  iframe | chrome-untrusted://new-tab-page/one-goog | chrome-untrusted://new-tab-page/one-google-bar?par
  browser_ui | Omnibox Popup | chrome://omnibox-popup.top-chrome/
  browser_ui | Omnibox Popup | chrome://omnibox-popup.top-chrome/omnibox_popup_ai
  service_worker | Service Worker chrome-extension://ilaikd | chrome-extension://ilaikdjkclophmmhodhgfmnimfeohkg
  service_worker | Service Worker chrome-extension://hbicfl | chrome-extension://hbicflpnhfmdndpibdegbnkkpngjbhf
  service_worker | Service Worker chrome-extension://epnhoe | chrome-extension://epnhoepnmfjdbjjfanpjklemanhkjgi
live debug ports: [9223]
# CLOSE a single tab, QUIT the whole browser, then RELAUNCH it HEADED for interactive login
import httpx, time
port = 9223
tabs = httpx.get(f'http://127.0.0.1:{port}/json/list').json()
if tabs: httpx.get(f"http://127.0.0.1:{port}/json/close/{tabs[0]['id']}")   # close one tab by target id
cdp = syncy(cdp_connect(port=port))
try: syncy(cdp.quit())                        # quit browser+connection; raises ConnectionClosedOK as the socket drops
except Exception: pass
time.sleep(1.5)
assert not _debug_running(port), 'browser should be down after quit()'
syncy(cdp_setup(port, headless=False))        # relaunch HEADED so you can log in by hand
assert _debug_running(port)
print('headed Chrome ready on', port, '— log in, then reuse it via fetch(url, session=True)')
headed Chrome ready on 9223 — log in, then reuse it via fetch(url, session=True)

App windows

cdp_app opens a headed app window on the persistent profile.

# cdp_app's flags: an app window, headed, on the persistent profile. No browser is launched here.
flags = _chrome_flags(9223, _profile_dir(), False, ['--app=http://alpha.localhost:5001/'])
assert '--app=http://alpha.localhost:5001/' in flags
assert '--headless=new' not in flags                  # an app window you cannot see is useless
assert f'--user-data-dir={_profile_dir()}' in flags   # same profile as cdp_setup, so logins carry
assert str(_profile_dir()).endswith('cdp-chrome')
assert _profile_dir('~/x') == Path.home()/'x'         # user_data_dir wins, and ~ is expanded
# cdp_app end to end against a stubbed browser: reuse a running Chrome, report its target.
# The stubs rebind this notebook's own names, because that is what `cdp_app` resolves at call
# time -- patching `fossick.cdp` would leave the definitions in these cells untouched.
import httpx, subprocess as _sp

_saved = (_debug_ready, _sp.Popen, httpx.get)
launched = []
class _Targets:
    def json(self): return [{'id': 'target-1', 'url': 'http://alpha.localhost:5001/'}]
try:
    _debug_ready = lambda port, host='127.0.0.1': True     # a debug Chrome is already up
    _sp.Popen = lambda args, **kw: launched.append(args)
    httpx.get = lambda url, **kw: _Targets()
    r = cdp_app('http://alpha.localhost:5001/', wait=1)
finally:
    _debug_ready, _sp.Popen, httpx.get = _saved

assert r['ok'] and r['reused'] is True         # reused, so no second resident browser
assert r['target'] == 'target-1'               # and the window was found by its url
assert r['url'] == 'http://alpha.localhost:5001/' and r['port'] == 9223
assert '--app=http://alpha.localhost:5001/' in launched[0]
assert f'--user-data-dir={_profile_dir()}' in launched[0]
print('cdp_app:', {k: r[k] for k in ('reused', 'target')})
cdp_app: {'reused': True, 'target': 'target-1'}
# Open two app windows on one browser and confirm the second reused the first's process.
a = cdp_app('https://example.com/')
b = cdp_app('https://example.org/')
assert a['ok'] and b['ok']
assert b['reused'], 'the second window should reuse the browser the first one started'
assert a['profile'] == b['profile']
print('windows:', a['target'], b['target'], '| reused:', a['reused'], b['reused'])

source

CDP.calls

async def calls(
    url:NoneType=None, pattern:str='.*', tail:int=3
):

Outgoing requests matching pattern. Navigates if url given, else passive.


source

cdp_cookies

def cdp_cookies(
    url_or_domain:NoneType=None, port:int=9223, as_dict:bool=False
):

Export cookies from the running debug Chrome. Returns Playwright-format list (for scrapling cookies=), or {name: value} if as_dict.


source

Page.md

async def md(
    sel:str=None, **kw
):

Live page as clean markdown via fossick’s to_md (optionally narrowed to CSS sel)


source

Page.selector

async def selector():

Live page as a scrapling Selector for CSS/xpath querying


source

Page.html

async def html():

Live outerHTML of the page (post-JS), as a string


source

Page.fill_form

async def fill_form(
    page:Page, fields:dict, submit:str=None
):

Fill a form by field label/name: {label: value}. Native