这几年的LLM Agent热潮,让不少团队误以为从此告别了“写脚本”的苦日子。任务规划、工具调用、自我纠错,一套能力组合拳打下来,看上去什么都能干。可一旦真把一套通用Agent框架直接搬进移动端自动化测试,问题马上就来了。我见过不少团队用一两周时间搭出惊艳的Demo,然后在接下来的几个月里被各种边缘Case反复教做人。这个系列写到第九篇,移动端部分也到了第五篇,正好卡在“通用Agent框架如何下沉到测试场景”这个坎上。
这篇文章只聊一件事:当一套原本面向通用任务的Agent框架被放到移动端自动化测试里,会发生什么、需要改什么、以及怎么让它从“看着很聪明”变成“用着很稳”。里面所有经验都来自我这边实际的落地过程,不是纸上谈兵。
- 通用Agent框架“下沉”前,先想清楚测试场景的特殊性
1.1 通用Agent的标准能力栈,到底是哪几板斧
主流的通用Agent框架,核心能力大致可以拆成四块。第一块是任务规划,给一个大目标,模型会把目标拆解成若干子步骤,比如“打开首页 → 进入登录页 → 输入账号 → 输入密码 → 点击登录”。第二块是工具调用,框架定义了若干函数或者接口,模型根据当前状态选择合适工具,并生成对应参数。第三块是自我反思,执行完一步之后,模型会观察执行结果,判断有没有达到预期,如果没达到就开始重试或者换一条路径。第四块是自然语言交互,使用者可以直接用“帮我验证一下登录流程是否正常”这种话去驱动整个系统。
这套能力放在通用场景里确实好用,比如让Agent帮你去查资料、整理文档、做数据分析。但问题在于,通用Agent的设计目标是“完成用户意图”,而自动化测试的设计目标是“验证系统行为是否符合预期”。这两个目标听起来接近,实际上差着十万八千里。
1.2 测试场景的四个硬约束,任何一个出问题都是灾难
我做了几年移动端自动化,回头总结下来,测试场景对Agent的约束可以归纳成四个词:可重复、可判定、可定位、可审计。
可重复,指的是同一条用例跑十次,步骤顺序、操作参数、断言逻辑都应该保持一致。Agent的规划能力在这里反而容易帮倒忙,因为LLM天然带有随机性,同样的Prompt可能生成不同的执行路径。可判定,指的是每一步或者最终状态都需要有一个明确的“通过/失败”标准。通用Agent可以告诉你“我觉得执行成功了”,但测试系统需要的是硬邦邦的断言结果。可定位,指的是操作和错误必须能对应到具体的页面元素、控件或者接口,方便排查。可审计,指的是每一次运行都要有完整的日志、截图、调用记录,出了问题能回溯到是哪个环节导致的。
这四个约束,每一项都在和“通用Agent的天性”做斗争。
1.3 为什么“什么都能干”反而测不好
我一直觉得,通用Agent在测试场景里的最大敌人不是能力不足,而是“太过聪明”。模型在规划的时候,会本能地选择“看起来最合理”的路径,而不是“脚本里规定好的那条路径”。举个我实际遇到的例子:让Agent验证登录失败时的错误提示,它第一次跑的时候直接绕过了错误步骤,理由是“当前页面并未出现用户名输入框,因此无法继续执行”。听起来很有道理,但这条用例已经稳定跑了大半年,问题出在页面加载动画还没结束、元素树还不完整的时候Agent就开始行动了。
还有一个更隐蔽的问题,就是Agent会“自证清白”。如果上下文里带着“登录成功”的历史信息,模型在执行断言阶段很容易被之前的步骤影响,倾向于得出“一切正常”的结论。这个现象在心理学上叫“确认偏差”,在LLM上表现得尤其明显。
所以,通用Agent框架“下沉”到测试场景时,我方的核心任务不是怎么教它做更多事,而是怎么给它划清楚边界:哪些地方允许自由规划,哪些地方必须机械执行。
- 下沉改造的关键设计:把“会规划”改造成“会测试”
2.1 工具集重构:别给Agent一堆“万能工具”
很多团队在接Agent平台时,第一反应是把所有自动化能力都暴露成工具函数给模型调用:启动应用、点击坐标、输入文本、滑动屏幕、截图,应有尽有。结果就是模型经常选不对工具,或者参数填得乱七八糟。
我这边做了两轮改造,效果立竿见影。第一轮是给每个工具都加上强类型的参数结构,不让模型自由发挥。比如点击操作不允许直接传像素坐标,而是要求传“通过元素ID定位”或者“通过文本内容定位”,把定位方式明确限定为几种。
第二轮更彻底,直接把工具按“动作类”和“断言类”分开。动作类工具负责操作页面:点击、输入、滑动、返回、等待。断言类工具负责验证状态:断言元素存在、断言文本匹配、断言控件可见。两类工具在定义上严格隔离,模型想通过“动作工具”去断言是做不到的,想通过“断言工具”去执行点击也做不到。这一条看着简单,实际操作下来能省掉一半的脑洞Bug。
2.2 状态感知:OCR、布局树、截图三种通道缺一不可
移动端Agent和Web端Agent相比,最大的难点在于端上页面没有统一的DOM结构可以拿来做语义解析。iOS是一套体系,Android是一套体系,自研引擎的App又完全不一样。所以想让Agent“看懂”屏幕,需要至少三条信息通道。
第一条是布局树或者说控件树。Android有无障碍节点,iOS有可访问性快照,这些数据能提供控件的类型、文本、坐标、可见性等结构化信息,相当于给模型一张“带标注的地图”。
第二条是OCR文字识别。布局树经常拿不到图片类内容的文本信息,比如背景图上的Banner文字、营销活动里的金句文案、验证码图案,这时候OCR就派上用场了。但OCR的结果没有坐标关系,也识别不了控件边界,只能作为辅助。
第三条是截图本身。模型需要看截图来判断视觉布局、颜色状态、弹窗样式,尤其是页面出现非预期弹窗时,截图往往是唯一能发现异常的通道。
我现在的方案是三步走:先拉布局树,再根据布局树的坐标区域截取局部图做OCR,最后把整屏截图和布局树叠加交给模型。整个过程要控制上下文数据量,不能每次把所有截图都塞进Prompt,否则成本会爆炸。
2.3 断言权限要单独拿出来,让Agent替“不容易”
我之前踩过一个特别典型的坑:Agent跑完一条用例之后,在总结里写“断言通过”,但日志里根本没有调用任何断言工具。去翻才发现,模型在思考链路里自己给了一个“看起来像是断言”的判定,并没有真正执行断言函数。这个问题的本质是,把“验证”这个动作交给了模型的主观判断。
后来我规定了一条铁律:Agent必须通过断言工具来结束一个阶段的任务,工具返回的结果是什么,报告就写什么;Agent不得在上下文里自行“总结”断言结论。同时,断言结果要求强制结构化输出,包含布尔值、置信度、快照地址、原始消息四部分。这样即便模型的结论和真实断言不一致,系统也能用结构化结果去比对,把主观干扰降到最低。
2.4 安全护栏:让Agent在笼子里跳舞
通用Agent框架最吓人的一点,是它在执行任务时可能做出一些越界操作。移动端测试场景里常见的风险包括:在登录状态下误点了“退出登录”、在支付页面误触了支付按钮、在填写表单时把线上数据改掉,甚至在某些App里触发了注销账号的流程。
我的做法是给Agent加了三层护栏。第一层是操作白名单,在Init的时候注入一套任务指令集,只有白名单内的控件类型才能被指定为操作目标,比如允许点击按钮、允许输入文本框,但禁止点击“注销”“删除”“退出登录”这类危险控件,除非显式加进白名单。第二层是沙箱环境,所有自动化运行都在专用的测试包和测试账号下执行,线上包和真实数据绝不接入。第三层是熔断机制,每个任务设定步数上限与时间盒,一旦超限立即停止并生成崩溃报告,不允许Agent无限重试下去。
- 实操:一套“提问—规划—执行—复核”四段式流程
3.1 四段式总览:把自由裁量权压缩最小
直接拿通用Agent的ReAct循环去跑移动端测试,日志会非常难看,因为模型一会儿思考、一会儿调用工具、一会儿观察结果,整个轨迹缺乏仪式感。我在项目里把流程重构成四个明确阶段,每个阶段都有专属的上下文模板和输出格式。
第一阶段叫“提问”,人类测试工程师用自然语言描述用例目标,同时附上前置条件、测试数据、必走路径和预期结果。第二阶段叫“规划”,Agent输出一条结构化的执行计划,每条计划由动作、目标控件、预期结果、下一步四个字段组成。规划结果要给人看,能人工修改后再进入执行。第三阶段叫“执行”,按照规划逐条调用动作工具,每执行完一步就抓取新一轮页面状态。第四阶段叫“复核”,Agent调用断言工具验证最终状态,并输出结构化报告。
这四个阶段的核心思想很简单:把“自由发挥”限制在“规划”这个环节,把“执行”变成机械过程,把“判定”完全交给工具。
3.2 一个完整的虚拟用例拆解:登录后领取新人券
拿“某电商App登录后领取新人券,并确认卡包数量增加”这条用例来跑一遍四段式流程。
提问阶段注入的信息是这样的:
前置条件:使用测试账号A登录,账号已注册但从未领过新人券。 必走路径:首页→个人中心→登录→新人券专区→领取→卡包。 预期结果:卡包内券数量+1,页面出现“领取成功”样式。
规划阶段,Agent给出的执行计划大致是:
- 启动应用,等待首页加载完成。
- 进入个人中心,检查当前登录态(通过断言工具:是否存在“未登录”字样)。
- 如果存在未登录字样,点击登录按钮,输入测试账号、密码,点击确认。
- 返回首页,进入新人券专区。
- 点击“领取新人券”按钮。
- 断言页面出现“领取成功”提示。
- 进入卡包,断言券数量较领取前增加1。
执行阶段,系统逐条执行计划里的步骤。这里有一个小细节:规划结果在真正执行前会先渲染成人类可读的列表,如果Agent在规划时漏掉了“检查登录态”这一步,测试工程师可以直接修改,改完再跑。这样既保留了Agent的规划效率,也加入了人的经验校验。
复核阶段,Agent分别调用“断言文本出现”“断言控件数量变化”等工具,最后输出一个JSON格式的结论,包括每一条断言的布尔值、截图路径、失败原因。整个过程下来,只有“规划”这一步使用了LLM能力,执行和断言全部走的是确定性代码,这保证了用例的高稳定性。
3.3 提示词模板设计的几个关键细节
四段式流程能不能跑通,一半取决于提示词模板设计。我说几个踩坑后总结出来的细节,比具体的模板文字更值得收藏。
第一个细节是,上下文里不要塞太多“无关业务规则”。最开始我把App的完整业务说明都放在System Prompt里面,结果模型执行规划时老是脑补出一些不存在的功能,比如在没有搜索框的页面上规划“使用搜索功能”。后来改成只注入用例相关的最小上下文,规划准确率反而上升。
第二个细节是,动作工具的命名和描述要符合“测试语言”习惯,不要用“通用语言”。比如一个点击操作,不要描述成“按下元素”,要描述成“点击目标控件并等待页面响应,返回结果中包含控件是否存在的信息”。
第三个细节是,要显式告诉模型哪些步骤“不允许跳过”。通用Agent在遇到页面没有立即出现目标元素时,可能会自动决定“跳过等待并继续下一步”,这在测试里是致命问题。在提示词里加一条硬性约束:任何目标控件在超时阈值内未出现,必须报告失败,不允许跳步。
第四个细节是,失败回传的信息要格式统一。我在模板里规定,执行步骤失败后回传的内容必须包含:步骤序号、失败类型(控件未找到/断言不匹配/页面弹窗冲突)、当前页面截图、当前页面布局树摘要。统一格式的好处是,下一次任务规划可以基于历史失败报告做针对性避让,坏处是模型会偷懒只贴截图链接,所以需要做一次格式校验,不合格就重新跑。
3.4 数据回流:报告、快照、日志构成闭环
一套Agent框架在测试场景里真正产生价值的标志,是它能把每一次运行的报告沉淀成结构化的语料,反哺后续的规划能力。
我这边目前跑下来的闭环包含三个产出物。一个是JSON格式的执行报告,记录每条用例的规划路径、动作序列、断言结果、耗时与Token消耗。一个是失败快照库,保存截图、OCR结果、布局树和错误日志,按失败类型打标。最后一个是从失败样本中抽取的“避坑经验集”,经过人工筛选后注入到下一次规划的提示词里,形成一个持续进化的小型知识库。
闭环跑起来之后,最大的感受是“同类错误会越来越少”。比如第一次运行时Agent因为页面弹窗遮挡而找不到按钮,失败样本被标记后,后续规划阶段就会自动在动作序列里加上“检查并关闭弹窗”作为前置步骤。
- 常见问题与排查技巧实录
4.1 问题速查表
| 常见问题 | 典型现象 | 排查方向 | 落地解法 |
|---|---|---|---|
| Agent定位控件“迷路” | 元素树存在但Agent报告找不到 | 控件树是否包含可点击信息,OCR是否误读 | 布局树与OCR双通道交叉校验 |
| 断言被跳过 | 用例绿了但无断言日志 | 检查模型是否自行“脑补”了结论 | 断言必须走独立工具,禁止自然语言总结 |
| 回放不稳定 | 同一条用例时绿时红 | 动画、加载时序、截图时机不一致 | 增加稳定等待器,统一截图时机 |
| 测试数据被污染 | 用例第二次跑就失败 | 全局状态或账号状态未重置 | 数据注入与清理阶段强制隔离 |
| Prompt泛化失控 | 改一个页面就废一片用例 | 提示词塞了过多业务规则 | 最小化上下文,约束只保留结构性规则 |
| Token成本飙升 | 一条用例跑出几百万Token | 每一步都在调用LLM | 把规划结果缓存为可复用执行计划 |
4.2 定位控件“迷路”的深层原因与处理
移动端页面的动态特性是Agent定位控件的第一杀手。页面上有轮播图、下拉刷新、局部刷新、弹窗,这些动态区域会让控件树持续变化,模型拿到的布局树和截图很可能不是同一帧的状态。
我这边的一个比较有效的做法是给Agent定义一个“稳定等待器”工具。这个工具不做任何业务判断,只负责监听页面是否进入“稳定状态”:无网络请求、无动画执行、控件树连续两次抓取结果一致,才判定稳定。所有动作执行之前先调用稳定等待器,至少能避免一半的时序问题。
另外一个与此相关的问题是OCR识别的准确率。OCR经常把按钮文字里的“领取”识别成“领收”,或者把倒计时数字拆成乱码。所以在设计用例时,我要求Agent在断言文本时优先用布局树中的控件文本,OCR的结果只作为视觉证据存在,不作为主要断言依据。
4.3 如何强制“不跳过断言”
跳过断言这个问题,渗入了很多团队的Agent测试项目里,因为它隐蔽。Agent会认为“页面已经显示了成功提示”,于是就直接在总结里写上“断言通过”,而不去调用断言工具。
我处理这个问题用了两个办法。第一个办法是前面提到的工具隔离,把断言工具单独放在“只读工具组”里,并且规定每个阶段的结束必须以一次断言工具调用为标志,没有断言结果就不允许进入下一个阶段。第二个办法是结果校验,执行完一条用例后,程序去翻阅工具调用日志,统计断言工具的执行次数,如果为0,就把整条用例标记为“异常通过”,直接打断机器人。
除此之外,我还会在用例结束时检查截图与操作日志,人工抽看几条,防止模型用一些“合法但不合理”的方式逃避断言,比如明明断言目标是“券数量+1”,模型却断言“页面出现‘卡包’标题”这种无关内容来凑数。
4.4 Prompt泛化失控的具体案例与对策
泛化失控这个词有点抽象,我举一个具体的例子。最初做登录场景的Agent用例时,我在提示词里写了很多关于App的业务细节,比如“登录按钮位于页面右下角”“老版本登录流程中有短信验证码步骤”。后来App改版,登录页彻底重做,结果Agent在规划时依然会莫名提到短信验证码,还试图在页面上寻找不存在的“验证码输入框”。
这个问题的根源在于提示词里塞了“场景化描述”,而模型无法区分“规则”和“知识”。“登录按钮位置”是知识,是会被版本迭代推翻的;“目标控件必须存在才能操作”是规则,是所有页面通用的。我现在做提示词的思路是:规则尽量写全,知识尽量不写。所有业务相关的细节,全部放在首轮“提问”阶段的用例描述里,垂直版本的更新不会影响提示词的稳定性。
4.5 成本控制:让LLM只在“关键判断点”出现
自动化测试的用例数量很大,如果每条用例的每一步都去调用LLM,那Token消耗会到令人惊讶的数量级。我做过一次统计,一条包含15个动作步骤的用例,如果规划、执行、反思每一轮都调大模型,跑一次的成本相当于写了几万字的文本,一个月下来团队预算根本扛不住。
后来我们做了一个执行计划缓存层。同一个测试用例如果在多次运行中规划结果完全一致,就直接复用历史规划,进入纯脚本化执行模式。只有两种情况会触发重新规划:一是页面结构变化导致执行失败,二是“避坑经验集”里新增了需要调整路径的信息。这样一来,日常回归的成本能降到原来的十分之一左右,Agent只在关键时刻介入决策,稳定性和成本都变得可控。
- 工具选型与工程化落地心得
5.1 选型:现成Agent框架、改造半成品、还是自研轻量编排
我见过很多团队在“要不要选一个现成的通用Agent框架”这个问题上反复纠结。我的看法是,要根据团队对“可控性”的要求来做决策。
如果团队只是做技术预研,验证一下LLM能不能理解测试场景,那直接用现成的Agent编排框架完全没问题,一两天就能搭出Demo。但如果要进入真实项目、每天跑几百条回归用例,现成通用框架大概率不够用,因为它们的工具调用机制、上下文管理、重试策略都是面向通用任务设计的,没有“稳定等待器”“断言隔离”“操作白名单”这类测试场景专用的原语。
我这边最终选择了“自研轻量编排”,只保留通用Agent框架的核心规划能力,把执行、断言、截图、日志、检视全部用代码实现。这样做的代价是前期工作量稍大,但换来的是测试脚手架和Agent能力的解耦。就算后续换一家底层模型,整个框架也无需重写,只需改模型接入层。
5.2 工程化必须做的三个动作
第一个动作是搭建离线评测集。从历史用例中抽一批结构完整、断言清晰、业务价值高的用例,固化成“黄金数据集”。每次改Agent框架或换模型,先跑一遍金数据集的通过率,低于基线就说明改动有回归。这个动作能防止团队在调Prompt时“拆东墙补西墙”。
第二个动作是“时间盒”和“步数盒”的强制约束。任何一条用例都不能无限跑下去。我这边规定单条用例的步数上限是30步,时间上限是5分钟,超过就直接判失败并保存现场快照。没有上限的Agent,遇到页面卡死或流程死循环时会一直烧钱烧时间,得不偿失。
第三个动作是失败样本的标注回路。每次Agent运行失败,生成的报告要自动进入一个待标注队列,由测试工程师判断错误类型是“应用缺陷”还是“框架缺陷”。应用缺陷转给开发团队,框架缺陷则用来反哺提示词和工具定义。这个回路跑起来后,整套系统的稳定性是逐步上升的,而不是每次都要从零开始排查。
5.3 测试团队的角色变化:从写脚本到写提示词
接入Agent之后,测试工程师的角色会发生一些变化,这个变化比技术本身更有意思。以前写一条自动化用例,至少要写几十行代码,维护成本主要花在定位器更新和等待策略调整上。现在写一条用例,本质上是在写一段高度结构化的“任务描述+数据约束+断言规则”,维护成本变成了提示词版本管理和执行计划复核。
这意味着测试团队要把“提示词”当作代码一样去做版本管理。我在内部推行了一个小规范:每条Agent用例都有一个专属的提示词配置文件,包含版本号、修改人、修改原因和关联的用例编号。每次模型版本升级后,都会用这个配置文件做回归,看看规划质量有没有下降。
5.4 确定性执行层:Agent的“刹车”和“保险”
最后再讲一个核心心得:无论Agent能力多强,都要保留一个确定性执行层。每次规划结果生成后,我都会把规划解析成机器可校验的中间表示,相比之下解析成JSON格式的步骤序列。这个步骤序列在执行前还会经过一个静态检查器,检查是否存在“点击不可点击控件”“输入框类型不匹配”“断言目标为空”等明显错误。
这个确定性执行层承担着两个职责:一是“刹车”,当Agent的规划明显离谱时,静态检查器会直接拦截,防止错误速度被执行;二是“保险”,当Agent因为环境异常而陷入重试循环时,执行层会强制回到最近的成功状态点,而不是让Agent自由发挥去“摸索”。
我个人在实际操作中的体会是,把Agent当“搭档”而不是“自动驾驶”,是它在测试场景里真正稳定下来的关键。Agent负责从自然语言目标到步骤序列的转换、负责失败后的策略调整和异常原因的总结,但绝不负责不受约束地乱跑一通。每一层都要有代码在把关,每一环都要有日志在回溯。
这个系列做到这里,移动端自动化也进入深水区了。后续我大概率会基于这次的下沉经验,单独聊一聊如何把Agent的“避坑经验集”做成自动化的知识库,让它摆脱人工维护。那是一条完全不同的路径,但起点正是这几个月里踩过的每一个坑。