核心用法 / Core Usage

交互操作

自定义下拉框会先即时滚入视口,再用真实指针事件打开和选择。pick 未确认选中时返回 verified: false 和警告;选中后清空搜索框时会报告隐藏表单字段的变化,但不会据此断言选中成功。页面及容器使用即时滚动,位置未变化时返回 scrolled: false 和警告。

读到页面之后,就该动手了。这一页覆盖驱动真实浏览器的全部交互动词—— 点击、输入、勾选、下拉、上传、滚动、拖拽。 大多数命令的第一个参数是 snapshot -i 拿到的 @ref(例如 @e42);先定位元素, 再用这里的命令操作它。

点击与指针

语义点击被遮挡时,错误会给出遮挡元素的标签、id/class 和截断文本。点击点检测无法确认目标时直接失败,不猜测遮挡来源,也不派发输入。

最常用的一组动词,都作用在一个 @ref 上。click 会自动把元素滚动进视口, 若坐标点击被遮挡,会回退到 DOM 的 .click()。

点击 / 悬停 / 聚焦
$ chrome-use click @e1                   # 点击
$ chrome-use click @e1 --new-tab         # 在新标签页打开链接,而不是当前页导航
$ chrome-use dblclick @e1                # 双击
$ chrome-use hover @e1                   # 悬停(真实鼠标移动,确认指针事件落在元素上)
$ chrome-use focus @e1                   # 聚焦(键盘输入前常用)
ℹ️ hover 只报告页面确认过的结果
hover 先把元素滚进视口,找一个元素本身在最上层的点,把真实鼠标移过去, 再确认有可信(isTrusted)的指针事件落到元素上(不检查任何 CSS 样式)。确认了才输出 ✓ Hovered … (a trusted pointer event landed on it); 元素被横幅、遮罩挡住时报 hover refused,什么都不发;移动发出去了但 没有事件落到元素上时报 did not take effect。跨源 iframe 里放不了真实指针, 只能派发合成事件(isTrusted=false),页面有没有反应未知,CSS :hover 不会生效,输出会标成未确认。iframe 被父页面的遮罩盖住,或 iframe 被缩放、旋转、zoom,都会在发送任何事件前直接拒绝。
✅ 点击成功却「没反应」?
典型场景:一个 autocomplete/菜单的 <li> 在输入框失焦时就关掉了。 换成 DOM 派发的点击再试一次: AGENT_BROWSER_CLICK_MODE=dom chrome-use click @e1, 或直接用 eval 在页面里选中那一项。
ℹ️ DOM 派发的点击也会移动焦点
扩展中继下左键点击是通过 DOM 派发的。它现在会像真实点击一样把焦点交给被点元素 (或最近的可聚焦祖先 / label 对应的控件),除非点击处理器已经自己移动了焦点。 所以 click <input> 之后 press Meta+a 落在这个输入框上, 不再落到上一个焦点字段。普通 <li> 没有可聚焦目标,上面的 autocomplete 技巧依然成立。

输入文本

Ant Design / rc-picker 日期范围请先填开始时间,再填结束时间。fill 会通过组件支持的真实键盘事件或可见确认控件确认当前边界并读回,避免失焦后重新聚焦清空草稿;焦点按组件自己的流程移动。日期无效、只读或禁用时会明确失败,不会用合成事件强行写入;无法核验弹窗时请直接操作日期选择器。查询前仍需核对完整范围。

fill 先清空再输入,type 在现有内容后追加。默认走 insertText(快),但很多带联想的字段只对真实按键有反应——这时加 --key-events。

fill / type
$ chrome-use fill @e2 "hello"            # 清空后输入
$ chrome-use type @e2 " world"          # 不清空,直接追加
$ chrome-use type @e2 "hello" --clear   # type 也能先清空字段再输入
$ chrome-use type @e2 "hello" --delay 80  # 每个按键之间间隔 80ms

# 真实按键(非 insertText)——用于只认按键事件的联想/组合框,
# 例如输入邮编后自动补全市区(Google Places 一类)
$ chrome-use type @e5 "100-0001" --key-events

