通用Agent框架下沉:LLM驱动的移动端自动化测试实践
发布时间:2026/10/19 6:32:25 锦皓数字建站

在移动端自动化测试这个方向上我写过不少东西从基础的控件定位到框架选型都有涉猎。但今天这篇想聊点不一样的——不是某个具体工具怎么用而是“当一个通用 Agent 框架被塞进测试场景里会发生什么”。1. 先聊聊这个题目到底在说什么1.1 “通用 Agent 框架下沉”是什么意思很多人看到“LLM 驱动的自动化测试”第一反应是“用大模型生成测试用例”或者“让 AI 写自动化脚本”。但真正的工程落地远不止这一步。我目前在做的事简单说就是把一个面向通用任务的 Agent 框架它能调用工具、规划步骤、处理多轮对话改造成一个专门跑移动端测试的执行引擎。这中间不是“拿来即用”而是要解决一系列从“能用”到“好用”的适配问题。这里的“下沉”两个字很关键。通用 Agent 在设计时面对的是开放场景——今天帮用户查天气明天订机票后天写周报。但测试场景完全不一样它要的是“稳定”“可预期”“可重复”。一个会在关键时刻自由发挥的 Agent对于测试来说不是助手是负担。这里要区分清楚两组概念第一组是“LLM 生成测试脚本”这属于离线辅助第二组是“Agent 直接操作应用”这是实时执行。我们现在做的是第二组难度高一个量级。因为 Agent 不只是写代码它要自己看屏幕、自己找按钮、自己判断结果对不对等于把一个初级测试工程师塞进了一个只能思考不能动手的躯壳里。这适合哪些人看如果你团队里已经在用 Appium、Airtest 这类传统框架做移动端自动化想探索下 LLM 怎么和现有体系结合或者说你正在评估“是不是该自研一套智能测试平台”这篇文章提到的思路和坑你应该能省不少试错时间。1.2 为什么偏偏是“移动端”选择移动端作为切入点不是因为好做恰恰是因为它难。Web 端做 Agent 自动化现在其实已经比较成熟了因为浏览器的 DOM 结构是一个天然的语义化接口。哪怕页面很复杂至少能找到按钮、输入框、超链接这些明确的元素。移动端的麻烦在于原生应用渲染出来的是一层画布。系统能拿到的是一棵控件树但每个控件往往只有个 resource-id、class name、text 属性没有“导航栏”更重要的是有些游戏或自定义控件场景连树都拿不完整。也就是说Agent 到了移动端多了一个“看”的过程——它得先理解屏幕上有什么然后才能谈操作。再加上移动端还有一些很琐碎但绕不开的事系统弹窗、权限请求、键盘遮挡、网络切换、深色模式切换导致元素颜色变化……这些都是 Web 端不太容易遇到的“环境干扰项”但对 LLM 驱动的 Agent 来说每个干扰项都是一次上下文污染。2. 移动端自动化的特殊性Agent 要先学会操作真实设备2.1 LLM 不具备“空间直觉”先说一个我踩过的最深刻的坑LLM 对坐标和空间关系的敏感度远低于人。拿一张真实应用截图给大模型看它能说出“左上角有一个返回按钮”但它不知道“左上角”在像素坐标系里对应多少。如果直接把坐标丢给 Agent 让它自己算结果通常是第一次靠运气蒙对了第二次就点歪了。所以我在做工具层设计时一个核心原则是不要让模型自己去算坐标把空间理解的问题交给代码去解决。具体做法是在截图标注阶段就对画面做元素级裁剪把每个可交互控件的位置、尺寸、DOM 路径针对 RN/Flutter 等跨端框架一起提供给模型。模型只需要说“点击返回按钮”工具再根据控件名去映射真实坐标。这等于把“空间推理”降维成了“名称选择”。2.2 等待机制是生命线传统自动化里等待机制的核心是“稳定”——显式等待、隐式等待、轮询都是为了等元素出现。但到了 Agent 场景等待的含义变了Agent 要判断“画面变了没”以此决定下一步动作。如果一个页面加载卡了 3 秒但 Agent 已经在这一帧截图上做完了判断、开始点击那它看到的是一个空页面、甚至一个过期页面。由此产生的错误动作会被当成“真实操作过的步骤”反馈给模型整个计划链就错了。可以这样理解人在操作手机时会一直盯着屏幕看到目标图标出现才动手但 Agent 的“视觉”是离散的帧它看不出一段动画中间状态的意义。所以工具层必须在截图前加一个“画面稳定检测”——对比相邻两帧的像素差异差异小于阈值后再截图送往模型。这个机制我用 OpenCV 的感知哈希做成本很低效果很好。如果你现在用的是现成的 Agent 框架很多没有内建这个能力要自己实现一下。2.3 真实设备状态的“脏”管理在模拟器上跑是一回事真机是另一回事。我遇到过不少这类问题某台测试机的屏幕没解锁Agent 以为是应用黑屏系统弹窗把权限请求弹出Agent 以为是应用的引导页点了一个“允许”后就没下文了输入法把页面顶起来Agent 的截图坐标整个偏移有些 App 会在最上层挂一个浮窗按钮恰好落在 Agent 想点的位置。这些问题如果不提前在设备服务里做处理Agent 在后端根本感知不到——它只看到“点了某个按钮没反应”或“点了之后跳到了奇怪的页面”。我的做法是在设备管理服务里内置一个“环境归一化”模块执行 Agent 动作之前先做三件事解锁屏幕并关掉系统通知、关闭当前应用的键盘弹起、把系统弹窗自动点击掉用无障碍服务或系统层广播。这些听起来简单但实现起来非常费力而且每台 Android 设备的厂商定制都有差异。提示如果你想快速验证 Agent 是否会被环境干扰影响可以在同一台设备上开飞行模式跑一遍同样的用例对比下输出结果差异——我测下来大量“疑似模型幻觉”的场景其实是网络延迟叠加系统弹窗造成的。2.4 分层服务Agent 不该直接操控设备很多人在搭这套系统时会走一个捷径让 Agent 直接调用 adb、uiautomator 这些底层工具。刚开始很爽命令直出但往后会越来越被动。第一层问题底层工具的输出格式不稳定。uiautomator dump 出来的 XML 在不同安卓版本上字段名都有差异Appium 的时间成本更高因为要多维护一层 Server 依赖。第二层问题更致命Agent 的输出是自由文本同一个意图有无数种表达方式。“点登录按钮”“点击登录”“按下登录键”“login”都可能是同一个动作。如果让 Agent 直接拼 adb 命令等于逼它对每个工具的命令格式都完全掌握稍微有一点偏差就是空操作。所以我把整个交互设计成了三层LLM 层意图输出语义化的动作描述 - 工具层意图解析映射到具体命令 - 设备层执行与反馈真实设备操作 状态采集LLM 从不直接接触 adb 或 UIAutomator它只管表述“我要做什么”后续执行由工具层负责。这很像“人在回路”的低配版——人用自然语言描述意图系统负责翻译成机器命令。我个人的体会是一个通用 Agent 框架要真正在测试领域落地首先要学会的就是“克制”——不要让模型输出太底层的东西输出越底层容错率越低。3. 当通用 Agent 框架被“改造”从工具链到测试专用命令中心3.1 工具选型逻辑为什么要改造而不是从头写先回答一个大家肯定会问的问题为什么不直接基于现成的 Agent 框架比如开源的 AutoGPT、LangChain 这类来改这里有一个非常现实的矛盾通用 Agent 框架的工具调用机制是面向开放域的它假设工具数量会持续增长、参数会不断变化所以工具描述符可以设计得非常“松散”。但测试场景恰恰相反它要的是“固定”“严格”“可审计”。一个把“点击坐标改成浮点数还是整数”都模棱两可的框架工程师是不敢拿去跑回归的。因此我的做法是保留底层 LLM 调用能力和多轮上下文组织机制这是通用框架的核心价值工具层全部重写。工具层按两个维度组织动作类点击、长按、滑动、输入、返回、等待、截图查询类当前页面控件树、当前截图、当前页面标题利用 OCR 或语义识别提取。这 12 个工具覆盖了大概 95% 的移动端手工测试动作。剩下 5% 的复杂手势双指缩放、旋转用的频率低我先砍掉了。砍掉的另一个原因是Agent 的步数是有限的工具越多每步思考的 token 消耗越大、推理链路越长。给 Agent 的“选择面”收窄反而能让它的准确率上升——这是我在实测中反复验证过的。3.2 工具描述符的三个关键维度命名、参数、返回结构工具描述符不是写给人看的注释它是模型决定“何时用什么工具”的依据。写不好Agent 就瞎调工具。我总结出三个必须注意的维度第一个是命名。工具名要让模型一眼看出用途不要出现“exec”“run”这种过于泛化的词。命名最好是“动作对象”一个工具干一件事。这类似于结构化接口设计——一个 locate 函数不该负责截图一个 click 函数不该负责输入。第二个是参数。参数的设计原则是“只给必要项且全部给默认值”。移动端设备的操作有大量可选字段坐标、时长、滑动距离如果这些全让模型自己填它会乱填。所以我给参数设计了“智能默认”逻辑如果不填坐标就自动用该控件的中心点。第三个是返回结构。工具返回的不能是一段长文本必须是结构化结果。返回内容统一为 JSON包含三个核心字段{ status: success, data: { screenshot_hash: ..., changed: true }, message: 点击已完成页面已变化 }这里有个设计细节特别值得分享返回结构里必须带上screenshot_hash。这是一个一帧画面的感知哈希值模型可以用它判断“点击之后画面是否真的变了”。这个字段的核心价值是帮 Agent 建立“因果”概念如果一个动作执行完毕但画面哈希没有变化Agent 会自动重新尝试或报错而不是傻傻地继续。3.3 意图解析与动作序列收敛通用 Agent 的两大软肋从业者的感受是其一过度自由表达导致动作波动同样一个“登录”操作今天输出三步明天输出五步其二无法严格收敛到固定的操作序列。为了解决这个问题我在工具层里加入了“动作收敛器”它把模型输出的一系列意图动作与金标动作集golden sequence做比对筛选出匹配度最高的分支。这个相当于把“生成式执行”拉回到“规则校验”的轨道上。动作收敛有一个度的问题完全严格比对就回到了普通脚本完全不比对又失去了 Agent 的灵活性。我的经验是取“关键路径严格比对 次要路径宽松”策略。关键路径指的是会改变数据、状态、跳转的动作次要路径指的是截图、等待、滑动这种辅助动作。举个例子测试一个登录流程关键路径点击登录按钮 → 输入手机号 → 输入验证码 → 点击提交按钮次要路径等待页面加载、滑动到可见区域、关闭弹窗。关键路径上的动作不允许偏差次要路径允许 Agent 自由发挥。这是“用 Agent 的灵活性处理异常环境用规则约束核心路径的稳定性”。3.4 设备管理服务Agent 的“物理载体”没有设备的 Agent 只是角色扮演的玩具。设备管理服务是这套系统的水泥目前我实现了以下这些能力设备注册与心跳检测每台设备持有一个连接 ID执行任务前检测设备是否在线、锁屏、电量多设备并行分配同一个用例可以分发给多台设备并行跑Agent 的执行日志按设备维度隔离设备状态快照每次执行前后都会生成 JSON 快照当前前台应用、activity、系统电量、网络状态用来排查环境差异。设备管理不直接产生测试价值但没有它任何工具层的优化都会因为“设备状态不确定”而失效。我可以分享一个经验设备池不要放太多不同型号的设备一起跑。不同设备的屏幕分辨率、系统版本差异会让 Agent 的截图标注效果飘忽不定。如果一定要多机型覆盖建议先在同一分辨率/同一系统版本上把脚本跑通再做横向扩展。4. 驱动模型从“Chat 形态”到“测试专用决策器”4.1 为什么不能套用通用对话模型的结构通用 Agent 框架默认的模型形态是“聊天型”用户说一句模型想一句输出一句。这种结构在闲聊场景没问题但在测试场景有一个致命缺陷它会不断累积对话历史然后上下文窗口爆炸。为了缓解这个问题通用 Agent 通常做“摘要压缩”但压缩本身又会丢失关键细节比如之前的操作是否成功。测试场景里每个动作都是“一锤定音”的压缩后可能把关键状态抹掉了Agent 后面的决策链全部断线。所以我把模型的指令结构改成了“状态操作”循环当前状态描述 - 模型输出动作 - 工具执行 - 新状态回填 - 模型再输出每一轮只给模型“上一轮的关键结论 这一轮的完整工具结果”前面的历史不保留。这个设计参考了状态机思想——Agent 的全部“记忆”应该体现在“当前状态”里而不是历史聊天记录里。可以这样类比传统脚本是拿着图纸一路走而这个是每走一步就拍一张照用照片决定下一脚踩哪。丢失一次历史顶多丢失一张照片不至于整条路线全废。4.2 动态决策三元组任务、状态、策略我用的驱动模型结构是一个三元组任务Task描述该轮循环要验证的目标路径以及预期结果状态State上一步动作造成的变化包含截图哈希、页面摘要、动作结果策略Policy一个约束列表限制模型的行为边界比如“如果弹窗出现优先点击‘取消’”。让模型基于三元组做决策而不是基于对话语境自由发挥思考质量会明显提升。因为任务和策略是从高一层注入的而不是让它自己从历史里猜。策略列表要有优先级。测试场景里常见优先级安全策略 业务策略 效率策略。安全策略是“不许越权操作、不许修改数据”业务策略是“必须走到哪个页面”效率策略是“不要做无意义的滚动”。模型如果在多个策略之间摇摆按优先级裁定。4.3 拒绝执行机制让 Agent 学会“说不”在测试环节拒绝执行refusal这个能力是保障安全的关键。如果一个 Agent 只会埋头执行指令那么在遇到“页面根本不是预期应用”的情况时它可能会把一个用户登录页当成目标页面继续操作甚至点击一些风险按钮。我加的拒绝条件是页面摘要与目标应用描述不匹配且差距过大用语义相似度计算连续三次执行同一动作但页面哈希没有变化陷入死循环检测到系统级页面如设置、权限请求、桌面且策略列表中明确禁止操作。一旦触发拒绝Agent 会停止当前流程并生成结构化报告给测试人员而不是强行继续执行。有了“能不干就不干”的能力Agent 的维护成本反而下降了——因为越是乱来后面出错排查越麻烦。5. 断言与结果判定LLM 不能直接下“最后结论”5.1 为什么纯 LLM 断言不靠谱到执行阶段末尾得回答一个问题“这条测试到底是过了还是没过”很多人会把截图丢给 LLM问它“页面正常吗”这非常危险。LLM 是一个概率模型它的输出天然存在幻觉。让它进行“对/错”判定它的置信度分布会出现很多噪声。尤其是涉及金额、数量、状态码这类数值断言时LLM 的精度完全不够。测试断言的本质是“确定性校验”而 LLM 的本质是“概率性生成”两者天然存在不可调和的紧张关系。我的原则是不要让 LLM 下最终结论它只负责提供证据和描述结论由规则引擎去判定。5.2 双层判定架构规则锁定 LLM 语义补盲最终我实现的双层判定是这样第一层规则判定硬条件。例如页面出现错误弹窗、某个元素缺失、Crash 日志出现等这些用代码直接判定准确率 100%。第二层LLM 语义判定软条件。当硬条件全部通过但页面样式、布局、文案语义可能有细小的偏差时用 LLM 做语义异常检测。比如“结算页的文案从‘确认支付’变成‘确认付款’”规则难以捕捉而 LLM 能捕捉到这种词汇级异常。这两层的权重不一样。规则判定的权重更高一旦触发就判定为“Fail”LLM 判定的结果分为“Pass”“Suspect”“Fail”三档只有 Suspect 才需要人工复查。这个架构的价值在于既利用了 LLM 的语义理解能力又不让它决定生死。我会在自己的工程里把 LLM 当做一个“高级实习生”它能发现可疑点但最终签字还得靠规则引擎。5.3 动作副作用检测所有做过 UI 自动化的人都有经验操作成功了但不代表没副作用。最典型的例子点击“结算”按钮支付成功了但同时也触发了一个不该出现的分享弹窗。传统脚本对这个情况基本无能为力因为脚本只会检查“有没有到达结算成功页”不会关心“有没有多出来一个东西”。LLM 驱动系统天然有机会处理这个问题因为每一轮动作都保存了页面哈希可以在断言时回放关键路径上的哈希序列检查是否有非预期页面的插入。这个功能要付出的代价是每一轮都得保存哈希会比较占空间。我的解决方法是只保存关键节点的哈希比如每一次跳转、每一次数据提交前后不保存辅助动作的。6. 实际落地中的典型问题和排查实录6.1 问题表从控制树乱序到输入法弹窗把碰过的典型问题整理成一个速查表方便后续排查现象根因解法控件树庞大模型选错目标页面控件树未做层级裁剪对不可见的 or 脏控件做过滤只保留可见区域控件点击无反应日志显示命令成功点击坐标落在控件边缘默认用控件中心点当控件尺寸过小时用回归偏移策略页面跳转失败模型以为是点击失败系统弹窗拦截了点击在环境归一化模块里强制处理系统弹窗输入中文变成拼音输入法未切换或输入方式选错优先用 ADB 广播输入屏蔽系统输入法同一用例两次执行动作序列不一致模型生成了不同的路径用动作收敛器做关键路径约束偶尔出现页面白屏Agent 认为是真实页面网络加载超时但未检测在画面稳定检测前增加“页面加载完成”探测Activity 状态模型反复点击同一按钮页面哈希未更新模型误判连续三次同一动作则强制切换策略真机执行比模拟器慢一倍真机 frame 提取速度慢截图接口改为异步预取只有关键帧做同步等待6.2 实战一控件树“膨胀”导致模型选错目标这是最典型的一坑。一个商城首页的控件树在原生 App 里动辄 200 到 400 个节点里面包含大量隐藏商品推荐位、不可见的 banner、已经滚出屏幕的列表项。直接把这棵树丢给 LLM它一定会被干扰。而且移动端的控件树里经常出现大量“无效节点”无文本、无 resource-id、无 bounds 的空容器这些空节点对模型毫无价值只会增加 token 消耗。我的修剪逻辑是过滤掉不可见节点bounds 不在屏幕内过滤掉无文本、无 resource-id、无 content-desc 的纯容器合并同义文本控件比如“加入购物车”在多个商品卡片上重复出现只保留每个可交互控件的路径、名称、矩形框。修剪后单页控件数能从 300 降到 15 到 25模型的选目标准确率大幅提升响应延迟也随之下降。6.3 实战二两个“登录”按钮Agent 点错了一个一个页面里出现两个文字相同的按钮导航栏上的“登录”和页面中间的“登录”这在真实的移动应用里太常见了。如果只把 text 属性传给模型它分不清该点哪个。我的解决方案是给控件描述加上“语义位置”字段比如“顶部导航栏右侧”“页面中部区域”。这个不能靠纯坐标计算得结合控件树层级推断。比如导航栏在控件树里通常是一个横向线性布局的子节点页面中部则是一个竖向滚动区域的子节点。这个语义位置字段由代码自动推导不是让 LLM 自己看截图猜。推导规则是根据控件的坐标y 值和父节点类型映射成“顶部 / 中部 / 底部 / 列表项 / 弹窗区”等枚举值。6.4 实战三偶发性的登录态过期被 Agent 误判为“用例失败”在某次测试电商下单流程时用例执行到中途用户的 Session 过期了App 自动跳回了登录页。Agent 发现自己执行的下单动作没有生效于是反复尝试“点击提交”但每次都因为登录态缺失失败。这个问题不同于页面白屏或弹窗它是业务逻辑层面的“环境噪音”。规则引擎判定不了因为页面很正常LLM 也容易误判它看不出这是个登录态问题。需要专门加一层“会话状态探测器”从截图或控件树中是否能提取到登录用户信息如果这个信息消失就自动终止流程并重登。这也给我一个启示LLM 驱动的自动化不是说把一切都交给模型而是在“模型能感知的”和“模型不能感知的”之间设计好代码兜底的边界。会话状态属于“模型不擅长感知”的范畴应该由代码代理。7. 对“LLM 测试框架”的定位思考它到底解决了什么问题7.1 一个被低估的价值测试经验的资产化说到底LLM 驱动的自动化测试系统本质上是在做一个知识工程问题把团队里的资深测试人员的经验通过自然语言沉淀成可执行的动作序列和判断规则。传统测试框架里经验靠的是“脚本 注释 文档”人和知识之间始终隔着一层代码。而 Agent 化之后经验表现形式可以是“如果用户看到 X 页面而 Y 按钮亮着那么首选操作是 Z”这个表达更接近人的直觉。当然在自动化的大量实践中我观察到当前实践离这个终极目标还有距离但方向是对的。至少它让“用例编写”这件事的门槛从“熟悉代码库 自动化框架 API”降低到“能描述业务逻辑”。7.2 人工介入的正确方式从“写脚本”到“处置异常”这套系统落地后我注意到测试团队的工作重心发生了明显转移。大多数用例的执行和结果判断都已经自动化人工开始处理那些 Agent 无法决策的“灰色地带”——页面语义异常、跨端行为差异、业务逻辑歧义。这一点很多团队抱怨 LLM 测试框架“不稳定”或者“没法用”我觉得一部分原因就是期望错位了。它的确不可能完全替代人工但可以让人工从“执行重复操作”中解放出来专注于真正的“判断”。7.3 未来我打算优化的方向当前系统的薄弱环节一个是复杂手势的覆盖还不足多指缩放、压感操作另一个是跨页面状态传参还不够灵活Agent 在页面 A 拿到的数据如何在页面 B 使用。后面我计划引入“临时变量缓冲区”和“手势模板库”把这两个短板补上。另外还有一个想法能不能让 Agent 直接根据自然语言用例生成对应的断言规则而不是每次由工程师手动配置规则现在这个方向已经有雏形了准确率大约七成但还达不到上生产的要求。这一块后续会持续迭代。关于标题里提到的“下沉”这个词最后多讲一点任何一个通用框架下放到垂直场景考验的都是“删减”而不是“添加”能力。你不需要让 Agent 变得更博学你需要让它把一件事做得足够收敛。对移动端这样做对 Web 端和接口层也一样。这套思路如果复用过去会省下不少重新造轮子的时间。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。