无障碍树驱动的 UI 自动化实践
无障碍树驱动的 UI 自动化实践
学习目标
本章学习 Chrome DevTools MCP 如何将 Chrome 的 Accessibility Tree 转换为 Agent 可用的页面快照。你将理解:
TextSnapshot如何将 AXNode 树转换为带 uid 的扁平快照- uid 的生命周期管理与复用策略
- 快照与 DOM 操作的联动(通过
backendNodeId解析ElementHandle) - 额外 Handle 注入(
extraHandles)处理动态生成元素
前置知识
- 无障碍树驱动的 UI 自动化 — AXNode 结构与 UID 管理模式
项目实践
TextSnapshot:AXNode 到 Agent 快照的转换
TextSnapshot 类(src/TextSnapshot.ts)负责将 Chrome 的 Accessibility Tree 转换为 Agent 可用的页面快照。
转换流程:
快照输出的关键特性:
- 节点过滤:
ignored为true的节点被移除(装饰性元素不暴露给 Agent) - UID 生成:每个可见节点获得一个自增的稳定 uid(如
"1","2","3") - backendNodeId 绑定:每个 uid 映射到 CDP 的
backendNodeId,用于后续 DOM 操作 - 扁平序列化:树结构被展平为线性列表,Agent 按顺序浏览
uid 生命周期管理
take_snapshot() ↓为每个可见节点生成 uid(自增计数器) ↓Agent 收到快照:[uid="1"] 按钮 "Submit" ↓Agent 操作:click { uid: "1" } ↓McpPage.getElementByUid("1") → idToNode.get("1") → node.backendNodeId → DOM.resolveNode → ElementHandle ↓Puppeteer element.click()UID 失效场景:
- 页面导航(URL 变化)
- 页面重载
- 大规模 DOM 变化(SPA 路由切换)
项目对策:每次 take_snapshot 后,McpPage.textSnapshot 被替换,旧的 uid 查找自然失效。Agent 需要在新快照中重新定位元素。
extraHandles:处理动态注入元素
某些场景下(如执行第三方开发者工具后),页面中会新增 DOM 元素,这些元素不在当前快照的 AXTree 中。
处理方式:
execute_3p_developer_tool(...) ↓页面执行工具,返回新 DOM 元素 ↓将 DOM 元素通过 window.__dtmcp.stashedElements 暂存 ↓TextSnapshot.create(page, { extraHandles: [...] }) ↓新快照包含暂存元素的 uid ↓替换旧快照,释放旧 ElementHandles这使得 Agent 在操作第三方工具后,仍然可以通过 uid 引用新出现的元素。
坐标方案(click_at)作为补充
项目通过 --experimentalVision 标志启用 click_at(x, y) 工具,适用于:
- Canvas / WebGL 内容(无 AXNode)
- 自定义渲染的 UI 组件
- 配合视觉模型(computer-use)的场景
设计决策:坐标方案是辅助而非主要方案,因为:
- 需要视觉模型才能理解截图内容
- 坐标受 DPI、缩放、滚动位置影响
- 不稳定的操作方式(页面布局微调即失效)
设计取舍
| 取舍 | 选择 | 原因 |
|---|---|---|
| 自增 uid vs hash uid | 自增计数器 | 简单、无冲突、快照内保证唯一 |
| 每次操作后自动快照 vs 手动快照 | 手动快照 | Agent 控制快照时机,减少冗余 Token |
| 仅 AXNode vs AXNode + DOM | AXNode + DOM 映射 | 操作仍需 DOM,AXNode 仅提供语义导航 |
陷阱与对策
| 陷阱 | 后果 | 对策 |
|---|---|---|
| 快照过期后使用旧 uid | 操作到错误元素 | 错误信息提示 uid 不存在,建议重新快照 |
| 动态内容未出现在快照中 | Agent 找不到目标元素 | 提供 wait_for 等待特定文本出现后再快照 |
| 大量隐藏节点污染快照 | 快照过大,信噪比低 | 过滤 ignored 和 hidden 节点 |