资讯动态

通用Agent框架下沉移动端测试:意图驱动替代脚本驱动

发布时间:2026/10/18 19:26:20 来源:尧图企业网站定制
1. 通用 Agent 框架下沉到移动端测试到底在解决什么问题移动端自动化测试这个领域做了几年的人都有一个共同的感受工具链其实不缺Appium、Espresso、XCUITest、Maestro随便挑一个都能跑起来。真正让人头疼的是用例的维护成本和场景覆盖的碎片化。一个中等规模的 AppUI 自动化用例动辄几百条页面改一版定位符挂掉一大片维护的人比写的人还累。这也是为什么很多团队自动化测试覆盖率始终卡在 30% 上下上不去——不是不想做是投入产出比算不过来。通用 Agent 框架这两年在测试圈被反复提起核心原因就在于它换了一个思路不再要求你把每一步操作都写死而是让模型去理解“我要做什么”然后自己决定怎么点、怎么填、怎么断言。把这种能力下沉到移动端测试场景本质上是用意图驱动替代脚本驱动。你告诉它“验证新用户注册流程能走通”它自己去截图、识别控件、规划操作路径、执行点击输入、最后判断结果是否符合预期。这件事的价值不在于炫技而在于它把测试用例的表达粒度从“操作步骤”提升到了“业务意图”。页面改版了只要业务意图没变用例大概率还能跑控件换了 IDAgent 靠视觉和语义重新定位不需要人去改代码。对于移动端这种 UI 变动频繁、机型碎片化严重的场景这个特性几乎是刚需。这篇文章适合两类人看一类是已经在做移动端自动化、想了解 Agent 框架怎么落地的测试开发另一类是手里有通用 Agent 框架、想把它用到测试场景但不知道从哪下手的工程师。我会把整个下沉过程中的设计取舍、关键实现、踩过的坑都摊开讲尽量让你看完能直接对着做。2. 为什么通用 Agent 框架不能直接拿来跑移动端测试2.1 通用框架和测试场景的根本差异通用 Agent 框架比如那些做浏览器操作、桌面操作的设计初衷是完成任务不是验证任务。这两者的差别非常大。完成任务只关心结果对不对中间怎么走的无所谓验证任务不仅要拿到结果还要能判断结果是否符合预期并且在不符合的时候给出可复现的证据。我一开始的想法很天真觉得把通用框架的 Action Space 换成移动端的点击、滑动、输入接上 ADB 或者 WebDriverAgent 就能跑。实际一跑就发现三个致命问题。第一个问题是状态不可控。通用 Agent 在网页上操作页面状态相对确定刷新一下就是初始态。移动端不一样App 有登录态、有缓存、有推送弹窗、有权限对话框你永远不知道点下去会冒出什么东西。Agent 如果没有处理这些“意外状态”的能力第一步就可能卡死。第二个问题是断言缺失。通用框架的输出通常是“任务完成”或“任务失败”但测试需要的是“预期结果 vs 实际结果”的结构化对比。你得让 Agent 在每一步都能产出可校验的中间状态而不是只有一个最终结论。第三个问题是可复现性差。模型每次规划路径可能都不一样今天这么点明天那么点。测试用例最忌讳的就是不确定性同样的输入必须得到同样的输出否则失败了都没法排查。2.2 下沉需要补齐的三块能力针对上面三个问题我在实际项目里给通用框架补了三块能力这也是整个下沉工作的核心。状态感知层在每次动作执行前先做一次“环境体检”。检查当前前台 Activity 是不是目标页面、有没有系统弹窗、有没有权限请求、网络是否可用。这一步用规则引擎做不交给模型因为规则判断快且确定。只有环境干净了才把控制权交给 Agent 做规划。结构化断言层把断言从“最后一步”拆到“每一步”。Agent 每执行一个动作都要输出一个中间状态快照页面元素树 截图 关键文本然后由断言模块去比对预期。这样即使最终任务失败了你也能知道是哪一步开始偏的。确定性约束层给 Agent 的规划加上约束。比如限定操作空间只能是当前页面可见的控件、限定输入内容必须来自预设的数据池、限定路径深度不超过 N 步。约束越强可复现性越好代价是灵活性下降。这个平衡点需要根据你的业务场景去调。提示不要一上来就追求全场景通用。先把一个高频、稳定的业务流程比如登录、下单跑通把确定性做扎实再逐步放开约束。我见过太多团队一上来就想做“万能测试 Agent”最后连登录都跑不稳。3. 移动端 Agent 测试框架的整体架构设计3.1 分层架构与职责划分整套框架我分成了五层从上到下依次是用例层、规划层、执行层、感知层、设备层。每一层的职责边界要划清楚否则后面调试会非常痛苦。层级核心职责关键组件是否用模型用例层描述业务意图、管理测试数据YAML/DSL 解析器否规划层把意图拆成可执行动作序列Agent Planner是执行层执行动作、处理异常、重试Action Executor否感知层获取页面状态、元素定位UI Tree 视觉识别部分设备层与真机/模拟器通信ADB / WDA / U2否这个分层的关键在于规划层和执行层分离。规划层只负责“想”输出的是抽象动作比如click(目标描述)执行层负责“做”把抽象动作翻译成具体的坐标点击或元素点击。这样做的好处是当执行失败时你可以选择是重新规划规划层的问题还是重试执行执行层的问题排查方向清晰。3.2 为什么选择“意图 DSL”而不是自然语言很多人第一反应是用自然语言写用例比如“打开 App点击登录输入用户名密码点确认验证进入首页”。听起来很美但实际用下来问题很多。自然语言太灵活同一个意思有无数种表达模型解析出来的动作序列每次可能都不一样可复现性直接崩掉。我最后选的是半结构化的意图 DSL。它长这样case: 新用户注册 steps: - intent: 打开应用并进入注册页 assert: 页面标题包含注册 - intent: 填写注册信息 data: ${register_data} assert: 提交按钮变为可点击 - intent: 提交注册 assert: 出现注册成功提示intent字段是给模型看的描述要做什么assert字段是给断言模块看的描述预期状态。模型只负责把 intent 翻译成动作序列不负责判断对错。这样既保留了灵活性又保证了断言的可控性。3.3 设备层的选型考量设备层我最终用的是UiAutomator2 WebDriverAgent 双引擎。Android 走 UiAutomator2iOS 走 WDA通过一层适配器统一接口。为什么不直接用 Appium因为 Appium 本身也是封装这两者中间多一层反而增加了不确定性而且 Agent 需要频繁获取页面元素树直接调底层 API 延迟更低。这里有个细节值得说元素树的获取频率。Agent 每执行一步都要拿一次页面状态如果每次都全量拉取在低端机上会明显拖慢速度。我的做法是增量拉取——只拉取当前可见区域和与上一步差异的部分实测下来单步耗时从 800ms 降到 200ms 左右。4. 核心实现从意图到动作的完整链路4.1 页面状态的结构化表示Agent 要能“看懂”页面前提是页面状态得有一个好的表示。我用的方案是元素树 视觉特征 语义摘要三合一。元素树来自 UiAutomator/WDA包含控件的层级、类型、文本、ID、坐标。但光有元素树不够因为很多控件在树里是空的比如自定义绘制的按钮这时候就要靠视觉特征补。视觉特征我用的是一个轻量级的图像编码模型把截图切成网格每个网格输出一个特征向量用来做相似度匹配。语义摘要是我额外加的一层用一个小模型把当前页面的关键信息压缩成一句话比如“这是一个登录页有用户名输入框、密码输入框、登录按钮和一个忘记密码链接”。这层摘要主要给规划层用让模型快速理解当前处境不用去啃整棵元素树。def get_page_state(device): tree device.dump_ui_tree() screenshot device.screenshot() visual_features encode_image(screenshot) semantic summarize_page(tree, screenshot) return { tree: tree, visual: visual_features, semantic: semantic, timestamp: now() }4.2 动作空间的裁剪与约束通用 Agent 的动作空间通常很大什么都能干。测试场景不需要这么大我把动作空间裁到了 8 个click、input、swipe、back、wait、assert、scroll_to、screenshot。每个动作都带约束。以click为例它的参数不是坐标而是目标描述。执行层拿到描述后先在元素树里找匹配的控件找不到再用视觉特征匹配还找不到就报错让规划层重新规划。这样做的目的是让动作和具体坐标解耦页面布局变了也不影响。def execute_click(target_desc, page_state): # 第一优先级元素树精确匹配 element match_by_tree(target_desc, page_state[tree]) if element: return device.click(element.bounds) # 第二优先级视觉特征匹配 region match_by_visual(target_desc, page_state[visual]) if region: return device.click(region.center) # 都失败抛出可重规划异常 raise ElementNotFound(target_desc)4.3 规划层的 Prompt 设计与输出约束规划层是整个框架里最需要调的地方。我试过很多版 Prompt最后稳定下来的结构是这样的系统提示定义角色移动端测试规划器、可用动作、输出格式JSON、约束条件。上下文当前页面语义摘要、上一步执行结果、剩余步骤预算。任务当前 intent 的描述。输出要求只输出一个动作不要输出解释。关键约束是一次只规划一步。早期我让模型一次输出整个动作序列结果它经常在第三步就规划了一个基于假设的页面状态实际页面根本不是那样。改成单步规划后每一步都基于真实页面状态成功率大幅提升代价是调用次数变多。为了平衡我加了一个缓存机制如果当前页面状态和上次遇到的一样直接复用上次的规划结果不再调模型。注意单步规划虽然稳但延迟高。如果你的用例步骤很多建议对稳定流程做规划缓存或者用一个小模型做快速规划、大模型做兜底。4.4 断言模块的实现细节断言模块我单独拎出来讲因为它是测试框架和通用 Agent 最大的区别。断言分三类文本断言检查页面上是否出现/不出现某段文本。这个最简单直接遍历元素树的文本字段。状态断言检查某个控件的状态比如是否可点击、是否选中、是否可见。视觉断言检查页面截图是否符合预期比如某个图标是否出现、布局是否错乱。这个用视觉特征做相似度比对阈值一般设在 0.85 左右太低会误报太高会漏报。def assert_text(expected, page_state, modecontain): texts extract_all_text(page_state[tree]) if mode contain: return any(expected in t for t in texts) elif mode exact: return expected in texts return False断言失败时框架会自动截图、保存元素树、记录当前步骤方便后续排查。这些证据会打包成一个报告直接附在测试结果里。5. 实操过程从零跑通一个注册流程用例5.1 环境准备与依赖安装先说环境。我用的是一台 Android 真机Android 11 一台 iOS 模拟器iOS 16Python 3.10 环境。核心依赖如下pip install uiautomator2 weditor pip install facebook-wda pip install opencv-python pillow pip install openai # 用于调用模型实际用你手上的模型服务Android 侧需要装 UiAutomator2 的 serveriOS 侧需要装 WebDriverAgent。这两步官方文档都有不展开。重点说一下设备连接稳定性真机长时间跑测试容易掉线我加了一个心跳检测每 30 秒 ping 一次设备掉线自动重连。5.2 用例编写与数据准备注册流程的用例我用 YAML 写数据单独放一个文件# register_case.yaml case: 新用户注册 steps: - intent: 打开应用并进入注册页 assert: type: text value: 注册 mode: contain - intent: 在用户名输入框填写用户名 data: ${username} assert: type: state target: 提交按钮 value: enabled - intent: 在密码输入框填写密码 data: ${password} - intent: 在确认密码框再次填写密码 data: ${password} - intent: 点击提交按钮 assert: type: text value: 注册成功 mode: contain# register_data.yaml username: test_user_${random_int} password: Test${random_int}数据里用了${random_int}占位符每次跑生成不同的值避免重复注册失败。这个细节很关键我一开始用固定用户名第二次跑就提示“用户已存在”排查了半天才发现是数据没清理。5.3 执行流程与关键日志执行入口很简单from agent_tester import Runner runner Runner( case_pathregister_case.yaml, data_pathregister_data.yaml, deviceandroid://emulator-5554, modelyour-model-endpoint ) result runner.run() print(result.summary())跑起来之后日志会输出每一步的规划、执行、断言结果。我截一段实际日志[STEP 1] intent打开应用并进入注册页 [PLAN] click(应用图标) - wait(2s) - click(注册入口) [EXEC] click 应用图标 ... OK [EXEC] wait 2s ... OK [EXEC] click 注册入口 ... OK [ASSERT] text contain 注册 ... PASS [STEP 2] intent在用户名输入框填写用户名 [PLAN] click(用户名输入框) - input(test_user_1234) [EXEC] click 用户名输入框 ... OK [EXEC] input test_user_1234 ... OK [ASSERT] state 提交按钮 enabled ... PASS ... [RESULT] 5 steps, 5 passed, 0 failed, total 42s整个流程跑下来 42 秒比手写脚本慢一些手写大概 25 秒但胜在页面改版不用改用例。我做过一个对比同一个注册流程页面改版后手写脚本要改 6 处定位符Agent 用例一行没改直接跑通。5.4 参数选择与性能调优几个关键参数我调了很久分享下最终值参数含义最终值调整理由max_steps单用例最大步数20超过 20 步基本是死循环了step_timeout单步超时15s低于 15s 容易误杀慢加载retry_times执行失败重试2重试太多会掩盖真实问题plan_cache规划缓存开关on稳定流程提速明显visual_threshold视觉匹配阈值0.85实测误报漏报平衡点step_timeout这个值特别重要。设太短页面加载慢的时候会误判失败设太长真卡死了要等很久。我的经验是设成页面平均加载时间的 3 倍你可以先跑几次统计一下。6. 常见问题与排查技巧实录6.1 典型问题速查表现象可能原因排查方向解决方式第一步就卡住环境不干净有弹窗看截图加环境体检先关弹窗元素找不到定位描述太模糊看元素树细化目标描述规划结果每次不同模型温度太高看规划日志温度调到 0.1 以下断言误报视觉阈值太低看比对分数阈值调到 0.85跑一半掉线设备连接不稳看心跳日志加自动重连用例越跑越慢缓存没命中看缓存日志检查页面状态哈希6.2 三个我踩过的坑坑一弹窗处理不能全靠模型。我一开始想让 Agent 自己识别并关闭弹窗结果它经常把业务弹窗当成系统弹窗关掉导致用例失败。后来改成规则优先维护一个系统弹窗的特征库权限请求、更新提示、广告命中就直接关没命中才交给 Agent 判断。坑二输入法会干扰输入操作。Android 上不同的输入法行为不一样有的会自动联想、有的会遮挡输入框。我的做法是测试前统一切换到系统自带输入法并且用set_text而不是模拟按键输入绕过输入法直接设值。坑三模型调用成本要控制。一个用例跑下来如果每步都调模型成本不低。我做了两件事一是规划缓存相同页面状态不重复调二是把简单动作比如back、wait用规则处理不走模型。实测下来模型调用次数减少了 60% 左右。6.3 稳定性提升的几个实操技巧技巧一给每个用例加前置清理。跑之前先force-stop应用、清缓存、重置权限保证每次从干净状态开始。这一步看起来多余但能避免 80% 的“偶发失败”。技巧二断言要分层。关键断言比如注册成功必须严格辅助断言比如按钮颜色可以宽松。全严格会导致误报多全宽松会漏掉真问题。技巧三失败自动录屏。断言失败时自动保存最近 10 秒的录屏比截图有用得多很多问题看录屏一眼就明白。技巧四用例分级。把用例分成 P0核心流程每次跑、P1重要流程每天跑、P2边缘场景每周跑。Agent 用例跑得慢全量跑不现实分级能保证核心流程的覆盖频率。7. 这套方案适合什么场景不适合什么场景7.1 适合的场景UI 变动频繁的业务比如电商大促期间页面天天改Agent 用例不用跟着改省下大量维护成本。跨机型兼容性测试同一套意图用例可以在不同机型上跑Agent 自己适配布局差异比写多套定位符省事。探索性测试给一个业务目标让 Agent 自己去探索路径能发现一些人工没想到的边界情况。长流程业务验证比如注册-登录-下单-支付这种跨多页面的流程Agent 的意图驱动方式比脚本更贴近真实用户行为。7.2 不适合的场景强性能要求的场景Agent 每步都要感知和规划延迟比脚本高做性能测试不合适。需要精确控制的场景比如测试某个动画的帧率、某个手势的精确轨迹Agent 的抽象动作做不到这个精度。纯接口测试没有 UI 的场景用 Agent 是杀鸡用牛刀直接写接口测试更高效。对确定性要求极高的场景比如金融核心流程的回归测试任何不确定性都不可接受还是老老实实写脚本。7.3 成本收益的实话实说这套方案不是免费的。模型调用有成本框架搭建有成本调试有成本。我的经验是用例数量超过 200 条、且 UI 月均变动超过 3 次的团队用 Agent 方案才划算。低于这个量级手写脚本加定期维护可能更省心。另外Agent 方案对团队的技术要求更高。你得有人懂模型调优、懂移动端底层、懂测试设计三者缺一不可。如果团队里只有纯功能测试直接上这套会很难维护。8. 后续可以继续深挖的几个方向这套框架我目前跑在三个业务线上稳定运行了几个月但还有不少可以优化的地方。多模态感知的增强现在视觉特征用的是轻量模型对复杂页面的理解还不够。后面想试试用更强的多模态模型做页面理解提升元素定位的准确率。用例自动生成现在是人工写意图用例后面想试试让模型根据业务文档自动生成用例草稿人工再审核。这样能把用例编写成本再降一个量级。失败自动归因现在失败还是要人工看日志。后面想做一个归因模块自动判断失败是环境问题、用例问题还是真实 bug直接给出结论。跨端统一现在 Android 和 iOS 是两套适配后面想抽象出更统一的设备接口让同一套用例真正跨端跑。我在实际使用中最大的体会是Agent 下沉到测试场景核心不是模型有多强而是约束设计得有多好。模型再聪明如果没有好的状态感知、结构化断言和确定性约束跑出来的结果就是不可信的。反过来约束设计到位了哪怕用中等能力的模型也能跑出稳定的结果。这个平衡点需要你在自己的业务里反复调没有通用答案。最后分享一个小技巧刚开始做的时候别急着上模型。先用规则把流程跑通把状态感知和断言模块做扎实等这些基础设施稳了再把规划层换成模型。这样出问题的时候你能快速定位是基础设施的问题还是模型的问题排查效率高很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