进阶 / Advanced
Canvas / WebGL / 游戏
游戏画面、语音房麦位、地图瓦片、3D 查看器、设计画布(Figma / 墨刀)——这些界面把一切都画在
<canvas> 上,背后没有 DOM 节点、也没有无障碍节点。
snapshot / find / eval querySelector 永远拿不到它们的 @ref。
这不是缺失的功能,而是硬限制。本页教你换一套打法:看像素、按坐标驱动。
为什么 canvas 拿不到 ref
<canvas> / WebGL 的命中区域,以及闭合 shadow root,都不会为画出来的控件暴露任何 DOM / AX 节点。
因此无论 snapshot 怎么扫都造不出 @ref——坐标是唯一的抓手。snapshot 检测到这种页面时会打印一行提示。
snapshot -i 正常呈现,优先用 @ref。
--launch 或自己拥有的标签页,或者干脆绕过画布、通过 App 后端 / API 确认真实状态。
参见 驱动真实 Chrome。
读取画面:canvas capture 与 screenshot
canvas 页面的 eval / get text 基本返回不了有用信息,所以看是你唯一的读取路径。
先枚举画布,再把渲染像素抓下来,然后由你来解读:
# 枚举页面上的 <canvas> 元素(尺寸、类型)
$ chrome-use canvas list
# 把画布的「渲染像素」存成 PNG —— toDataURL 拿到完整
# 后备存储分辨率(如 Figma 的 2522x1904);对没开
# preserveDrawingBuffer / 被污染的 WebGL 回退到 screenshot
$ chrome-use canvas capture out.png
# 或者直接截一张区域图来「看」当前状态
$ chrome-use screenshot /tmp/s.png
canvas capture 抓的是画出来的画面。Figma 的源资产、游戏的隐藏状态等都活在 App 的二进制存储 / API 里,
画布上并没有。需要源数据时走那个 App 自己的接口。
症状:get text 里有文字,却没有 @ref
语音房麦位(Zego / Agora)、原型画布(墨刀 / modao)、游戏 HUD 和某些 Web Component 会把控件画出来——
于是文字出现在 get text / read_page 里(比如一串 "Add Add Add…"),但 snapshot -i 什么都列不出、
querySelectorAll 返回 0。没有可寻址节点,@ref / find 就够不着它。按位置驱动:
# 先试:如果只是「闭合」shadow root(不是 canvas),
# 这条能读穿它 —— 便宜,值得先试一下
$ chrome-use get text --pierce
# 否则先「看」控件在哪
$ chrome-use screenshot /tmp/s.png
# 点那个像素(裸数字 = 坐标)
$ chrome-use click <x> <y>
按坐标交互:click 与 press --hold
canvas 上一切靠坐标。算出目标点直接 click x y;需要先知道某个容器的 CSS-px 盒子时,用
box @ref 拿到它的位置再换算。坐标在这里是正确的工具——
snapshot-first 规则明确为 canvas 开了这个口子。
# 按视口坐标交互
$ chrome-use click 640 360
# 按住移动,精确定时(在 daemon 内计时 ——
# 不是 keydown + shell-sleep + keyup,那样每次往返
# 会多出约 250ms 抖动)
$ chrome-use press d --hold 800
# 离散动作(跳 / 攻击 / 确认)
$ chrome-use press Space
press --hold <ms>,计时发生在 daemon 内部,比 shell 里 keydown / sleep / keyup 拼出来的
稳得多。更多按键与输入见 交互操作。
batch:一次往返跑完定时序列
别一条 CLI 一个动作地逐帧驱动——那是最慢、保真度最低的方式,每次调用都是一次进程启动 + 往返。
用 batch 把一段定时序列塞进单次往返:它把每一步发给正在运行的 daemon,
press --hold 和 wait 都在 daemon 内阻塞,所以时序很准。
$ chrome-use batch "press d --hold 900" "press j" "press j" "wait 200" "press d --hold 500"
stream WebSocket。选到刚好够用的那层就行。
读引擎全局变量,而不是猜像素
与其从截图里猜,不如直接读真实状态。eval 运行在页面的 main world,
对基于框架 / 引擎的游戏,你往往能直接够到它的全局对象——比如一个 Phaser / PIXI / Three 实例、
一个 store、或者 window.__GAME__——直接读出位置 / 分数,远胜于对着截图猜。
# eval 在 main world,能读到引擎挂在 window 上的实例
$ chrome-use eval "window.__GAME__.player.position"
实时驱动:WebSocket 流
要真正做实时驱动,就彻底抛开 CLI,改用 WebSocket。
chrome-use stream enable 打开一条双向 WS(stream status 会打印
ws://127.0.0.1:<port>)。连上一次,你就同时拿到 ~60fps 的实时投屏,
并且能在同一条 socket 上发输入——没有逐动作的进程启动、没有往返,而且能跨扩展 relay 工作。
$ chrome-use stream enable
$ chrome-use stream status # 打印 ws://127.0.0.1:<port>
// node(全局 WebSocket):实时帧 + 本地计时输入
const ws = new WebSocket("ws://127.0.0.1:PORT")
ws.onmessage = e => { const m = JSON.parse(e.data); if (m.type==="frame") {/* base64 jpeg */} }
const k = (eventType,key,code,vk) => ws.send(JSON.stringify({type:"input_keyboard",eventType,key,code,windowsVirtualKeyCode:vk}))
k("keyDown"," ","Space",32); setTimeout(()=>k("keyUp"," ","Space",32), 80) // 一次跳跃
// 鼠标同理:{type:"input_mouse",eventType:"mousePressed",x,y,button:"left",clickCount:1}
screenshot;任何持续的实时控制都用 WS。
虚拟化富文本编辑器:先探针,再落笔
Google Docs / Sheets、Notion、Figma、飞书文档——这些和 canvas 不同,有 DOM,但 DOM 会骗你:
真正的编辑面是虚拟化层,周围散落着一堆诱饵——隐藏的 <textarea> 镜像、屏外输入框、工具栏 / 搜索框、标题输入。
对着 snapshot -i 挑出来的 ref 直接 fill / type,文字常常落进标题栏或某个隐藏框,而不是文档里。
导航(菜单、按钮、对话框)照旧 snapshot-first;但对主编辑面,落一整段之前先证明键盘落在哪。
# 1. 写探针:真实按键打一个丢弃 token,别一上来就灌整段
$ chrome-use click 520 300 # 按坐标点进正文
$ chrome-use keyboard type "zzprobe" # 真实 keydown/keypress/keyup,不是 fill/insertText
# 2. 核对它落在文档里,而不是标题 / 工具栏 / 隐藏输入
$ chrome-use screenshot /tmp/probe.png # 看 "zzprobe" 到底出现在哪(或走 App 的导出/API 回读)
# 3a. 落在文档里 → 用真实按键继续打正文
# 3b. 落错了 → 这个面别再用 DOM/fill:按坐标点正文,之后只用真实键盘
fill @ref / type @ref,先探针证明目标(工具栏、菜单、评论框、分享弹窗是真 DOM,@ref 没问题,坑在画布 / 表格本身);
优先真实按键(keyboard type / keyboard press)而不是 fill/insertText(虚拟化编辑器只听 key 事件,insertText 常静默失败或写进镜像);
用回读核对而不是假设——截图或走 App 导出 / API 确认内容真落下了,再报完成。