# 真实按键并回车提交候选(--enter 隐含 --key-events)——
# 用于异步联想 / 标签控件,例如掘金「添加标签」
$ chrome-use type @e6 "ChatGPT" --enter
ℹ️ --key-events 还是 --enter?
如果只想确认一个标签、且单纯输入不弹下拉,用 --enter 一步到位。 如果你更想从候选列表里挑一个,先 type … --key-events 触发下拉, 再 snapshot -i 找到候选并 click @ref。
✅ --clear 与 --delay
--clear 让 type 也能先清空字段(不用再切回 fill); --delay <ms> 在按键之间插入固定间隔,模拟更慢、更真实的打字节奏。 两者都是各自独立的 flag,不会污染实际打进去的文本。
ℹ️ 页面改写了输入?type 会警告,fill 会报错
type 打完会回读字段:如果里面没有刚输入的文本,会打印 ⚠ 警告,引用写入的内容和字段当前的值 (退出码仍为 0,JSON 多出 readBack)。fill 校验失败时的报错同样引用两边的值, 例如 read back "" after writing "千代田区";若非 ASCII 字符全部消失而 ASCII 仍在, 会指出页面过滤了非拉丁输入(仅限拉丁 / 带掩码的字段),重打一遍没有用。正常字段上两者都能正确输入中日韩文, 这些提示针对的是会改写输入的页面。
⚠️ 字段显示了新值,页面却没记下(#358)
fill 和 type --clear 按用户的方式输入:选中原值后用可信事件 (isTrusted: true 的 beforeinput / input,失焦时再触发可信的 change)写入, 只认真实输入的页面也能收到。fill 发现所在表单或弹窗的保存 / 提交按钮在填写前后都是禁用状态时会警告 (JSON 多出 commitControl);可信写入得不到目标值时退回原生 setter,engine 显示 input-synthetic 并附警告。click 拒绝点击 :disabled 的控件, 退回 element.click() 时报告 dispatch: dom。keyboard type 报告焦点元素和回读值, 有焦点的字段没变化时报错。保存类按钮用 click @ref --observe,看点击后有没有请求发出。

页面在 fill 中途丢掉了元素句柄(CDP 报 "Could not find object with given id",在扩展 relay 上见过)时,fill 会重新定位元素一次,先读值再决定:字段里已经是目标值就带 ⚠ 说明返回、不再输入;不是才再填一次并说明。完全读不回来的字段视为未知而不是不同:fill 以 "value is unknown" 失败、不写入,先用 get value <ref> 确认。看到这些说明都不要自己重复 fill。

另外:snapshot 里的 button "简体中文" [toggles=checkbox(checked=true)] 是开关,不是链接。 在 LinkedIn 的「编辑档案语言」弹窗里点它会删除该语言的档案,不要拿它切换标签。

字段内部编辑与带格式粘贴

fill 是整体替换,type 是追加。改一段写好的文字里的某一处、 或者只把光标放到某个位置继续写,这两个都做不到 —— 这是 select-text 的活。

select-text
$ chrome-use select-text @e3 "确认" --prefix "请"   # 选中「确认」,不含「请」
$ chrome-use select-text @e3 "Hi Sam," --cursor-after  # 只放光标
$ chrome-use type @e3 " 顺便说一句:"                # 从光标处继续输入
⚠️ 有多处匹配时它会拒绝,而不是替你挑第一个
prefix / suffix 是消歧用的上下文,不属于被选中的内容。 「找不到」「找到了但上下文对不上」「有 N 处」是三条不同的提示 —— 解法本来就不同。 Monaco / CodeMirror 有自己的选区模型,会被点名拒绝:在那上面做 DOM 选区看起来成功、实际没发生。

富文本编辑器里,敲字和粘贴的结果不一样:type "<b>粗体</b>" 得到的是这几个字符,paste --format html 得到的才是粗体。换行同理 —— type 的换行会变成 Enter(在多数编辑器里等于提交或另起一块),paste 不会。

paste
$ chrome-use paste $'第一行\n第二行' --selector "#notes"  # $'...' 才是真换行
$ chrome-use paste "<b>粗体</b>文字" --format html --selector "#editor"
$ chrome-use paste "# 标题" --format md            # Markdown 源码按纯文本插入
✅ 全程不碰你的真实剪贴板
内容走的是合成 ClipboardEvent,不调用 navigator.clipboard,也不模拟 Ctrl+V。 这种事件是 untrusted 的、没有默认行为:监听 paste 的编辑器从自己的 handler 拿到内容, 不监听的则走一次真实插入 —— 回复里会写明走的是哪条。两条都没生效时它报错,不会打 ✓。

键盘

press 在当前焦点上按一个键(按下+抬起),也支持组合键。 keydown/keyup 把按下和抬起拆开,成对使用可以实现「按住」。

press / keydown / keyup
$ chrome-use press Enter                 # 在当前焦点按一个键,输出会报告落点
                                       # ✓ Pressed Enter → textarea[name="q"]
$ chrome-use press Enter --selector @e2  # 先聚焦目标再按(别名 --on)——每次调用
                                       # 都是独立进程,别假设焦点还在原处
$ chrome-use press Control+a             # 组合键
$ chrome-use keydown d                   # 按住某键不放(不自动抬起)
$ chrome-use keyup d                     # 抬起——成对用来「按住移动」
                                       # 游戏里:keydown d; sleep; keyup d
⚠️ 按键「确实什么都没做」时会有警告
对只靠页面 JavaScript 才有效果的键(文本框上的 Arrow/Home/End/PageUp/PageDown、Escape、表单外裸输入框上的 Enter), press 会探测从焦点元素、祖先到 document / window 有没有 keydown/keyup/keypress 监听器。 一个都没有时仍退出 0,但会打印 ⚠ 警告说明页面无法响应该键,建议直接 click 目标选项; JSON 多出 keyListeners: <count>。组合键(Ctrl/Meta+键)、Tab、Backspace、可打印字符和原生默认行为不探测。
ℹ️ 游戏 / Canvas 里更精确的按住
对 canvas / WebGL 应用,用 press d --hold 800 更好——按住时长在守护进程里计时, 比 keydown + shell sleep + keyup 每轮少 ~250ms 抖动。详见 Canvas 与游戏。

勾选、选择与下拉

复选框用 check/uncheck。下拉:原生 <select> 和 自定义组合框(react-select / ARIA 等)都可以用 select—— 自 v1.5.72 起,select 遇到非原生控件会自动走 portal-aware 逻辑(开控件→等选项→按可见文本点); pick 是同一能力的显式别名。 原生下拉使用平台自带的 value/selected setter,并同时触发 input 与 change,因此 React/Vue 受控表单也会提交新状态。

check / select / pick
$ chrome-use check @e3                   # 勾选复选框
$ chrome-use uncheck @e3                 # 取消勾选

$ chrome-use select @e4 "Pageview"       # 原生 <select> 或自定义组合框(v1.5.72+)
$ chrome-use select @e4 "a" "b"          # 多选

# 任意组合框:打开它、等菜单出现(含 portal 渲染)、按可见文本匹配、
# 触发正确事件;若选项始终不出现会「报错」(不会静默无操作)
$ chrome-use pick @e4 --option "Europe" --observe  # 和 click 一样返回它造成的变化
✅ v1.5.72:原生命令扛 shadow-DOM 重站(#105)
以前 select 对自定义下拉会「返回 ✓ 却没变化」(静默假成功)——现已修复: 非原生控件走 portal-aware 逻辑,选项始终不出现时明确报错并列出可见选项,不再静默。 fill 同样加固:通过 Monaco 的全局或 AMD model API 做一次原子的 setValue,并在返回成功前精确回读 model。若应用隐藏了该 API,则执行一次可信的编辑器 paste,通过编辑器自己的 copy 处理器精确回读,并在结束后恢复浏览器剪贴板; 操作无法校验或内容不一致时会明确报错。@ref 落在包裹元素上的受控输入仍会自动下钻到内部 editable。
受 CSP 限制的页面
Cloudflare Zaraz 和启用严格内容安全策略的页面不能依赖 eval。 请使用原生 select 与 fill 命令,以免要求页面开放 unsafe-eval。

点击之外的动作

有些控件不止能点:折叠块要展开、菜单按钮要弹出、数字框和滑块要按范围步进。 actions 告诉你这个元素此刻支持哪些, do 只执行其中之一。

actions / do
$ chrome-use actions @e15
@e15 DisclosureTriangle "Disclosure summary"
  expand

$ chrome-use do @e15 expand
✓ expand on @e15
now: collapse

动作集是实时读的,不是从快照里带出来的 —— 它是状态而不是身份:你截图时收着的折叠块现在可能已经开了, 这时候再给它 expand 就是让你做反。

集合之外的动作会被拒绝并附上支持列表,而不是勉强做点相近的事。 执行完会再报一次动作集,所以一个没动的控件不会读起来像成功。

ℹ️ 支持哪些
expand / collapse(折叠块)、showMenu(会弹出的控件)、 increment / decrement(有取值范围的控件)、 toggle(可按下的控件)。都由元素的无障碍属性推导, disabled 的元素不提供任何动作。

上传文件

upload 给文件 <input> 或拖拽/粘贴式的编辑器(如 X 的输入框)投喂文件。 它在扩展中继模式下也能工作。 React dropzone 消费文件后立即清空或替换隐藏 input 时,命令会成功并给出提示,不再误报页面拒绝了上传。

upload
$ chrome-use upload @e5 file1.pdf        # 上传文件
ℹ️ 为什么中继模式也能上传
扩展没有「允许访问文件网址」时(从 Chrome 应用商店安装默认关闭),chrome.debugger 禁止 setFileInputFiles,所以文件字节会被流式送进页面、在页面里重建成一个 File, 每块都远小于 native-messaging 的 1 MiB 上限。每块的开销和文件大小无关(200 MB 的视频约 15 秒), 同一个浏览器的上传一次只跑一个:后来的会排队,回复里的 delivery.queuedMs 是等了多久。 因此文件 <input> 和 drop/paste 编辑器都能收到。给 chrome-use 扩展打开「允许访问文件网址」后, Chrome 会直接读取文件。

滚动

scroll 按方向滚动页面;scrollintoview 把某个元素滚进视口。 对跨源 iframe(支付 / 结算 / KYC 组件)里的内容,普通页面滚动够不着,用 --at x,y 在像素点上滚滚轮,或 --frame n 指定帧。

scroll / scrollintoview
$ chrome-use scroll down 500              # 滚动页面(up/down/left/right)
$ chrome-use scroll down 700 --at 640,400  # 在某像素点滚滚轮——能滚到普通页面
                                        # 滚不到的跨源 iframe(Stripe/结算/KYC)
$ chrome-use scroll down 700 --frame 2    # 滚 `chrome-use frames` 里的第 2 帧
$ chrome-use scroll down --until "#comments" # 逐步滚动直到目标进入视口(也支持 @ref、text=、--until-text)
                                        # 找不到则退出码 1 并说明滚了多远;--max-steps / --timeout 设上限
$ chrome-use scrollintoview @e1           # 把元素滚进视口

拖拽与滑块验证码

drag 既能在两个 ref 之间拖放,也能把一个手柄按像素偏移拖动(滑块 / canvas)。 对无人值守登录常见的网易易盾滑块验证码,用 solve-slider 自动过。

drag / solve-slider
$ chrome-use drag @e1 @e2                # 拖放
$ chrome-use drag @e1 60                 # 把手柄拖 +60px(滑块/canvas);`+60,-3` 表示 dx,dy
$ chrome-use solve-slider                # 自动解页面上的网易易盾滑块验证码
$ chrome-use solve-slider 5              # ……最多重试 5 次(每次失败会刷新拼图)
ℹ️ solve-slider 是怎么过的
它按 URL 抓取验证码自己的背景图 + 拼图切片(不截图),离线用边缘 + 掩码互相关定位缺口, 再用拟人化、自校准的闭环轨迹把手柄拖进缺口——正是这段人类般的运动通过了易盾的行为检测。 float(内嵌)和 popup(模态,如知乎)两种模式都支持,在触发滑块的提交之后立即运行即可。 这个拖拽会强制启用 humanize 轨迹,不受全局 AGENT_BROWSER_HUMANIZE 影响。
滑块与点选的区别
solve-slider 支持普通拼图和旋转图标拼块:先测量位移与角度的关系, 匹配主要轮廓,修正实际 CSS 位置,并核验当前这道题的结果。 按顺序点选由 agent 看截图、识别顺序、换算坐标并点击,不能把它当成滑块执行。

遇到验证码先尝试

登录或浏览任务已获授权时,先读当前题型并尝试页面上的正常控件。验证码字样或 observed.humanCheck 警告不等于必须交给人。 易盾普通拼图先用 solve-slider 1;图标点选先截图,看清指令和目标后按顺序点击。 截图坐标要按截图尺寸与视口尺寸换算成 CSS 像素,不要直接除以 devicePixelRatio。 先激活目标标签,再截图和取视口尺寸;激活可能改变页面布局。

用 chrome-use skills get core/captcha 加载完整流程。最多尝试三道新题(含命令内重试); 换题后重新读取,不复用旧坐标,不为重试反复发送短信。 点击命令返回 Done 不代表通过,要读页面的验证结果,再继续已授权的任务。 solve-slider 耗尽尝试时返回错误并以非零状态退出。 识别不清、尝试失败或需要本人操作时才用 session handoff,并在用户确认后恢复。 这套流程不保证所有厂商和题型都能解开,也不允许关闭校验或伪造通过结果。 倒计时不走或验证码面板仍显示时,不要报告通过;厂商返回成功后仍要核验网站是否接受。


下载

ab-connect 0.5.13 及以上版本会通过 Chrome downloads API 下载 HTTP(S) 链接。chrome-use 会先解析链接的 href,包括 snapshot -i 中能看到的动态 anchor,从而避免跨源 download 链接把当前标签页导航到媒体 URL。

bash
$ chrome-use download @e2 ./video.mp4
$ chrome-use download-url https://example.com/report.pdf ./report.pdf
$ chrome-use downloads --limit 10 --json
$ chrome-use downloads --clear

download-url 省略目标路径时保留 Chrome 的默认下载位置。 downloads --clear 只清除历史,不会删除文件。


跨源 iframe:始终按 ref 操作

内嵌的支付 / 结算 / KYC 组件(Google Payments、Stripe 等)是跨进程的 out-of-process iframe。 用 ref 驱动它们,永远不要靠截图。 snapshot -i 会穿透这些 iframe,按 @ref 列出里面的元素(含输入框的值); get text --all-frames 读它们的文本。拿到 ref 后照常操作即可。

在 iframe 里按 ref 操作
# snapshot -i 列出跨源 iframe 内的元素,然后:
$ chrome-use click @e
$ chrome-use type @e "…"
$ chrome-use hover @e          # 帧内只能派发合成事件,CSS :hover 不生效,输出标为未确认
$ chrome-use dblclick @e
$ chrome-use drag @a @b

# 帧内的邮编/联想框——用真实按键:
$ chrome-use type @e "100-0001" --key-events
⚠️ 不要把 <iframe> 元素本身当作目标
对 Iframe ref 执行 focus / press,命中的是父文档里的 容器元素,按键会落到父页面而不是帧内的输入框。 现在这两个命令会返回一条指明帧边界的警告,而不是在错误目标上打一个干净的 ✓。 请从帧内部操作:先用 chrome-use frames 列出所有帧,再 chrome-use frame <id> 并操作帧内部的 ref,或者用 chrome-use eval --frame <id> "…"。
✅ 为什么按 ref 而不是坐标
在扩展中继模式下,这些操作是通过 DOM 在元素自己的帧里派发的, 所以能精准命中正确标签页里的正确元素。而坐标点击/滚动可能漂移到当前前台的其它标签页上—— 所以优先用 ref。帧内需要滚动到下方内容时,用 scroll down N --at x,y(帧上的一个像素点)或 --frame n。
⚠️ find 够不到闭合 shadow root 或跨源 iframe
find/选择器匹配的是页面 DOM(querySelectorAll),所以对这两类元素会报 「Element not found」——即便 snapshot -i 能列出它们(它走 CDP 可访问性树,能穿透两者)、 get text 能读到。这时请用快照里的 @ref 定位,而不是 find; box @ref 可给出坐标兜底。另外,先核对准确的标签文案再断定它「不存在」: LinkedIn 的「Save」按钮实际标注为 收藏 而非 保存, snapshot -i 一直显示着 button "收藏" [ref=eN],直接 click @eN 即可。

会自动格式化的输入框

Stripe 会把有效期 1234 显示成 12 / 34,卡号显示成四位一组。fill 遇到这种只是多了空格和分隔符的情况算成功,并用 ⚠ 说明页面改了格式;值有其他任何不同仍然报错,而且卡号、密码这类值不会出现在报错里。

富文本编辑器(contenteditable)按可见文字比较:编辑器给每行和话题节点补的零宽字符(U+200B)、NBSP 和连续空白都不算差异。只是被编辑器改了格式的写入算成功,并附“the editor formatted the text”说明;少字或改字仍然报错。


点击之后发生了什么

加 --observe 时,observed.announced 列出点击执行和页面稳定期间出现的 toast 以及 alert/status/aria-live 区域的文字,回复前已经消失的也在(例如“视频审核中,修改未保存”)。靠文字命名的可点击 <div>(generic "投稿")在快照后可以直接当 @ref 用;页面把这个节点换成别的内容时,ref 会被拒绝。从不属于本会话的标签(--tab … --force、侧边栏操作你的标签)打开的新标签仍归你:点击会报告它并给出 followed: false,用 tab adopt <url> 接管。


接下来

交互只是核心循环的一环,搭配这几页一起看:

语义 find role/text/label/placeholder/alt/title/testid 要求恰好一个可见匹配,只定位不操作时也一样。多个匹配会返回最多八个候选及可见状态、选择器和上下文提示,不派发动作,也不输出输入框的值。用 --name/--exact 消歧,或用 --within <CSS|@ref> 限定到唯一容器。范围必须属于当前标签页的主文档,跨 iframe 的 ref 和已选中的 iframe 上下文会被拒绝;可用 iframe 内的直接 ref 动作,或先 `frame main`。find first/last/nth 保留显式按序选择;传给动作(click、fill、type、hover、check、select、drag 等)的普通 CSS 选择器或 XPath 也必须恰好匹配一个元素:匹配多个时以 selector_ambiguous 拒绝,列出前五个匹配的文字和位置,什么都不做。收窄选择器、改用 @ref,或用 find nth <序号> <选择器> <动作> 按序号指定;旧脚本需要首个匹配时设 AGENT_BROWSER_STRICT_SELECTORS=0(会话的 daemon 启动时读取)。role 和 label 名称来自 Chrome 无障碍数据,包含 aria-labelledby;每次语义调用重新定位。派发前目标已脱离时安全失败,结果不确定的动作不会重放。

先从页面发现范围:find query "Beta account" 返回候选的选择器锚点;选出对应标题的 article/容器,再执行 find role button click --name Save --exact --within "<返回的选择器>"。范围有歧义时用 snapshot -s "<选择器>" 查看并缩小范围,不取第一个。未知语义 find 参数和重复 --within 会被拒绝;输入像参数的文字可写 find label Email fill -- --name。--exact false 使用子串匹配。

现有 mcp --tools all 的 chrome_use_find 工具接受 within,使用相同的唯一范围规则,工具数量不变。fill/type 的 text 按文字原样传入,--name --observe 不会被当作 CLI 参数。

语义 find 的 --observe 仍按已有行为提示不支持;已知范围和回执时可在同一个 batch 中执行范围内 find 动作及任务专属的 get text 核验。MCP find 工具不声明 observe 字段。