接手UI自动化项目的人应该都有同感:真正难的根本不是写断言、搭框架,而是怎么让测试稳定地跑到那些埋在层层交互后面的页面。传统做法是靠人手动点一遍,把路径录下来,写成脚本。可只要UI改一个入口文案、挪一个按钮位置,脚本就废掉,更不用说那些藏在二级菜单、弹窗逻辑里的分支路径,测试覆盖率全靠人工记忆硬撑。
我最近在一套跨平台系统的UI测试里引入了多模态Agent来规划测试路径。简单说,让Agent像人一样"看"屏幕截图、理解界面结构,再自主决定下一步点哪里、输什么内容、什么时候回退,最后把探索到的路径自动沉淀成可回归的测试用例。实测下来,对于那种菜单层级深、状态切换多、组合条件复杂的界面,路径发现效率比人工录制高出不少,而且UI小改动后Agent能自适应调整,不用每改一次界面就重录一遍脚本。
这篇文章就把这套思路从头到尾拆一遍。适合正在被UI自动化维护成本折磨的同学,也适合想了解多模态模型怎么落地到测试场景的读者。我会把感知层的设计、路径规划的决策逻辑、主控循环怎么搭、常见坑怎么避开,一次性写清楚。
1. 先聊明白:UI测试路径规划到底难在哪
1.1 传统UI自动化的痛点:脚本固化、选择器脆弱、只能验证预设路径
传统UI自动化的核心思路是"录制回放 + 固定断言"。测试人员手动走一遍业务流,工具把每一步操作记录成脚本,之后每次回归都重复执行。问题就出在"固定"这两个字上:一旦界面元素发生变化,脚本里的选择器就会失效。
举个例子,某个配置中心的入口从左侧导航栏挪到了首页工具栏,测试人员需要手动修改若干条用例里的元素定位路径。改动小还好,要是涉及几十个页面、上百条用例,这一个改动就能耗掉大半天。更隐蔽的问题是业务状态导致的路径漂移。比如某个流程依赖前置状态,测试路径必须先进入特定条件分支再操作,人工漏掉一个前置步骤,脚本跑起来就会莫名其妙地失败,而且很难排查。
还有一层更容易被忽略:传统脚本验证的都是"预设路径",你录过什么它就跑什么。那些在真实的用户操作习惯里有可能出现的路径——比如先切到某个Tab再返回、快速双击、在加载动画期间点击——几乎不会被覆盖到。这些恰恰又是最容易出线上事故的交互。
1.2 为什么是"多模态Agent":核心在于感知能力对齐人眼
多模态Agent解决的核心问题不是"自动执行",而是"自主探索"。它的感知层直接吃截图和布局文本,相当于拿人眼的输入方式去理解界面。人的点击依据是视觉可见的,比如"右上角有个蓝色按钮""这个弹窗里第一个选项是黑色粗体字",而多模态模型恰好擅长这种视觉加语义的综合判断。
这跟传统基于源码级DOM解析的自动化有本质区别。传统自动化拿到的元素树虽然结构化,但很多视觉上明确、DOM里没有语义信息的区域,比如Canvas绘制的图表、视频播放器的控制条、地图组件,传统方式根本定位不到。而多模态Agent把这些统统视为"我看到的像素",不需要额外接入定制化的组件SDK就能操作。
我在实践中最大的感受是:多模态Agent生成的路径是"意图级的",而不是"坐标级的"。它理解当前页面的业务含义,再决定下一步操作。这种能力让它面对UI改动时具备一定的自适应性——按钮位置改了但文案还在,模型依然能通过语义定位找到可点击目标,路径规划就不会全盘崩溃。
1.3 核心需求拆解:感知、意图与决策三条线
把"规划UI测试路径"这件事拆开看,底层有三条能力线要打通。
第一是感知。Agent需要回答"当前屏幕上有什么",包括哪些区域可交互、元素之间的层级关系、弹窗是否遮挡了主界面。这一层的难点在于用视觉方式还原出结构化的UI状态,而不是简单返回一张图。
第二是意图理解。Agent要回答"当前任务需要做什么"。同样是"配置一个告警规则",落到UI上的操作可能是先填表单、再点确认、然后验证列表里出现新条目。意图理解如果不到位,后续所有决策都是空转。
第三是决策规划。Agent要回答"下一步点哪里、什么时候需要回退、路径搜索遇到死路怎么处理"。这一层直接决定了路径的质量和覆盖率,也是整个方案里最考验设计的地方。
这三条线缺一不可。感知弱,Agent会对着错误的界面瞎规划;意图弱,Agent做的事跟任务目标南辕北辙;决策弱,Agent会在一个状态里原地打转,路径永远探索不出去。
2. 让Agent"看见"界面:多模态感知层的设计与选型
2.1 先解决三个基本问题:看见、认到、定位到
感知层看起来只是"给模型传一张截图",实际要过三个关卡。
"看见"指的是图像输入通道要通。正常情况下直接截取当前窗口,需要做的预处理很少。但截图分辨率如果超过视觉模型支持的输入上限,就必须考虑缩放或切片,否则模型对远距离小元素的感知会失真。
"认到"指的是模型能从截图中区分出"这是一个输入框""这是一组Tab""这是一个不可点击的普通标签"。这一步依赖视觉语言模型本身的UI理解能力。市面上的通用模型对常见组件的识别都还可以,但如果界面里大量使用自定义控件、不规则图标,就需要针对性补充示例提示,或者在测试样本上做小规模微调。
"定位到"指的是把语义描述映射到屏幕上的具体坐标或区域。比如模型说"点击登录按钮",坐标落在哪里。这个环节属于UI grounding能力,也是我选型时最关注的维度。一个简单验证方法:把一张截图发给模型,要求它输出"确认按钮的中心坐标",看它与标注框交并比是否稳定。
2.2 截图预处理:分辨率、缩放与分区域喂图
截图不是随便扔给模型就行。我踩过最明显的坑是高分屏下整张截图直接上传,模型要么忽略角落区域,要么把坐标换算搞得一塌糊涂。
我在实践中采用的方案是三步走。第一步截取全屏视图,统一等比缩放到视觉模型推荐的最长边尺寸,比如1280或者1024,保持宽高比。第二步做分区域增强:如果界面信息密度高,把截图按网格切分成若干块,配合"这是页面的左上区域"的补充说明分别让模型分析,最后汇总。第三步是关键——坐标换算。模型感知到的是缩放后的坐标系,执行点击时必须按缩放比例映射回真实屏幕坐标,否则误差会直接转化成点击失败。
很多人会忽略的一个小细节:截图里鼠标光标也会被拍进去,模型有时会把光标当成一个UI元素来理解。我在执行截图前会先把光标移走,或者统一隐藏系统指针,有效避免了这类"幽灵元素"导致的路径规划偏差。
2.3 双通道输入:截图加布局文本,优势互补
只用截图有一个明显短板:视觉模型对密集列表、长文本内容的精确识别能力不够稳定。比如一堆商品名称旁边对应的具体价格数字,看是看得见,但OCR级别的精确输出可能出错。
我的方案是"截图 + 布局文本"双通道。截图为模型提供空间布局和视觉层次,同时在请求里附上一份精简的布局树文本,包含关键元素的文本内容、大致位置和类型标注。这听起来有点像DOM树,但注意区别:布局文本是我从系统辅助功能接口或者渲染管线里抽取出来的简化表示,内容只包含模型决策需要的关键字段。
双通道的好处是在空间感知和语义精度之间取平衡。模型先通过布局文本建立准确的元素清单,再通过截图验证每个元素的视觉状态。比如某个按钮上虽有文案"确认",但截图显示它处于置灰状态,模型应当判定它不可点击,而不是仅仅依据文本就规划点击动作。
2.4 视觉模型的选型思路:先看UI grounding,再比上下文与延迟
我只讲选型经验,不指定具体厂商。模型选型有几个硬性评估点。
第一是UI grounding准确率。准备一组带有标注框的UI截图样本,让模型输出自然语言指令对应的元素坐标,统计定位准确率。这个指标决定了路径规划的上限。
第二是上下文长度和图像编码能力。UI路径规划是一个多轮决策过程,Agent需要记忆"已经访问过哪些页面",轮次一多,上下文窗口不够就会遗忘。图像编码质量也很关键,目前主流方案普遍采用高分辨率视觉编码器,对密集UI细节识别更好。
第三是推理延迟和成本。路径探索阶段需要大量"看-想-动"循环,单步延迟超过5秒的话,一条路径跑下来体验会很差。我通常会准备两套模型:快速模型负责常规状态识别,精度更高的模型负责关键节点决策或者信息汇总,在成本和效率之间做切分。
如果你是第一次接手这类项目,我的建议是:先用容易获取的通用视觉语言模型把整条链路跑通,不要一开始就追求最高的grounding精度,链路通了之后再逐步替换或优化模型组件。
3. 从"看见"到"规划路径":意图解析与状态决策
3.1 任务意图解析:把用户级需求翻译成UI原子操作
路径规划的起点是"任务意图解析"。用户给Agent的需求往往是"检查一下发布流程是否正常""新增一条策略然后验证它在列表页展示"。这种高层描述不能直接执行,需要拆解成UI原子操作序列。
直观理解就是人怎么完成这个任务。人在"新增一条策略"时,脑子里会自然浮现出几个子任务:进入新增页面、填写对应字段、保存、回到列表页确认数据。Agent要做同样的事,但它是通过模型推理产生这个子任务序列的。
我建议在主控循环里单独加一个"任务解析阶段"。第一次拿到任务描述时,先让模型输出它在当前界面上的执行计划,用文字描述预期步骤,然后再进入具体执行循环。这样做有两个好处:一是后续如果执行偏离计划,可以判断是计划理解有问题还是执行环境变化了;二是这段计划文本本身可以沉淀成未来人工审计路径的文档。
3.2 状态表示与记忆:三个关键状态必须分开记
路径规划里最容易乱套的是"状态"。我自己的经验是把状态分成三层:
当前真实状态——模型从最新截图和布局文本中感知到的界面。这是决策的直接依据。
目标状态——完成任务的判定条件。比如"表单成功提交后出现的成功提示页""列表中新增了一条记录且状态为运行中"。目标状态要尽可能定义成可验证的断言,而不是模糊的"任务完成"。
已探索状态集合——记录Agent访问过的页面状态和操作序列。这层记忆用于避免循环探索和规划回退路径,是路径规划能否收敛的核心。
已探索状态的记录方式,我是用状态签名来做的。把当前界面的关键特征(页面标题、几个主按钮文案、核心Tab选中态)拼成一个哈希字符串。每次动作执行后重新计算签名,如果发现签名与历史某个状态完全一致且最近动作没有实质改变界面,就判定进入了死循环,触发回退分支。
3.3 路径搜索策略:深度优先为主,条件分支做广度补充
探索路径本质上是一个在界面状态空间里搜索最优轨迹的过程。我在实践中的策略组合是:深度优先为主、条件分支广度补充。
深度优先意味着沿着一条业务流一直往下走,走到终点或死胡同再回溯。这条策略的优点是很符合人的操作直觉,能快速打通主线路径,适合大多数表单流程、引导流程。遇到分支页面——比如一个弹窗同时提供"确定"和"取消"两个功能——我倾向于把分支动作记录下来,在主线跑完之后主动回到该分支状态探索备选路径,这就相当于做了广度优先展开。
还有一个重要的搜索维度是"基于可疑元素的优先探索"。模型识别到一个罕见的按钮或者一个从未见过的组件时,优先点击它往往能发现意想不到的状态分支。我把这作为一种启发式规则注入到路径规划提示词里,显著提高了对异常分支的覆盖能力。
3.4 动作空间定义:交互粒度决定了路径质量
路径规划里的"动作"不能抽象成"做某件事",要细化为可执行、可验证的原子操作。我维护的动作空间包括:点击(附带坐标和语义描述)、输入(附带文本内容和目标字段描述)、滑动(附带方向和距离)、长按、等待、回退、刷新。每个动作执行之后都跟着一次观察点,更新状态和结果评估。
这里有一个初学者容易犯的错误:把"输入"动作直接绑定到具体的框,而没有记录这个框的语义特征。一旦界面布局变化,输入的定位就失效。我的做法是每个动作同时保存语义描述和位置坐标。执行时优先按语义描述让模型重新定位,找不到再退而使用坐标,这种双保险机制让动作的鲁棒性提升非常明显。
不要小看"等待"这个动作。界面加载动画、网络请求返回、数据渲染都是非瞬时状态变化。规划路径时,Agent要学会根据界面变化动态决定等待长短,而不是固定睡几秒。当截图显示加载指示器消失且目标元素出现时,才算状态稳定,再执行下一步。
4. 实操:搭一个能跑通的多模态UI路径规划Agent
4.1 整体架构:主控循环加三个独立组件
整条链路我建议拆成四个模块:主控循环(Orchestrator)、感知引擎(Perception)、决策引擎(Planner)、执行器(Executor)。它们各司其职,之间通过标准接口传递数据。
主控循环是大脑,负责调用感知引擎拉取当前状态,把状态交给决策引擎生成下一步动作,再把动作交给执行器去做,最后接收回报,判断是否完成或者需要回溯。就是这样一个"看-想-动-验"的循环。
感知引擎屏蔽了不同平台截图接口和布局提取的差异。无论是桌面端、移动端还是Web端,它输出的数据结构统一为"截图路径 + 布局文本 + 可交互元素列表"。决策引擎是纯模型推理模块,负责把统一状态输入转化为动作。执行器会把模型给的语义动作转换成真正操作系统的鼠标键盘事件,并返回执行结果。
这样一个完整链路大约千行代码量,核心实现思路见下面的伪代码:
class Orchestrator: def __init__(self, perception, planner, executor): self.perception = perception self.planner = planner self.executor = executor self.visited = set() def run(self, task): state = self.perception.observe() task_plan = self.planner.plan(task, state) for step in task_plan: result = self.execute_step(step, task) if result.reached_goal: return self.export_path() return None def execute_step(self, step, task): state = self.perception.observe() if self.detect_loop(state): self.executor.back() return action = self.planner.decide(step, state) self.executor.perform(action) new_state = self.perception.observe() self.remember(state, new_state, action) return Evaluation(state, new_state, task)实际项目中模块化很重要。路径探索过程中,感知引擎可能被调用成百上千次,如果感知和决策耦合在一起,排查问题时完全无从下手。
4.2 提示词模板:双阶段设计让Agent不乱来
我试过单条提示词一把梭给模型一个完整任务然后让它直接开玩,效果并不理想——模型往往会一通乱点,或者过度自信地跳到不存在的操作上。后来我改成"系统提示词 + 用户提示词"双阶段结构。
系统提示词里放的是规则层,包括:
你是UI测试路径探索Agent。你只能基于感知引擎提供的截图和布局文本做决策。 每次你只能输出一个动作,格式必须是JSON。 坐标必须基于截图上的坐标空间,并在动作字段里说明该元素的语义依据。 如果当前状态与任务无关,优先点击"取消"/"关闭"回到上一个有意义的页面。 每5次动作后,回顾一次已探索的状态清单,如果连续多步没有状态变化,主动回退。用户提示词放的是当前这一轮的决策输入,包括任务描述、当前截图、布局文本,以及历史动作摘要。这里有一个我反复调整出来的经验:不要在提示词里塞太多的历史对话,只需让模型看到最近三次动作和对应的状态摘要,信息更多反而会干扰决策。
4.3 动作执行与校验:每一个动作都要闭环
执行器不是简单把动作发出去就完事。每个动作在执行前后都要做快照校验。执行前,感知引擎记录当前界面摘要,确认这个元素确实存在且可交互;执行后,再次观察界面状态,对比前后状态变化是否与预期一致。
举个例子,模型规划点击"确定"按钮后应该弹出二次确认弹窗。执行器点击后立刻重新取图,如果发现弹窗没有出现,就要把这次执行标记为"动作无效果",并反馈给决策引擎调整策略。这条反馈回路非常重要,否则Agent会在错误的路径上越走越远而不自知。
我在实现里还为每个动作加了一个"效果分":界面发生了预期变化记正分,没有变化或出现意外页面记负分。当连续出现负分动作时,主控循环会自动触发回退,而不是让Agent继续在当前分支上消耗轮次。
4.4 路径生成与输出:探索结果沉淀成可回归的用例
路径探索完成后,Agent产出的是一个带语义标注的动作序列。这段序列不能直接当脚本用,要再经过一轮"路径固化"处理。
路径固化要做三件事。第一是去冗余,把探索过程中产生的试错步骤删掉,比如点错后的回退操作、多余的等待节奏,只保留直接完成任务的最小动作序列。第二是加校验断言,在每个关键状态节点补充界面上应当出现什么元素的描述,让回归测试具备自我验证能力。第三是参数化,把输入的测试数据抽成占位符,这样同一路径可以配不同的输入数据跑多轮。
我最终的导出格式倾向于结构化的数据文件,而不是传统测试框架的函数代码。好处是路径数据与执行引擎解耦,UI变化导致路径失效时,可以直接用Agent重新探索生成新路径,而不需要手写大量代码。
5. 常见问题与排查技巧实录
5.1 元素识别不准?先看截图质量,再谈模型能力
我遇到过大量元素识别错乱的情况,排查下来很多都不是模型的问题,而是截图质量不过关。界面存在模糊效果、毛玻璃元素时,静态截图会让模型产生理解偏差;深色模式下部分控件对比度太低,也会导致识别遗漏。
排查建议:先把截图用图像处理库做一次简单的清晰度统计,如果区域模糊严重,考虑调整系统显示缩放级别,或者切换成更标准的渲染模式执行测试。除此之外,界面文字偏小时要注意检查缩放后的截图是否仍能保持文字可读,必要时为模型提供局部放大图。
5.2 坐标偏移:模型给的点击位置总是点歪
这是路径规划里最让人抓狂的坑。我总结下来主要有三个原因:截图缩放后坐标换算没做对;模型对截图坐标系的理解偏差;热区坐标与视觉中心坐标不一致。
前两个好解决,在坐标换算工具里统一处理就行。第三个比较隐蔽,有些按钮的可点击区域并不是它的可见范围,视觉模型按照可见文字中心输出坐标,实际点击区却在别处。我的对策是在执行点击动作前,额外调用一次元素命中检测,判断目标坐标是否真的落在可交互区域,如果不是则修正到有效热区。
5.3 路径规划走进死循环怎么办
死循环的根源在于Agent在一个界面状态里反复尝试相似操作,而主控循环没有识别出来。我有两套应对机制。
第一套是状态签名去重。前面提过,把页面关键特征哈希化,动作执行后重新计算签名,一旦重复就回退。第二套是最大轮次限制。无论探索多么顺利,都要为单个任务设置动作上限,超限强制进入回退或终止流程,避免测试挂起。
实际项目中死循环往往还跟弹窗、遮罩有关。Agent以为自己在操作主界面,实际上被某个透明遮罩拦住了,每次点击都落在遮罩上,界面状态不变。建议在状态签名里显式包含"当前是否存在遮罩或弹窗"的特征字段。
5.4 Agent"幻觉"点了不该点的地方
多模态模型偶尔会推断出界面上并不存在的元素,比如提示词过度引导时,模型脑补了一个"确定"按钮的位置。这类幻觉动作比定位不准更危险,因为它会带着路径探索往错误的方向走。
我的经验是给每个动作加"元素依据"字段,模型必须输出它判定这个按钮存在的依据文本。执行前我对依据文本做一致性校验,如果模型中提到的元素特征与布局文本明显不一致,这条动作就会被拦截,不让执行器执行。这个防线挽救了很多次测试运行。
5.5 探索效率低:单轮决策太慢,跑一条路径要半小时
效率问题是这类方案规模化应用最大的阻力。每步都要经过"截图、上传、模型推理、动作执行、状态校验"五个环节,单轮耗时主要卡在模型推理上。
优化手段可以优先考虑四个方向:缩小截图分辨率到刚好能决策的程度;用轻量模型做常规状态识别,重模型只处理决策分支点;把感知引擎抽出的布局文本精简到只保留可交互元素和关键文本;同时在本地缓存重复出现的状态签名,如果签名完全相同可以直接复用历史动作。
我实测下来,这四个方向叠加使用,单轮决策时间最多可以压缩到原来的三分之一。对于日均回归上百条路径的团队来说,这个优化幅度有实际意义。
5.6 常见问题速查表
| 现象 | 直接原因 | 排查/修复思路 |
|---|---|---|
| 元素识别频繁出错 | 截图质量低、界面模糊 | 检查渲染清晰度、提高对比度、局部放大 |
| 模型点击坐标偏右上 | 坐标换算未考虑缩放比例 | 统一缩放前后的坐标系,使用中心点换算 |
| 永远点不到确认框 | 弹窗遮罩未识别 | 状态签名中加入遮挡层特征,识别后先关闭 |
| 路径探索原地打转 | 状态签名去重失效 | 检查签名字段是否包含弹窗与加载状态 |
| 动作依据与界面不符 | 模型幻觉,生成了不存在的元素 | 强制输出依据文本,执行前一致性校验 |
| 单轮耗时过长 | 模型推理+大图传输 | 降低分辨率、轻/重模型分层、本地缓存状态 |
写在最后的一个经验
这套多模态路径规划方案我做了快一年,中间踩过无数坑,最大的收获是意识到了一个问题:多模态Agent不是传统自动化脚本的替代品,它是一种"路径产生器"。它的价值是快速、大规模地探索UI空间,把人类容易遗漏的路径找出来,但真正进入稳定回归阶段,还是需要把Agent产出的路径经过人工确认和固化,再纳入常规测试体系。
我现在的习惯是:每周让Agent对核心业务模块做一次无约束自由探索,把新发现的路径与既有用例对比,有差异就人工判断是否需要补充进自动化回归集。这样既保留了Agent探索的广度,又守住了回归测试的确定性。对这种方案感兴趣的读者,我建议先从单个核心流程入手,别急着全量铺开,跑通之后再逐步扩大探索范围。