| 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. Trigger phrases include "go to [url]", "click on", "fill out the form", "take a screenshot", "scrape", "automate", "test the website", "log into", or any browser interaction request. |
Dev Browser Skill
保持页面状态跨脚本执行的浏览器自动化。编写小型、专注的脚本以增量方式完成任务。一旦你验证了部分工作流且存在重复工作时,可以编写一个脚本在单次执行中完成重复工作。
选择你的方法
- 本地/源码可用的站点: 先阅读源代码以直接编写选择器
- 未知页面布局: 使用
getAISnapshot() 发现元素,使用 selectSnapshotRef() 与之交互
- 视觉反馈: 截取屏幕截图以查看用户所看到的内容
设置
安装: 详见 references/installation.md,包含 Windows 支持说明。
有两种模式可用。如果不确定使用哪种,请询问用户。
独立模式(默认)
为新的自动化会话启动新的 Chromium 浏览器。
./skills/dev-browser/server.sh &
如果用户要求,添加 --headless 标志。在运行脚本前等待 Ready 消息。
扩展模式
连接到用户现有的 Chrome 浏览器。在以下情况使用:
- 用户已登录站点,希望你在已认证的体验中执行操作(非本地开发)
- 用户要求使用扩展
重要: 核心流程仍然是相同的。你在他们的浏览器中创建命名页面。
启动中继服务器:
cd skills/dev-browser && npm i && npm run start-extension &
等待控制台显示 Waiting for extension to connect... 然后是 Extension connected。这表示客户端已连接,浏览器已准备好被控制。
工作流:
- 脚本像普通模式一样调用
client.page("name") 来创建新页面或连接到现有页面
- 自动化在用户实际的浏览器会话中运行
如果扩展尚未连接,请告诉用户启动并激活它。下载链接:https://github.com/SawyerHood/dev-browser/releases
编写脚本
从 skills/dev-browser/ 目录运行所有脚本。 @/ 导入别名需要此目录的配置。
使用 heredoc 内联执行脚本:
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
仅在以下情况下写入 tmp/ 文件:脚本需要重用、复杂或用户明确要求。
关键原则
- 小型脚本: 每个脚本做一件事(导航、点击、填写、检查)
- 评估状态: 在最后记录/返回状态以决定下一步
- 描述性页面名称: 使用
"checkout"、"login",而不是 "main"
- 断开连接以退出:
await client.disconnect() - 页面在服务器上持久存在
- 在 evaluate 中使用纯 JS:
page.evaluate() 在浏览器中运行 - 不支持 TypeScript 语法
工作流循环
对于复杂任务,遵循此模式:
- 编写脚本 执行一个操作
- 运行它 并观察输出
- 评估 - 它是否有效?当前状态是什么?
- 决定 - 任务是否完成,还是需要另一个脚本?
- 重复 直到任务完成
不要在浏览器上下文中使用 TypeScript
传递给 page.evaluate() 的代码在浏览器中运行,浏览器不理解 TypeScript:
const text = await page.evaluate(() => {
return document.body.innerText;
});
const text = await page.evaluate(() => {
const el: HTMLElement = document.body;
return el.innerText;
});
抓取数据
对于抓取大型数据集,请拦截并重放网络请求,而不是滚动 DOM。详见 references/scraping.md,其中包含请求捕获、模式发现和分页 API 重放的完整指南。
客户端 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");
page 对象是标准的 Playwright Page。
等待
import { waitForPageLoad } from "@/client.js";
await waitForPageLoad(page);
await page.waitForSelector(".results");
await page.waitForURL("**/success");
检查页面状态
屏幕截图
await page.screenshot({ path: "tmp/screenshot.png" });
await page.screenshot({ path: "tmp/full.png", fullPage: true });
ARIA 快照(元素发现)
使用 getAISnapshot() 发现页面元素。返回 YAML 格式的无障碍树:
- banner:
- link "Hacker News" [ref=e1]
- navigation:
- link "new" [ref=e2]
- main:
- list:
- listitem:
- link "Article Title" [ref=e8]
- link "328 comments" [ref=e9]
- contentinfo:
- textbox [ref=e10]
- /placeholder: "Search"
解读引用:
[ref=eN] - 用于交互的元素引用(仅限可见的、可点击的元素)
[checked]、[disabled]、[expanded] - 元素状态
[level=N] - 标题级别
/url:、/placeholder: - 元素属性
通过引用交互:
const snapshot = await client.getAISnapshot("hackernews");
console.log(snapshot);
const element = await client.selectSnapshotRef("hackernews", "e2");
await element.click();
错误恢复
页面状态在失败后持久存在。使用以下方式调试:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("hackernews");
await page.screenshot({ path: "tmp/debug.png" });
console.log({
url: page.url(),
title: await page.title(),
bodyText: await page.textContent("body").then((t) => t?.slice(0, 200)),
});
await client.disconnect();
EOF