Browser API
Getting tabs
Section titled “Getting tabs”tab = await browser.get("*example.com*") # URL globtab = await browser.get("9222:a1b2c3") # target IDtab = await browser.get("*app*", fresh=True) # only newly-appearing tabstab = await browser.get("*app*", timeout=10) # wait up to 10stab = await browser.get("*app*", ready="#root") # wait for element after attachtab = await browser.open("https://...") # open new tabawait browser.watch("*pattern*") # auto-attach current + future
browser.tabs # list[Tab] attachedawait browser.pages() # all Chrome targetsbrowser.patterns # active watch patterns (property)await browser.detach("*pattern*") # detach by patternawait browser.detach() # detach everythingbrowser.clear(target=) # clear captured data
await browser.connect(9223) # add another Chrome instanceawait browser.connect(profile="/path/to/profile") # port from DevToolsActivePortawait browser.disconnect() # unpin tabs, close all WebSocketsawait browser.disconnect(port=9222) # unpin + close one Chrome instanceready= parameter
Section titled “ready= parameter”Stores a selector or a JS expression on the Tab. Used by get(), open(), navigate(), reload(), and session recovery after HMR. It’s the one parameter that accepts either, so the shape decides:
- Selector → resolved and polled every 100ms. Anything starting with
.,#,[,data-,text=,role=orlabel=, anything containing:has-text(, and any bare name (main,my-app) — the same setclick()andwait_for()accept. - JS expression →
Runtime.evaluate, must return truthy. Anything else, which in practice means it has a dot or a call in it (window.ready,app.isLoaded()). - Default (no
ready=): waits fordocument.readyState === 'complete'.
The bare-name case is the one worth knowing: ready="main" and ready="my-app" are ordinary CSS, and are treated as such.
Async methods
Section titled “Async methods”await tab.js(expr, *, await_promise=True, user_gesture=True) → AnyEvaluate JavaScript with REPL semantics. Top-level await works. Promise results are awaited by default. let/const can be redeclared across calls. Raises BrowserJSError on exceptions.
await tab.click(selector, *, button='left', click_count=1) → NoneMouse click via Input.dispatchMouseEvent. Auto-waits up to 2s. Produces isTrusted=true events.
type_text
Section titled “type_text”await tab.type_text(selector, text, *, delay_ms=0, press_enter=False) → NoneFocus element, select-all, type character-by-character. Auto-waits up to 2s.
tap / swipe / scroll
Section titled “tap / swipe / scroll”await tab.tap(selector_or_x, y=None) → Noneawait tab.swipe(x1, y1, x2, y2, *, steps=10, duration_ms=300) → Noneawait tab.scroll(selector, dy=0, dx=0, *, steps=10, duration_ms=300) → NoneTouch events for mobile Chrome via ADB. scroll() is sugar over swipe() —
resolves selector to its center and swipes the opposite direction
(scrollBy semantics: positive dy scrolls down, positive dx scrolls right).
await tab.key(key) → NoneDispatch a keyDown+keyUp pair for a named key (e.g. "Enter", "Escape",
"Space"). "Enter" and "Space" carry the produced character so Chromium’s
native button/form activation fires — without it, the DOM keydown/keyup still
dispatch but a focused button silently doesn’t click.
await tab.fetch(url, *, method='GET', body=None, headers=None) → dictIn-page fetch() — inherits cookies, session, CORS origin. Returns {"status": int, "ok": bool, "body": Any}. Body is auto-parsed as JSON when content-type includes json.
navigate / reload
Section titled “navigate / reload”await tab.navigate(url) → Noneawait tab.reload() → NoneBoth wait for the ready signal after page load.
await tab.close() → NoneCloses this tab (Target.closeTarget). Session cleanup follows from the resulting Target.targetDestroyed event, same as a user closing it.
await tab.tree() → list[str]Compact accessibility tree as text lines. Crosses iframes.
screenshot
Section titled “screenshot”await tab.screenshot(*, full_page=False, path=None) → dictAlways writes a PNG — to path if given, otherwise a 0600 file under $XDG_RUNTIME_DIR/repld/. Returns {path, source: {width, height}, model: {width, height}, scale, bytes}. The image is resized to the vision API’s token grid; when scale < 1, multiply coordinates by 1/scale to map back to page pixels.
wait_for / wait_for_idle
Section titled “wait_for / wait_for_idle”await tab.wait_for(selector, *, timeout=5.0) → Noneawait tab.wait_for_idle(*, timeout=5.0, quiet=0.5) → int # settle mspin / unpin / gates
Section titled “pin / unpin / gates”await tab.pin(reason='', guard_unload=True) → None # guard_unload=False for live-reload dev serversawait tab.unpin() → Noneawait tab.confirm(prompt) → boolawait tab.choose(prompt, options) → strawait tab.ask(prompt) → strawait tab.cdp(method, **params) → dictRaw CDP passthrough.
cookies
Section titled “cookies”await tab.cookies() → list[dict]All cookies for this tab via Network.getCookies.
controls / invoke
Section titled “controls / invoke”await tab.controls() → dict | Noneawait tab.invoke(control, action, args=None) → dictcontrols() calls the page’s window.controls.describeAll(), returning the schema for every registered control — or None if the page exposes no window.controls. invoke() runs one action and returns {returned, stateBefore, stateAfter, duration}. See the controls guide for the protocol a page implements.
Sync query methods (DuckDB-backed)
Section titled “Sync query methods (DuckDB-backed)”All four take since=, and on all four it is epoch seconds — pass time.time(). The three underlying CDP clocks (wall-time seconds, Runtime.Timestamp milliseconds, Network.MonotonicTime from an arbitrary origin) are converted for you.
network
Section titled “network”tab.network(url=, method=, status=, type=, since=, include_assets=False) → RowsQuery captured requests. url uses LIKE matching (* → %). Assets excluded by default. Max 500 rows, newest-first.
console
Section titled “console”tab.console(level=, source=, since=) → RowsQuery console messages. Max 200 rows.
tab.sse(url=, event_name=, since=) → RowsQuery SSE (EventSource) messages. Each row: request_id, event_name, event_id, data, timestamp.
lifecycle
Section titled “lifecycle”tab.lifecycle(name=, since=) → RowsQuery Page.lifecycleEvent entries: DOMContentLoaded, load, networkIdle, etc.
request / body
Section titled “request / body”tab.request(request_id) → dict # full HAR entry (headers, timing, postData)tab.body(request_id) → dict # response body {"body": str, "base64Encoded": bool}row.body() → dict # shortcut on any network Rowtab.clear() → NoneMulti-browser
Section titled “Multi-browser”browser.connect(port) adds a Chrome instance to the pool — call it multiple times for multi-browser setups. Target IDs include the port prefix (42829:abc123 vs 43213:def456), so tab-scoped tools route to the right Chrome automatically.
await browser.connect(42829)await browser.connect(43213)await browser.watch("*localhost:5200*") # watches across bothbrowser.tabs # tabs from all instancesConnected ports and watch patterns persist across kernel restarts. On boot, repld prompts on the terminal ([Y/n], default yes) before reconnecting and re-watching — headless boot (--no-display) or non-tty stdin skips the restore entirely.
The dashboard’s Connections tab gives you the same connect/watch/disconnect controls from a browser instead of exec.
Console error push
Section titled “Console error push”Console errors and uncaught exceptions from watched tabs push as [console:error] channel messages the moment they happen — no polling:
[console:error] 9222:af5ae1: TypeError: Cannot read property 'x' of nullCross-tab duplicates within 2 seconds are collapsed into one follow-up message (... (×14 tabs)). Mute noisy patterns:
browser.suppress("[vite] failed to connect") # mute matching errorsbrowser.unsuppress("[vite] failed to connect") # un-mutebrowser.suppressed # list active patternsSuppress patterns persist across kernel restarts.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
tab.url | str | Current URL (cached — use tab.js("location.href") for live) |
tab.title | str | Page title (cached) |
tab.type | str | "page", "iframe", "service_worker", etc. |
tab.target_id | str | Short ID in {port}:{6-hex} format |
tab.capture_bodies | bool | Toggle Fetch body capture (True on get/open, False on watch) |
tab.label | str | Human-readable identifier |
Selectors
Section titled “Selectors”| Pattern | Type | Focus-safe |
|---|---|---|
.class, #id, [attr] | CSS | Yes |
[data-testid='name'] | CSS | Yes |
text=Submit | Text match | No |
role=button[name="Save"] | ARIA | No |
label=Username | Label | No |
tag:has-text('OK') | CSS + text | No |