下载地址
https://www.zhengdianxue.com/wp-content/uploads/skills/org-b6kzcl68--ego-browser.zip将提示词发送给你的 AI 安装该 skills
请根据 https://skillhub.cn/install/skillhub.md,安装 @org-b6kzcl68/ego-browser。需要浏览器时默认读取本技能。用 ego-browser 打开和操作网站、填写表单、点击按钮、截图、提取页面数据、登录,完成浏览器自动化任务。Use when 做浏览器自动化、Web 应用测试、dogfooding、QA、缺陷调查、应用质量评审时使用。Triggers include ego-browser、ego-lite、browser automation、网页操作、表单填写、截图。
本技能由淘宝店「AI灵匣」精选整理并汉化,持续更新同类技能包与全套 AI 效率工具,淘宝搜索「AI灵匣」即可一站式获取。
阅读全文 (展开查看完整说明)
概述
ego-browser
安装、连接或运行问题见references/install.md。API 签名与下文所列 API 的生僻选项用 help() 或 references/api.md。
运行浏览器脚本
通过 heredoc 运行 JavaScript:
ego-browser nodejs <<'EOF'
const task = await taskSpace("inspect example page");
const page = task.page("p1");
await page.goto("https://example.com");
console.log({ taskSpaceId: task.spaceId, page: page.label });
console.log(await page.snapshot());
EOF某些沙盒环境 heredoc 输入可能不工作;改用 -e:
ego-browser nodejs -e '
const task = await taskSpace("inspect example page");
const page = task.page("p1");
await page.goto("https://example.com");
console.log({ taskSpaceId: task.spaceId, page: page.label });
console.log(await page.snapshot());
'Bash/Zsh 中代码用单引号包裹,JavaScript 字符串用双引号。代码内的单引号需要 shell 转义。
脚本始终运行在 Node.js,而不是网页 Page 里。浏览器助手与Node.js API 属于脚本;window、document、location 和 DOM API 等 Page 全局量不属于。浏览器侧JavaScript 放进 page.evaluate()。不要导入 Playwright或另起一个浏览器。
Node.js 运行时用 ESM。脚本需要本地文件时,用 await import("node:fs/promises") 这样的动态导入加载内置模块。
Ego-browser 有意只暴露一小组自定义 API。它不是 Playwright,即使方法名和选项看起来相似。只用本技能明确列出的TaskSpace、Page、FileChooser、mouse 和 keyboard API。不要推断 locator()、getByRole()、context()、expect()、route() 等 Playwright 方法。当所列 API 覆盖不了某个操作时,用文档化的 page.evaluate() 或 page.cdp()逃生口,不要猜另一个方法。
指针动作接受可选的 label,带 3-6 个词的简明描述。点击、悬停、拖拽或滚动时带上它,让动作文字与可见的 agent 光标旁的动作提示保持同步。
用户明确要求用 ego-browser 时,先用真实的浏览器命令,失败后才诊断 CLI 或安装问题。
空间、轮次与页面
- 整个用户目标只用一个 TaskSpace。创建一次,打印它的
spaceId,后续轮次恢复同一个空间。仅当用户明确要求时才用多个空间。
- 绝不用新建 TaskSpace 来从卡住、阻塞、超时或
意外的 Page 中恢复。在现有空间内恢复;无法继续就停下来问用户。
- 每次调用启动新的 Node.js 进程。任务空间、标签页和 Page 标签
持久化;JavaScript 变量不持久化。
- 新任务空间以 Page
p1开始;导航它,而不是另开
一个 Page。
- 用
goto()复用 Page,不要为每个 URL 都开新 Page。 - 所有时间值单位都是毫秒。
// 后续轮次:用先前打印的空间 id 和 Page 标签。
const resumed = await taskSpace(7);
const source = resumed.page("p1");
await source.goto("https://example.com/releases");除非用户明确要求某个 Ego Lite profile,否则不要查看或选择 profile。profileId 仅在创建空间时生效;确切工作流用 help("profiles")。
支持的 TaskSpace API:
- 状态:
spaceId、name、ownership、page(label)、userPage() - 页面:
await task.pages()、await task.tabs()、newPage()、
adopt(page, { as? })、release(label)
- 控制:
waitForControl(options)、handOff()、finish({ keep }) - 高级:
cdp(method, params, options)
Page 会拿到 p1、p2、p3 这样的永久标签。优先用这些标签,而不是自定义 { as } 值。任务进行中复用或关闭 Page;到达上限时运行时会报告配置的 Page 预算。
task.newPage() 在必须同时保持多个 Page 打开时创建另一个空白Page。用 page.goto() 单独导航它。
await task.pages() 返回托管的 Page。await task.tabs() 返回空间内每个标签页:{ label?, page, targetId, title, url, active, openedBy }。没有标签的标签页是未托管的;操作前先 adopt:
const active = (await task.tabs()).find((item) => item.active);
if (active && !active.label) {
const page = await task.adopt(active.page);
console.log({ page: page.label, url: await page.url() });
}release(label) 把来源未知的 Page 交还用户而不关闭其标签页。Agent 创建的 Page 用 page.close() 关闭。判断某个 Page 能否关闭时,把 openedBy: "unknown" 当用户所有。
Page 操作
ego-browser 提供以下 Page API:
- 状态与观察:
label、spaceId、openedBy、targetId、url()、
title()、info()、snapshot()、screenshot()
- 导航与等待:
goto()、reload()、waitForURL()、
waitForEvent()、waitForSelector()、waitForLoadState()、waitForFunction()、waitForTimeout()
- 元素:
click()、dblclick()、hover()、dragAndDrop()、fill()、
selectOption()、focus()、press()、setInputFiles()、waitForFileChooser()、close()
- 对话框:
acceptDialog(promptText?)、dismissDialog() - 指针:
mouse.click()、move()、down()、up()、wheel() - 键盘:
keyboard.down()、up()、press()、type()、insertText()、
paste()
- 页面代码与协议:
evaluate(fnOrString, argument)、
fetch(url, options)、cdp(method, params, options)
page.evaluate() 回调只在 Page 内运行;读不到周围脚本的变量或 Node.js 模块。浏览器侧助手在回调内定义,或只传一个可 JSON 序列化的值作为其第二参数。
高效工作:
- 每次观察,只收集选择下一步动作所需的最便宜页面状态。
语义或定位基准用 snapshot,视觉确认用 screenshot;默认不要两者都要。
- 如果动作没产生预期结果,先检查当前页面,
再决定是否重试。不要盲目重复,也不要立刻退回坐标或裸 CDP。
- 页面清楚显示请求的结果后,停;不要
通过多个界面重复确认同一个结果。
语义页面:快照与选择器
普通 DOM 页面优先用快照和语义选择器。仅在有用的 DOM 语义不可用时用截图和坐标。
选择陌生目标前先取快照。当前状态足以规划同一 Page 上多个动作时,在一次脚本调用中把它们做完,然后观察一次结果。仅当中间结果会改变下一步该做什么时,才在动作之间观察。动作序列、对其最终预期状态的等待、下一次快照放在同一次脚本调用中。快照最后打印,下一轮就能直接据此行动。最终快照是下一轮看到的变化后页面的起始视图;没有它,那一轮通常得多花一次浏览器调用来观察,才能选择下一个目标,浪费算力。
等待预期结果:导航用 waitForURL(),元素状态用 waitForSelector(),应用状态用 waitForFunction()。有可观察条件时避免固定延时。快照只捕获当前瞬间;不等页面变稳定。page.snapshot() 捕获当前视口。视口外的内容用page.snapshot({ scope: "full_page" })。
默认视口快照包含浏览器返回的可见 iframe 内容。要聚焦某个 frame 的子树,复用它 iframe行上打印的 ref:
console.log(await page.snapshot({ scope: "subtree", root: "@12" }));iframe 内的动作用子树返回的 ref。子树快照不会限定后续的定位动作;它们仍然优先在顶层文档里找可操作匹配,找不到才搜 frame。
waitForLoadState() 默认 load。waitForFunction() 遵循Playwright 参数顺序;没有 Page 参数时在选项前传 undefined:
await page.waitForFunction(() => window.appReady, undefined, {
timeout: 10_000,
});// 第 1 轮:从这份输出里检查并选目标。
const page = task.page("p1");
console.log(await page.snapshot());// 下一轮:用上一轮输出行动,验证,然后为再下一轮做准备。
const page = task.page("p1");
await page.fill("@21", "user@example.com");
await page.click("loc=role:button[name='Sign in']");
await page.waitForSelector("loc=css:#account-home", { state: "visible" });
console.log(await page.snapshot());元素动作接受:
- 快照 ref,如
@21或ref=21 text=...匹配页面内容loc=css:、loc=role:、loc=href:定位器xpath=...- 裸 CSS 选择器
选择器动作要求恰好一个匹配。不带引号的 text 归一化空白、忽略大小写、匹配子串;带引号的如text="Save changes" 是精确且大小写敏感的。
也接受一小组 Playwright 兼容选择器子集:css=...、终端 :has-text("...") 与 :text-is("...")、CSS/text/href选择器后的 >> nth=N(N 为 -1 或非负数),外加loc=role:...[name*="..."] 匹配可访问名子串。其他 Playwright 选择器语法不支持。
选择器选中包裹元素时,focus() 和 press() 可能用它的可交互祖先或唯一可编辑后代;fill() 和setInputFiles() 只继续到唯一的兼容控件。
click()、fill()、hover() 和 dragAndDrop() 会自动用浏览器滚轮输入把目标滚进视野。不要仅为让DOM 目标可操作而预先滚动。
快照节点名是可访问性角色。现在用 ref,或之后用 loc=... 再找到该元素。页面变化后重新取快照。有用的节点没有 ref 时,用它的角色、文本或周围上下文构造选择器。CSS 搜索嵌套的 open shadow root。动作先在顶层文档里找可操作匹配,顶层没有才搜 frame。所选文档或 frame 里多个可操作匹配即歧义。
选项按 value、可见标签或从零开始的序号选。字符串匹配 value 或标签之一;多选传数组:
await page.selectOption("select[name=month]", { label: "October" });传 null 或 [] 清除当前选择。
视觉页面:截图、鼠标与键盘
canvas、富文本、电子表格、地图等缺少有用DOM 语义的界面,用截图配合鼠标键盘操作:
const path = await page.screenshot({ path: "/absolute/path/before.png" });
await page.mouse.click(420, 260, { label: "open spreadsheet cell" });
await page.mouse.wheel(0, 600, { label: "scroll project board" });
await page.keyboard.paste("hellotworld");
console.log({ screenshot: path });用看图工具检查截图。坐标用 CSS 像素;键名和 + 分隔的组合键遵循 Playwright 语法。用ControlOrMeta 做可移植快捷键并验证结果页面状态。mouse.wheel() 在当前鼠标位置做一次短滚轮输入动作,该动作完成时 resolve。每次脚本调用中,用它之前先移到或点击目标可滚动区域。
macOS 上 keyboard.paste() 发送原生粘贴快捷键,然后恢复用户的剪贴板。富编辑器需要结构化剪贴板内容时传 { text, html };text 是纯文本回退。其他平台用 keyboard.insertText() 插纯文本。
await page.keyboard.paste({
text: "NametStatus",
html: "<table><tr><td>Name</td><td>Status</td></tr></table>",
});富文本编辑器和可编辑网格,小规模验证一次编辑再批量重复。canvas 支撑的编辑器可能不通过 DOM文本或选择器暴露可见内容;这些结果用截图或应用特定的可见状态验证。
页面 JavaScript 与 CDP
批量提取或复杂的页内操作用 page.evaluate()。它接受一个可 JSON 序列化参数,返回可 JSON 序列化值:
const rows = await page.evaluate(
({ selector, limit }) =>
[...document.querySelectorAll(selector)].slice(0, limit).map((node) => ({
text: node.textContent?.trim(),
href: node.querySelector("a")?.href,
})),
{ selector: "article", limit: 20 },
);page.evaluate() 没有 timeout 选项。长工作拆成有界调用;安全超时时,用 executionStopped 和 mayHaveLateEffects决定不安全的后续动作是否需要先重载或关闭 Page。
先用文档化的 Page 方法。某个包装缺失或在当前页面不可靠时,用 page.cdp() 作底层诊断或控制路径。它接受 Page、Runtime、DOM、Network、Input 等命令;Target 和 Browser 命令用 task.cdp()。裸 CDP 会使 ref 失效。不要把 page.targetId 跨轮次持久化。
动作回执、弹窗与对话框
动作预期会打开新 Page 时,先启动等待再执行动作:
const popupPromise = page.waitForEvent("popup");
await page.click('a[target="_blank"]');
const popupPage = await popupPromise;
await popupPage.waitForLoadState();高层动作也会在 receipt.popups 里立即报告刚观察到的弹窗:{ label, targetId }。用task.page(receipt.popups[0].label) 解析出 Page 并在那里继续;目标地址重要时等它的 URL。
生僻的协议事件工作流,await page.events() 返回并清空缓冲的事件数组;它不是 EventEmitter。
同步 JavaScript 对话框可能出现在 receipt.dialog 或page.info() 里。继续之前先处理它:
await page.acceptDialog("prompt response");
// 或:await page.dismissDialog();回执只描述已派发的动作和即时观察到的弹窗或对话框;不验证结果应用状态。
文件与请求
用绝对路径设置已存在的文件输入框:
await page.setInputFiles("input[type=file]", ["/absolute/path/report.pdf"]);如果点击会创建文件输入框,先启动等待再点击:
const chooserPromise = page.waitForFileChooser({ timeout: 10_000 });
await page.click("button.upload");
const chooser = await chooserPromise;
const result = await chooser.setFiles("/absolute/path/report.pdf");上传触发的 JavaScript 对话框可能作为 result.dialog 返回;有就用上面的对话框方法处理。
浏览器下载,在触发动作前布防事件,并在同一脚本里把返回的产物存到绝对路径:
const downloadPromise = page.waitForEvent("download", { timeout: 30_000 });
await page.click("button.download");
const download = await downloadPromise;
console.log({
url: download.url(),
suggestedFilename: download.suggestedFilename(),
});
await download.saveAs("/absolute/path/report.pdf");download.saveAs() 等待完成并创建缺失的父目录。download.path() 返回轮次本地临时文件;failure()、cancel()、delete() 管理其生命周期。SDK 轮次释放时临时下载文件会被删除,所以脚本结束前调用 saveAs()。不要用裸 CDP 设全局下载目录;每次下载等待只为被处理的 Page 会话配置并恢复。
page.fetch() 在 Page 里运行 window.fetch():相对 URL、cookie和 service worker 都用该 Page,浏览器 CORS 仍然适用。返回{ ok, status, statusText, url, headers, body }:
const response = await page.fetch("/api/items", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ limit: 20 }),
timeout: 10_000,
});二进制响应不要转文本,直接存:
await page.fetch("/image.png", { saveAs: "/absolute/path/image.png" });不需要 Page 浏览器语义的后台请求用标准 Node.js fetch()。
用户控制与完成
用户接管或空间处于非活动、未分配状态时停下。不要重试或绕路。权限弹窗、设备选择器和其他浏览器owned的弹窗需要用户自己处理。
需要用户在浏览器里操作时,调 await task.handOff(),结束本轮,并向他们说明该做什么。用户确认后,恢复同一空间:
const task = await takeOverTaskSpace(7);
const userPage = task.userPage();userPage 未托管就 adopt 它。仅当当前脚本必须原地等待时才用 waitForControl()。仅当用户明确要求时认领用户所有或非活动空间。先找到它的数字 id;名字可能重名:
const spaces = await listTaskSpaces();
console.log(spaces.filter((space) => space.ownership === "user"));
const task = await claimTaskSpace(7);
const userPage = task.userPage();任务成功时,默认用 await task.finish({ keep: [] }) 关闭TaskSpace。finish() 只调一次,等它 resolve 后再报告完成。
保留 Page 是罕见例外:仅当用户明确要求,或结果必须留在浏览器里供用户查看或继续操作时,才保留必要的 Page。只是访问过的页面、搜索结果、中间步骤不需要留着。
await task.finish({ keep: [] }); // 默认:不留任何 Agent 托管的 Page。
await task.finish({ keep: ["p2"] }); // 例外:只为用户保留结果 Page。用户创建和未托管的标签页受保护;如果有残留,keep: [] 不会关闭整个空间。完成时不要逐个关掉不想要的 Page;改为列出要保留的 Page。page.close() 只在任务仍在进行时用。任务因用户控制或错误而停止时不要调 finish()。
如果最终输出含 [ego-browser:notice],结束当前浏览器任务,告诉用户有 Ego Lite 更新可用,只有他们同意才运行 ego-browser upgrade。升级后重读本技能。
参考资料
- [安装与连接](references/install.md)
- [API 签名与选项](references/api.md)
- [清除 cookie、缓存与存储](references/clearing-state.md) — 清除任何
cookie、缓存或存储前先读;某些清除会波及整个浏览器 profile。