LLM Agent下沉移动端自动化测试:架构改造与实操指南 聊到 LLM 驱动自动化测试前面几篇我们把 Web 端、接口端的 Agent 玩法都过了一遍这次终于轮到移动端了。在移动端自动化系列的前四篇里我分别拆解了 Appium 的接入、控件树提取、手势操作模拟和跨端用例复用。这一篇要谈的是一个更“上头”的话题当一套通用的 Agent 框架被下沉到移动端测试场景时会发生什么以及我们怎么让它真正可用。我这边说的“通用 Agent 框架”指的是那种本身不是为测试设计的、具备工具调用、多轮规划、记忆管理能力的 LLM Agent 底座。它一开始可能是用来做通用任务编排的比如查资料、调接口、操作桌面软件。但当你想让它去点 App、划页面、填表单、验证流程时你会发现它既强大又脆弱。强大在于它能理解自然语言描述的测试意图脆弱在于移动端的控件树、页面状态、异步加载这些问题会让它频频翻车。这篇文章会从架构设计、工具改造、Prompt 工程、实操链路、问题排查几个维度把“通用 Agent 下沉到移动端测试”这件事讲透。适合已经跑通 Appium 基础脚本、想往智能化方向迈一步的测试开发工程师也适合正在头疼“LLM 生成的用例没法稳定执行”的团队。内容偏实操代码和配置都是可以直接抄作业的。1. 从“什么都会”到“专精测试”Agent 框架下沉的核心思路1.1 通用 Agent 为什么能在测试场景落地先说一个很多人都会问的问题通用 Agent 框架又不是为测试写的我们凭什么把它拉过来用在测试上我的答案是测试本质上是“目标驱动的状态遍历”而 Agent 框架恰好就是干这个的。一个通用 Agent 的核心能力可以拆成四块任务理解、工具调用、多步规划、记忆管理。这四块对应到移动端测试里分别是看懂测试人员的自然语言指令、操作 App 的控件元素、在多个界面间穿行并动态调整路径、记住前面操作产生的页面状态。你看几乎是一一对应的。举个例子。传统自动化测试脚本要验证“登录失败时提示错误”你得先写死点击输入框、输入内容、点击登录按钮、等待 toast 出现、断言 toast 文案。每一步都是预设的页面一旦有变化脚本就废了。而 Agent 的方式是告诉它“验证一下登录失败时的提示文案”它自己决定去点哪个输入框、怎么输入、点完登录后等到什么时候、最后提取什么信息来断言。页面布局调整了只要控件语义还在它就能适应。这里有个前提就是 LLM 必须能“看懂”移动端的页面结构。控件树虽然在技术上是一堆 XML 节点但只要我们把关键属性——resource-id、content-desc、text、bounds——用结构化的方式喂给模型它就能像人看地图一样理解当前页面有什么可操作的东西。这一点是通用 Agent 框架能下沉到测试场景的根基。1.2 下沉要解决的三个核心问题把通用框架搬过来绝不是改个配置文件就能跑。我实际做下来觉得有三个坎必须迈过去。第一个坎是工具层的语义鸿沟。通用 Agent 框架里的工具往往是“搜索网页”“打开文件”“发送请求”这类通用动作而移动端测试需要的是“点击”“长按”“滑动”“输入文本”“等待元素出现”。工具名好改但工具的输入输出参数如果设计不好模型就不知道什么场景该调哪个工具。比如“滑动”这个动作参数是起点坐标和终点坐标还是方向加距离后者对模型更友好因为模型更容易决策“从下往上滑”而不是“从 (540, 1800) 滑到 (540, 400)”。第二个坎是状态感知的粒度。通用 Agent 处理的任务环境状态往往是文字或者文件而移动端测试的状态是“当前屏幕上有什么”。我们必须把控件树转成 LLM 容易消费的观察文本但又不能一股脑全塞进去——一张页面几百个节点全给模型不仅 token 爆炸还会让模型抓不住重点。这中间的取舍是下沉改造里最花时间的部分。第三个坎是错误恢复策略。通用 Agent 调用工具失败后通常会换个工具或者直接告诉用户“我做不到”。但在测试场景里“做不到”不是一个可接受的答案。页面加载慢了、弹窗挡路了、控件还没渲染出来这些都是偶发问题Agent 需要具备重试、等待、绕行等测试人员特有的容错逻辑。这部分逻辑不能靠提示词硬写得在框架层面做机制支持。这三个坎我在第一节里先点出来后面每一节实际上都在展开它们的具体解法。2. 移动端环境与工具链的准备2.1 设备接入与驱动层选型在谈 Agent 之前得先把“手”准备好。移动端自动化里“手”就是设备驱动层。我在这套方案里用的是 Appium原因有三个跨平台支持好、控件树获取方便、社区生态成熟。当然你也可以用其他方案但如果你是第一次做从 Appium 起步最稳。环境上需要注意几个点设备端要开启开发者模式并允许 USB 调试模拟器或者真机都可以但模拟器在控件渲染上有时和真机有细微差异断言时间需要放宽一点。Appium Server 建议单独起一个进程不要和测试进程混在一起否则日志和资源会互相干扰。控件树的获取要设置合理的等待时间我一般是显式等待 3 到 5 秒避免页面还没渲染完就抓取导致树不完整。驱动层和 Agent 层的连接我建议做一个薄薄的适配层。Appium 的 WebDriver 接口本来就很接近通用 Agent 需要的工具接口——查找元素、点击、输入、滑动、获取页面源码每个操作天然就是一个“工具”。适配层要做的只是把 Appium 的命令包装成 Agent 框架认识的结构工具名、参数描述、返回值格式。这里有一个参数设计的实操细节。对于“点击”这个工具参数不应该只传 resource-id因为很多第三方 App 的控件根本没有 id。我实际用的参数是“控件描述”就是让我们自己实现的控件树序列化模块为每个可操作节点生成一句自然语言描述比如“按钮: 登录, 位于页面中下部”。然后点击工具接收的就是这个描述Agent 先在控件树里做语义匹配找到对应的节点坐标再执行点击。这样模型不需要和坐标打交道命中率反而更高。2.2 LLM 接口与 Agent 运行时的配置要点LLM 接口的选择坦白说没有标准答案。我自己的经验是如果预算允许优先选推理能力强的模型因为测试场景里多步推理和工具选择是高频操作。但模型大不等于无所不能关键还得看你喂给它的上下文结构。运行时配置里最容易被忽略的是“温度参数”。通用对话场景里我们喜欢把温度设高一点让模型更有“创造性”但测试场景恰恰相反。测试需要的是稳定复现同样一个指令最好每次都走同样的路径所以温度我一般设为 0 或者接近 0。这是一个小参数但对稳定性影响非常大。另外Agent 运行的并发策略也要考虑。移动端测试经常要跑多机型如果你让每个设备各开一个 Agent 实例LLM 接口的限流很容易被打满。我的做法是做一个请求队列让多个 Agent 实例共享同一个 LLM 调用入口同时配置重试机制遇到限流就指数退避。别小看这个设计实际跑起来它能省掉你大量和“429 错误”搏斗的时间。还有一个容易被忽略的点上下文窗口的长度。移动端测试里一次完整的任务可能要经历十几个页面每个页面都会产生控件树文本。如果每步都往上下文里追加窗口很快就不够用。在后面第三节我会详细讲截断和摘要策略这里先提示一句别把所有历史页面信息都留着有用的是最近两三步的状态和之前提取到的关键断言值。3. Agent 框架的测试化改造关键环节拆解3.1 工具注册从“搜索网页”到“点击控件”通用 Agent 框架通常自带一个工具注册中心你只需要把工具函数挂载上去框架就会在每次决策时把工具列表随 Prompt 一起发给模型。但下沉到测试场景后工具注册这一步有讲究。首先是工具数量控制。框架自带的通用工具比如“搜索网页”“读取链接”在测试场景里基本用不上。如果一个工具列表里有七八个工具其中一半无关模型决策时就会分心甚至误选。我的做法是测试场景只注册和操作 App 相关的工具——获取页面控件、点击控件、输入文本、滑动页面、等待元素、获取页面截图、执行断言、记录日志。每个工具的名字和描述要写得让模型一看就知道“什么场景用我”。工具描述的写法也很关键。拿“滑动页面”举例不要写成“swipe(startX, startY, endX, endY)”而要写成“在当前页面向指定方向滑动方向可选 up、down、left、right。用于浏览列表内容或翻页”。模型看到这种描述才知道“哦我想看列表后面的内容时该用它”。工具描述本质上就是给模型看的“使用说明书”说明书写得越清楚模型用起来越准。其次是工具返回值的设计。Appium 的点击操作返回的是一堆 WebDriver 协议数据对模型来说毫无意义。我们在适配层里要把返回值统一成一个 JSON操作是否成功、当前页面是否发生变化、如果变化了给出新的页面摘要。这里最核心的技巧是“工具返回结果应该包含下一步决策所需的全部信息”。比如点击登录按钮后返回值里最好带上“页面跳转至首页”或者“出现错误提示密码不正确”这样模型就不需要额外调用获取页面工具才能判断当前状态。3.2 记忆与上下文管理让 Agent 记得上一步点了什么通用 Agent 框架里的记忆通常分两块短期记忆和长期记忆。短期记忆就是多轮对话里的历史消息长期记忆可能是向量数据库里存的知识。在移动端测试里这两块都有对应改法。短期记忆这块最大的问题是历史消息膨胀。假设一个流程要经过 12 个页面每个页面产生 1500 字左右的控件树文本12 步就是 18000 字再乘以多轮工具调用的消息数很快就把上下文窗口塞满了。我用的策略是“窗口滑动 摘要重写”保留最近 3 轮的完整工具调用记录更早的记录在每 5 轮结束时用一个小模型生成一段摘要比如“已完成打开应用、进入登录页、输入账号密码当前在首页已断言到文案‘欢迎回来’”。测试任务的执行路径是线性的摘要重写不会丢失关键上下文反而让模型更聚焦在“接下来做什么”。长期记忆这块下沉到测试场景后的形态是“用例经验库”。比如某次执行中Agent 发现某个弹窗总是拦截点击它可以把“遇到这个弹窗先点击关闭按钮”的经验写入本地存储。下次执行类似路径时框架会优先把相关经验注入到上下文开头。这个做法的价值一开始不明显但随着用例数量积累你会发现 Agent 的首轮规划质量越来越高踩坑次数明显下降。3.3 测试意图的 Prompt 设计Prompt 设计是 Agent 下沉改造里最能体现“测试味儿”的地方。通用的 Agent Prompt 强调“尽你所能完成任务”而测试场景的 Prompt 必须强调“按步骤执行、可验证、可复现”。我实际在用的系统提示词核心框架是四段式角色定义你是移动端自动化测试 Agent任务是按照用户描述的测试场景执行操作并收集证据。执行准则每一步操作前先说明操作目标和理由只使用已注册的工具不臆造不存在的控件遇到异常时先尝试等待和重试连续失败三次再报告失败。断言要求每当完成一个关键步骤使用“执行断言”工具记录实际结果断言信息必须包含实际观测到的文案或状态不能只写“成功”。输出格式测试结束后输出一份结构化的测试报告包括步骤列表、每一步的操作摘要、断言结果、以及失败时的截图路径。这套 Prompt 看起来不复杂但每一句都是有原因的。比如“不臆造不存在的控件”是因为 LLM 在翻车的时候特别爱“一本正经地假装自己点了某个按钮”实际上页面上根本没这个按钮它只是猜的。加上这条准则后再配合工具返回值里“操作失败”的真实反馈模型会更快进入纠错流程而不是自说自话。还有一个细节是“每一步操作前先说明理由”。这看似增加了 token 消耗但实际收益极大。一旦脚本执行到中间某一步出问题你可以通过 Agent 的思考链日志快速定位是“决策错了”还是“工具执行错了”排查效率完全不一样。4. 实操实录一个端到端测试任务的完整链路4.1 任务定义与初始规划前面讲了一堆设计这一节直接上一个完整的实操例子。假设我们要验证一个社区类 App 的核心路径注册新用户 → 完善个人资料 → 发布一条动态 → 在首页看到这条动态。在传统脚本里这个流程至少要写两百行代码中间每个页面的等待、输入、断言都要手写。在 Agent 框架下任务定义只需要一句话“注册一个新用户完善资料发布一条动态并确认首页可见。”框架收到任务后第一件事是调用“获取页面控件”工具看看 App 当前停在什么页面。这一步很重要因为 Agent 不能假设 App 一定处于首页——可能上一次测试崩溃后留在某个异常页面。拿到当前页面状态后模型开始生成初始计划。你可以从 Agent 的日志里看到类似这样的思考链当前页面是登录页未登录状态。任务需要注册新用户因此第一步应寻找“注册”入口。先查看页面控件中是否有注册按钮。这段计划会自动转为工具调用进入执行阶段。初始规划质量取决于两个因素Prompt 里的执行准则是否清晰以及当前页面控件树里节点描述是否准确。后者就是我们前面说的“控件树序列化”模块的功劳它把 XML 转成了模型友好的文本。4.2 分步执行与动态纠错执行阶段是最能看到 Agent 和传统脚本差距的地方。传统脚本只能按写好的步骤走而 Agent 每完成一步都会重新观察页面再决定下一步怎么做。比如注册页面Agent 先找到“手机号输入框”节点并点击然后调用“输入文本”工具填入一个随机生成的手机号。下一步它发现页面出现了“获取验证码”按钮它不会像脚本那样写死点击而是根据当前观察到的验证码输入框决定先等一秒再输入。验证码从哪来这就是移动端测试特有的一个问题。我这里的做法是在测试环境里接入一个验证码网关验证码会通过接口返回给 AgentAgent 收到后填入输入框。整个过程丝滑得像一个真人测试员在操作。动态纠错是这个环节最核心的能力。我随便列举几个实际发生过的纠错场景点击“下一步”后页面没有跳转Agent 观察到按钮文案变成“校验中”于是它没有重复点击而是等待 1 秒后重新获取页面发现跳转成功。发布动态时弹出权限询问框请求访问相册Agent 识别到这是一个中断性弹窗主动点击“允许”而不是断言失败。首页加载较慢动态没有立刻出现Agent 调用“滑动页面”工具向下滑动一次再刷新获取页面成功找到刚发布的动态。这些纠错行为没有一行是提前写死的。它们完全来自模型对“当前页面发生了什么”的理解。只要能保证每次工具调用后都重新观察页面这些动态适配能力就会自然涌现。4.3 断言与报告生成测试的终点是断言和报告Agent 在这方面也需要专门设计。通用 Agent 完成任务后只会给你一段描述性的总结而测试需要的是结构化的、可追朔的证据。我在框架里做了一个“执行断言”工具它的工作方式很简单Agent 传入期望值和实际观测值工具内部比对并生成一条断言记录。比如“期望首页出现刚发布的动态实际首页第一条动态包含文本‘测试动态标题’结果通过”。每条断言都会自动附带当前截图截图路径存进报告。报告生成则是把整个执行过程——思考链、工具调用参数、返回结果、断言记录、截图路径——汇总成一份 Markdown 文档。这份文档的粒度远高于传统测试报告因为它不仅告诉你“通过还是失败”还告诉你 Agent 在每一步是怎么想的、为什么那么选。出了问题你不需要复现直接翻报告就能定位到具体决策节点。这里想提醒一点报告别做得太花哨。我见过有人把 Agent 报告做成带图表、带看板的页面好看是好看但维护成本太高。测试报告的核心价值是“可读、可追、可比对”一份结构清晰的 Markdown 文档配上报错现场截图就已经及格了。5. 常见问题与排查技巧实录5.1 控件识别不准怎么办移动端测试里控件识别不准是最常见的问题Agent 框架下也不例外。典型表现是Agent 明明看到了“登录”按钮的描述点击时报错说找不到元素。根因通常是两个。第一控件树序列化时可交互节点太多模型在长文本里找错目标。第二App 的控件描述和视觉呈现不一致比如文本是“登录”但 accessibility text 是“log_in”模型按“登录”去匹配自然匹配不上。我的排查套路是这样先在浏览器里打开控件树序列化的原始输出看 Agent 眼里到底看到了什么。然后对照模型实际调用的工具参数看它是按哪个字段去匹配的。绝大多数情况下问题出在序列化模块没有把可视化文本和语义化文本合并。我的解决方式是在序列化时生成一个“复合描述”把 text、content-desc、resource-id 三者拼接在一起比如“登录按钮log_in, id/btn_login”。信息量大了模型匹配的容错率就高了。另一个实用技巧是给点击工具增加一个“模糊匹配”模式。当精确匹配失败时工具会在控件树里搜索包含目标关键词的节点按文本相似度排序返回候选列表。这相当于给 Agent 一个“找不到就换种说法找”的兜底能力实测下来能把点击类操作的成功率从 80% 拉到 95% 以上。5.2 步骤漂移与页面状态不同步步骤漂移是我自创的一个词意思是 Agent 以为自己在做某一步但实际页面状态已经不是它以为的那样了。最典型的场景是异步加载点击“保存”后页面显示 loadingAgent 立刻抓取控件树发现页面没变化于是认为点击无效又点了一次。结果等 loading 结束第二次点击反而触发了一个重复操作。解决这个问题我用了三层机制第一层是工具层加一个“等待稳定”的动作。每次点击或输入后框架自动等待 1 秒再用显式等待的方式等待页面状态稳定比如等待 loading 控件消失。这一步是机械性的不依赖模型判断。第二层是页面指纹比对。每次获取控件树时框架计算一个简单的指纹比如可交互节点数量加首屏文本拼接的哈希。如果连续两次获取的指纹一致就认为页面没有变化。模型看到工具返回“页面状态未变化”就不会误判为点击失败了。第三层是执行历史校验。在关键步骤比如注册完成、提交订单之后Agent 会被强制调用一次“确认页面状态”工具动态更新自己的“当前位于哪个页面”的内部认知。如果状态和预期不符Agent 会主动回退而不是硬着头皮往下走。这套三层机制组合下来步骤漂移出现的频率大幅下降。说实话第一层机械等待是最有效的后面两层是保险丝。5.3 成本控制与超时处理最后聊钱的问题。LLM 驱动测试相比传统脚本最大的痛点就是每次执行都要花 token。一次完整的移动端流程如果每步都传完整控件树一次跑下来可能要消耗几万 token。高频回归的时候这个成本是团队没法忽略的。我的成本控制三板斧分享给大家参考。第一板斧是“先抓重点再抓全量”。不是每个页面都需要完整控件树。登录页、首页这种关键页面给完整数据列表中间的某个详情页给摘要就行。我实现了一个页面重要度配置表按页面匹配规则决定给模型的上下文量级。第二板斧是“失败才重跑成功不重蹈”。对于成功执行的流程把每步决策记录存下来下次执行同路径时模型不需要重新推理直接按之前的决策记录走。只有当执行失败或者页面变化导致记录失效时才回到推理模式。这相当于把“从零推理”变成了“参考归档”成本能降一个数量级。第三板斧是超时熔断。Agent 执行超过设定轮数比如 20 轮时框架强制终止并标记为失败同时把中间步骤导出为报告。这避免了模型在一个死胡同里反复尝试白白烧 token。超时轮数要按业务流程复杂度设置太松浪费钱太紧容易误杀正常流程我通常按人工操作步骤数的 3 倍来设。6. 下沉之后这套方案的边界与扩展空间6.1 下沉到什么程度才算“成功”接这个方案做了两个迭代之后我反复问自己一个问题通用 Agent 框架下沉到测试场景到底应该下沉到什么程度我现在的答案是框架的通用能力要保留但测试特有的行为逻辑应该内嵌到机制里而不是叠加在提示词上。什么意思如果你发现某个测试场景的稳定运行完全依赖一段特别长的、写满了各种边界情况的提示词那就说明下沉还没做透。真正下沉到位的标志是大多数测试特有的行为已经变成了机制。比如等待页面稳定是工具层的机制状态校验是框架层的机制经验复用是存储层的机制。提示词只负责描述任务和执行准则不需要事无巨细地告诉模型“遇到弹窗怎么办”。判断下沉是否成功我还用三个量化指标来衡量执行成功率同样一批用例Agent 方案相比传统脚本方案成功率不能低太多。低于 80% 说明机制设计还有问题。调试时间比出了问题定位根因的时间。Agent 方案如果报告有好好做这个时间应该比传统脚本短一倍以上。新增用例成本写一个新用例的耗时。Agent 方案的目标是“描述任务即可”而不是“编写步骤脚本”。如果这个指标没有明显下降说明你的 Agent 还是套了一层壳的脚本编码器。拿这三个指标去衡量你会发现很多号称“LLM 驱动”的方案其实没有真正下沉。它们只是用 LLM 生成了传统脚本执行时依然是死板的。那不是 Agent那只是代码补全。6.2 后续还能往哪些方向扩展这套方案跑稳之后自然会产生一些扩展需求。我目前看到三个比较有价值的方向。第一个方向是多设备协同。现在是一个设备一个 Agent把多设备的执行日志汇到一个协调 Agent 那里可以做跨设备的数据比对。比如同一个功能在 iOS 和 Android 上的表现差异能不能由系统自动标注出来这是很有意思的方向。第二个方向是测试数据自动构造。Agent 在执行流程中需要大量测试数据——账号、手机号、动态内容。目前这些数据还是预先准备好的扩展空间是让 Agent 自己根据被测 App 的业务规则生成合理的数据并在执行时动态使用和清理。第三个方向是异常场景主动探索。现在的 Agent 是“按任务执行”扩展方向是让 Agent 在完成主流程后主动尝试一些异常操作比如弱网环境、断网重连、快速连点、后台切前台。这类探索性测试如果由 Agent 来做比写专门的故障脚本效率高得多。这些方向我都有初步验证过有些已经在内部跑起来了。等后续有稳定成果我再单独写一篇来分享。这篇就先到这儿毕竟移动端自动化的路还长Agent 下沉只是其中一个比较值得聊的节点。