关于 browser-control
Browser Control 是在用户可见浏览器中运行确定性 Playwright 代码的驱动,由 Agent 决定执行哪些操作。它强调先检查页面、核验结果,并保留会话以便后续继续操作。
主要功能
- 01确定性 Playwright 执行
- 02检查、操作、验证流程
- 03命名浏览器会话
- 04接管现有标签页
- 05受限操作的人工交接
- 06执行日志记录
适用场景
- 01在已登录的浏览器标签页中自动化操作
- 02核验表单提交或目标页面
- 03暂停并等待用户完成安全验证
- 04跨进程继续浏览器任务
MCP 相关
作为 OpenCode 插件,它会注册 MCP 服务,并将打包工作流作为 `@browser` 技能提供。MCP 调用默认使用隐式进程会话,也可显式创建多个会话。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ anomalyco/ browser-control --skill browser-control安装后在 Claude Code 中输入/browser-control确认已加载。
详细介绍
浏览器控制
Browser Control 是一种驱动程序,而非 Agent。调用方 Agent 决定要做什么;Browser Control 会在用户可见的浏览器中运行确定性的 Playwright 代码。
将 Browser Control 安装为 OpenCode 插件后,插件会自动注册此 MCP 服务,并将打包的工作流作为 @browser 技能公开。不要要求用户单独安装此技能或添加 MCP 配置。浏览器扩展仍需由用户单独批准。
全程遵循一个循环:检查、操作、验证。选择定位器前,先检查实际页面;通过范围最窄且稳定的控件执行操作;然后通过 URL 或重新读取页面来验证结果。切勿将点击成功或用户确认视为任务已成功的证明。
核心工作流
1. 直接执行任务
从所请求的浏览器操作开始。由中继支持的命令会启动分离式中继并等待扩展;不要先启动 browser-control serve。
browser-control execute 'return { url: page.url(), title: await page.title() }'
只有在设置或运行时行为不明确时才使用 browser-control doctor。
status 和 doctor 仅用于观察,不会启动中继。
MCP 启动、工具发现、skill 和 session_current 不会联系中继。首次调用操作类工具时,如果需要便会启动中继;由中继支持的观察类工具则会报告不可用,而不会启动中继。
对于由外部监管的中继,请设置 BROWSER_CONTROL_AUTOSTART=false,这样当中继不存在时,常规调用会失败,而不会启动另一个进程。现有中继连接和显式执行的 relay restart 仍可正常工作。
常规 CLI/MCP/SDK 调用绝不会替换正在运行的中继。版本不匹配时,运行 browser-control relay restart 前应与其他 Agent 协调。重启会保留浏览器标签页和持久会话,但会重置 JavaScript 状态和快照引用。如果排空操作繁忙或超时,旧中继会继续运行;应完成录制/捕获并断开原始 CDP 客户端,而不是强制停止中继。
browser-control doctor
browser-control status --json
完成条件:一次执行即可返回页面结果和可读的会话 ID,或者由 doctor 确定具体的设置故障。
2. 谨慎选择页面
捆绑的 shim 0.0.26 可在重新连接时保留标签页所有权。安装此 shim 更新后,请重新加载已解压的扩展程序。连接了 DevTools 的标签页不会纳入 Browser Control 的清单。如果页面执行上下文仍不可用,page.title() 的读取会在五秒后超时;读取超时不会关闭或替换标签页。
在 OpenCode 中直接执行 CLI 命令会复用小写形式的 oc-$OPENCODE_SESSION_ID,因此来自同一 Agent 的调用会共享一个默认标签页。--session 或 BROWSER_CONTROL_SESSION 的优先级更高。在 OpenCode 之外,直接执行会创建一个新的可读取会话,并输出用于继续操作的命令。对于一次性脚本,请使用 execute --rm:无论成功还是失败,中继都会关闭该会话打开的所有标签页,即使调用方 CLI 已断开也不例外。它不能与显式会话一起使用。
browser-control session new "🎙️ elevenlabs"
browser-control execute --session "🎙️ elevenlabs" 'return page.url()'
browser-control execute 'return page.url()'
browser-control execute --rm 'await page.goto("https://example.com"); return page.title()'
MCP 会保留一个隐式进程会话。正常情况下请省略 session;如果一个 MCP 进程需要多个会话,则调用 session_new 并传入显式 ID。
若要控制用户浏览器中已打开的标签页,请让用户点击该标签页上的 Browser Control 工具栏按钮。可以只为一次 execute 选择该标签页,也可以采用它以便持续复用(如果只连接了一个用户标签页,请省略 --target-url / targetUrl):
browser-control session adopt --session github
browser-control session adopt --target-url github.com --session github
browser-control execute --target-url github.com 'return page.url()'
execute --target-url 仅为本次调用选择页面。之后只使用 --session 继续操作时,会使用该会话的默认页面,该页面可能仍是 about:blank。若要在已有用户标签页中执行多步任务,请先采用该标签页。诊断空快照时,务必包含 page.url()。快照标签是简洁的描述;执行操作时请使用 ref(),不要假定标签文本就是准确的 Playwright 可访问名称。
targetUrl 和 targetIndex 用于选择已连接的现有页面;它们不会触发导航。URL 选择器必须与唯一一个页面完全匹配,且 URL 和索引选择器不能同时使用。采用标签页后,该标签页会成为会话的默认页面,会话之前由中继创建的页面会关闭,并且该标签页只能由一个 Browser Control 会话独占。重置或删除会释放已采用的用户标签页,但不会将其关闭。
采用操作会在初始化自动化之前绑定所有权和精确的目标身份。在首次 execute 成功解析页面之前,会话状态会报告 connected: false 和 pageUrl: null;adoptedUrl 是注册表选中的 URL,并非实时读取的页面地址。页面繁忙时,execute 可能返回 session-page/adopted-initialization-timeout:用户代码尚未运行,且系统会保留精确的已采用目标。请等页面稳定后重试;不要通过重置或采用其他标签页来恢复。
对于经过身份验证的浏览器状态,优先采用现有页面,而不是在新页面中重复登录。
每个中继一次只能控制一个浏览器/配置文件。第二个扩展程序连接无法替换健康的活动连接。如果 status 或 doctor 报告有竞争连接遭到拒绝,请确保扩展程序只在预期的浏览器/配置文件中启用。切换浏览器前,请先断开当前扩展程序;创建新的 execute 会话不会切换浏览器。
完成条件:所选页面的 URL 是预期页面,且后续操作要么保留返回的会话 ID,要么有意使用 MCP 进程会话。
3. 检查、操作、验证
先检查,再判断角色或选择器:
return await snapshot()
然后根据返回的结构执行操作,并验证目标位置:
await ref("e12").click()
await page.getByRole("heading", { name: "Settings" }).waitFor()
if (!page.url().includes("/settings")) {
throw new Error(`Unexpected destination: ${page.url()}`)
}
return { url: page.url(), heading: await page.getByRole("heading").first().innerText() }
优先使用常规 Playwright。如果后续交互依赖临时 UI 状态,例如已打开的菜单、已选中的行、悬停状态或正在填写的表单,请在一次 execute 中完成这些交互。
如果原生 locator.fill() 卡住,请先检查失败原因,再使用显式的 input、textarea 或 contenteditable 后备方案。target/cross-extension-page 诊断意味着必须先由用户关闭提示或完成操作;fillInput 不能绕过受保护的扩展程序 UI:
await fillInput(page.getByPlaceholder("Username"), "standard_user")
完成条件:最终返回值包含所请求结果的证据,而不仅仅是尝试执行操作的证据。
表单与对外操作
点击后返回、HTTP 200、发生重定向或字段已清空,都不能证明操作已送达。 点击前先注册预期的导航,然后只点击一次,再读取目标页面并断言工作流特有的回执或确认信息。根据你实际检查过的页面协调拒绝和确认检查;驱动程序无法根据通用页面文本推断业务是否成功。
await Promise.all([
page.waitForURL(expectedDestination, { waitUntil: "domcontentloaded", timeout: 15_000 }),
ref(sendRef).click({ timeout: 15_000 }),
])
const observed = await snapshot()
await expectedReceipt.waitFor({ state: "visible", timeout: 5_000 })
return { url: page.url(), observed, receipt: await expectedReceipt.innerText() }
expectedDestination、sendRef 和 expectedReceipt 来自已检查的工作流,而不是猜测的选择器。对于 URL 不变的导航,请改为注册主框架导航或响应等待。鼠标坐标点击不一定需要等待导航;立即获取的快照仍可能是旧文档。
如果提交超时或无法读取目标页面,请报告未验证,并在不再次点击的情况下检查一次。仅在确切的标签页/上下文稳定后重新读取。绝不要重放结果不确定的发送、预订、购买或付款操作。网站明确拒绝即表示已拒绝,而非已送达;如果保护机制要求人工交互,请将普通页面交给用户处理。不要修改保护令牌、伪造人为信号,也不要通过派发 DOM 点击来规避安全边界。
4. 继续操作或妥善结束
命名会话会在短暂的 CLI 和 MCP 进程间保留默认页面。它们也能在中继重启后继续存在:Browser Control 会恢复 ID、只读模式和确切的默认目标。JavaScript state 和快照引用仅限于当前进程,中继重启后会重置,并明确发出警告。
browser-control session list
browser-control session reset github
browser-control session delete github
对于明确指定的会话 ID,删除操作是幂等的,因此即使该会话已不存在,也可以安全地重试清理。
重置、删除和空闲清理会关闭会话打开的所有标签页,包括弹出窗口。空闲 15 分钟后,所有会话名称都会释放其标签页、JavaScript 状态和 CDP 连接;显式命名的会话仍可供下次执行使用。用户拥有的标签页会保持打开状态,其调试器会在空闲 15 分钟后断开。再次接管前,请通过工具栏重新连接。
每次执行都会记录在 ~/.browser-control/sessions/<id>/journal.jsonl 中。日志会记录代码、状态、耗时、URL 变更、警告、交接情况和有界诊断信息。切勿将凭据直接写入执行源代码。
browser-control journal --session github --limit 50
完成:仅当预计会有后续工作时才保留会话;否则,请重置或删除会话拥有的页面,并报告可能影响后续工作的任何警告。
标准身份验证流程
Browser Control 的典型工作流是使用经过身份验证的标签页,并提示用户执行仅限人工完成的操作:
连接并接管现有标签页,检查其真实 UI,填写普通字段,然后在触发 WebAuthn、2FA、CAPTCHA 或支付 UI 之前注册 handoff。用户完成操作后,验证身份验证后的目标页面。即使 MCP 进程或中继重启,同一会话仍可继续使用。
如果触发提示的操作本身可能会阻塞,只将该操作放入 start。Browser Control 会先显示并确认 WAIT,然后再调用该操作:
await handoff("Complete the security-key prompt, then continue", {
timeoutMs: 600_000,
start: () => page
.getByRole("button", { name: /passkey|security key|sign in/i })
.click({ timeout: 600_000 }),
})
await page.waitForURL((url) => !url.pathname.startsWith("/login"))
await page.getByRole("heading", { name: /account|dashboard/i }).waitFor()
return { authenticatedUrl: page.url(), title: await page.title() }
handoff 解决后,Browser Control 会等待目标页面的上下文替换结束再返回,因此可以在同一次 execute 中完成此验证。
使用 start 时,即使用户已按下 Continue,handoff 的截止时间和目标取消仍会保持生效,直到操作结束。提前确认并不意味着可以让操作无限期挂起。
如果要在其他页面上执行 handoff,请传入 { page: otherPage }。就绪检查会针对该页面,而不是会话的默认页面。如果非默认页面被替换或关闭,请检查剩余页面,不要假定旧的 Playwright 引用现在指向其替代页面。
告诉用户当前正在等待什么操作。人工确认不等于验证:handoff 后始终要断言预期 URL 或稳定元素。如果操作已完成,只剩下人工步骤,请调用不带 start 的 handoff(message)。默认超时时间为十分钟。
完成条件:仅在注册 WAIT 后才显示提示,操作已结束,并且已独立验证身份验证结果。
如需将用户演示的流程转为可复用的 Playwright 代码,请使用 demonstrate()。它使用相同的精确标签页交接机制,记录点击、编辑、复选框/选择控件变更和同一标签页内的导航,然后返回可编辑代码:
return await demonstrate("Perform the workflow once, then continue")
密码字段会生成明确的机密来源注释,而不是复制字段值。重新使用生成的代码前,请检查选择器并添加结果断言;演示只记录操作,并不能证明工作流已成功。
密码管理器提示
普通网页字段和可访问的开放 Shadow Root 控件仍可使用。1Password 的内嵌菜单属于扩展拥有的 iframe,并非普通网页 DOM。Chromium 会阻止一个扩展调试另一个扩展的页面;工具栏弹窗以及原生解锁、Touch ID 和 Windows Hello 提示都不是受支持的 Playwright 控制界面。
聚焦或填写银行卡号字段或凭据字段本身就可能打开内嵌菜单,即使该字段位于第三方支付 iframe 中也不例外。菜单打开期间,Chrome 会拒绝该标签页上的所有自动化命令;Playwright 会显示“Execution context was destroyed”或定位器超时,因此 execute 结果会附带 target/cross-extension-page 诊断信息和警告,并且 browser-control status 会将该标签页标记为 protected-ui=true。
target/cross-extension-page 表示存在权限边界。请让用户完成或关闭提示,然后重试;不要重置页面、读取保管库内容,也不要削弱浏览器安全性来绕过此问题。页面本身运行正常:不要将此故障视为标签页无响应,也不要新建页面。可行时,请在触发仅限人工完成的提示之前,在发起操作的网页上注册 handoff。如果提示已阻止页面连接,请直接告知用户所需操作,而不要假定可以显示页面内的交接提示。提示完成后,请验证预期网页状态。
页面残留的密码管理器状态文字并不能证明其菜单仍处于打开状态。用户关闭提示后,请使用当前中继诊断信息和全新的普通网页读取结果,不要检查扩展的私有框架内容。
检查工具
使用能够回答问题的最轻量视图:
snapshot()是简洁的操作前读取默认选项。它优先显示语义分组、警报、列表、表格、标题、链接和控件。文本输入框和文本区域的值会被省略。- 默认情况下,快照仅限定在单个可见模态框内;
main外的门户对话框仍可发现。需要有意查看背景内容时,请使用显式的within作用域。重复的列表包装器具有有界预留空间,因此在内容密集的页面中仍能显示产品链接。 - 原生数字/搜索输入框分别对应
spinbutton/searchbox。原生展开控件标记为summary,这是元素类型,而非 ARIA 角色;请使用返回的ref()来操作它们。 ref("e12")会从最新快照中解析控件。导航或 DOM 发生不兼容变化后,引用会安全失效。兼容的引用在同一文档的重复捕获中会保持相同的 ID。snapshot({ diff: true })会报告相对于兼容的先前基线的语义变化。snapshot({ delta: true })会在首次返回完整基线,之后返回精简增量。现有的兼容引用仍可使用。snapshot({ find: "checkout", context: 2 })会搜索有界语义快照,并返回匹配行、附近上下文和可操作引用。ariaSnapshot(target?, { timeout })会在精简快照省略所需结构时,返回 Playwright 的详细 YAML aria 树。原生文本控件值、自定义 ARIA 范围值和可编辑内容会被省略,不会进入工具输出。请单独等待它完成;不要在同一页面上并发运行其他操作。screenshotWithLabels({ page?, path? })会在布局相关时添加视觉标签和元数据,并将其e1..eN标签注册为可供ref()使用的引用。screenshotDiff({ baseline, path?, threshold?, fullPage? })会将已保存的 PNG(绝对路径或 Buffer)与当前会话页面按 CSS 像素比例进行比较。它会返回matches、changedPixels、changedRatio(0..1)、尺寸以及红色高亮 PNG。省略path可将图像作为执行媒体返回;否则请提供新的绝对.png路径。不会覆盖现有输出文件。
return await snapshot({ within: "main", maxItems: 200 })
return await snapshot({ find: /checkout|payment/i, context: 2 })
return await snapshot({ delta: true })
// When layout matters, return the image through MCP so it can be inspected.
return await screenshotWithLabels({ page })
保存图像并仅返回 "ok" 只能证明文件已创建,不能证明视觉正确性。需要视觉证据时,请通过 MCP 返回屏幕截图缓冲区。
进行视觉回归检查时,请在更改 UI 前保存基线:
await page.screenshot({ path: "/absolute/before.png", scale: "css" })
// After the intended UI change, in the same viewport:
return await screenshotDiff({ baseline: "/absolute/before.png" })
阈值默认值为 0.1,用于控制逐像素颜色容差,而非允许的变化区域。设为 0 可要求像素完全一致。抗锯齿变化也会计入。请自行等待动画稳定,并在两次捕获中使用相同的视口和 fullPage 设置。尺寸不匹配会明确报错;图像不会调整大小。比较仅支持每张不超过 32 MiB / 16 百万像素的 PNG。屏幕截图和差异图会包含可见的页面内容:分享前请检查是否含有私人信息。
执行接口
执行代码可以使用 page、context、browser、持久化 state、通过 modules 选择的 Node 模块及 fs 和 path 等别名,以及此处记录的 Browser Control 辅助工具。执行代码在 Node 中运行。使用 page.evaluate 访问 window、document、存储空间,以及使用页面 Cookie 发起同源 fetch。单个表达式会自动返回结果;
多语句脚本需要 return。较长的脚本请使用 --file:
browser-control execute --session github --file ./perform-flow.js
人类可读的 CLI 输出包含日志、警告和简洁的后续信息。当其他命令需要根据 ok、value、error、warnings 或 aftermath 的值进行分支时,请使用 --json:
browser-control execute --json --session github '({ url: page.url() })' | jq .value.url
page.waitForEvent("download") 会拦截 Content-Disposition: attachment 响应(通过 CDP Fetch),以及 <a download> / blob: / data: 链接,不会打开 Chrome 原生的“保存”确认对话框,并返回一个 Download,其中包含 suggestedFilename()、await dl.path() 和 await dl.saveAs(targetPath):
const dl = page.waitForEvent("download", { timeout: 30_000 })
await page.getByRole("button", { name: /Generate a private key/i }).click()
const download = await dl
await download.saveAs("/tmp/private-key.pem")
return { filename: download.suggestedFilename(), savedPath: await download.path() }
启用了 WebMCP 的页面可以提供结构化页面工具。通过执行辅助工具发现并调用这些工具;名称、描述、架构和结果均来自页面:
const tools = await webmcp.list()
const result = await webmcp.call("search_catalog", { query: "adapter" })
return { tools, result }
发现操作会覆盖所有 frame。如果多个 frame 中出现相同名称,请将报告中的确切 frame 标签作为 { frame } 传入。Browser Control 会在调用前立即重新发现该工具,因此过时的注册会直接失败。
安全
Browser Control 会阻止可能破坏共享浏览器状态的 CDP 命令,包括关闭浏览器和清除 Cookie/缓存。切勿绕过这些保护措施。
对于仅检查的操作,请使用只读会话:
browser-control session new inspect-prod --read-only
browser-control execute --session inspect-prod 'await page.goto("https://example.com"); return page.title()'
只读会话会拒绝 Input.*,因此无法通过 Playwright 点击或输入。page.evaluate 仍然可以修改 DOM;只读功能只能防止无意的错误操作,不能防止恶意代码。
对于具有破坏性的 UI 操作,请遵循分两阶段的读取、确认、验证流程:
- 读取候选项,并返回确切且稳定的标识符或行文本。
- 获取用户对这些确切项目的批准。
- 仅重新选择已批准的项目,并断言所选数量。
- 读取确认对话框;如果内容与已批准的操作不符,则抛出错误。
- 确认,然后通过重新读取页面或独立的 CLI/API 路径进行验证。
除非用户已批准确切且稳定的标识符,否则不要在同一脚本中发现并确认具有破坏性的候选项。切勿全局自动接受原生对话框;等待预期的对话框出现,并断言其类型和消息后再接受。
TypeScript 客户端
应用和 CLI 工具可以使用 BrowserControlClient.origin() 发起由会话页面认证的直接同源 JSON 请求(可选默认请求头;当会话过期或重定向到登录页面时,会自动在页面内恢复 handoff):
import { BrowserControlClient } from "@opencode-ai/browser-control"
const ubereats = BrowserControlClient.origin("https://www.ubereats.com", {
session: "🥤 karma-cafe",
startUrl: "/feed",
headers: { "x-csrf-token": "x" },
handoffOnAuthFailure: true,
})
const store = await ubereats.post("/_p/api/getStoreV1", {
storeUuid: "78cb1602-9f58-57bb-ad08-f6b8f80bb788",
diningMode: "DELIVERY",
})
对于需要通过架构解码或处理 sensitive: true 的 Redacted 响应的 Effect 原生应用,请使用 BrowserControlClient.Service:
import { BrowserControlClient } from "@opencode-ai/browser-control"
const sensitive = yield* origin.json({
path: "/api/session",
method: "POST",
body: {},
response: SessionResponse,
sensitive: true,
})
const session = BrowserControlClient.reveal(sensitive)
已认证的网络捕获
当浏览器用于身份验证或发现工作流,但重复直接调用 HTTP 更快、更可靠时,请使用网络捕获。至少使用不同的输入捕获每个流程两次,以便区分常量和参数。
browser-control network start --session github --url /api/ \
--resource-type fetch --resource-type xhr
browser-control execute --session github --file ./perform-flow.js
browser-control network status --session github
browser-control network stop --session github \
--output ./github.har --secrets github
写入的产物会将包含凭据的请求头、Cookie、查询字段、OAuth 片段字段和结构化请求体字段替换为稳定引用,例如 ${BC_SECRET_1}。
--secrets github 会将无损值单独存储在权限模式为 0600 的 Secret Profile 中。切勿将配置文件中的值复制到源代码、输出、诊断信息或日志中,也切勿故意返回或记录凭据。
离线检查经过脱敏的产物,为每个观察到的流程生成一个类型化函数,然后用无害的实时请求验证每个函数。运行生成的客户端时,不要暴露值:
browser-control secrets status github
browser-control secrets run github -- ./github-cli repositories
生成的 TypeScript 应用可以通过公共 SDK 在内部封装该包装器:
import { SecretProfile } from "@opencode-ai/browser-control"
import { Effect } from "effect"
const result = await Effect.runPromise(SecretProfile.run({
name: "github",
command: process.execPath,
args: ["./github-cli.js", "repositories"],
}))
受信任的工作进程会接收 BC_SECRET_N 变量,其有界输出也会经过脱敏。公共 SDK 会公开配置文件元数据,但绝不公开原始配置文件值。
使用以下命令刷新通常由页面重新加载续期的凭据:
browser-control secrets refresh github --session github --url /api/
如果刷新需要登录或人工提示,请在已接管的标签页中重新进行身份验证,并使用同一配置文件重复捕获。MCP 提供等效的 network_* 和 secrets_* 工具。
完成标准:产物包含的是引用而非凭据值,生成的操作通过无害的实时检查,且源代码或输出中均未出现任何机密值。
录制
使用以下命令录制已附加或由会话拥有的标签页:
browser-control recording start ./tmp/demo.mp4 --session github --mode cdp
browser-control recording status --session github
browser-control recording stop --session github
启动、停止和状态命令均接受 --json。CDP 的停止/状态结果包含一份
quality 收据:输出尺寸/帧率、源帧和保留图像的数量/帧率、
合并/丢弃的帧数,以及 screenshotFallback。该回退表示没有收到
合成器帧,视频只保留了一张停止时的屏幕截图;不要将其说成录到了动态画面。
源计数统计的是合成器事件,而非视觉上不同的帧。静态页面的低帧率可能是正常的。
标签页捕获和较旧的中继不会提供质量遥测,而不是编造测量结果。
无论使用哪种模式,显式帧率都必须是 1 到 60 之间的整数;无效值会导致失败, 而不是静默限制到范围内。启动结果会报告所选帧率。
--mode auto 会对用户拥有的标签页使用标签页捕获,对中继拥有的标签页使用
CDP。标签页捕获可以包含音频;CDP 需要 ffmpeg,且不包含音频。格式和光标选项
请参阅该命令的 --help。
Chrome 仅在用户点击该标签页上的 Browser Control 图标后才允许标签页捕获; 没有任何扩展程序权限可以绕过此限制。对于无需点击即可由 Agent 运行的录制, 请使用 CDP:它可以录制任何已附加调试器的标签页,而且编码开销很低(录制 15 fps、1440x900 的 MP4 时约占一个核心的四分之一)。
Playwright 鼠标操作会自动显示页面上的 Ghost Cursor
(distance-glide 动画 + tactile-bloom 点击冲击波)。录制面向用户的演示视频或
PR 演示视频时,请在 execute 中启用 ghostCursor 助手,以突出关键步骤和已验证的后置条件:
await showGhostCursor()
await ghostCursor.caption("Verify cluster & promote release", { step: "01", tone: "neutral" })
await ghostCursor.zoom("#release-card", { scale: 1.45 })
await page.locator("#promote-btn").click()
await ghostCursor.keys("⌘+⇧+P", "Promote Release")
await ghostCursor.resetZoom()
await ghostCursor.spotlight("#status-badge", { label: "Verified", detail: "200 OK", tone: "success" })
CDP 录制会保留起始 CSS 视口(而非固定的 720p 画布),使用高质量源帧,
并默认采用 60 fps。使用 --frame-rate 30 可减小文件体积。实际动态效果仍取决于
Chrome 是否提供新帧;60 fps 输出不保证有 60 个不同的帧。视口越大,CPU 占用、
传输带宽和存储需求越高。请在录制前设置视口,录制过程中不要更改视口/模拟设置。
奇数尺寸会向下取整为偶数。
对于难以复现的问题,请保留循环 CDP 帧缓冲区,并在问题发生后保存近期记录:
browser-control flight-recorder start --session github --retention-ms 60000
browser-control flight-recorder status --session github
browser-control flight-recorder save-last ./tmp/failure.mp4 --session github --duration-ms 30000
browser-control flight-recorder cancel --session github
保存不会停止缓冲。录制器有内存上限,会报告保留时长/帧数/字节数/丢帧计数, 并在每个片段旁写入一份 JSON 收据;同一标签页上不能同时使用该录制器和普通录制。 CLI 录制和飞行记录器生命周期操作也可作为 MCP 工具使用。
分享前,请以原生尺寸检查编码后的帧:整个视口必须填满画面,小字必须清晰可读, 动态画面不能是反复播放的静止图像。不要将低分辨率捕获内容裁剪并放大后称为 HD。 如果较旧的已安装中继将页面缩小到带填充的角落中,请记录该缺陷并协调更新录制器; 更改文件分辨率并不能修复问题。
完成标准:停止录制器,检查生成的媒体,而不只是确认文件存在,并报告实际测试的视口、 状态和交互路径。
故障排除
- 运行
browser-control doctor;它会检查软件包元数据、CLI/中继构建标识、 扩展协议兼容性、会话、目标和产物。 - 使用
status --json检查确切的会话和目标所有权。 - 更改代码前,先用最精简的 execute 复现一次。
常见诊断:
session-page/context-read-timeout; operation=page.title; timeoutMs=5000: 标题读取未能在其预算时间内完成。执行上下文可能不可用或正忙;这并不能证明渲染器已冻结。看门狗不会取消底层读取,也不会触发页面替换。缓存的page.url()读取仍可能正常工作;请等页面稳定后重试页面读取。 普通的定位器未找到超时不会收到此诊断信息。connected:false:运行一条由中继支持的命令,并等待扩展启动或闹钟唤醒后重新连接。休眠中的扩展会在 30 秒闹钟触发时唤醒,因此命令最多等待 35 秒。只有此循环无法恢复时,才重新加载已解压的扩展。- 扩展协议不兼容:更新扩展或 npm 包;扩展和中继的确切版本不必匹配。
- 浏览器/配置文件连接冲突:保留当前活动的浏览器,并拒绝额外连接。每个中继使用一个浏览器/配置文件;反复创建会话或重置标签页不会切换浏览器。
- 中继构建版本过旧:检查
doctor,然后协调执行明确的browser-control relay restart。这需要精确匹配的受管实例以及安全关闭协议 2。旧版中继需要协调一次手动停止;绝不会强制终止或降级前台运行/源码运行的新中继。 不匹配时,MCP 观察工具仍可用。 Relay is draining…或Relay is shutting down…:status和doctor会显示阶段、持续时间以及阻止其结束的因素。正在关闭的中继会在BROWSER_CONTROL_SHUTDOWN_TIMEOUT_MS(默认值为 10s)后强制拆除,下一条命令会启动新的中继;命名会话会重新连接。无法完成的重启排空会被取消,旧中继将继续运行。- 意外重启:检查按私有端点划分的
~/.browser-control/relays/<port>/lifecycle.jsonl,查看请求方/构建版本/实例元数据。准备或选择开发候选版本不得重启守护进程。开发 Browser Control 本身时,请使用隔离的runtime:prepare/runtime:select,不要链接到正在使用的检出目录。 Target not found:附加目标标签页,然后使用唯一 URL 子字符串或明确索引来选择或接管它。- 所有目标均已消失:关闭 Chromium 的调试横幅会分离所有标签页。请通过工具栏重新附加。
- 中继已重启:命名会话会重新认领确切目标,但 JavaScript
state和快照引用会重置。看到警告后继续操作。 - 扩展更新后重置/删除可能需要短暂等待目标重新通告;如果已完成的清单中不存在旧的中继所有目标,Browser Control 会忘记该失效身份,而不会关闭猜测的标签页。
- 反复出现执行上下文错误:再运行一条简短的后续命令,让 Browser Control 对页面执行健康检查。对于仍在运行的页面,会予以保留:Browser Control 会重新连接并重新解析同一标签页一次;如果页面仍无响应,则报出
session-page/*-unresponsive诊断信息。空白或 URL 未知的页面会予以保留:其中可能包含未保存的内容。只有崩溃的中继所有页面或chrome-error://页面才会被关闭并重新创建。绝不会替换已接管的用户标签页。主框架导航会清除之前的崩溃诊断;子框架导航则不会。当页面持续无响应时(受机器人防护的网站可能会导致自动化操作的主世界卡住,而对用户来说页面仍正常渲染),请使用context.newPage()打开新标签页,或将该标签页交给用户。 - 交接以“页面执行上下文未变为可用”结束:用户已完成操作;只是 Browser Control 对标签页的视图已过时。运行一条简短的后续执行命令,让页面重新检查,而不是直接假定页面已丢失。
- 登录字段填充超时:先检查,再确认选择器或定位器可以解析后尝试
fillInput。字符串选择器会递归搜索打开的 shadow root;无法访问封闭的 shadow root。 - 已确认的对话框遮罩层阻止点击:先检查确切的目标和阻挡元素。如果目标是已批准的操作,且 Playwright 的可操作性状态本身已过时,可执行
await locator.evaluate((element) => element.click()),然后验证结果。不要默认使用派发点击。 - 隐藏的复选框/单选框输入框:点击其可见的关联标签或包装元素,然后验证
isChecked()。不要强制点击不可见输入框,也不要随意推断祖先元素。 - 下载等待失败:使用 fetch 和
fs;基于扩展的 Playwright 无法保留原生下载产物。 - 在无限动画的目标上悬停:Playwright 可能永远不会认为元素已稳定。读取其当前的
getBoundingClientRect(),并在适合使用坐标输入时调用page.mouse.move()。 - Chrome 网上应用店开发者信息中心等受 Chromium 保护的页面可能会分离
chrome.debugger。不要重试或绕过这一边界;请打开页面进行手动操作。 如需进行更深入的中继诊断,请使用BROWSER_CONTROL_DEBUG=1重启。调试跟踪绝不能包含表达式、参数、结果、标头、Cookie 或表单值。
每当 Browser Control 失败、卡住、替换页面或会话,或行为异常时,请创建或更新 browser-control 项目待办事项,记录 Browser Control 版本、安全的会话/页面上下文、确切错误、可确定性复现步骤、预期行为与实际行为,以及尝试过的恢复措施。绝不要包含凭据、表单值或私人账户数据。