ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

大模型驱动的UI遍历测试:从理解界面到自动化探索的工程实践

大模型驱动的UI遍历测试:从理解界面到自动化探索的工程实践 一直做UI自动化的朋友应该都有这个体感功能用例、回归用例跑得很顺但一提到“遍历测试”心里多少有点发毛。传统的Monkey测试能点但点得很笨Appium脚本能跑但脚本只能按写好的路径走遇到没见过的新页面就抓瞎。KuiTest这个项目说白了就是冲着这个痛点去的——把大模型当作遍历测试的“大脑”让它在UI上像人一样理解界面、判断动作、执行交互而不是靠预设脚本或纯随机点击去碰运气。这篇文章我会把整个思路、核心设计、工程实现细节和踩过的坑都摊开来讲适合正在做移动端或跨端UI测试、想引入大模型但又不知道怎么落地的同学参考。1. 为什么UI遍历测试绕不开“理解界面”这个坎1.1 传统遍历方案的三个死穴先聊背景。我们团队之前有一套很成熟的自动化体系Appium负责跑业务用例Monkey负责做压力遍历。但这个组合撑了两年后问题越来越明显尤其是当产品从单页工具型App变成以内容、交易、社交为主的重业务App之后。第一个死穴是重复遍历和无效点击。Monkey生成的是纯随机的坐标流它不关心屏幕上是“确认支付”还是“关闭页面”只要坐标落在可点击区域就会触发。结果就是一个弹窗出现后Monkey能连续点它几十次但产品的深层页面永远进不去。你说可以加黑白名单每次版本迭代UI一变黑白名单就要重新维护维护成本高到直接劝退。第二个死穴是无法完成上下文关联的多步操作。很多核心业务路径是“填表单→选条件→提交→校验结果”中间每一步都依赖上一步的数据状态。传统脚本能完成但脚本是写死的一旦页面结构变化脚本就要跟着改。更要命的是遍历测试的目的本来就是发现“没写脚本的那部分页面”的问题脚本覆盖不到的地方恰恰是最需要遍历的地方。第三个死穴是状态污染和恢复成本。遍历过程中很容易进入登录态、半提交态、断网态这些中间状态。跑完一轮App环境已经被改得乱七八糟后续手工测试或者回归脚本都没法继续。如果要恢复就得重新安装、重置数据CI耗时直接翻倍。这三个死穴背后其实是同一个问题方案里没有人没有对界面的语义理解只有规则和坐标。而真正的测试人员去手工探索一个App时凭的是什么是常识——知道“登录”要输入账号密码、知道“更多”下面还有二级菜单、知道“关闭”按钮在右上角。这个常识就是大模型最擅长补上的那块拼图。1.2 大模型给UI测试带来的新变量大模型进入UI测试领域最直接的变化是它带来了一种“通识能力”。所谓通识不是针对某个App训练出来的专有知识而是它对“UI界面长什么样、人是怎么操作界面的”这件事本身有大量推理基础。我总结下来大模型在遍历测试里能干的活主要有四类界面意图判断看到“登录”按钮知道它下一步大概率要输入账号密码而不是直接点击“登录”去触发校验失败。动作选择屏幕上有列表、有Tab、有弹窗、有关闭按钮时能根据当前遍历目标选出“最有信息增益”的动作比如先去遍历内容列表弹窗优先关闭。异常弹窗处理系统权限弹窗、版本更新弹窗、广告弹窗模型可以靠文本和布局特征判断“这是临时阻断先关掉不能让它打断主线”。状态摘要遍历过程中能自然总结当前页面是什么、已经访问过哪些区域、还差什么没看。这些能力不一定百分之百准确但已经足够把遍历测试从“随机乱撞”提升到“有意图地探索”。KuiTest项目的核心命题就是怎么把这种通识能力工程化变成一条能稳定跑在CI里、可度量覆盖率、可复现缺陷的测试链路。1.3 KuiTest的定位先摆清楚KuiTest不是什么。它不是要替代Appium、Maestro这些既有工具也不打算自研一套设备管理平台。KuiTest的业务边界非常窄它就是遍历测试这一步的“决策引擎”。你可以把整个遍历过程拆成三层来理解设备连接和指令执行是底层页面解析和动作执行结果汇聚是中间层决定“下一步该做什么”的是决策层。Appium、Maestro、XCUITest这些工具已经把底层和中间层做得很成熟了KuiTest只负责决策层——它拿到当前页面的截图和UI结构输出一条“执行什么动作、作用于哪个元素”的指令然后由底层框架去执行。这样设计的好处是解耦。设备侧可以用Appium也可以用Maestro甚至可以接自己公司内部的云真机平台决策侧可以接云端大模型API也可以在保密要求高的时候换成私有部署的开源模型。我见过有些团队想把“理解界面”和“执行动作”塞进同一个框架结果两头都做不深这是KuiTest从一开始就尽量避免的。2. KuiTest核心思路把“通识”变成可执行的遍历策略2.1 一次遍历的完整决策链路KuiTest跑一次遍历测试的闭环大概是这样的采集快照拿当前页面的截图、UI层级树Android的XML或者iOS的AX元素、屏幕尺寸、当前Activity/路由信息。汇总状态从UI树里提取可交互元素的文本、类型、位置、可见性过滤掉不可见的、被遮挡的元素生成一个精简后的“界面摘要”。模型决策把界面摘要、遍历历史、本轮目标拼成提示词交给大模型让模型返回一条结构化指令。动作执行把模型返回的指令翻译成设备操作比如tap({elementId})、input({elementId},测试数据)、swipe(up)、back()。结果校验执行后重新采集快照对比执行前后的页面状态是否发生变化、有没有出现崩溃闪退、有没有新增的异常日志。循环或退出如果页面状态没变化判定这个动作无效换别的动作如果达到轮次上限或遍历覆盖目标就终止。这六步里最容易出问题的其实是第3步和第6步。模型返回的指令格式不稳定会直接导致执行层解析失败而“什么时候算遍历完了”如果定义不清楚要么跑个没完要么提前退出漏掉深层页面。这两块后面我会专门讲踩坑。2.2 提示词模板的设计要点KuiTest早期原型跑通靠的是一个大而全的提示词但很快就发现大而全的提示词有两个问题一是模型容易“自由发挥”输出一些格式不规范的指令二是页面描述太长超出上下文窗口后模型会丢掉前面的关键信息。后来我们把提示词拆成了固定系统提示词 动态页面摘要 动态历史摘要三段。系统提示词里我用了这样一段结构你可以直接参考你是一个移动端UI遍历测试的决策代理。 我会给你当前页面的结构化摘要和历史操作记录。 你必须基于页面中的可交互元素选择下一步最有探索价值的动作。 可选动作包括 - tap(elementId): 点击指定元素 - input(elementId, text): 在输入框中输入文本 - swipe(direction): 向上/向下/向左/向右滑动 - back(): 返回上一页 - wait(): 等待页面变化 - done(): 结束遍历 要求 1. 优先探索未访问过的功能和内容区域避免无意义重复点击。 2. 遇到弹窗、权限请求等阻断类元素优先选择关闭或允许再继续主线遍历。 3. 输入框填写有意义的测试数据如姓名、邮箱、手机号、地址等。 4. 每次只输出一个动作不要解释严格输出JSON格式。 5. 如果当前页面已有连续3个动作均未引起状态变化返回done()。这个系统提示词看起来简单但几个关键点都是调试出来的只输出一个动作不输出动作序列。一开始我让模型输出“下一步三步计划”结果执行中第一步就改变了页面结构后面两步全作废还会造成幻觉。严格JSON格式不加Markdown、不加解释。解析层用递归下降也扛不住模型乱输出格式所以宁可多次失败重试也不能放开格式限制。明确输入框策略。不写这条模型对输入框的典型反应是跳过或者瞎点写了之后才会生成“有效”的测试数据。把“连续3个动作无状态变化”作为终止信号之一写进系统提示词能显著减少死循环。动态页面摘要的格式也是固定的避免模型在理解不同格式上浪费推理能力。比如一个页面的摘要长这样当前页面: 商品列表页 可交互元素: - idbtn_search, 文本搜索, 类型Button - idinput_keyword, 文本搜索商品, 类型EditText - idtab_category, 文本分类, 类型Tab - iditem_64, 文本蓝牙耳机, 类型RecyclerViewItem - idbtn_cart, 文本购物车, 类型ImageView(icon) 历史动作: 最近3个动作为: tap(item_15), swipe(up), tap(btn_cart) 本轮目标: 覆盖分类页和搜索结果页把“当前页面标题、可交互元素、历史动作、本轮目标”四段消息拼好模型返回靠谱动作的概率会高很多。尤其是历史动作这段一定要放“最近3个动作”不要放整个会话历史上百条记录否则又臭又长还没用。2.3 从界面语义线索到遍历命令的映射系统提示词给了模型自由度但落到执行层仍然需要一套从语义到具体命令的映射规则。KuiTest的做法是让模型只输出动作名和元素ID具体坐标、滑动距离由执行层根据屏幕尺寸和控件bounds计算。我整理了实际跑项目时最常出现的几类语义线索和对应策略做个表格方便你对照界面线索典型特征KuiTest动作备注主按钮/CTA文本含“下一步”“确认”“提交”tap优先触发主线流程弹窗阻断文本含“以后再说”“暂不”“关闭”tap先撤销阻断再继续权限请求文本含“允许”“使用App时允许”tap允许权限不处理会卡死后续输入框EditText/TextFieldinput填“姓名、13800138000”类数据列表/瀑布流RecyclerView/UICollectionViewswipe(up)滑动几次后再回头已读卡片/过期状态无变化或置灰跳过避免无意义重试二级入口文本含“更多”“全部”“查看全部”tap深层页面常藏在里面无可交互元素页面空白或加载失败back()回退恢复有效状态这些映射不一定每次都对但方向是对的模型负责判断“界面在表达什么”执行层负责“怎么把判断变成安全的操作”。如果模型判断错了状态校验环节会兜底——执行完没变化就记录一条无效动作下次不再选它。3. 工程实现中的关键细节3.1 截图和UI树的时间对齐刚开始做的时候我犯过一个低级错误截图和UI树是分两次异步获取的中间隔了300毫秒左右。看起来没多长但恰好有一次截图的瞬间页面在加载动画中UI树里却是加载完成的状态模型根据树里的“确认”按钮做了决策执行时却点在了还在转圈的位置上动作就丢了。后来我们加了一个快照对齐校验拿到截图后把UI树里的可点击元素坐标映射回截图像素用边缘检测判断“元素当前是否存在”。如果映射出的元素区域和截图实际内容差异太大就丢弃这个快照重新采集。这个校验逻辑不复杂但直接把决策准确率提了十几个百分点。另一个细节是UI树裁剪。原生框架返回的UI树经常包含大量不可见节点、占位节点、0×0大小的控件如果全量塞给模型提示词上下文会很快就满。我们处理的方式是遍历树节点只保留clickabletrue、visibletrue、宽高大于0、且不在屏幕边缘遮挡区域的节点然后再加一层按文本和类型去重。3.2 状态签名与死循环检测遍历测试最怕的是在原地打转。KuiTest用了一个轻量级的状态签名机制来识别死循环。每次执行动作后我们会从新快照里提取一组“页面特征”然后做哈希形成当前页面的签名页面路径 可见文本的排序集合 主要控件类型数量 输入框内容摘要哈希冲突的时候再叠加截图感知哈希做二次确认。如果发现当前签名在最近20步内出现过两次以上就判定进入了循环分支立刻用全局动作池里的“备用动作”比如back、切换Tab来打破循环。这里有个实际问题正常业务里也有合理重复比如多次“下一页”就是类似的页面状态。所以KuiTest的循环判定不能只看签名是否相同还要看“两个相同签名之间发生了多少动作”。如果中间只有1个动作说明卡在原地如果中间有8个动作且页面系统良性推进说明只是路径相似可以继续。3.3 动作执行层与设备框架的对接KuiTest没有从零实现设备操作而是做了一层适配抽象。接入Appium时模型输出的tap(elementId)会被翻译成WebElement.click()接入Maestro时则翻译成tapOn:指令。这个适配层在最开始觉得是多余设计后来发现特别值得——团队里同时维护Android和iOS两套设备池一旦底层框架升级只需要改适配层的一小段代码决策引擎一行不用动。执行层还要管一个很容易被忽略的事动作执行后的等待策略。点击之后马上等页面稳定不能靠sleep(2)硬等要轮询“UI树是否出现明显变化”如果3秒内没变化就认为该动作无效。这个等待参数也可以做成配置项网络差的时候调大本地应用可以调小。4. 踩坑记录与优化4.1 模型幻觉和无效点击KuiTest在实际跑项目的时候有一类问题非常扎眼模型会“虚构”页面上不存在的元素。明明UI树里只有3个按钮模型却输出一个tap(idmanage_subscription)元素ID完全不存在。查了多轮日志后发现两个来源一是模型对“常见界面”的过度泛化它觉得“设置页应该有个订阅管理入口”就自己脑补出来二是历史动作里出现过类似的元素ID模型沿用了。解决方法是给决策层加了一道动作合法性校验模型输出指令后先和执行层里的元素索引对比如果元素ID不在当前UI树中直接丢弃并要求模型重新决策一次。这样虽然会多一次模型调用但有效避免了一条非法指令污染后续状态。反过来模型也会遇到“能选的动作都在历史里验证过无效”的情况。这时候策略是给模型额外一个信息字段history_invalid_actions把最近10步内被判定为“无效动作”的元素ID列出来系统提示词里明确禁止再选它们。这个字段改动让无效动作比例下降了很多也减少了无效点击对设备状态的破坏。4.2 上下文丢失和多步关联早期版本里我们把整个遍历会话的所有历史动作都拼进提示词。结果是上下文超过一定长度后模型开始“忘记”前面的操作反复进入同一个Tab。后来改成了“分段记忆”策略短期记忆保留最近3步的完整页面摘要和动作用于判断当前状态。中期记忆保存最近 20 步的元素访问情况用于避免重复点击。长期记忆保存本轮已经过的页面路径Activity/路由名集合用于告诉模型“这些页面不用优先再访”。三档记忆分别用不同的字段拼进动态摘要并限制长度。这样一个上下文窗口内模型既不会丢失关键状态也不会被冗长的历史日志搅乱判断。4.3 API延迟和成本控制一开始我们默认用长上下文的大模型API并发一开费用涨得飞快而且长上下文接口的响应延迟高遍历一个App动辄几百步跑一趟下来比手工还慢。后来做了两个优化决策用轻量模型复杂校验才用重量级模型。大部分页面判断轻量模型足够遇到页面状态异常、决策置信度低的情况再把完整快照发给大模型做二次校验。同时做结果缓存同一个页面签名 同样的遍历目标如果之前已经决策过直接复用历史决策结果不再调用模型。缓存命中率在成熟产品上能到30%左右还是很可观。成本这块没有统一答案但如果你的团队对数据保密有要求可以考虑本地部署一个中等规模的模型。决策任务对模型“理解力”的要求没有写代码那么高反而对输出格式稳定性的要求更高。量级合适的前提下本地部署的私有化调用成本是可以压到很低的。4.4 权限弹窗和无文本图标权限弹窗是每轮遍历几乎都会遇到的坎。iOS的系统弹窗和Android的运行时权限弹窗UI树里能拿到的文本不太一样有的只有“Allow”或者中文“允许”。KuiTest的处理是把它单独抽成了一个预处理器每次采集快照后先用正则匹配文本“允许”“Allow”“使用App时允许”“OK”如果在界面上检测到直接执行点击再重新采集不进决策模型。这样既省了一次模型调用又避免模型对系统弹窗产生困惑。无文本图标是另一个坑。像底部Tab栏的“我的”“首页”如果只用纯文本图标都是ImageButtonUI树里广泛缺少语义。我们的方案是把截图里该控件周围的小图裁出来送模型做一次图片标注配一句“这个图标通常是什么功能”。这个代价比每次都传整图低很多但能显著改善对Tab、工具栏这类无文本元素的决策。5. 实际运行效果与落地建议5.1 覆盖率和缺陷发现效率的对比分享一组我们在一个中型电商App上跑出来的数据这个App大概有40个一级页面、200多个二级页面。对比对象是Monkey随机遍历和人工编写的冒烟用例。指标Monkey人工冒烟用例KuiTest页面覆盖率26%大量重复弹窗38%61%触发崩溃/ANR数2重复同1个问题14平均遍历时长45分钟2小时38分钟有效操作占比11%100%但覆盖少47%最核心的差异在有效操作占比和覆盖率上。KuiTest的动作不是每次都正确但每一步基本都是“有目的”的所以同等的步数内能探索到更多页面。而且因为会自动处理弹窗、权限这类干扰不至于把一个崩溃刷出4条重复记录。缺陷发现这块也很有意思。KuiTest跳出来的4个崩溃里只有一个在冒烟用例范围内另外三个都是深层链路的问题——一个是搜索页连续切换筛选条件时的内存抖动一个是购物车在弱网下重复点击结算按钮导致的空指针还有一个是WebView混合页面在返回手势和页面刷新同时发生时的小概率崩溃。这三个问题靠人肉探索要碰运气Monkey则大概率会卡在弹窗上根本摸不到。5.2 接入CI的完整过程KuiTest接入CI的时候并不是简单挂一个任务就行。我建议按下面的步骤来做准备一台独立的测试机或者沙箱环境不要让遍历任务和回功能回归共抢一台真机。把遍历轮数、超时时间、失败重试次数做成脚本参数。推荐每轮200步、单步超时10秒、整轮超时30分钟起步。日志和截图全部落盘并且按“场景名时间戳”归档方便事后定位问题。崩溃捕获和遍历解耦。崩溃日志通过崩溃SDK或设备日志返回遍历引擎只负责标记“这一步之后触发了崩溃”两边的数据在报告层合并。任务跑完生成结构化报告覆盖页面列表、访问过的主要控件、崩溃堆栈、无效动作列表直接推送给IM群。这里有一个取舍要提前想清楚CI跑KuiTest肯定比跑普通用例慢而且模型调用的费用是持续性的。我个人的建议是让KuiTest跑daily build而不是每个commit都跑低频高频结合每个commit跑一个轻量的冒烟遍历比如50步每天凌晨跑一个完整深遍历比如300步这样成本和效率相对平衡。5.3 后续可以扩展的方向KuiTest目前的版本解决的是“遍历决策”问题但大模型在这个链路里能做的事情远不止于此。基于已经跑通的基础设施我盘了三个可以继续深挖的方向。第一个方向是和视觉回归结合。遍历过程中已经天然按状态签名保存了大量截图如果能给每个状态签名配一张基准图之后的遍历自动做了快照对比视觉回归就不再需要单独维护一套截图用例。相当于一次遍历同时拿下了功能探索和视觉监控。第二个方向是多语言产品遍历。大模型懂多语言界面字号变化并不会破坏UI树里的元素ID所以只要把“界面文案理解”切换到对应语言体系同一个框架可以一套代码跑中文版、英文版、日文版App。这个对出海产品团队非常有吸引力比单独雇人在每个语言包下手工点页面节省大量时间。第三个方向是试错修复闭环。遍历过程中如果发现崩溃目前还是记录堆栈交给开发处理。但模型既然已经理解了这个页面完全可以再进一步让模型读取崩溃堆栈、对比页面截图给开发生成一份“复现路径操作步骤初步问题定位”的现场分析报告。这个还处在探索阶段但方向上很值得投入毕竟能把“发现问题”变成“辅助修复”对大模型应用来说是质变。6. 写给小团队的一些体感建议最后聊点经验以外的东西。KuiTest这个项目我做下来有个很明显的感觉跟大模型结合的工具最难的地方不是模型选型也不是提示词调优而是怎么把“模型决策”和“工程确定性”平衡好。测试领域的同学习惯了“结果可预期”但大模型天然带概率性今天跑和明天跑决策序列不一定完全一致。面对这种不确定性我总结的法则是模型只做需要语义理解的那一小步其他全用确定性逻辑兜底。动作合法性校验要做状态签名死循环检测要做页面变化无效动作判定要做这些全部是确定性代码不依赖模型。模型只是那个“在岔路口选择方向”的人走到哪、走完没有、摔倒没有都得靠工程护栏来保证。如果你的团队也想做类似的事我建议先不要一上来就想着做一整套平台。拿一个不算太复杂的App接好一个能稳定输出结构化指令的模型API跑通一次完整的遍历闭环把日志、报告、成功率看明白再考虑横向扩展和优化成本。先跑起来再迭代比憋大招靠谱得多。我自己接下来会继续把KuiTest往“遍历后自动生成产品缺陷报告”这个方向推一推也希望更多同行在这个领域一起折腾。UI遍历测试这个被Monkey统治了这么久的角落确实也该换换引擎了。
返回列表