Agent Browser
Browser automation CLI for AI agents. Installed globally as agent-browser.
Core Workflow
Every browser automation session follows this pattern:
agent-browser open <url> --session <name>
agent-browser snapshot -i --session <name> # get interactive elements
agent-browser click @e5 --session <name> # act on refs
agent-browser eval "document.title" --session <name> # extract data
agent-browser close --session <name> # always clean up
Session Management
Always use --session <name> to isolate browser sessions. Always close sessions when done.
agent-browser open https://example.com --session my-test
# ... interactions ...
agent-browser close --session my-test
Snapshot-First Pattern
Before interacting with any element, take a snapshot to get ref IDs:
# Get only interactive elements (buttons, links, inputs, etc.)
agent-browser snapshot -i --session s1
# Output example:
# - button "Submit" [ref=e3]
# - link "Home" [ref=e5]
# - combobox "Search" [ref=e7]
# Then act on refs
agent-browser click @e3 --session s1
agent-browser fill @e7 "search query" --session s1
Refs go stale after sleep/wait: If a snapshot is taken, then a
sleeporwaitoccurs, the refs may no longer be valid because the browser's internal element mapping drifts. Always take a freshsnapshot -iimmediately before acting on refs.
Snapshot options:
-i/--interactive— only interactive elements (preferred)-c/--compact— remove empty structural elements-d <n>/--depth <n>— limit tree depth-s <sel>/--selector <sel>— scope to CSS selector
Data Extraction with eval
Always prefer eval over console for extracting data from pages. eval returns structured data directly to stdout. console output is noisy and mixed with unrelated application logs.
# Get structured data
agent-browser eval "JSON.stringify(someObject)" --session s1
# Get text content
agent-browser eval "document.querySelector('h1').textContent" --session s1
# Run async code (return a Promise)
agent-browser eval "
new Promise(resolve => {
setTimeout(() => resolve('done'), 1000)
})
" --session s1
# Query window globals
agent-browser eval "JSON.stringify(window.__WEB_VITALS__)" --session s1
Element Selection
Three ways to select elements (in order of preference):
- Refs from snapshot —
@e3(most reliable after a snapshot) - CSS selectors —
button.submit,#login-form input[type=email] - Find locators —
agent-browser find role button click --name Submit
Key Commands
Navigation
agent-browser open <url> # navigate to URL
agent-browser back # go back
agent-browser forward # go forward
agent-browser reload # reload page
Interaction
agent-browser click <sel> # click element
agent-browser type <sel> <text> # type into element (appends)
agent-browser fill <sel> <text> # clear and fill element
agent-browser press <key> # press key (Enter, Tab, Escape)
agent-browser hover <sel> # hover element
agent-browser select <sel> <val> # select dropdown option
agent-browser check <sel> # check checkbox
agent-browser uncheck <sel> # uncheck checkbox
agent-browser scroll <direction> # scroll up/down/left/right
Get Information
agent-browser get text <sel> # get text content
agent-browser get html <sel> # get innerHTML
agent-browser get value <sel> # get input value
agent-browser get attr <sel> <n> # get attribute
agent-browser get title # get page title
agent-browser get url # get current URL
agent-browser get count <sel> # count matching elements
Check State
agent-browser is visible <sel> # check visibility
agent-browser is enabled <sel> # check if enabled
agent-browser is checked <sel> # check if checked
Capture
agent-browser screenshot [path] # take screenshot
agent-browser screenshot --full # full page screenshot
agent-browser pdf <path> # save as PDF
Waiting
agent-browser wait <sel> # wait for element to appear
agent-browser wait 2000 # wait N milliseconds
Debug
agent-browser console --session s1 # view console logs
agent-browser console --clear --session s1 # clear console buffer
agent-browser errors --session s1 # view page errors
agent-browser highlight <sel> --session s1 # highlight element
Common Patterns
Verify page content after navigation
agent-browser open http://localhost:4001/path --session test
agent-browser get text "h1" --session test
agent-browser screenshot --session test
agent-browser close --session test
Fill a form and submit
agent-browser open http://localhost:4001/login --session test
agent-browser snapshot -i --session test
agent-browser fill @e2 "user@example.com" --session test
agent-browser fill @e3 "password123" --session test
agent-browser click @e4 --session test
agent-browser wait ".dashboard" --session test
agent-browser close --session test
Extract structured data from a page
agent-browser open http://localhost:4001/page --session test
agent-browser eval "JSON.stringify({
title: document.title,
h1: document.querySelector('h1')?.textContent,
links: document.querySelectorAll('a').length,
})" --session test
agent-browser close --session test
Filter snapshot to find specific elements
agent-browser snapshot -i --session s1 2>&1 | grep "Submit"
Wait for dynamic content then interact
agent-browser open http://localhost:4001/page --session test
agent-browser wait ".loaded-indicator" --session test
agent-browser snapshot -i --session test
# Now interact with dynamically loaded elements
agent-browser close --session test
Authentication
Basic login flow
agent-browser open https://app.example.com/login --session auth
agent-browser snapshot -i --session auth
agent-browser fill @e1 "user@example.com" --session auth
agent-browser fill @e2 "password123" --session auth
agent-browser click @e3 --session auth
agent-browser wait --load networkidle --session auth
agent-browser get url --session auth # verify redirect to dashboard
Save and restore auth state
Always pass state save/state load an absolute path into the scratchpad directory — never a relative one. Relative paths resolve against the CLI's own working directory, not yours, and have landed state.json files full of session cookies inside repo checkouts.
# After logging in, save state for reuse
agent-browser state save "$SCRATCHPAD/auth-state.json" --session auth
# Later, skip login by loading saved state
agent-browser state load "$SCRATCHPAD/auth-state.json" --session auth
agent-browser open https://app.example.com/dashboard --session auth
For advanced patterns (OAuth, 2FA, cookies, token refresh), see references/authentication.md.
Known failure modes — rule these out before blaming the app
These automation artifacts reliably mimic real application bugs and have each burned significant debugging time:
- Below-fold clicks silently miss. A center-click on an element outside the viewport (common in tall dialogs/drawers) can land on the backdrop — dismissing a Radix dialog instead of pressing its button. Scroll the target into view first, and treat any click that produces no DOM/network change as a suspected miss, not an app bug.
fill("")doesn't clear React-controlled inputs. React's value tracker swallows it (and Cmd+A doesn't select inside number inputs). Clear with trusted keystrokes: click into the field, pressEnd, thenBackspacerepeatedly.filldoesn't fire the events debounced fields listen to.fill <sel> <text>sets the value, but a debouncedonChange(search boxes and kin) never sees a keystroke, so nothing submits — the field looks filled while the page never updates. Drive such fields withtype(real key events), clearing any previous value first with the keystroke ritual above.- Synthetic events don't drive Radix or react-hook-form.
check/selectpointer events can trigger Radix's outside-click dismiss; Radix DropdownMenu opens onpointerdown, not click; native<select>changes don't fire React's controlledonChange; programmatically-set field values fail react-hook-form client validation, so the submit silently no-ops. Useevalwith native value setters plus dispatched events, submit forms viaform.requestSubmit(), or drive the route action directly (session-cookie POST) and document the deviation. - Proof of a write is the POST plus the resulting row — never a screenshot. A filled-in form can render perfectly and still be unsubmittable (an unregistered field cancels the submit with zero feedback). Confirm the mutation landed by re-fetching the page or checking the database.
- A 404 or empty page often means the record belongs to a different access scope than the session, not a broken route. Verify the record is in scope for the current session before treating it as a bug.
- Don't edit HMR-watched files while a browser session runs against a dev server. A reload mid-run invalidates the session's state and poisons its results — queue the edits or give the browser run an isolated worktree.
Anti-Patterns
- Don't use
consoleto extract data — it's noisy and mixed with app logs. Useevalinstead. - Don't forget to close sessions — leaked sessions keep browser processes running.
- Don't interact without snapshotting first — refs change between page loads; always get fresh refs.
- Don't use
--headedin automated workflows — headless is the default and preferred for agent use.
For the full CLI reference, see references/cli-reference.md. For authentication patterns, see references/authentication.md.