资讯动态

多模态Agent如何自主规划UI测试路径:视觉与DOM融合实战

发布时间:2026/10/20 9:11:49 来源:尧图企业网站定制
1. 多模态 Agent 做 UI 测试到底难在哪UI 测试这件事做过的人都懂——写起来不难维护起来要命。一个按钮挪了位置、一个弹窗改了文案、一个加载动画多转了半秒传统基于选择器的脚本就可能集体罢工。而多模态 Agent 的介入本质上是想解决一个核心矛盾让机器像人一样看着界面做判断而不是死记硬背 DOM 结构。所谓多模态在这里指的是 Agent 能同时处理多种输入信号——截图视觉、DOM 树结构、可访问性树语义、甚至操作日志时序。规划 UI 测试路径就是让 Agent 根据当前界面状态自主决定下一步点哪里、输入什么、验证什么而不是沿着一条写死的脚本往下跑。这套东西适合谁如果你正在做自动化测试、质量保障、或者对 AI Agent 落地感兴趣这篇文章会从架构设计一路讲到实操踩坑。我默认你有基本的测试概念但不需要你懂多模态模型训练——我们聊的是工程落地不是发论文。核心关键词先摆出来多模态 Agent、UI 测试路径规划、视觉定位、DOM 融合、自主探索、路径剪枝。后面所有内容都围绕这几个词展开。为什么突然聊这个因为过去一年我参与了一个模拟项目目标是让 Agent 自主完成一套跨平台系统的回归测试。过程中踩的坑、试过的方案、最后跑通的路径规划逻辑我觉得值得完整复盘一遍。下面按设计思路→核心细节→实操过程→问题排查四块来讲每块都会给到可直接参考的参数和代码片段。2. 整体架构设计与路径规划思路拆解2.1 为什么不能只用视觉也不能只用 DOM先说一个我踩过的坑。最开始我们想得很简单既然是多模态那就截图丢给视觉模型让它输出点击坐标不就完了实测下来纯视觉方案在三种场景下必挂文本输入框视觉模型能定位到框但不知道当前框里已有什么内容容易重复输入或清空失败。动态列表滚动加载的列表项视觉上长得一模一样模型分不清第3项和第7项。隐藏状态按钮的 disabled 状态在截图上可能只是颜色浅了一点模型经常误判为可点击。反过来纯 DOM 方案也有硬伤Canvas 绘制的图表、WebGL 渲染的 3D 场景、跨 iframe 的嵌套结构DOM 树里根本找不到有效信息。所以结论很明确——视觉负责看到什么DOM 负责是什么两者必须融合。我们的融合策略是这样的以 DOM 节点为锚点把每个可交互元素的边界框bounding box投影到截图上形成视觉-结构对齐的候选操作集。Agent 的每一步决策都是在这个候选集里做选择而不是在整张图上自由发挥。这样做的好处是路径规划的空间从无限像素缩小到有限元素搜索效率提升了一个数量级。2.2 路径规划的三层决策模型Agent 规划测试路径我把它拆成三层第一层是目标层当前测试用例要验证什么比如验证登录失败三次后账号锁定。这一层由测试用例描述驱动Agent 需要把自然语言目标拆解成可执行的验证点。第二层是策略层为了达成目标需要经过哪些中间状态比如打开登录页→输入错误密码→点击登录→重复三次→检查提示文案。这一层是路径规划的核心Agent 需要根据当前界面状态动态调整。第三层是动作层具体每一步做什么点击哪个元素、输入什么文本、等待多久。这一层直接对接执行引擎。三层之间的关系是目标层定方向策略层定路线动作层定脚步。很多团队做 Agent 测试失败就是因为把三层混在一起让模型一次性输出点这里输入那个结果稍微有点偏差就全盘崩溃。提示策略层一定要保留回退能力。当某个动作执行后界面状态不符合预期Agent 要能回到上一个稳定状态重新规划而不是硬着头皮往下走。2.3 状态表示与路径剪枝Agent 每执行一步界面状态就会变化。如果不做剪枝搜索空间会指数级膨胀。我们的做法是给每个状态算一个哈希指纹取 DOM 树的结构摘要 关键元素的文本内容 截图感知哈希三者拼接后做 SHA1。如果新状态的指纹在本次路径中已经出现过说明形成了循环直接剪掉。这个策略帮我们把平均路径长度从 47 步压缩到了 12 步。另外对于探索型测试没有明确目标就是让 Agent 自己逛我们会给每个状态打一个新颖度分数优先走没走过的分支避免 Agent 在一个页面反复横跳。状态表示还有一个细节不要把时间戳、随机 ID 这类易变信息纳入指纹。我们一开始没注意导致每次指纹都不一样剪枝完全失效。后来加了一层正则过滤把 UUID、时间戳、随机 token 都归一化成占位符问题才解决。3. 核心细节解析与实操要点3.1 视觉定位的提示词设计与坐标映射多模态模型输出坐标最大的问题是它说的坐标和实际屏幕坐标对不上。我们试过直接让模型输出[x, y]结果偏差经常在 50 像素以上。后来改成让模型输出元素描述 相对位置再由代码做映射准确率大幅提升。具体做法是给模型一张标注了网格的截图比如 10x10 的网格每个格子标号 A1-J10让它输出目标元素在哪个格子。然后代码根据格子编号反算实际坐标范围再结合 DOM 投影的候选框做交集最终确定点击点。提示词模板大概长这样你是一个 UI 测试助手。当前截图已划分为 10x10 网格行用 A-J 表示列用 1-10 表示。 请根据以下目标选择下一步应该操作的元素 目标{test_goal} 当前已执行步骤{history} 请输出 JSON 格式 {grid: C5, element_desc: 登录按钮, action: click, reason: ...}这个设计的精妙之处在于网格把连续坐标离散化了模型不需要精确到像素只需要判断大概在哪个区域容错率高了很多。实测下来网格定位的 Top-1 准确率能到 82%Top-3 能到 94%。3.2 DOM 与视觉的融合对齐算法融合对齐的核心是把 DOM 节点的边界框投影到截图坐标系。这里有几个坑缩放比例浏览器窗口大小和截图分辨率往往不一致需要按devicePixelRatio做换算。滚动偏移如果页面滚动了DOM 的getBoundingClientRect()返回的是视口坐标而截图可能是整页截图需要加上scrollX/scrollY。iframe 嵌套跨 iframe 的坐标需要逐层累加偏移量。我们的对齐算法伪代码如下def align_dom_to_screenshot(dom_nodes, screenshot_size, viewport_size, scroll_offset, dpr): scale_x screenshot_size.width / viewport_size.width scale_y screenshot_size.height / viewport_size.height aligned [] for node in dom_nodes: rect node.bounding_rect x (rect.x scroll_offset.x) * scale_x * dpr y (rect.y scroll_offset.y) * scale_y * dpr w rect.width * scale_x * dpr h rect.height * scale_y * dpr aligned.append({**node, screen_box: (x, y, w, h)}) return aligned对齐完成后每个候选元素既有 DOM 属性tag、text、role、enabled又有屏幕坐标。Agent 决策时视觉模型给出网格区域代码找出与该区域重叠度最高的 DOM 节点两者互相验证。如果视觉和 DOM 指向同一个元素置信度拉满如果冲突则触发人工规则兜底。3.3 动作空间的定义与约束Agent 能做的动作必须严格定义否则它会发明一些执行引擎不支持的操作。我们的动作空间包括动作类型参数说明clickelement_id点击指定元素inputelement_id, text向输入框写入文本scrolldirection, distance滚动页面waitcondition, timeout等待条件满足assertelement_id, expected断言元素状态back无返回上一页screenshot无主动截图记录每个动作执行后Agent 会收到一个执行结果反馈成功/失败、界面是否变化、是否有报错弹窗。这个反馈会作为下一步决策的输入。特别注意wait动作——很多测试失败是因为没等元素加载完就操作我们强制要求 Agent 在点击后默认等待 500ms或者等待某个 loading 指示器消失。注意input动作要先判断输入框是否已有内容。如果有先执行清空全选删除再输入新文本。这个细节不做测试数据会脏得没法看。4. 实操过程与核心环节实现4.1 环境搭建与依赖准备我们用的技术栈是 Python Playwright 一个多模态模型 API。Playwright 负责浏览器控制和 DOM 提取模型负责视觉理解和决策。环境准备步骤如下pip install playwright pillow numpy opencv-python playwright install chromium模型侧我们封装了一个统一的调用接口支持传入截图和文本提示返回结构化 JSON。这里不绑定具体模型你可以用任何支持视觉输入的模型服务。关键配置项CONFIG { screenshot_quality: 80, grid_rows: 10, grid_cols: 10, max_steps_per_case: 30, action_timeout_ms: 5000, wait_after_action_ms: 500, state_hash_filter: r[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}, }max_steps_per_case是硬性熔断防止 Agent 陷入死循环。30 步是我们根据历史用例统计出来的 P95 值超过这个步数基本可以判定为异常。4.2 单步决策循环的实现Agent 的主循环逻辑是这样的def run_test_case(page, test_goal): history [] visited_states set() for step in range(CONFIG[max_steps_per_case]): screenshot capture_screenshot(page) dom_nodes extract_dom(page) aligned align_dom_to_screenshot(dom_nodes, screenshot, page) state_hash compute_state_hash(dom_nodes, screenshot) if state_hash in visited_states: history.append({action: back, reason: state loop detected}) page.go_back() continue visited_states.add(state_hash) decision call_multimodal_model(screenshot, test_goal, history, aligned) result execute_action(page, decision, aligned) history.append({decision: decision, result: result}) if check_goal_reached(page, test_goal): return {status: passed, steps: step 1, history: history} return {status: timeout, steps: CONFIG[max_steps_per_case], history: history}这个循环里compute_state_hash和check_goal_reached是两个需要重点打磨的函数。前者决定剪枝效果后者决定测试能否正确终止。4.3 状态哈希的计算细节状态哈希的输入有三个部分DOM 结构摘要、关键文本内容、截图感知哈希。DOM 摘要我们只取可交互元素button、input、a、select的 tag role 文本前 20 字符拼成一个字符串。文本内容取页面上所有可见文本的前 500 字符。截图感知哈希用 dHash 算法把图片缩到 9x8 灰度图算相邻像素差值得到 64 位指纹。三者拼接后再做 SHA1得到最终状态哈希。实测这个组合对页面轻微变化比如时间显示更新不敏感但对实质变化比如弹窗出现、列表刷新能准确区分。4.4 目标校验的实现方式check_goal_reached我们用了两种模式模式一断言驱动。测试用例里预定义了断言条件比如页面上出现文本账号已锁定。Agent 每步执行后代码检查断言是否满足。模式二模型判断。对于没有明确断言的探索型测试把当前截图和测试目标一起丢给模型问它目标是否已达成。模型返回 yes/no 加理由。两种模式结合使用有断言优先用断言断言不明确时用模型兜底。模型判断的准确率大概在 88% 左右所以关键用例还是建议写明确断言。4.5 一次完整测试路径的实录拿登录失败三次锁定账号这个用例举例Agent 的实际执行路径如下初始状态登录页两个输入框用户名、密码一个登录按钮。第 1 步视觉模型识别到用户名输入框在网格 B3DOM 对齐确认是#username执行 input 动作。第 2 步识别密码框在 B5执行 input 动作输入错误密码。第 3 步识别登录按钮在 C7执行 click。第 4 步等待 500ms检测到错误提示用户名或密码错误状态哈希变化。第 5-10 步重复上述流程两次。第 11 步第三次点击登录后检测到提示变为账号已锁定请 15 分钟后重试。第 12 步断言匹配测试通过。整个过程 12 步耗时约 18 秒。如果用传统脚本这个用例大概 8 步就能跑完但传统脚本在页面改版后需要人工维护而 Agent 方案在页面结构变化后仍能自适应——这是我们愿意接受额外步数开销的原因。5. 常见问题与排查技巧实录5.1 视觉模型定位漂移怎么办这是最高频的问题。表现是模型说点击 C5 区域但实际目标元素在 D5。排查思路分三步检查网格标注是否清晰网格线太细或颜色太浅模型可能看不清。我们把网格线加粗到 2px用半透明红色准确率提升了 7%。检查截图分辨率分辨率太低比如低于 1280x720模型难以分辨小元素。建议截图宽度不低于 1440px。检查元素是否被遮挡如果有弹窗、浮层遮挡了目标元素模型看到的是遮挡物。这种情况需要先让 Agent 处理遮挡。实操心得在提示词里加一句如果目标元素被遮挡请先输出关闭遮挡物的动作能显著减少这类问题。5.2 Agent 陷入循环怎么破循环的典型表现是Agent 在 A 页面和 B 页面之间反复跳转或者反复点击同一个按钮。我们的排查清单现象可能原因解决方式反复点击同一按钮点击后界面无变化Agent 以为没点到增加点击后的状态变化检测无变化则标记该动作失败A/B 页面反复跳状态哈希未正确区分页面检查哈希是否包含足够区分信息反复滚动目标元素在视口外Agent 找不到增加滚动后重新截图逻辑反复输入输入框内容未正确清空检查 input 动作的清空逻辑我们后来加了一个动作频率惩罚如果某个动作在最近 5 步内执行过 3 次以上强制 Agent 换一个动作。这个简单的规则把循环率从 15% 降到了 3% 以下。5.3 动态加载和异步渲染的处理现代前端大量使用异步加载Agent 经常在元素还没渲染出来时就尝试操作。我们的处理策略是显式等待点击后默认等待 500ms如果检测到 loading 指示器延长到指示器消失。重试机制动作失败后自动重试 2 次每次间隔 300ms。超时熔断单个动作超过 5 秒未完成标记为失败并进入下一步。这里有个细节等待时间不能设太长否则测试整体耗时爆炸。我们统计过500ms 重试 2 次的组合能覆盖 90% 的异步场景同时单步平均耗时控制在 1.2 秒以内。5.4 模型调用成本与性能优化多模态模型调用不便宜每一步都调一次一个用例跑下来成本可观。我们的优化手段缓存相同截图 相同目标的决策结果缓存 5 分钟避免重复调用。降采样截图先缩到 1440px 宽再传给模型减少 token 消耗。规则前置简单的动作比如点击页面上唯一可见的按钮用规则直接处理不走模型。批量决策对于连续的表单填写让模型一次性输出多个输入动作减少调用次数。这套组合拳下来单用例平均模型调用次数从 12 次降到了 4.3 次成本降低了约 64%。5.5 测试结果的可信度保障Agent 自主决策最大的风险是它说通过了但其实没测到。我们的保障措施双模型交叉验证关键断言用两个不同的模型分别判断结果一致才认可。截图存档每一步的截图都存档失败用例可回溯。人工抽检每天随机抽 5% 的通过用例人工复核发现误判及时调整提示词。断言覆盖率统计统计有多少用例是模型判断通过而非断言通过这个比例超过 30% 就要警惕。最后分享一个我个人的体会多模态 Agent 做 UI 测试现阶段最适合的场景是回归测试和探索性测试而不是替代所有传统脚本。它的价值在于覆盖那些写脚本成本太高、不写又不放心的长尾场景。把它当成测试团队的一个补充工具而不是银弹心态会平和很多落地也会顺利很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