开始 / Getting Started

核心循环

驱动浏览器完成一件事,节奏几乎永远是这四步:打开页面 → 看清页面上有什么 → 对着快照给出的 @ref 动手 → 页面一变就重新快照。 把这个循环跑顺,再理解 @ref 何时失效、何时自愈,你就掌握了 chrome-use 九成的日常用法。

四步循环

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

core loop
$ chrome-use open <url>        # 1. 打开一个页面
$ chrome-use snapshot -i       # 2. 看清页面上有什么(只列可交互元素)
$ chrome-use click @e3         # 3. 对着快照给出的 ref 动手
$ chrome-use snapshot -i       # 4. 页面一变,就重新快照

浏览器在多条命令之间保持运行,所以这些命令感觉像同一个会话。完事后用 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 会失效:页面一变就重新快照

Ref(@e1@e2 …)在每次 snapshot 时重新分配。 它们会在页面变化的那一刻失效——导航的点击、表单提交、动态重渲染、弹窗打开之后都可能失效。 所以在一次导航或结构性变化之后、下一次用 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>

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


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

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

🧩 结构化路径

snapshot + @reffind、带类型的动作。方便、可读,适合直白的表单和导航。 但可访问性视图是有损而脆弱的: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 搅乱的内联写法。

下一步

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