| name | dev-browser |
| description | Browser automation with persistent page state. Use when users ask to navigate websites, fill forms, take screenshots, extract web data, test web apps, or automate browser workflows. |
| user-invocable | true |
| disable-model-invocation | true |
| argument-hint | [url or automation task] |
Dev Browser Skill
Task: $ARGUMENTS
No task specified → ask the user what to automate.
Browser automation with persistent page state. Small, focused scripts per action. Proven workflow → combine into single script.
Approach
- Source-available sites: Read source for selectors
- Unknown layouts:
getAISnapshot() → selectSnapshotRef()
- Visual feedback: Screenshots
Setup (MANDATORY)
Browser server must be running. Two modes: ask user if unclear.
Standalone Mode (Default)
New Chromium browser for fresh sessions.
${CLAUDE_PLUGIN_ROOT}/skills/dev-browser/server.sh &
Note (Windows): These instructions apply to the scripting layer only.
The plugin's hook infrastructure requires bash — use WSL or Git Bash on Windows.
Add --headless flag if user requests it. Wait for the Ready message before running scripts.
Extension Mode
Connect to user's existing Chrome. Use when already logged in or user requests extension.
If the companion extension is not already installed, ask the user to install it from the supported project release/source they trust. Do not invent download URLs or load arbitrary extensions.
Start relay:
cd skills/dev-browser && npm i && npm run start-extension &
Writing Scripts
Run all from skills/dev-browser/. Use inline heredocs for one-off scripts. For reusable or complex scripts, write them under tmp/ only.
cd skills/dev-browser && npx tsx <<'EOF'
import { connect, waitForPageLoad } from "@/client.js";
const client = await connect();
const page = await client.page("example", { viewport: { width: 1920, height: 1080 } });
await page.goto("https://example.com");
await waitForPageLoad(page);
console.log({ title: await page.title(), url: page.url() });
await client.disconnect();
EOF
Key Principles
- One thing per script (navigate, click, fill, check)
- Log state at end for next steps
- Descriptive page names (
"checkout", not "main")
- Disconnect to exit (pages persist)
- Plain JS in evaluate — no TypeScript syntax
No TypeScript in Browser Context
page.evaluate() runs in browser — TS annotations fail at runtime:
const text = await page.evaluate(() => document.body.innerText);
const text = await page.evaluate(() => {
const el: HTMLElement = document.body;
return el.innerText;
});
Workflow Loop
- Write a script to perform one action
- Run it and observe the output
- Evaluate - did it work? What's the current state?
- Decide - is the task complete or do we need another script?
- Repeat until task is done
Client API
const client = await connect();
const page = await client.page("name");
const pageWithSize = await client.page("name", { viewport: { width: 1920, height: 1080 } });
const pages = await client.list();
await client.close("name");
await client.disconnect();
const snapshot = await client.getAISnapshot("name");
const element = await client.selectSnapshotRef("name", "e5");
Waiting
import { waitForPageLoad } from "@/client.js";
await waitForPageLoad(page);
await page.waitForSelector(".results");
await page.waitForURL("**/success");
Screenshots
await page.screenshot({ path: "tmp/screenshot.png" });
await page.screenshot({ path: "tmp/full.png", fullPage: true });
ARIA Snapshot (Element Discovery)
Use getAISnapshot() to discover page elements. Returns YAML-formatted accessibility tree:
- banner:
- link "Hacker News" [ref=e1]
- navigation:
- link "new" [ref=e2]
- main:
- list:
- listitem:
- link "Article Title" [ref=e8]
Interacting with refs:
Refs are tied to the current snapshot and may change after navigation, re-rendering, or dynamic updates. Take a fresh snapshot before selecting a ref if the page state changed. Prefer accessible roles/names from the snapshot over brittle CSS selectors when possible.
const snapshot = await client.getAISnapshot("hackernews");
console.log(snapshot);
const element = await client.selectSnapshotRef("hackernews", "e2");
await element.click();
Error Recovery
Page state persists after failures. Debug the current state:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("debug-target");
console.log({ url: page.url() });
await client.disconnect();
EOF