mcpField
B
browser-control

browser-control

github.com/anomalycoAgent 工作流4671.0k 次安装3 天前更新
代码仓库

关于 browser-control

Browser Control 是在用户可见浏览器中运行确定性 Playwright 代码的驱动,由 Agent 决定执行哪些操作。它强调先检查页面、核验结果,并保留会话以便后续继续操作。

主要功能

  1. 01确定性 Playwright 执行
  2. 02检查、操作、验证流程
  3. 03命名浏览器会话
  4. 04接管现有标签页
  5. 05受限操作的人工交接
  6. 06执行日志记录

适用场景

  1. 01在已登录的浏览器标签页中自动化操作
  2. 02核验表单提交或目标页面
  3. 03暂停并等待用户完成安全验证
  4. 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 操作,请遵循分两阶段的读取、确认、验证流程:

  1. 读取候选项,并返回确切且稳定的标识符或行文本。
  2. 获取用户对这些确切项目的批准。
  3. 仅重新选择已批准的项目,并断言所选数量。
  4. 读取确认对话框;如果内容与已批准的操作不符,则抛出错误。
  5. 确认,然后通过重新读取页面或独立的 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。 如果较旧的已安装中继将页面缩小到带填充的角落中,请记录该缺陷并协调更新录制器; 更改文件分辨率并不能修复问题。

完成标准:停止录制器,检查生成的媒体,而不只是确认文件存在,并报告实际测试的视口、 状态和交互路径。

故障排除

  1. 运行 browser-control doctor;它会检查软件包元数据、CLI/中继构建标识、 扩展协议兼容性、会话、目标和产物。
  2. 使用 status --json 检查确切的会话和目标所有权。
  3. 更改代码前,先用最精简的 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 版本、安全的会话/页面上下文、确切错误、可确定性复现步骤、预期行为与实际行为,以及尝试过的恢复措施。绝不要包含凭据、表单值或私人账户数据。

评论

支持 Markdown