1. 从通用 Agent 到测试场景:为什么下沉是必然
通用 Agent 框架在过去一年里几乎成了所有自动化领域的热词。从浏览器操作到桌面软件控制,从文档处理到代码生成,Agent 的触角伸得到处都是。但真正把通用 Agent 框架往移动端自动化测试场景里塞的时候,你会发现一个很尴尬的现实:通用框架在演示视频里无所不能,一进真实测试环境就各种水土不服。
我自己在这个方向上折腾了大半年,从最早的纯脚本驱动,到后来接入 LLM 做元素定位,再到尝试把通用 Agent 框架直接搬过来用,踩的坑比想象中多得多。这一篇主要聊的就是这个"下沉"过程——通用 Agent 框架从通用场景落到移动端自动化测试这个具体场景时,到底需要做哪些改造,哪些设计是必须保留的,哪些是必须砍掉的,以及最终跑通之后整个链路的实际表现。
先说清楚这个内容适合谁看。如果你已经在做移动端自动化测试,不管是 Appium、UiAutomator 还是其他方案,并且开始琢磨怎么把 LLM 和 Agent 能力接进来,那这篇的实操细节应该对你有直接帮助。如果你还在纯脚本阶段,也没关系,前面关于架构选型和场景分析的思路同样适用。我会尽量把每个决策背后的"为什么"讲透,而不是只丢一堆配置。
移动端自动化测试和 Web 端最大的区别在于:元素定位的不稳定性高出一个量级。Web 端好歹有相对稳定的 DOM 结构,移动端原生控件的层级、ID、坐标在不同机型、不同系统版本、不同分辨率下都可能变化。再加上混合应用里 WebView 和原生控件的切换、动态加载的列表、弹窗遮挡、键盘弹出导致的布局位移,这些因素叠加起来,让传统基于固定选择器的脚本极其脆弱。通用 Agent 框架擅长的"感知-决策-执行"循环,理论上正好能应对这种不确定性,但前提是你要把感知层和执行层针对移动端做深度适配。
2. 通用 Agent 框架的核心能力拆解与场景匹配
2.1 通用框架到底提供了什么
一个典型的通用 Agent 框架,核心能力通常包括这几块:任务规划(把大目标拆成子步骤)、工具调用(调用外部函数或 API)、记忆管理(短期上下文和长期知识)、以及反馈循环(根据执行结果调整下一步)。这套东西在通用场景下很灵活,因为工具集可以无限扩展,任务边界也相对模糊。
但移动端测试场景有个特点:任务边界其实非常清晰。一个测试用例就是"打开 App → 登录 → 进入某个页面 → 执行某个操作 → 验证某个结果",步骤是确定的,只是每一步的"怎么执行"需要动态决策。这就意味着通用框架里那套复杂的任务规划模块,在测试场景下大部分是冗余的。你不需要 Agent 去思考"我应该先做什么",你需要的是它去解决"这个按钮到底在哪"和"点了之后结果对不对"。
我最初的做法是直接把通用框架整个搬过来,结果发现任务规划层经常自作聪明,把一个简单的点击操作拆成好几步,反而增加了不确定性。后来我把规划层砍掉,只保留感知、决策、执行、验证四个环节,整个链路立刻稳定了很多。
2.2 移动端测试真正需要 Agent 解决什么问题
把移动端自动化测试的痛点列一下,你会发现真正需要 Agent 能力的地方集中在三个点:
第一是元素定位的鲁棒性。传统方式靠 resource-id、text、xpath,一旦这些属性变化或者有多个匹配项,脚本就挂了。Agent 可以通过截图理解界面,结合控件树信息,用语义理解的方式找到目标元素。比如"点击登录按钮",Agent 不需要知道这个按钮的 ID 是什么,它通过视觉和文本语义就能定位。
第二是异常场景的自适应处理。测试过程中经常遇到意外弹窗、权限请求、网络异常提示,传统脚本要么提前写一堆 if-else,要么直接失败。Agent 可以在执行过程中识别这些异常并决定如何处理,比如"检测到权限弹窗,点击允许"。
第三是验证逻辑的灵活性。传统断言是硬编码的,比如检查某个文本是否等于预期值。但实际界面可能有细微差异,Agent 可以用语义比对的方式判断"这个页面是否达到了预期状态",容忍一定的表述差异。
这三个点对应的正是通用 Agent 框架里感知、决策、验证三个核心环节。所以下沉的关键不是把整个框架搬过来,而是把框架里对应的能力模块抽出来,针对移动端做专项强化。
2.3 哪些模块必须砍掉,哪些必须保留
基于上面的分析,我整理了一个模块取舍对照表,这是实际改造过程中总结出来的:
| 通用框架模块 | 测试场景是否需要 | 处理方式 | 原因 |
|---|---|---|---|
| 复杂任务规划 | 不需要 | 砍掉 | 测试用例步骤确定,规划层反而增加不确定性 |
| 多工具动态选择 | 部分需要 | 精简 | 只保留移动端操作相关工具,减少选择空间 |
| 短期记忆 | 需要 | 保留并强化 | 需要记住当前页面状态和已执行步骤 |
| 长期知识库 | 需要 | 改造 | 改为存储元素定位经验和异常处理策略 |
| 视觉感知 | 需要 | 深度定制 | 需要结合控件树,不能纯靠截图 |
| 反馈循环 | 需要 | 保留 | 用于验证执行结果和重试决策 |
| 通用对话能力 | 不需要 | 砍掉 | 测试场景不需要闲聊式交互 |
这个表是我踩了无数坑之后才理清的。最开始我什么都想保留,觉得功能越多越好,结果就是系统复杂度爆炸,调试成本极高。后来做减法,只保留测试场景真正需要的,整个系统才跑得起来。
3. 移动端感知层的深度适配方案
3.1 为什么纯截图方案不够用
通用 Agent 框架在 Web 场景下,很多是纯靠截图 + 视觉模型来理解界面的。这套方案搬到移动端,第一个问题就是分辨率碎片化。同一张界面,在不同机型上截图出来的尺寸、比例、控件位置都不一样,视觉模型很难稳定识别。
第二个问题是控件信息丢失。截图只能看到渲染后的像素,看不到控件的层级关系、可点击属性、滚动容器边界这些信息。而这些信息对于精准操作至关重要。比如两个视觉上重叠的控件,截图上看不出谁在上层,但控件树里一目了然。
第三个问题是性能。纯截图方案每一步都要调视觉模型,延迟高,一个测试用例跑下来时间成本很大。而移动端测试往往需要跑大量用例,性能不可接受。
所以我的方案是控件树为主,截图为辅。控件树提供结构化的控件信息,截图用于处理控件树无法覆盖的场景,比如自定义绘制控件、游戏界面、WebView 内部内容。
3.2 控件树与截图的融合策略
具体怎么融合?我的做法是分三层:
第一层是控件树解析。通过移动端自动化框架提供的接口获取当前界面的控件树,解析出每个控件的类型、文本、ID、坐标、可点击状态等属性。这一层能覆盖大部分标准控件。
第二层是截图语义标注。对截图做 OCR 和视觉理解,提取出文本内容和视觉元素的位置。这一层主要用于补充控件树里缺失的信息,比如图片按钮上的文字、自定义控件的语义。
第三层是融合对齐。把控件树信息和截图信息做空间对齐,同一个位置的控件信息合并。这样每个界面元素既有结构化的属性,又有视觉语义的描述。
融合之后,Agent 拿到的界面表示大概是这样的:一个元素列表,每个元素包含控件类型、文本内容、视觉描述、坐标范围、可交互状态。Agent 基于这个表示做决策,比纯截图或纯控件树都稳定得多。
3.3 感知层的性能优化实操
性能这块我做了几个优化,效果比较明显:
增量感知。不是每一步都重新解析整个界面,而是对比前后两帧的差异,只更新变化的部分。移动端界面大部分时候只有局部变化,增量感知能省掉大量重复计算。
控件树缓存。对于静态界面,控件树解析结果可以缓存,设置一个合理的过期时间。比如登录页面,如果 500ms 内没有变化,就复用上次的解析结果。
截图降频。截图和视觉理解是性能瓶颈,所以不是每一步都截图。只在控件树信息不足以决策时才触发截图。实测下来,截图频率降低 60% 以上,整体速度提升明显。
异步并行。控件树解析和截图可以并行执行,不用串行等待。这个在工程上需要处理好线程同步,但收益很大。
提示:增量感知的差异对比要注意处理动画场景。如果界面正在播放动画,前后两帧差异会很大,导致频繁全量更新。我的做法是检测到连续多帧差异都很大时,暂停感知,等界面稳定后再继续。
4. 决策层的提示词工程与策略设计
4.1 决策层的输入输出设计
决策层是 Agent 的核心,它接收感知层的界面表示,输出下一步操作。这里的输入输出设计直接决定了系统的稳定性。
输入方面,我最终确定的格式是:当前界面元素列表 + 当前任务目标 + 已执行步骤历史 + 可用操作列表。界面元素列表用结构化格式,每个元素一行,包含索引、类型、文本、坐标。任务目标是自然语言描述,比如"点击登录按钮"。已执行步骤历史用于避免重复操作。可用操作列表告诉 Agent 它能做什么,比如点击、输入、滑动、返回等。
输出方面,我要求 Agent 返回结构化的操作指令,而不是自然语言。格式大概是:操作类型 + 目标元素索引 + 操作参数。比如{"action": "click", "target": 3}表示点击第 3 个元素。这种结构化输出便于程序解析,也减少了歧义。
4.2 提示词模板的迭代过程
提示词这块我迭代了十几版,说几个关键的经验。
第一版是直接把界面元素列表丢给模型,让它自己决定怎么做。结果模型经常输出一些无法解析的格式,或者选择不存在的元素索引。问题在于没有明确约束输出格式。
第二版加了输出格式约束,要求必须返回 JSON。好了一些,但模型还是经常选错元素。分析下来是因为元素列表太长,模型注意力分散。
第三版做了元素筛选,只把可能相关的元素传给模型。比如任务是"点击登录按钮",就只传包含"登录"文本或者可点击的按钮类元素。这样模型的选择空间小了,准确率大幅提升。
第四版加了 few-shot 示例,给几个典型的决策案例。这个对提升稳定性帮助很大,尤其是处理一些边界情况。
第五版加了思维链,要求模型先分析当前界面状态,再决定操作。这个让决策过程更可解释,出问题时也容易排查。
最终的提示词模板大概长这样:
你是一个移动端自动化测试助手。当前任务:{task} 当前界面元素: {elements} 已执行步骤: {history} 请分析当前界面,决定下一步操作。输出格式: {"reasoning": "分析过程", "action": "操作类型", "target": 元素索引, "params": {}} 可用操作:click, input, swipe, back, wait4.3 决策失败的兜底策略
再好的提示词也不能保证 100% 正确,所以必须有兜底策略。我设计了三层兜底:
第一层是重试。如果决策执行后验证失败,让 Agent 重新决策,但把上次的失败信息加入上下文,避免重复同样的错误。
第二层是降级。如果重试多次仍然失败,降级到传统方式。比如元素定位失败,就尝试用固定的 resource-id 或坐标点击。
第三层是人工介入。如果降级也失败,标记这个用例为需要人工处理,记录详细的上下文信息,方便后续排查。
这三层兜底让整个系统的鲁棒性提升了很多。实测下来,纯 Agent 决策的成功率大概在 85% 左右,加上兜底策略后整体成功率能到 95% 以上。
5. 执行层与验证层的工程实现
5.1 执行层的操作封装
执行层负责把决策层的指令转换成实际的移动端操作。这块看起来简单,实际上有很多细节。
点击操作要考虑坐标点击和控件点击的区别。坐标点击快但不稳定,控件点击稳定但需要先定位。我的做法是优先控件点击,失败后降级到坐标点击。
输入操作要处理键盘弹出、输入法切换、清空已有内容这些细节。尤其是清空内容,不同应用的处理方式不一样,有的需要长按全选删除,有的需要连续退格。
滑动操作要处理惯性滚动和边界检测。滑动距离和速度需要根据实际界面调整,滑多了会跳过目标,滑少了到不了位置。
等待操作是最容易被忽视的。移动端界面加载有延迟,操作后需要等待界面稳定。我的做法是封装一个智能等待,检测界面连续多帧无变化后才继续。
5.2 验证层的语义比对
验证层是判断测试是否通过的关键。传统方式是硬编码断言,比如检查某个文本是否等于预期值。但实际界面可能有细微差异,比如多了空格、标点不同、表述略有出入。
我的做法是用语义比对替代精确匹配。把预期结果和实际界面内容都转成语义表示,然后计算相似度。相似度超过阈值就认为通过。这个阈值需要根据实际场景调整,太严格会误报,太宽松会漏报。
具体实现上,我用的是文本嵌入 + 余弦相似度。预期结果和实际内容分别做嵌入,计算相似度。实测下来,阈值设在 0.85 左右比较合适,既能容忍表述差异,又能捕捉实质性的错误。
5.3 完整链路的串联与调试
把感知、决策、执行、验证四个环节串起来,就是一个完整的 Agent 测试循环。但串联过程中有很多工程细节。
状态管理要处理好。每个环节的输出要传递给下一个环节,同时要维护全局的测试状态,比如当前在哪个页面、已经执行了哪些步骤。
异常处理要统一。任何一个环节出错,都要有明确的处理策略,不能直接崩溃。
日志记录要详细。每个环节的输入输出都要记录,方便出问题时排查。尤其是决策环节,要记录模型的完整输出,包括思维链。
性能监控要有。记录每个环节的耗时,找出瓶颈。实测下来,决策环节通常是耗时大户,因为要调模型。
调试这块我的经验是,先单独调试每个环节,确保每个环节都能稳定工作,再串联起来。串联后如果出问题,通过日志定位是哪个环节的问题。不要一上来就调整个链路,那样问题定位会非常困难。
6. 常见问题与排查技巧实录
6.1 元素定位失败的排查思路
元素定位失败是最常见的问题。排查思路我总结了一个流程:
首先看感知层是否解析到了目标元素。如果控件树里没有,可能是控件还没加载出来,需要增加等待。如果控件树里有但截图里没有,可能是控件被遮挡或者不可见。
其次看决策层是否选对了元素。如果感知层有多个相似元素,模型可能选错。这时候需要优化元素筛选逻辑,或者给模型更明确的描述。
最后看执行层是否执行成功。有时候元素定位对了,但点击没生效,可能是坐标偏移或者控件不可点击。这时候需要检查控件的可点击状态。
6.2 决策不稳定的优化方法
决策不稳定表现为同样的界面,模型有时候选对有时候选错。这个问题我遇到过很多次,优化方法有几个:
降低温度参数。模型的温度参数越低,输出越确定。测试场景不需要创造性,温度设成 0 或者很低的值就行。
增加示例。在提示词里加更多 few-shot 示例,尤其是容易出错的场景。示例要覆盖各种边界情况。
简化选择空间。元素列表越短,模型越不容易选错。做好元素筛选,只传相关元素。
固定随机种子。如果模型接口支持,固定随机种子能让输出更稳定。
6.3 性能瓶颈的定位与优化
性能问题通常出在决策环节,因为要调模型。优化方法:
批量决策。如果多个步骤之间没有依赖,可以批量决策,减少模型调用次数。
缓存决策。对于重复出现的界面和任务,缓存决策结果,直接复用。
模型选型。不是所有场景都需要大模型,一些简单的决策用小模型就够了。根据任务复杂度动态选择模型。
异步执行。决策和执行可以流水线化,决策下一步的同时执行上一步。
6.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 元素定位失败 | 控件未加载 | 检查控件树 | 增加等待时间 |
| 元素定位失败 | 控件被遮挡 | 检查截图 | 先处理遮挡 |
| 决策选错元素 | 元素列表太长 | 检查传入元素 | 优化筛选逻辑 |
| 决策格式错误 | 提示词约束不足 | 检查模型输出 | 强化格式约束 |
| 执行无效果 | 坐标偏移 | 检查点击坐标 | 改用控件点击 |
| 验证误报 | 阈值太宽松 | 检查相似度 | 调整阈值 |
| 验证漏报 | 阈值太严格 | 检查相似度 | 调整阈值 |
| 整体速度慢 | 模型调用频繁 | 检查调用次数 | 批量+缓存 |
注意:这个速查表是动态的,实际使用中遇到新问题要持续补充。我自己的版本已经积累了几十条,覆盖了各种奇奇怪怪的场景。
7. 实际跑通后的效果与经验总结
整套方案跑通之后,我拿几个实际项目做了对比测试。传统脚本方案的用例通过率大概在 70% 左右,主要失败原因是元素定位失败和界面变化。接入 Agent 方案后,通过率提升到 92% 左右,失败原因主要是模型决策错误和极端异常场景。
性能方面,单个用例的平均执行时间从传统方案的 30 秒左右增加到 45 秒左右,主要增加在模型调用上。但考虑到通过率的大幅提升,这个时间成本是值得的。而且通过批量决策和缓存优化,实际时间可以压缩到 35 秒左右。
维护成本方面,传统脚本每次界面变化都要改脚本,Agent 方案基本不需要改,因为它是语义理解而不是硬编码。这个在长期来看节省的人力非常可观。
我个人在实际操作中的体会是,通用 Agent 框架下沉到测试场景,关键不是把框架搬过来,而是理解测试场景的本质需求,然后针对性地抽取和改造框架能力。砍掉冗余的规划层,强化感知和决策层,做好兜底策略,这套组合拳打下来,效果比预期好很多。
最后分享一个小技巧:Agent 的决策日志一定要详细记录,包括完整的提示词、模型输出、执行结果。这些日志不仅是排查问题的依据,也是优化提示词的素材。我每次优化提示词,都是先翻日志找出错案例,分析原因,再针对性调整。这个迭代过程虽然慢,但每一步都扎实,最终系统的稳定性就是这么一点点磨出来的。