Android真机测试大概是移动端开发里最没成就感又最逃不掉的一环。Google最近开源的ARTEMIS直接把AI Agent塞进了这个环节——让一个多模态大模型接管你的手机屏幕用自然语言描述任务由模型自己决定点哪里、输入什么、怎么验证结果。前阵子为了验收一个登录页的视觉改版我抱着真机在工位上点了二十多遍同一套流程启动、输入、登录、看首页、退出。改动明明只有按钮圆角和间距我却得把整条链路手工回归一遍因为没人敢赌那条跑了两年的UiAutomator脚本在改版后还能不能找到控件。这篇文章我会从它要解决的问题讲起拆一拆Agent到底是怎么看手机的再给出我踩过坑之后整理出的落地实践希望对做测试基建和移动端开发的团队都有点参考价值。1. 真机测试的旧账为什么靠脚本追不上App的迭代做过Android测试的人心里都有本账真机测试最花时间的部分永远不是跑而是维护和理解。手工回归一遍几百条用例看起来简单粗暴实际上每一个步骤都是人肉在跟系统弹窗、网络抖动、机型差异做博弈。而脚本自动化表面上把人从重复点击里解放出来却在另一个维度制造了新债务这个债务就是脚本本身。1.1 手工回归的账根本算不完一个中型电商类App每次发版前的基础回归至少覆盖登录、首页、搜索、商品详情、下单、支付回调、消息中心这几条主链路。按两条链路一个工程师来算全量走一遍大概需要两个工作日中间还要穿插换账号、清缓存、断网重连这些脏活。问题是这类手工回归每次都得从头再来没有任何一次执行能为下一次积累资产点错的概率还很高——我见过有人在回归时把测试账号的订单数量点成了几十单导致后续所有依赖空状态的用例集体翻车。真机之所以绕不开是因为模拟器覆盖不了几个真实场景推送通知到达后的跳转行为、不同厂商ROM对权限弹窗的特殊处理、弱网下的请求超时表现、以及手势导航与物理返回键的差异。这些恰恰是用户最容易感知到问题的部分。所以行业里嘴上说着模拟器优先实际上每个成熟团队都养着一排真机柜这就是真机测试的真实地位——又贵又重要。1.2 脚本自动化从人肉点变成代码点但脚本本身成了债传统UI自动化的思路很直接把人的点击序列录制成代码或者手工编写查找控件-执行动作-断言的步骤。问题在于这种方式要求你把每一步都写死。坐标脚本在换了分辨率之后整体偏移resource-id脚本在开发重构布局后全部失效文本断言在运营改了文案之后红灯一片。举个例子产品经理把立即购买改成马上抢一行文案的改动就能让一条用例挂掉。而且这种失败几乎没有信息量——脚本只知道找不到元素并不知道页面其实已经正确跳转只是按钮文案变了。于是测试团队陷入一个尴尬循环开发改一行测试改一段模板工程越来越大维护脚本的时间逐渐逼近甚至超过手工回归的时间。这不是工具不行而是思路有问题我们一直在教机器每一步怎么做却忘了真正想验证的其实是最终结果对不对。1.3 Agent的切入点把如何做交给模型把做什么留给人类ARTEMIS代表的思路和传统脚本有本质区别。它不再要求你预先描述每一步操作而是给定一个自然语言目标比如用测试账号登录并完成下单然后由模型自己观察屏幕、规划动作、执行操作、验证结果。遇到找不到按钮的情况Agent会自动尝试滑动、返回、等待重试而不是像脚本那样直接抛异常。这个转变改变了测试资产的形态。以前你维护的是几千行脆弱的选择器代码以后你维护的可能是一份几百字的任务描述和一套验收标准。脚本断裂是彻底停摆而Agent失败是局部修正后再试脚本的产物是红/绿两个状态Agent的产物是屏幕截图、操作轨迹和失败原因的完整证据链。当然把决策权交给模型也意味着引入了新的不确定性后面我会专门写这块的坑和成本控制。2. Agent眼中的Android真机视觉、控件树与语义意图的三层观察要让Agent接管真机第一步是解决它怎么看手机。这里不是单一看截图而是三层信息同时输入屏幕像素、可访问性控件树、以及你下达的任务语义。这三层各有所长也各有短板理解它们才能理解Agent为什么会犯某些错以及该怎么调。2.1 第一层屏幕像素——模型在看图绝大多数多模态大模型的第一步输入是当前屏幕截图。采集方式很简单通过adb拿到实时画面adb exec-out screencap -p screen.png如果要做连续操作用minicap这类工具做屏幕流式传输会更高效帧率和压缩比可以调节。拿到截图后框架通常会做缩放和压缩把图片控制在模型能接受的token预算内。为什么还需要像素层因为有些信息只有视觉能发现按钮圆角不一致、文字被遮挡、图标和文案语义不匹配、深色模式下对比度不足。这些视觉层面的缺陷控件树里看不出来纯脚本更不可能断言。但像素层也有明显的弱点。小字号文字识别不稳定深色壁纸和页面背景混淆时模型可能看错边界横竖屏切换时截图内容剧烈变化这些都会成为Agent误判的来源。所以成熟的设计不会只靠截图一定还会叠加第二层信息。2.2 第二层控件树——比人眼更稳的事实来源Android系统本身维护着一棵UI控件的可访问性层级树每个可见控件都记录了文本、描述、坐标范围、是否可点击、是否可滚动等属性。传统UiAutomator做的就是把这棵树导出成XMLnode index3 text登录 resource-idcom.demo:id/btn_login classandroid.widget.Button bounds[120,980][960,1180] clickabletrue/ARTEMIS这类Agent框架拿到这棵树的XML之后不会直接把原文丢给模型因为一颗真实页面的控件树动辄几百上千个节点token消耗撑不住。标准做法是解析XML滤掉不可见节点、非交互节点、屏幕外节点只保留可点击、可输入、可滚动的关键元素再压缩成精简JSON。这样做既保住了这一屏有哪些可操作的东西这个核心信息又把输入量降了一个数量级。控件树的另一个价值是稳定不同分辨率手机上同一个登录按钮的坐标可能完全不同但它的text属性始终是登录。所以Agent执行动作时应该尽量绑定控件而不是绑定坐标坐标只在控件不可用时作为兜底。这一点在后文讲分辨率适配时会重点展开。2.3 第三层语义理解——把描述落到控件上的Grounding有了截图和控件树接下来就是模型的语义理解环节。所谓Grounding指的是把任务描述里的抽象词映射到具体控件上。比如登录这个词在页面上可能同时对应一个按钮、一个输入框的Placeholder、甚至一个底部导航栏的Tab模型需要结合上下文判断该操作哪一个。这一步Agent输出的不是一串自然语言而是结构化指令方便框架直接执行。一次典型决策大概是这样的{ thought: 当前页面停留在一级页需要找到登录入口。按钮登录在屏幕中下部文案唯一直接点击。, action: tap, element: com.demo:id/btn_login, fallback_coordinates: [540, 1080] }当页面里出现两个相同文本的控件时比如购物车列表里的多个删除按钮模型就需要借助控件树里的index、资源ID、相邻控件内容来消歧。我见过不少Agent在这类场景翻车原因往往是任务描述太含糊模型不知道要删的是哪一条。所以后面讲任务描述时我会反复强调一个原则目标越具体Grounding越准。3. 从指令到动作一次登录下单任务的完整执行链路把三层观察拼起来Agent就进入了一个观察-思考-行动-再观察的循环业内一般叫ReAct模式。这个循环是AI Agent测试的核心引擎理解它你才能知道为什么一个任务有时快有时慢为什么建议把大任务拆小。3.1 任务下达与首轮规划假设你给ARTEMIS下达这样一个任务冷启动示例App用测试账号登录进入首页后搜索手机将第一个搜索结果加入购物车截图并返回首页。框架收到任务后模型会先做一个粗略规划冷启动、登录、搜索、加购、截图返回。这个规划可能不完整没关系真正执行时会根据观察到的页面不断修正。这里要提醒一下规划阶段模型不会真的去操作手机它只是生成一个待办列表。很多刚开始用Agent的人以为给一个目标它就会自己从头跑到尾实际上它每一小步都需要新的屏幕反馈来确认。这也解释了为什么任务描述里如果缺少当前处于什么状态这种起点信息Agent很容易在第一步就走错方向。3.2 逐帧执行的Agent循环真实执行过程可以归纳成五步每一轮循环模型只做一件事框架抓取当前截图和精简控件树模型根据任务目标分析当前页面状态模型输出一个结构化动作指令框架通过adb把动作执行到真机上等待界面空闲后再次抓取屏幕和控件树进入下一轮以登录环节为例Agent的第一轮可能是点击登录按钮第二轮发现出现账号和密码输入框第三轮分别填入测试账号与密码第四轮点击登录按钮第五轮等待跳转后断言首页是否出现用户昵称。每一步都有截图留痕一旦某一步断言失败我们拿到的不是一句测试失败而是一整条带着中间状态的操作轨迹。这种小步快跑的设计很有必要。模型是概率输出的如果让它一口气连续执行十个动作再统一验证任何一个环节偏差都会被后续动作放大最终结果根本无法定位问题出在哪儿。每步一验代价是多消耗几轮模型调用但换来的是可解释性和可调试性这笔账划算。3.3 找不到控件时的自愈策略传统脚本的失败模式是定位不到元素就抛出异常Agent则有一套自愈策略。常见的路数包括向上或向下滚动以试图让目标控件进入屏幕、点击返回键退回到上一层重新判断、等待几秒后重试以应对异步加载、或者尝试用坐标兜底点击。我在实践中发现Agent最常做的一个动作其实是滑动一下再看因为列表类页面目标元素常常在可视区域之外。但自愈不是无限制的。每个Agent框架通常都设置了最大重试次数和最大步数比如单任务最多30步、单动作最多重试3次。超过限制就必须标记失败并保存现场证据否则Agent会陷入死循环白白消耗模型额度。所以设计任务时我会在描述里显式写明如果在第X步仍未出现目标元素直接判定失败把不确定性控制在可接受的范围内。4. 本地复现把ARTEMIS跑到一台真机上需要准备什么说了这么多原理很多人最关心的是我能不能在自己电脑上把它跑起来。以我的实践来看把ARTEMIS接到一台真机的门槛其实不高甚至低于当年配置Espresso测试环境的复杂度。真正麻烦的反倒是跑通之后如何让它稳定这一节先讲环境准备和最小配置。4.1 环境清单硬件、系统与依赖我整理了一张最小环境清单按重要性排序项目建议配置说明手机Android 8.0及以上API 26以下机型在控件树导出上问题较多数据线支持adb直连的USB线不要用只充电的线排查半天以为是软件问题电脑能跑Android Studio即可Agent计算在云端/服务端本地负载不高adb最新版platform-tools负责截图、注入事件、控件树导出Python3.10及以上大多数Agent框架的运行时基础模型API支持图像输入的多模态模型这是唯一需要外部服务的部分模拟器也能跑但如果你要验证推送、权限、厂商ROM行为真机仍是必须。而且多厂商真机混用才是这套方案发挥价值的地方毕竟Agent的意义之一就是跨机型自适应。4.2 真机接入与基础配置连接真机后第一件事是打开开发者选项里的USB调试然后执行adb devices -l能看到设备串号就说明连接成功。接下来做三件小事关闭系统动画、保持屏幕常亮、防止锁屏。因为Agent依赖截图判断状态如果动画还在播放模型看到的可能是过渡帧点下去就会落空如果屏幕休眠截图就是黑的所有判断直接失效。建议直接执行这几条adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 adb shell svc power stayon true adb shell input keyevent KEYCODE_WAKEUP关闭动画这件事很多跑脚本的团队本来就在做但对Agent来说它不再只是提速手段而是正确性的前提这点务必重视。4.3 最小配置文件长什么样ARTEMIS这类Agent框架通常用配置文件来定义一次任务。以我本地跑的版本为例一份最小配置大概是这样的字段名以你拉下来的仓库最新代码为准task: 清除App数据后冷启动点击登录断言出现密码输入框 device_id: R5CR123456 model: provider: gemini model_name: gemini-2.0-flash temperature: 0.2 loop: max_steps: 30 max_retries: 3 idle_wait_ms: 500 output: screenshot_dir: ./artifacts report_path: ./report.jsondevice_id就是adb devices里看到的串号max_steps是整个任务的上限步数idle_wait_ms是执行动作后等待页面稳定的时间。temperature设为0.2是为了让模型尽量输出确定性动作Agent测试场景里我们不需要模型有创意我们需要它稳定复现。4.4 第一批跑通时最容易踩的坑第一次跑通的体验大概率是能跑但经常莫名其妙失败。我复盘下来主要三个坑一是完整的控件树XML巨大直接喂给模型会触发token限制必须先做筛选压缩二是任务描述写得太宽泛比如只写测一下登录页模型不知道要断言什么只能东点点西点点三是多设备并发时如果框架按设备串号做目录隔离截图和中间状态文件经常互相覆盖导致报告对不上设备。这三个问题每个都有成熟解法前两个分别是做UI树压缩和把任务描述改成起点动作验收三段式第三个则是在配置里强制按device_id分目录别偷懒。5. 从能跑到跑得稳任务描述、流程拆分与结果断言跑通只是第一步稳定性才是这个方案能不能进团队流程的关键。我在前两轮试用里踩过最多的坑几乎都出在任务描述不清楚和拆分粒度不对上这一节重点讲方法论。5.1 给Agent下任务的三段式口诀我给团队成员定了一个口诀起点状态 操作路径 验收标准。任何一条任务描述必须包含这三段信息缺一段就会在某个环节出幺蛾子。反面例子是检查订单流程。这条任务的问题在于起点未知是冷启动还是已登录路径含糊要走到哪一步验收标准缺失什么算通过。Model面对这种任务会自由发挥——今天它觉得该先看首页明天它觉得该直接搜商品结果就是同一份任务描述每次跑出不同的覆盖路径根本无法当作回归用例来用。正面例子应该是这样在设置中清除App数据冷启动进入首页用测试账号test001/123456登录在搜索框输入手机并提交点击第一个搜索结果选择红色M码加入购物车进入购物车断言总金额为299元全流程截图。这条描述虽然长但每个关键节点都有明确判断依据Agent能稳定复现同样的操作序列。5.2 复杂业务怎么拆成可回放的子任务很多团队一上来就想让Agent跑一条八步的大链路结果模型在第三步出错后后续所有步骤都建立在错误状态上根本无法回放。正确的做法是把一条大链路拆成多个独立子任务每个子任务自带启动条件。比如电商下单可以拆成子任务A注册新账号并登录、子任务B从首页搜索进入商品详情、子任务C加购并填写收货地址、子任务D提交订单并断言成功页。每个子任务之间不依赖上一个的残留状态而是通过清除App数据重置到首页这类显式步骤重新建立起点。这样任何一个子任务失败只需要重跑它自己而不是重跑整条链路。拆分还能直接控制成本。Agent每执行一步就是一次模型调用一条八步子任务可能消耗40到60次调用拆成四个两步子任务后失败的子任务单独重跑成功的不再重复花钱。在模型按量计费的现实下这个优化不是锦上添花是能算进月度账单的。5.3 Agent的断言不再是人肉检查点传统脚本的断言写在代码里等于提前把所有预期写死。Agent的断言方式更灵活也更多样常用的有这么几类状态断言检查关键文本或控件是否存在比如首页出现用户昵称。截图对比用视觉语言模型判断两张截图是否满足条件比如页面顶部出现红色横幅。这种方式要注意容差像素级比对在真机测试里基本不可用不同机型的渲染差异太大。日志断言通过logcat抓取目标进程的特定日志关键字验证后端调用是否被触发。比如下单成功后App通常会打一条ORDER_CREATE_SUCCESS的业务日志。数据断言任务完成后查测试环境数据库或接口验证订单、金额、状态是否符合预期。我个人的习惯是UI状态断言尽量做覆盖用户体验数据断言在涉及金额、库存等核心业务时必做因为Agent就算点对了按钮也可能因为环境数据脏而拿到错误结果。最后所有断言结果都要落到报告里至少包含步骤截图、模型thought、执行耗时这样测试失败时才有人能判断这是产品bug还是Agent自己犯傻。6. 真机上的脏活这些问题不解决Agent测试跑不起来如果说前几节是理论和方法这一节就是真正的实战。真机环境的复杂程度远超模拟器我在让Agent跑真实App时遇到过一堆模拟器里根本不会出现的问题这里挑最典型的五类展开。6.1 权限弹窗与首次启动引导不同Android版本对权限弹窗的处理完全不一样Android 6到12是运行时弹窗Android 13开始细分为通知、定位、摄像头等独立权限组。Agent跑到点击登录这一步突然跳出一个定位权限弹窗模型可能误以为这是登录流程的一部分顺手就点了拒绝后续所有依赖定位的页面全部出错。对策分两种。如果被测场景本身就是权限流程那就保留弹窗并且把权限行为写进任务描述如果场景不关心权限直接预授权绕开随机弹窗adb shell pm grant com.demo.app android.permission.CAMERA adb shell pm grant com.demo.app android.permission.ACCESS_FINE_LOCATION预授权属于给Agent一个干净房间但要注意这种做法掩盖了真实用户首启时的权限路径所以权限专项测试一定要单独留一条不走预授权的任务。6.2 启动页、广告与网络延迟的干扰真机上App冷启动通常会闪过启动页、开屏广告、首屏加载骨架图。Agent在启动App后立即抓屏看到的可能是广告于是它以为登录按钮不在开始漫无目的地点击广告区域——这是我在试跑中踩过最冤的坑甚至点进过广告落地页整个任务链直接废掉。解法一是在配置里给启动阶段加固定等待时间比如先sleep三秒再开始第一轮观察解法二是利用框架的waitForIdle能力等待页面主线程空闲后再截图解法三是把首屏稳定写进任务描述让模型自己判断什么时候页面真正加载完成。我实测下来三种结合最稳妥。弱网场景更麻烦请求超时后页面会弹网络异常的ToastAgent可能把Toast误认为是断言目标。所以涉及弱网的测试一定要搭配网络控制工具把网速降到一个可复现的档位而不是靠真实网络随机波动。6.3 分辨率与机型差异为什么坐标方案要不得坐标是Agent执行动作时最容易踩的深坑。同一款App在1080x2400和1440x3200的两台手机上同一个登录按钮的中心点坐标一个是(540,1080)另一个是(720,1440)。如果Agent输出的是绝对坐标换一台手机就偏了。更麻烦的是Android的导航栏、状态栏高度、屏幕圆角在不同厂商ROM里都不同底部按钮的实际位置差异比理论值还大。所以我的原则是所有动作优先绑定控件节点用控件的bounds在目标设备上实时计算可点击坐标只有控件树里找不到目标时才允许模型输出归一化坐标相对于屏幕宽高的比例最后在设备端换算成像素坐标。这样同样一份任务描述才能在跨机型池子里稳定复用。控件树给出的bounds本身就是那台设备上的绝对坐标执行时天然适配这就是我坚持用控件树作为主信息源的原因。6.4 WebView、Canvas与无障碍信息缺失的角落混合应用里的H5页面经常出现控件树信息缺失的情况——有些WebView没有正确暴露content-desc整棵子树里只有一堆没有文本的div节点。游戏类App的Canvas界面更极端整屏几乎没有可交互节点Agent只能靠纯视觉看图点坐标。这类视觉盲区场景下Agent会把主要希望寄托在像素层上但它识别小按钮很容易出错而且没有控件树做二次校验误点率显著上升。我的建议是不要指望Agent在这种场景下跑出高稳定性把它定位成快速探索而不是回归断言。同时反过来推动开发团队在自定义View里补上content-desc这不仅是无障碍规范的要求更是为AI测试铺路——让应用对机器的手语和对人的无障碍说明早日统一。6.5 数据状态污染一次登录状态毁掉整个测试序列真机测试最隐蔽的敌人是数据状态残留。上一轮任务登录过的账号还在登录态里这一轮任务一启动就发现已经在首页完全跳过了登录流程测试账号上一轮加购的商品还在购物车导致本轮加购断言的总金额对不上。这类问题在脚本时代也存在但Agent因为步子更自由更容易被残留状态带偏。标准做法是把每条任务做成可重置的任务开头统一执行清数据命令必要时重启Appadb shell pm clear com.demo.app同时为每条任务分配独立的测试账号账号之间不要共用任何订单、优惠券之类的业务数据。我在团队里立过一条规矩谁的任务里没有重置步骤谁的任务报告就不许进汇总这条规矩直接让整体成功率从六成左右提到了八成以上。7. 它到底取代谁与Espresso、UiAutomator的边界怎么划每次讨论AI Agent测试总有人问是不是以后不用写自动化脚本了。我的答案很明确短期不会也不应该。Espresso、UiAutomator和ARTEMIS这种Agent工具不是替代关系而是在测试金字塔里各管一段。7.1 三层自动化是互补而不是替代方案定位优点瓶颈Espresso白盒组件级测试毫秒级速度稳定能断言View内部状态需要源代码配合运行环境耦合UiAutomator黑盒脚本测试真机可跑选择器精确时非常稳定元素定位编写成本高UI改动即断AgentARTEMIS自然语言驱动流程测试自适应UI变化描述的是意图而非步骤速度慢单步成本高输出有概率性Espresso再快也测不到真实推送链路UiAutomator再稳也扛不住一周三次的UI改版Agent再聪明让它在高性能要求、毫秒级断言、海量数据比对的场景里成本上就是不划算。所以我建议的排布是核心逻辑用Espresso把底高频关键路径用UiAutomator保证回归速度Agent负责视觉改版验收、跨机型兼容冒烟、复杂用户旅程的快速探索。7.2 Agent适合接管的场景和应该绕开的场景以我目前的实践Agent最适合接管的场景有这么几类视觉改版后的整链路回归因为它不依赖具体选择器、新装机用户的首启引导流程天然适合自然语言描述、跨Android版本的兼容性冒烟同一份任务描述直接跑不同机型池、以及那些以前靠人肉才能完成的半探索式验收工作。反过来这几类场景现阶段应该绕开高频大规模回归几十条用例的成本会让账单失控、毫秒级性能断言Agent的单步决策太慢、纯接口或协议层测试它压根不需要看屏幕、以及像对账这种对确定性要求极高的业务模型再强也不该在这种场景当唯一裁判。把Agent当成带脑子的探索探针而不是万能自动测试机团队才能获得最大收益。7.3 团队落地路线从偶尔跑跑到进迭代流程想把这套东西真正落到团队里我建议分三步走。第一步选一条团队每周都要手工回归的核心链路写成任务描述每天跑一次产出报告给人看——这个阶段的目标是积累信任看看Agent的失败到底是产品问题还是任务描述问题。第二步把跑通的稳定任务沉淀成模板库把常见失败的样例补进任务描述里作为约束条件让模型的动作路径逐步收敛。第三步接多台真机做并行执行把Agent任务挂到发版验收列表里失败时自动保留截图和操作轨迹由测试人员做人工复审。我个人在这几轮实践里的体会是真机测试最大的成本从来不是执行而是人工理解发生了什么。一个人盯着日志排查半小时很可能只是为了确认是环境问题不是产品问题。ARTEMIS这类Agent工具真正替我们省掉的恰恰是这一段最磨人的工作——它先把操作做了再把证据链摆在你面前你只需要做最后的裁决。这个转变就是AI Agent在真机测试里最大的价值。