开始 / Getting Started

核心循环

驱动浏览器完成一件事,节奏几乎永远是这四步:打开页面 → 读取控件 → 用 @ref 操作并观察 → 只按需要重读。 把这个循环跑顺,再理解 @ref 何时失效、何时自愈,你就掌握了 chrome-use 九成的日常用法。

core 技能已包含默认观察、停止验证规则,以及普通点击和表单命令。按需加载 core/reading、core/connection 或 core/site-adapters。每次只读取能回答下一步问题的状态,不默认同时请求 DOM 和截图。页面信号足以确认目标且没有矛盾时,停止验证;观察缺失不能作为重放动作的理由。

减少调用,按完整任务测量

用 snapshot -i -c 精简控件视图,已知面板用 scoped read。已知动作序列用 batch,有条件分支的流程用 script,多个字段用 form fill --map。最后核验任务要求的页面信号;把动作放在一起执行不等于证明动作生效。

默认读取文字和 ref。只有树无法回答视觉问题时才加 --with-screenshot <path>。observed.noProgress 在重复操作仍未观察到变化时提示检查状态,不改变 success,也不重试。见检测范围。

JSON 命令计时中,cdpMs 是已完成前台请求的耗时之和,cdpBusyMs 是请求时间区间在命令总耗时内的并集,nonCdpMs 是总耗时减去并集。并发请求可让累计耗时超过总耗时。这些值都是经过时间,不是 CPU 占用或模型耗时。后台任务不继承计时器,剩余墙钟也不等于纯 daemon 处理时间。

比较时使用新构建的二进制并记录版本,在相同初始状态执行相同任务;cold 和 warm 分开,交替运行两个版本。记录成功率、unknown 结果、tool/CDP 调用数、返回字节和任务总耗时。模型回合需要调用方 trace,不能用 tool/HTTP 调用数代替。token 需用指定 tokenizer 或模型用量报告测量。失败任务跑得更快不能算提速。计时细节见 chrome-use skills get core/profiling,回放方法见仓库的 bench/README.md。

在仓库根目录用 python3 bench/run-task.py TASK --binary cli/target/release/chrome-use --output /tmp/run.tsv --run-id run-1 采集一次显式 run,再用 python3 bench/task-metrics.py /tmp/run.tsv 汇总。没有完整且匹配的调用方 trace(--trace)时,模型回合为 null。字节数合并 CLI 的 stdout/stderr,不是精确的模型输入。--cold 只跳过 warmup,不证明 daemon 已停止。

四步循环

每一步都对着上一步的结果做决定:snapshot -i 给你一份只含可交互元素的结构化视图, 每个元素带一个短 @ref;你拿 ref 去 click / type;页面一旦变化,就再快照一次拿到新的 ref。

core loop
$ chrome-use open <url>        # 1. 打开一个页面
$ chrome-use snapshot -i       # 2. 看清页面上有什么(只列可交互元素)
$ chrome-use click @e3 --observe   # 3. 动手,同一次调用里拿到变化
$ chrome-use snapshot -i --diff    # 4. 需要重读时,只要差异

第 3、4 步各只有一次往返。--observe 把动作后的差异(navigate 之后、或一次点击把整页换掉时,则是新页面的整棵树)和动作触发的请求 一起带回来,所以「每点一下就重新快照」是最该戒掉的习惯;--diff 在什么都没变时会明确说 no change,而不是给你一棵一样的树。围绕这个循环的行为规则见 chrome-use skills get core/behaviour。

有效增量结果的 JSON refs 只包含返回片段中的节点;无变化时为空,但后台实时映射仍保留。回退完整树时保持普通完整快照的行为。

只有动作后的捕获失败时,observed.refs 说明会话里的 ref 怎么处理。kept-unverified:保留上一次快照的 ref,但之后的命令使用其中某个 ref 前,chrome-use 会实时确认标签页、frame、文档和元素都没变;确认不了就在动作前拒绝,绝不按角色或名称重新定位。DOM 遍历快照(snapshot --dom)得到的 ref 在这种状态下一律拒绝。dropped:页面可能已换成别的文档。两种情况都用 snapshot -i 取当前 ref,不要重放动作。

浏览器在多条命令之间保持运行,所以这些命令感觉像同一个会话。完事后用 chrome-use close 收尾。close --all 会连其他会话一起关掉,所以有其他会话在跑时会拒绝执行,不用于日常收尾。想更细地读页面、找元素、做交互,分别见 读取页面、定位元素、交互操作。

ℹ️ 一个完整的小例子
搜索、点结果、截图——就是这个循环在跑:
search → click → capture
$ chrome-use open https://duckduckgo.com
$ chrome-use snapshot -i                      # 找到搜索框的 ref
$ chrome-use fill @e1 "chrome-use cli"
$ chrome-use press Enter
$ chrome-use wait --load networkidle
$ chrome-use snapshot -i                      # 现在的 ref 反映的是搜索结果
$ chrome-use click @e5                        # 点一个结果
$ chrome-use screenshot result.png

@ref 在同一文档内稳定,导航后重置

同一个 backend DOM 节点在连续 snapshot 中会保留原来的 Ref(@e1、@e2 …)。 打开或关闭弹窗、列表插入新节点,不再让后面的控件整体改号。导航和切换标签页会硬重置身份映射; 越过这些边界后,下一次用 ref 交互前先重新快照。

⚠️ "Ref not found" / "Element not found: @eN"
原节点可能被框架替换或已经移除。别猜——重新 chrome-use snapshot -i 一次,确认当前节点与 ref。

@ref 也会自愈:不必为每次微小 DOM 变动都重新快照

