多模态大模型驱动的UI自动化测试:从截图到可执行用例
做测试开发这行这几年最绕不开的话题就是AI怎么落地。我自己的体会是与其争论“AI会不会取代测试”不如先把一个具体问题想明白UI自动化最贵的那部分——写脚本、维护脚本——能不能让大模型来干多模态大模型出现之后这件事第一次有了真正可行的技术路径。它可以直接“看”页面截图理解布局、识别控件、推断交互流程然后输出结构化信息再和现有的自动化框架Selenium、Appium、Playwright、Pytest对接起来形成一条从截图到用例再到执行的完整链路。这篇文章就把我在这套系统设计上的完整思路拿出来聊一聊包括方案选型、数据结构设计、Prompt工程、执行链路搭建以及落地时踩过的坑和对应的排查办法。1. 为什么是“多模态大模型自动化测试”这个组合1.1 传统UI自动化的三个老大难问题先说实话。做了这么多年UI自动化我最怕的不是写脚本本身而是脚本上线之后迎来的漫长维护期。页面改个按钮文案用例挂了前端框架从Vue换到React选择器全部失效测试环境的数据稍微变一下断言全部失败。这背后其实是三个长期无解的问题第一脚本编写成本高。一条完整的UI用例需要测试开发工程师手工从页面结构里提取元素定位信息然后逐行写代码一个中等复杂度的模块写几十条用例就是好几天的工作量。而且这个工作极度依赖人的眼睛和大脑——你得先看明白页面才能告诉代码该干什么。第二页面变更导致脚本失效维护成本远高于初始开发成本。改一个class名、调一下DOM层级旧脚本就不能用了必须人工排查、重新定位。第三工具链割裂。元素定位信息、操作步骤、断言逻辑分散在代码、配置文件、测试数据里一条用例的完整语义很难被结构化表达也因此很难被自动化地分析、检索和复用。1.2 多模态大模型到底改变了什么多模态大模型带来的变化本质上是把“人眼看页面、人脑理解交互、人手写代码”这个过程压缩成了一步模型直接读截图理解页面结构和交互语义输出结构化的中间结果。这里说的“理解”不是简单的OCR识别文本。一个成熟的多模态大模型在测试场景下能做到的事包括识别页面中的输入框、按钮、下拉框、复选框等UI元素及其坐标理解元素之间的层级和布局关系根据用户描述或页面语义推断候选操作序列比如“先输入用户名再输入密码点击登录”判断一张截图里是否存在异常状态比如弹窗、报错信息、加载超时提示甚至能对比两张截图输出UI层面的差异点。打个不恰当的比方这就像是给自动化测试配了一个“会看图的实习生”——你给它一张页面截图它能告诉你页面上有什么、能做什么、大概应该怎么操作。而我们的系统要做的事情就是让这个“实习生”的输出变成一条真正能跑的、格式规范的自动化用例。这就是从截图到结构化用例这个系统设计的核心逻辑。2. 系统整体设计思路2.1 端到端链路从截图到用例的四个阶段我把整套系统拆成了四个阶段分别处理“图像输入”“语义理解”“结构生成”“执行反馈”四个问题。第一阶段是截图采集与预处理。截图来源可以是手动上传的图片文件也可以由测试脚本在页面渲染完成后自动截取还可以从录屏视频中按帧抽取关键画面。这一步要做的基础工作包括图像格式统一、尺寸压缩、必要时做超分辨率或降噪处理以及对敏感信息做脱敏——这个后面会细说。第二阶段是多模态大模型解析。模型接收截图和文字指令输出一个中间表示我把它定义为“页面结构化描述”。这一步是整个系统的核心模型输出的质量直接决定后续用例能否生成。我会在Prompt里约束模型只输出JSON并且严格规定JSON的字段结构。第三阶段是测试用例生成。把“页面结构化描述”映射到具体的测试框架上生成符合Pytest/unittest规范的用例脚本、定位器表达式、操作步骤和断言逻辑。这一步可以做得很重比如引入规则引擎来补充业务约束也可以做得很轻比如直接做模板填充。我的建议是先从轻量级模板开始跑通之后再逐步增加复杂度。第四阶段是执行与结果回传。生成好的用例交给已有的自动化执行框架去跑执行结果通过、失败、异常截图、日志再回流到系统里。失败率高的用例会被标记用于后续的模型调优或Prompt优化。这样就形成了一个闭环不是一次性生成完就结束。2.2 核心数据结构让模型输出“可执行”而不是“可读”最开始做这个系统的时候我先试过直接让模型输出自然语言步骤比如“点击登录按钮输入用户名admin”。看起来没问题但要对接执行引擎就很麻烦每一步都要做一层语义解析不够稳定。后来我换了个思路把模型输出定义成严格的JSON Schema让模型按Schema输出结构化数据。数据结构分成三层第一层是页面对象层描述当前页面的基本信息包括页面标题、URL、屏幕分辨率以及页面上所有关键元素的列表。每个元素要包含字段element_id页面内唯一标识、element_typebutton、input、link、checkbox、dropdown等、text_content可见文本、attribute_hint辅助属性比如placeholder、aria-label、location归一化坐标用屏幕宽高的百分比表示、possible_locators候选定位策略比如xpath、css_selector、id等。第二层是操作序列层描述一条用例要执行的操作步骤。每个步骤包含step_id、action_typeclick、input、select、hover、scroll、assert_text、assert_visibility等、target_element_id指向第一层的元素、input_value输入类的操作需要给定值、wait_condition操作前需要等待的条件比如等待元素可见、description给人看的一句话步骤说明。第三层是断言层描述操作执行完之后要验证的内容包括断言类型text_equal、element_visible、element_exists、url_contains等、目标元素、期望值。这里我特别加了一个字段叫priority用来标记这条断言是强校验还是弱校验强校验失败就终止用例弱校验失败只打Warning。这样一个三层结构既保留了多模态模型擅长理解和表达的语义信息又给下游执行引擎提供了清晰、规范的接口契约。模型输出一份JSON引擎解析JSON后就能生成代码不用再做额外的语义转译。3. 关键技术选型与Prompt工程设计3.1 模型选型闭源、开源还是本地化部署模型选型是整个系统最敏感的决策点。我的判断标准有三个视觉理解能力、输出稳定性、成本和合规要求。从能力上讲GPT-4o、Gemini 1.5 Pro、Claude 3.5 Sonnet 这一档的闭源模型在复杂UI截图理解上表现确实领先尤其对页面中元素重叠、弹窗遮挡、动态内容的处理比较稳。目前很多做AI测试助手的创业公司实际用的也是这一档模型。如果你有数据合规要求不允许把截图发到外部API那就要选开源多模态模型搞本地化部署。国内能打的有Qwen-VL系列、InternVL系列、GLM-4V国际上可选LLaVA和CogVLM。我的经验是Qwen-VL-Max在线版本的能力接近闭源第一梯队在中文UI截图上有优势本地部署的话InternVL2-26B 或 Qwen2.5-VL-32B 在单张4090上部署已经可用但如果你是CPU推理速度会慢到怀疑人生至少要有GPU。还有一个细节值得注意给模型发送图片之前要压缩分辨率。很多模型对长边有上限超过会失败或降质。我一般把截图等比压缩到长边不超过1568像素质量损失不大但能避免不少边界问题。3.2 Prompt设计把“看图说话”变成“结构化输出”模型能力再强Prompt写得不行输出也是一坨。我的Prompt模板经过多轮迭代核心原则有四个先说任务再给约束再给样例最后强调输出格式。任务描述要具体不要只说“帮我分析这个页面”。我会写“你是一名资深UI测试工程师请分析这张网页截图识别页面上的所有关键UI元素并推断出一组覆盖主要用户路径的测试操作序列。只输出JSON不要输出任何解释性文字。”约束条件要写清楚比如“元素坐标使用占全屏宽高的百分比表示保留两位小数不确定的元素不要编造宁可不输出被遮挡、不可交互的元素请标记enabledfalse并把reason字段写清楚。”Few-shot样例非常重要而且要比读者想象的更重要。我会在Prompt里贴一个输入截图加一个对应JSON输出让模型照着这个格式来。经验数据是加了样例之后输出格式合规率能从70%左右直接拉到95%以上。最后是格式约束用“只输出一个JSON对象不要包含Markdown代码块标记不要包含注释”这样的句子做硬性兜底。这一步看似简单但如果没有它模型偶尔会在JSON外面套一层json下游解析就会报错处理起来很烦躁。4. 完整实操案例从登录页截图到可执行用例4.1 准备输入材料我用一个登录页面做演示截图内容是一个标准登录页顶部Logo“欢迎登录”标题手机号输入框密码输入框验证码输入框“登录”按钮底部的“忘记密码”和“注册新账号”链接。截图尺寸是1440x900。送进模型之前我先做了两步预处理把图片转成PNG格式等比压缩到长边1440像素然后把图片中可能存在的个人头像、真实手机号做了模糊脱敏。这里多说一句如果是处理线上页面截图脱敏这步必须做测试人员的职业素养体现在这些细节里。4.2 模型输出与用例生成Prompt发送之后模型返回的JSON结构大致是这样做了部分简化{ page_info: { title: 登录页, main_url_pattern: login, screen_size: {width: 1440, height: 900} }, elements: [ { element_id: e1, element_type: input, text_content: , attribute_hint: 手机号, location: {x: 0.5, y: 0.34, width: 0.28, height: 0.05}, possible_locators: [ {method: xpath, value: //input[placeholder请输入手机号], confidence: 0.95}, {method: css, value: input[placeholder*手机号], confidence: 0.92} ], enabled: true }, { element_id: e2, element_type: input, text_content: , attribute_hint: 密码, location: {x: 0.5, y: 0.43, width: 0.28, height: 0.05}, possible_locators: [ {method: xpath, value: //input[placeholder请输入密码], confidence: 0.96} ], enabled: true } ], operations: [ {step_id: 1, action_type: input, target_element_id: e1, input_value: 13800000000, wait_condition: element_visible}, {step_id: 2, action_type: input, target_element_id: e2, input_value: Test1234, wait_condition: element_visible}, {step_id: 3, action_type: click, target_element_id: e3, wait_condition: element_visible} ], assertions: [ {assert_type: element_visible, target_element_id: e4, expected_value: true, priority: high} ] }拿到这份JSON之后我的执行引擎会做两件事先用possible_locators定位元素如果模型给的xpath在当前页面上确实能匹配到直接用如果匹配不到就启动一个回退机制优先按attribute_hint找找不到再按归一化坐标换算成绝对坐标去定位。这个回退逻辑极大提高了用例的稳定性。4.3 执行链路对接生成用例这一步我采用模板加数据分离的方案。以PytestSelenium举例我会让引擎读取JSON然后按模板生成一段测试代码def test_login_by_multimodal_case(driver): # 断言层 page driver.page_source assert 登录 in page or 手机号 in page # 操作层 step1 LocatorResolver.resolve(driver, case.elements[0]) step1.send_keys(13800000000) step2 LocatorResolver.resolve(driver, case.elements[1]) step2.send_keys(Test1234) step3 LocatorResolver.resolve(driver, case.elements[2]) step3.click() # 断言登录成功后能看到用户中心入口 target LocatorResolver.resolve(driver, case.assertions[0].target_element) assert target.is_displayed()这里我要强调一个关键设计LocatorResolver不是一个简单的查找类它内部实现了“多策略定位优先级调度”。比如xpath匹配不到就把attribute_hint和坐标值组合起来生成一个新的xpath如果还是找不到就触发全页面元素快照用Levenshtein距离做文本模糊匹配。这套逻辑写起来麻烦但实战价值非常大它让生成用例的首次通过率从50%以下提升到了80%以上。5. 常见问题与排查技巧实录5.1 元素定位不可靠是头号问题模型给的xpath可能基于截图猜测不一定和真实DOM完全一致。字符差一个引号、属性值多一个空格都会定位失败。我踩过的坑是模型经常把button的文本当成id来用比如给出//button[id登录]显然不对。后来我在Prompt约束里加了“务必优先采用稳定属性id、name其次placeholder、aria-label不要使用纯中文文本构造定位表达式”这样的规则情况才好转。如果定位信息确实不可用至少有两个保底思路一是用坐标辅助定位模型能给出相对坐标换算成像素坐标后配合Selenium的ActionChains移动点击适合canvas绘制的界面二是OCR兜底在截图无法定位时用PaddleOCR或Tesseract提取文本块坐标再映射到操作点。OCR兜底虽然笨但它是最后一层安全网。5.2 动态内容和截图状态不一致另一个典型问题模型分析的是截图A执行的时候页面已经变成了状态B。比如弹窗突然出现把按钮挡住了或者某个元素是异步加载截图时还没渲染出来。针对这个我在每步操作之前都加了一个等待策略优先等待目标元素可见等待超时后自动截取当前页面状态发回模型做“动态差异判断”判断当前页面是否处于阻塞状态弹窗、加载中、空白页。这一步能让系统具备一定的自愈能力。5.3 模型幻觉与误识别多模态模型的幻觉问题在UI描述上同样存在。模型可能“看到”一个并不存在的按钮或者把一个文本框误判成按钮。我的处理方案是“多模型投票”对同一张截图用两个不同模型或两次不同采样进行独立解析然后对元素描述做一致性比对只有两者一致或高度相似的才采信。代价是成本翻倍所以我只在关键页面登录、支付、注册开启这个机制普通页面单模型就够。还有一个更轻量级的方案如果公司内部有页面DOM快照可以把DOM和截图一起喂给模型做交叉验证。模型看到了DOM结构输出准确率会明显提升。这一点在很多开源项目和公开分享里都被验证过。5.4 模型输出解析错误和处理| 报错信息 | 原因分析 | 定位方法 | 解决方案 | | --- | --- | --- | --- | | JSONDecodeError: Extra data | 模型在JSON后输出了多余文字、Markdown标记或“”代码围栏 | 打印原始响应检查首尾 | 在Prompt最后加一行“只输出JSON禁止包含Markdown代码块标记”解析时用正则提取首尾的大括号再json.loads | | KeyError: possible_locators | 模型未输出候选定位器或字段结构被截断max_tokens不够 | 查看模型原始返回长度和截断标记 | 调大max_tokens至少1024在Prompt中显式列出所有必输出字段名对省略字段做默认值兜底 | | ValueError: 元素坐标为字符串 | 模型把坐标输出成字符串如“13.5%”而非数值 | 查看字段类型检查是否带了百分号 | 在Prompt中声明“坐标仅输出数值不包含百分号”解析层写一个to_float函数自动清洗单位 | | 元素全无法定位 | 模型给出的定位信息与真实DOM不一致或页面已变化 | 保存截图执行定位器探测脚本打印DOM片段 | 开启LocatorResolver多策略回退截图差异对比必要时重跑模型解析 | | 测试反复失败但手工执行通过 | 用例执行时机问题页面未渲染完成就触发操作 | 查看WebDriver日志和截图时间戳 | 全局增加显式等待操作前对目标元素先等待visibility_of_element_located | | 生成的步骤有误比如先点击后输入 | 模型没有严格按照用户路径推断操作顺序 | 回看Prompt中的流程描述和样例 | 在Prompt中给定“登录操作顺序输入手机号-密码-验证码-点击登录”这类强约束样例 | | 多模型结果冲突无法采信 | 两个模型对同一元素的描述不同 | 输出依赖度得分人工审核冲突项 | 定义union合并规则元素类型优先采信confidence更高的结果双方都低置信时标为待人工确认 |排查这类问题最忌讳的就是不看模型原始响应就直接改代码。要先保存原始输出肉眼确认是结构问题还是内容问题。结构问题改Prompt内容问题换模型或加约束这是基本操作。6. 从Demo到工程化的关键一步6.1 人机协同的校验闭环很多人做完Demo之后会卡在这一步模型生成的用例质量不稳定不敢直接接入CI。我的建议是不要试图一步到位实现“全自动”先做一个人机协同的中间态。具体做法是系统生成的用例先进入一个“人工预审队列”测试人员打开一个简单的Web界面左边是截图右边是系统生成的JSON和可执行脚本。人工只需要做三件事确认元素定位对不对确认操作顺序合不合理确认断言条件是否覆盖了核心业务规则。审批通过后再进入正式用例库。这个流程看起来多了人工步骤但实际上把测试人员的精力从“逐行写代码”转移到了“审核代码”效率提升依然非常明显。等系统跑了一段时间积累了足够多的通过案例之后再逐步放宽审核比例新页面用例先自动跑失败率低于阈值就直接进CI否则才转人工。6.2 效果评估指标要提前定好工程化落地必须回答一个问题这个系统好不好用。我建议定义三个核心指标用例生成有效率指生成的用例在目标页面首次执行通过的比例定位器可复用率指模型输出的定位信息可以直接匹配到DOM的比例人工介入修正率指测试人员审核时需要改动步骤的比例。这三个指标可以帮你在系统演进过程中发现瓶颈到底出在哪。比如人工介入修正率很高说明Prompt里的业务约束不够定位器可复用率低说明模型的定位策略需要调整或者要引入DOM快照辅助用例生成有效率低多半是动态内容等待机制不够完善。指标不是用来对外汇报的是用来指导你改代码改Prompt的。6.3 成本控制与资源规划多模态模型调用不是免费的。如果一天跑几千张截图按token计费的开销不小。我的经验做法是截图去重同一页面在短时间内重复生成的用例不重复调用模型先查缓存小图优先一些简单的页面用轻量模型处理遇到复杂的交互页面再升级到能力更强的模型局部重试定位失败时只把当前截图和错误信息发给模型而不是把整个页面重新分析一遍。另外如果预算允许建议把调用层做一层抽象封装一套统一的client接口底层可以随时切换不同模型的API。模型迭代太快了今天的最优选择半年后可能就不是了接口层不留好扩展点后面替换的成本非常高。最后再分享一点个人体会。做这套系统的过程中我最深的感受是多模态大模型不是银弹但它确实把自动化测试的生产力边界往前推了一大截。以前写UI自动化最大的成本是人力现在最大的成本变成了Prompt迭代和数据沉淀。真正让系统好用的反而不是模型本身有多强而是你有没有把输出结构定清楚、把回退策略做扎实、把人机审核闭环跑顺。这套思路不只是适用于Web端移动端Appium场景、桌面端应用、甚至连车机HMI界面的自动化测试逻辑上都是相通的。未来如果再把Agent的能力接入进来让模型不只能生成用例还能根据执行结果自主调整策略、自动探索新功能路径那这套系统的想象空间会更大。当下最务实的做法还是先把从截图到结构化用例这条链路打磨到足够稳定跑出真实业务价值。