通用Agent框架下沉移动端测试:从工具调用到自主决策的工程实践
移动端自动化做了这么多年我越来越觉得当前测试体系真正短缺的不是执行框架而是决策层。Appium、XCUITest 这类工具把手练得很强壮但每一次点击、滑动、断言还是要人肉提前写进脚本。页面上一个按钮挪了位置脚本就废了弹窗出现时机稍有变化assert 就红了。而所谓 LLM 驱动的自动化测试我和团队同事最近一段时间集中尝试的方向不是拿大模型去生成一堆一次性测试代码而是把一套通用的 Agent 框架直接下沉到移动端测试场景里让 Agent 自己看屏幕、自己做决策、自己调用工具执行并验证结果。这篇文章是这个系列的第 9 篇也是移动端自动化子专题的第 5 篇。前面几篇聊过元素定位、设备调度和脚本稳定性这篇重点换个视角当一个通用 Agent能推理、能规划、能调用工具的那套东西落到移动测试任务中它和传统 UI 自动化碰撞之后会冒出哪些新问题又该怎么设计整套调用关系。如果你正在纠结LLM 到底能在测试里干什么或者已经试过用大模型生成测试脚本但效果一般这篇文章应该能给你一些不一样的思路。1. 先搞清楚通用 Agent 框架到底通用在哪1.1 Agent 不是聊天机器人而是一套观察-决策-执行的闭环很多人一听到 Agent 就想到对话框其实那是最浅层的用法。真正能下沉到测试领域的 Agent是一个可以持续运转的决策循环接收任务描述读取当前环境状态由大模型推理下一步动作调用对应工具执行再观察执行结果调整计划直到任务完成或触发终止条件。这个循环对应到测试工作上其实就是测试执行的核心逻辑打开页面、看清界面、决定下一步操作、执行操作、验证结果。传统自动化测试把这套逻辑用代码写死而 Agent 把这套逻辑交给模型在运行时动态生成。它不需要事先定义从 A 到 B 的每一条路径只需要给它一个目标和一组工具它自己会绕开弹窗、处理异常、寻找替代入口。用个生活化的类比传统脚本像一个严格按照剧本演戏的演员台词动作一个不能差舞台布景一变动就演不下去Agent 更像一个任务导向的临时工你跟他说去把那个文件找出来他自己会看柜子标签、拉开抽屉、翻翻桌面找不到还会换个思路。移动端测试里页面状态千变万化恰恰需要这种灵活性。1.2 移动端测试为什么是 Agent 下沉的好场景移动端 App 的界面层级复杂远不是 Web 页面能比的。一个页面可能同时存在原生控件、H5 容器、小程序页面还有各种动态弹出的授权框、更新提示、运营弹窗。控件树动不动几千个节点而且不同机型、不同系统版本渲染结果还不一样。传统自动化在这一层非常吃力每个弹窗都要写专门的兜底逻辑每条业务路径都要覆盖大量分支。而 Agent 框架天然擅长处理这种环境不确定的任务它理解当前页面上有弹窗应该先关掉再继续这种常识不需要你把每个弹窗规则都写死。另外移动端测试断言也比 Web 复杂。Web 里经常断言一个元素可见、一段文本存在移动端却经常要验证登录状态是否真的写入本地这个页面的某个组件到底加载完没有。Agent 可以通过组合工具完成多维度验证比如截图找关键元素、读取控件树、调本地存储接口确认数据状态这些在传统脚本里往往要写不少胶水代码。这里要澄清一个容易误会的地方Agent 下沉到移动测试不是要完全取代现有自动化框架。它更合适的定位是接管那些路径不固定、页面易变化、需要探索的测试任务比如冒烟测试、兼容性探索测试、异常路径回归。固定主路径上的高频回归用确定性脚本仍然更稳、更快、更便宜。2. 下沉的第一步把 Driver API 翻译成任务友好的工具集2.1 直接暴露 Element 操作接口Agent 根本用不好Agent 要操作手机界面绕不开底层驱动但直接把 Appium 的find_element、click、send_keys这些接口暴露给大模型效果会非常差。原因很简单这些接口要求的入参比如定位器、元素属性是机器视角的东西而大模型擅长的是意图表达。你跟它说点击登录按钮它很容易找出文案是登 录的那个控件但你让它填一个xpath//android.widget.TextView[text登录]它反而容易在长长的层级结构里迷路。所以第一步要做的是工具面的重新封装把底层 Driver 能力抽成一组语义化工具。我在实际项目中常用的工具集大致是这样工具名输入参数作用ui_launch_appapp_name, 可选参数启动或恢复应用ui_observe_page无获取当前页面控件树摘要和截图ui_click目标描述如登录按钮个人中心入口点击某个界面元素ui_input输入框描述, 文本内容在指定输入框中输入内容ui_swipe方向, 距离滑动页面ui_assert条件描述如页面出现登录成功提示验证界面状态ui_wait_for描述, 超时时间等待某个条件满足get_local_state键名读取 App 本地存储或进程内状态screen_record时长录屏留证每个工具的参数都尽量是自然语言描述Agent 只需要说清楚想做什么工具内部再把这些描述翻译成真正的定位策略和执行动作。这就是语义化工具层。另外一个关键设计是工具内部必须有护栏。比如ui_click收到的目标描述如果无法唯一对应控件不能硬点要返回一个明确的状态码告诉 Agent有多个候选或者找不到目标并附上候选列表。否则 Agent 会在错误的方向上越走越远连续误操作。2.2 状态观察把整棵控件树压缩成 Agent 能消化的页面摘要Agent 做决策依赖对当前页面的理解因此每次循环都要把页面状态喂给模型。但直接丢原始控件树是不行的一个深层次页面可能有一千多个节点全部转成文本会轻松打爆上下文窗口还会让模型抓不住重点。我的做法是分层处理先做节点压缩。过滤掉大量纯布局容器、无文本无 id 的 ViewGroup只保留可交互节点和带关键文本、content-desc、resource-id 的节点。然后做层级拍平把多级嵌套转成一串扁平描述每个节点给一个短编号方便后续工具引用。压缩之后一般一个页面从几千节点降到几十个关键节点。截图同样重要但不必每次都传给模型。有些控件树描述不了的场景比如纯图标按钮、Canvas 绘制区域必须靠视觉。我采用的策略是按需截图:常规页面只传控件树摘要Agent 发出特定请求或控件树里找不到可匹配项时才附带截图让多模态模型辅助识别。这样既控制成本又保留视觉兜底能力。页面摘要给出来后Agent 的观察就变成了一个高密度的环境描述它能看到页面有什么、可操作什么、当前状态是什么。观察质量直接决定决策质量这部分值得花大力气优化。2.3 会话与记忆管理Agent 上下文需要做分段保存移动端测试任务往往不是单步操作而是一个连续流程。Agent 需要记住自己已经做过什么、当前进行到哪一步、页面上有哪些历史状态。但大模型的上下文窗口不是无限的而且塞太多历史信息也会干扰判断。我建议把记忆拆成三层短期工作记忆只保留最近几轮操作轨迹用于理解当前进度中期任务记忆保存本任务的关键中间结果比如已经验证过的接口、已经出现过的异常长期知识库则以提示词或检索库形式保存项目特有的业务规则和页面特征。这三层分别消费不同的上下文预算既保证模型决策所需的最小信息量又避免上下文爆炸。实际操作中我会把每轮 Agent 的执行轨迹按固定格式写入结构化日志这样即使模型自身短期记忆被压缩外部记录也能在任务中断后帮助恢复现场。后面排查问题这份日志就是第一手证据。3. 核心实操把通用 Agent 跑在一个真实移动测试任务上3.1 一个典型场景登录流程自动探索回归空谈抽象设计不够我们来看一个具体任务。假设要对一个模拟 App 做主路径回归目标是验证未登录 - 登录 - 登录成功 - 退出登录这条链路是否正常。传统脚本大概会写四五十行定位代码遇到弹窗再加条件分支。用 Agent 做任务描述可能就一句话任务验证登录流程 步骤要求如果当前未登录使用测试账号完成登录登录后确认首页加载成功最后退出登录确认回到未登录状态。所有关键节点截图留证。接下来 Agent 会自主执行过程中的推理轨迹大致长这样[Action] ui_launch_app(app_namemock_app) [Observation] 应用启动完成当前处于首页界面右上角显示我的入口 [Action] ui_click(target我的入口按钮) [Observation] 页面跳转到个人中心检测到未登录提示卡片并出现立即登录按钮 [Action] ui_click(target立即登录按钮) [Observation] 进入登录页存在手机号输入框、密码输入框和登录提交按钮 [Action] ui_input(target手机号输入框, text13800138000) [Action] ui_input(target密码输入框, texttest123456) [Action] ui_click(target登录按钮) [Observation] 请求已提交当前页面出现 loading 状态 [Action] ui_wait_for(condition首页加载完成, timeout10) [Observation] 条件满足已回到首页右上角显示用户头像 [Action] ui_assert(condition本地登录状态为已登录) [Observation] 断言通过本地存储包含有效会话字段 [Action] ui_click(target用户头像) [Action] ui_click(target退出登录) [Action] ui_assert(condition回到未登录状态) [Observation] 断言通过登录流程回归完成共耗时 42 秒这段轨迹里Agent 没有预先给它任何 xpath 或资源 id它完全通过页面摘要理解当前状态自己决定下一步。中间如果弹出更新提示它会判断这不是当前任务要素自动找关闭按钮绕过如果登录按钮文案变成了登 录它也能通过模糊匹配和上下文理解找到正确控件。3.2 定位兜底意图定位与确定性定位的混合策略有人会问让 Agent 完全自由定位会不会不可控我的答案是不要让它完全自由而是要做混合定位策略分三层兜底第一层是确定性优先。工程里维护一份常见关键控件的稳定标识映射表例如资源 id、全局唯一 testIDAgent 在拿到目标描述后优先查映射表。如果映射表命中直接走传统定位速度和稳定性都有保障。第二层是意图推理。映射表没有命中就交给模型结合压缩后的控件树和页面截图做判断。模型把登录按钮这个意图和当前页面里的候选节点做匹配选出最可能的一个。这一层能覆盖大量页面结构变化。第三层是执行后验证。点击或输入动作完成后Agent 必须验证页面状态是否真的变了。如果动作执行了但状态没变化不能当作成功要触发重试或换一种方式实现目标。例如点登录没反应可能弹出键盘遮挡了按钮这时就先收起键盘再点。这套策略的好处是兼顾稳定性与灵活性。确定性映射保证了高频路径的可靠执行意图推理覆盖了页面频繁变动的场景执行后验证则防止 Agent自欺欺人地把操作完成当作任务完成。3.3 断言设计界面验证之外还要验证真实状态移动端测试最容易被忽略的是断言深度。从上面的轨迹可以看到Agent 不只验证首页加载完成这种界面文本还通过get_local_state去读取本地存储确认登录态真的写入成功。这是移动端场景下非常重要的设计。很多 App 的界面渲染和实际数据状态并不同步。登录页面跳转了但这可能是因为前端缓存导致的假象退出按钮不见了不一定是真的退出了可能是某个网络请求还在途。如果 Agent 只依赖视觉文本很容易得出错误结论。所以我在工具集里专门设计了状态验证类工具用来读取应用本地数据库、SharedPreferences、文件系统中的关键字段或者通过测试后门接口查询服务端会话状态。Agent 在做关键断言时必须同时做界面级和数据级验证两者都通过才算断言成功。这个习惯能挡住不少看着绿了实际是假绿的测试结果。4. 踩坑实录Agent 下沉测试场景的五个经典问题4.1 模型误触点错按钮、乱操作大模型不是测试专家它在理解页面时会犯错。最典型的是意图正确但目标选错比如界面上既有登录又有登录遇到问题模型可能选错。还有更让人头疼的某些页面布局里按钮层次较深模型把返回箭头当成关闭按钮。我的处理办法是在工具内部加操作校验规则比如点击前先检查目标控件是否真正可见、是否在可点击区域内、是否被其他弹窗遮挡。另外在页面摘要中给关键操作区加标注把主导航区域弹窗区域底部 Tab 栏区分开来引导模型选对范围。如果模型连续两次选择同一目标但页面状态没有变化我会让工具返回一个警告提示可能选错目标强制它换策略。4.2 上下文爆炸与 token 成本失控Agent 跑的时间越长观测日志越多上下文就越大。有一次我跑一个 20 分钟的长链路任务最后模型开始把以前的操作记录当成当前页面状态来推理明显是上下文被历史信息污染了。解决思路是前面提到的分层记忆。同时对页面摘要本身也要做缓存相同页面重复出现时不需要重新把完整控件树喂给模型只传页面标识和变化部分。这一步优化能把 token 消耗降到原来的三分之一左右。启动任务前还会先做上下文预算检查如果预判会超过窗口上限就提前把历史轨迹固化到外部存储只保留几个关键摘要。4.3 断言只看看起来正常漏掉真实故障Agent 执行操作后我最早踩的一个坑是它点完登录看到页面跳转就报告成功但后来人工复测发现登录请求其实失败了跳转只是因为前端路由提前切换。这提醒我Agent 的观察不一定等于事实。现在我对关键断言有一个强制性约定凡是涉及登录态、支付状态、数据提交这类核心动作Agent 必须调用状态验证工具去后端或本地存储确认不能只看界面。遇到网络超时这类常见异常还必须区分是环境问题还是产品 bug环境问题要自动重试产品 bug 才标记失败。4.4 重试死循环同一个错误动作反复执行模型有时会陷入死循环尤其在遇到它不理解的弹窗时可能尝试关闭弹窗 - 失败 - 再关闭 - 再失败同一个动作重复十几遍。这种循环不仅浪费时间还会让测试报告失去参考价值。两个手段配合使用第一在 Agent 循环里加动作去重机制记录最近 N 步执行过的动作和对应页面状态如果页面状态未变化且动作重复就判定为无效操作强制 Agent 换一种方式第二设置总步数上限一般是 30 到 50 步超出后自动终止任务并标记为未完成疑似环境阻塞。宁可让任务失败也不能让它无意义地空转。4.5 多设备并行时的会话隔离问题移动端测试很少只跑一台设备Agent 用在多设备上最大的问题是会话串线。如果多个 Agent 共用一个模型上下文A 设备的页面状态可能被 B 设备的操作污染导致定位错乱。我的方案是一个设备绑定一个独立的 Agent 实例上下文完全隔离。模型服务本身可以共享但要做并发控制和独立 session 标记。设备资源调度用队列管理每台设备上的任务复用一个常驻 Agent 进程避免频繁创建销毁带来的额外开销。下面整理成一份排查速查表方便大家对照问题典型现象核心对策模型误触点了错误按钮、关闭了不该关的弹窗工具层做可见性/遮挡校验标注主要操作区域上下文爆炸模型把历史日志当页面状态分层记忆缓存页面摘要控制 token 预算断言假绿界面看起来正常实际数据未写入关键断言必须调用状态验证工具双重确认重试死循环同一动作反复执行页面无变化动作去重 最大步数上限 强制换策略会话串线多设备状态相互污染设备绑定独立 Agent 实例上下文隔离5. 效果衡量与适用范围别把 Agent 测试捧上天5.1 怎么衡量 Agent 测试到底有没有用团队里引入 Agent 测试后最常被问的是它到底比传统自动化强在哪里我需要一组能说服自己的指标。我目前主要看四个数字一是任务完成率在一批固定测试任务中Agent 自主完成且断言正确的比例二是平均执行时长尤其关注长链路任务是否能控制在可接受延迟内三是脚本维护人天也就是页面改版后需要人工修改脚本的工作量四是单任务平均工具调用次数这个数字直接对应 token 成本和执行稳定性。从我们自己体感看任务完成率从最初的不到五成经过工具集优化和页面摘要改进能稳定到八成以上。单独看这个数字可能还比不上精心维护的传统脚本但脚本维护人天下降得非常明显页面结构小幅变动时Agent 几乎不需要干预。真正好用的不是全 Agent 化或全脚本化而是两者混用主路径、高频回归用确定性脚本保证速度和稳定探索性测试、异常路径、快速冒烟交给 Agent 处理。把 Agent 当成团队的探索型测试工程师而不是替代全部自动化它的价值才最容易呈现。5.2 三个不适合用 Agent 的场景Agent 不是万能的我实际用下来有三类场景明确不会交给它。一是毫秒级性能断言Agent 的决策和执行链路本身有延迟无法精确测量页面渲染耗时二是像素级视觉回归这类任务需要精确到像素的对比大模型的大概像判断达不到要求三是强依赖外部服务且服务状态不可控的流程Agent 遇到外部超时会反复试探浪费大量时间。另外还要提醒一点Agent 并不是越聪明越好它只是个决策大脑真正干活还是靠底层工具链。如果底层驱动不稳定、页面摘要质量差换再强的模型也补不回来。所以要对整套链路分层做稳定性建设决策层、工具层、设备层都有各自的性能和失败处理机制整条链路才扛得住真实业务压力。我自己的经验是Agent 下沉到测试场景最大的价值不是省掉了写脚本的人而是把测试思维从代码实现提升到了目标管理层面。测试人员不再需要关心每个控件怎么定位而是把精力放在设计更合理的测试任务、更严谨的验证标准、更完整的业务覆盖上。至于 Agent 具体怎么点、怎么滑那是工具层的事不该占用测试设计者的注意力。如果你们团队正准备上手这个方向我给的建议是别一上来就想着端到端全部跑通。先选一条最关键的业务路径把工具集和页面摘要打磨顺跑出第一批可信的结果再逐步扩大范围。过程中一定要保留完整的执行轨迹日志那是后续定位所有问题的基础。踩过几次坑之后你会和我有同样的感受通用 Agent 框架下沉到移动测试拼的不是模型有多聪明而是你有没有想清楚怎么让它和现有的测试工程平稳共处。