每个 ref 都记录了一个指纹——role + 可访问名称 + 祖先路径。当你使用它时,如果原节点已经不在, chrome-use 会自动在当前页面上重新定位到那个匹配的元素并继续执行。所以在一次 React / Vue 列表重渲染(标签没变)之后,click @e3 依然会命中正确的元素。

如果那个元素是真的没了,它会明确报错而不是去点一个错误的节点——它从不会悄悄打错目标。 因此:只有当你发生了导航,或者标签 / 结构真的变了时,才需要重新快照。

✅ 什么时候一定要重新快照
导航到了新页面、切换了标签页、表单提交后结构大改、弹窗 / 对话框打开——这些之后一定重新 snapshot -i。 单纯的列表重渲染(标签不变)不用,交给 ref 自愈就好。

硬规则:快照优先,绝不靠截图定位

对于表单字段和按钮,永远先 snapshot -i,然后对 ref / selector 动手。 不要用 screenshot + 坐标点击去找元素或命中元素。

snapshot -i 现在能穿透跨域 iframe(内嵌的 Google Payments / Stripe / 结账 / KYC 表单), 把它们的元素连同输入值一起按 @ref 列出来——所以 click @e / type @e / fill @e 直接就能打进 iframe 里。只有 canvas / WebGL,或者 snapshot 对你的目标确实什么都返回不了时,才用坐标。

⚠️ 截图是输出,不是输入
截图用于你报告给用户看的视觉验证,永远不作为 agent 自己的输入。而且一张真实 retina 浏览器的整页截图 对图像阅读器来说往往太大了。如果你觉得"需要一张截图才能读到状态或找到元素",那是个 bug——请提 issue。 在 relay 上按像素驱动还有额外风险:一次坐标事件可能漂移到用户的前台标签上,而 ref 永远不会。

两种不同意图——只有一种被劝阻

上面这条规则针对的是 screenshot-to-locate(用一张图去找 / 命中元素)——那才是 bug。 而 screenshot-to-capture(把一块区域或某个元素存成文件,作为可复用的图片素材—— 地图、图表、og 图、视觉回归基线、报告插图)是完全支持且鼓励的:

capture 是对的用法
# 把一块渲染出来的区域存成可复用的图片素材(不是用来定位)
$ chrome-use screenshot [selector] --clip x,y,w,h <file>

# 想点一个用 ref 命中不了的东西?box 给出元素的 CSS 像素框 + 中心点
$ chrome-use box @ref
$ chrome-use click <centerX> <centerY>

截图会自动降采样到最长边 ≤ 1200px(整页截图只限制宽度),好让图像阅读器读得下、并让像素和 click x y 对齐; 用 --max-width / --max-height / --scale 可以覆盖。想点一个用 ref 命中不了的元素, box @ref 会给出它的 CSS 像素框和 centerX/centerY,直接喂给 click <centerX> <centerY>——不需要截图。 canvas / WebGL 的坐标玩法见 截图 · 录像 · 视口 与画布相关章节。


两条驱动路径——以及何时切到 eval

你手上是一个带着用户真实 DOM 的真实 Chrome。有两层,可以随意混用:

🧩 结构化路径

snapshot + @ref、find、带类型的动作。方便、可读,适合直白的表单和导航。 但可访问性视图是有损而脆弱的:ref 一变就失效、隐藏 input 从不出现、遮罩会挡住坐标点击。

🛠️ eval 优先路径

chrome-use eval "<js>"——你在真实 DOM 上的眼睛和手:读隐藏 input、探进 Shadow DOM / iframe、 查 form.elements 和 .validity、抽出你要的确切结构,或直接 el.click()。

✅ 结构化路径一开始跟你较劲,立刻切 eval
结构化路径一开始跟你较劲的那一刻,就切到 eval,别反复重试它—— 这是查明"为什么失败"最快的方式(比如一个 UI 从不暴露的隐藏 point_choice=none)。 别把一个不稳的结构化 locator 试三遍;直接掉到 eval 上对 DOM 动手。
这表单里到底有什么 / 为什么提交不了?
# 列出表单里所有字段的 name / type / value / checked
$ chrome-use eval "[...document.forms[0].elements].map(e=>[e.name,e.type,e.value,e.checked])"

# 读一个 UI 从不暴露的隐藏字段
$ chrome-use eval "document.querySelector('[name=point_choice]')?.value"

# 找出所有校验不通过的字段和它们的报错信息
$ chrome-use eval "[...document.forms[0].elements].filter(e=>!e.validity.valid).map(e=>e.name+': '+e.validationMessage)"

# 直接 DOM 点击,绕过遮罩
$ chrome-use eval "document.querySelector('#stubborn').click()"

升级阶梯是:直白页面用 snapshot + @eN 最快 → 不想快照时用 find role/text/label → 原生 CSS selector → 上面任何一条跟你较劲的那一刻,就上 eval 直接对 DOM 动手。更多定位手段见 定位元素。

ℹ️ eval 跑在页面的 MAIN world
eval 默认在顶层文档的主世界执行,状态跨调用保留——所以顶层的 const x / let x 会和下一次调用冲突(Identifier 'x' has already been declared)。用唯一名字、挂到 window.x, 或把函数体包进 IIFE。带引号 / 非 ASCII / 大段脚本时用 eval --stdin(heredoc)、 eval --file <path> 或 eval -b <base64>,别用会被 shell 搅乱的内联写法。

下一步

把核心循环跑顺之后,按需深入:

macOS、Linux 和 Windows 的本机 agent 侧栏支持输入 / 选择技能、图片附件、会话工具与模型参数,以及独立后台 session 的定时提示。完整流程见 本机 agent 侧栏。