| name | brow |
| description | Browser automation CLI for agents — control Chromium with simple commands |
brow — Browser Automation for Agents
Control a real Chromium browser from Claude Code. Navigate pages, click buttons, fill forms, take screenshots, and read page content — all through simple CLI commands.
Setup
pip install brow-cli
brow setup
Usage
Syntax is subcommand-first, with -s <id> on the subcommand:
brow session new --headed
brow navigate -s 1 "https://example.com"
brow snapshot -s 1
brow click -s 1 "text=Login"
brow fill -s 1 "#email" "user@example.com"
brow fill -s 1 "#password" "secret"
brow key -s 1 Enter
brow screenshot -s 1
brow session delete 1
Key Commands
| Command | Description |
|---|
session new [--headed] [--profile <name>] | Start browser session |
navigate -s <id> <url> | Go to URL |
snapshot -s <id> | Get accessibility tree (best for understanding page) |
screenshot -s <id> | Capture screenshot |
click -s <id> <selector> | Click element |
fill -s <id> <selector> <value> | Fill input field |
type -s <id> <text> | Type with keyboard |
key -s <id> <key> | Press key (Enter, Tab, Space, PageDown, End, Home) |
scroll -s <id> [--pixels <n>] | Scroll the outermost page |
upload -s <id> <selector> <file> | Upload file (use CSS selector, not numeric ID) |
html -s <id> | Get page HTML |
logs -s <id> [--count N] [--search <str>] | Get console logs |
network -s <id> [--count N] [--search <str>] [--response] [--clear] | Get network requests |
fetch -s <id> <url> [-X method] [-H header] [-d body] [--no-cookies] | Browser fetch → stdout; --no-cookies does plain HTTP to test auth requirement |
websocket -s <id> [--count N] [--search <str>] [--clear] | Get WebSocket messages |
actions -s <id> [--json] [--clear] | View recorded action log for current session |
replay -s <id> <playbook.yaml> [--var k=v] | Replay a playbook YAML (with optional var overrides) |
eval -s <id> <code> | Run Playwright Python; returns result + stdout. Helpers: text(sel), texts(sel) |
Scrolling
scroll only works on the outermost page — it does nothing inside iframes or fixed-height containers (Streamlit, React dashboards):
brow scroll -s 1 --pixels 3000
For content inside iframes or constrained containers, use keyboard navigation instead:
brow click -s 1 "text=Section Heading"
brow key -s 1 Space
brow key -s 1 PageDown
brow key -s 1 End
Network Inspection
Captures all requests across the session (accumulates across navigations). Static assets (images, fonts, scripts, CSS) are filtered out by default:
brow network -s 1
brow network -s 1 --count 100
brow network -s 1 --search "api"
brow network -s 1 --response
brow network -s 1 --include-static
brow network -s 1 --clear
Output format: METHOD STATUS content-type url
Authenticated Fetch
Make requests with the browser's session cookies — essential for reverse-engineering authenticated APIs:
brow fetch -s 1 "/rest/offer/v2/competitions/136806/matches" | jq
brow fetch -s 1 "https://example.com/api/data" -X POST -d '{"key":"val"}' -H "Content-Type: application/json"
WebSocket Inspection
Live sites push updates over WebSocket (socket.io, etc.). Capture and inspect frames:
brow websocket -s 1
brow websocket -s 1 --search "patch"
brow websocket -s 1 --clear
WebSocket messages are captured automatically from session start. Use --clear before a navigation to isolate messages for a specific page.
File Upload
Use CSS selectors — numeric snapshot IDs return 500:
brow upload -s 1 "input[type='file']" data.csv
brow upload -s 1 "[3]" data.csv
Profiles
Persistent Chromium profiles survive across sessions. Log in once, reuse forever:
brow session new --profile gmail --headed
brow navigate -s 1 "https://gmail.com"
brow session delete 1
brow session new --profile gmail
Profile conflict: session new fails if the profile is already in use: Profile 'default' already in use by session N. Fix: use a unique --profile <name> per concurrent session, or pass --reclaim to close the stale session and take over the profile.
Tips
- Use
--headed to see the browser while debugging
- Use
snapshot over screenshot — it's faster and uses fewer tokens
- Click/fill accept the snapshot ref directly:
click "[1]" works (same as click --ref 1)
eval returns result and stdout. page is async — await everything, or use the text(sel) / texts(sel) helpers for quick extraction
navigate --wait networkidle for JS-heavy pages instead of guessing with sleep
- Session IDs are simple integers: 1, 2, 3...
API Scouting
To reverse-engineer a site's API and generate a minimal scraper script, read the reference:
references/api-scout.md
Playbook Writer
After completing a brow session that accomplishes a task, crystallize it into a reusable YAML playbook and Python script:
references/playbook-writer.md
Selectors
- CSS:
#login, button.submit, input[type='file']
- Text:
text=Sign In
- Role:
role=button[name="Save"]