| name | browser-automation |
| description | Browser automation via self-managed Chrome + Playwright CDP. Use when: (1) automating browser workflows (scrape, fill forms, login, publish), (2) exploring a web page to understand its structure, (3) converting explorations into replayable zero-token scripts. NOT for: simple URL fetches (use WebFetch instead). |
Browser Automation
Explore (playwright-mcp) โ Record (script) โ Replay (browser-lock.sh). Self-managed Chrome, no external browser service required.
Architecture
Chrome (self-managed, CDP 19900-19999)
โโโ profile: <agent-dir>/browser/user-data/
โโโ config: <agent-dir>/browser/chrome.json
โโโ CDP port: auto-assigned
โ CDP
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
โ playwright-mcp โ โ Standalone scripts โ
โ (interactive) โ โโโ โ (replay, zero token) โ
โโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโ
After exploring, record as a script; browser-lock.sh runs it.
Requires: npm install playwright inside the agent directory (done during init).
Chrome Management
./scripts/chrome-launcher.sh start
./scripts/chrome-launcher.sh stop
./scripts/chrome-launcher.sh restart
./scripts/chrome-launcher.sh status
Phase 1: Explore (playwright-mcp)
Default: use the mcp__playwright-browser__* MCP tools for interactive exploration. The
harness lists them โ navigate, snapshot, click, type, evaluate and the rest; no roster here.
Exploration flow:
- Ensure Chrome is running (
chrome-launcher.sh start).
browser_navigate to target URL.
browser_snapshot to get elements + refs.
browser_click/browser_type + ref.
- Repeat snapshot โ act until the flow works.
Every MCP call returns the underlying Playwright code โ that's the material you'll record.
Phase 2: Record (save as script)
Collect Playwright snippets from the exploration and integrate into a reusable script:
- Start from
scripts/browser/playwright-template.js.
- Save as
scripts/browser/<verb>-<target>.js (e.g. publish-blog.js, read-inbox.js).
- Rules:
- On third-party sites, use
human-like.js helpers instead of direct operations
(anti-detection โ see below).
- Apply
applyStealthToContext(context) before opening any page.
page.close() in finally, NEVER browser.close() (the Chrome instance is shared
โ closing the browser kills every other script's session and the login profile).
- End with
process.exit(0) โ the open CDP connection otherwise keeps node alive and the
script hangs instead of exiting.
Conversion cheatsheet:
| playwright-mcp output | In the script |
|---|
await page.goto(url) | await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 }) |
await page.click(sel) | await humanClick(page, sel) |
await page.fill(sel, text) | await humanType(page, sel, text) |
await page.type(sel, text) | await humanType(page, sel, text) |
| no delay | await humanDelay(1000, 3000) between steps |
Phase 3: Replay
./scripts/browser-lock.sh run scripts/browser/<name>.js [args...]
./scripts/browser-lock.sh run --timeout 120 scripts/browser/<name>.js
browser-lock.sh auto-acquires a lock (prevents concurrent runs) โ starts Chrome if needed โ runs the script โ releases the lock.
Anti-Detection (MANDATORY)
All scripts must use anti-detection:
Layer 1: Stealth (fingerprint)
const { applyStealthToContext } = require('./utils/stealth');
await applyStealthToContext(context);
Layer 2: Human-Like (behavior)
| Banned | Required |
|---|
page.click(sel) | humanClick(page, sel) |
page.fill(sel, text) | humanType(page, sel, text) |
waitForTimeout(fixed) | humanDelay(min, max) |
| act immediately after load | humanBrowse(page) to simulate reading |
Module API
human-like.js: humanDelay, humanThink, humanClick, humanType, humanFillContentEditable, humanBrowse, humanScroll, jitterWait
stealth.js: applyStealthToContext, applyStealthToPage, verifyStealthStatus
Troubleshooting
| Problem | Fix |
|---|
| Lock held | ./scripts/browser-lock.sh release |
| CDP timeout | ./scripts/chrome-launcher.sh restart |
| Login expired | Relogin via playwright-mcp |
| Selector broken | Re-explore via playwright-mcp, update script |