跳转到内容

无障碍树驱动的 UI 自动化实践

无障碍树驱动的 UI 自动化实践

学习目标

本章学习 Chrome DevTools MCP 如何将 Chrome 的 Accessibility Tree 转换为 Agent 可用的页面快照。你将理解:

  • TextSnapshot 如何将 AXNode 树转换为带 uid 的扁平快照
  • uid 的生命周期管理与复用策略
  • 快照与 DOM 操作的联动(通过 backendNodeId 解析 ElementHandle
  • 额外 Handle 注入(extraHandles)处理动态生成元素

前置知识


项目实践

TextSnapshot:AXNode 到 Agent 快照的转换

TextSnapshot 类(src/TextSnapshot.ts)负责将 Chrome 的 Accessibility Tree 转换为 Agent 可用的页面快照。

转换流程

快照输出的关键特性

  1. 节点过滤ignoredtrue 的节点被移除(装饰性元素不暴露给 Agent)
  2. UID 生成:每个可见节点获得一个自增的稳定 uid(如 "1", "2", "3"
  3. backendNodeId 绑定:每个 uid 映射到 CDP 的 backendNodeId,用于后续 DOM 操作
  4. 扁平序列化:树结构被展平为线性列表,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 + DOMAXNode + DOM 映射操作仍需 DOM,AXNode 仅提供语义导航

陷阱与对策

陷阱后果对策
快照过期后使用旧 uid操作到错误元素错误信息提示 uid 不存在,建议重新快照
动态内容未出现在快照中Agent 找不到目标元素提供 wait_for 等待特定文本出现后再快照
大量隐藏节点污染快照快照过大,信噪比低过滤 ignoredhidden 节点

参考来源