1. 真机测试这件事,为什么一直是个"脏活累活"
做过 Android 自动化测试的人都有一个共识:模拟器跑得再顺,一上真机就原形毕露。厂商 ROM 差异、后台进程抢占、权限弹窗、网络抖动、系统版本碎片化——这些问题在模拟器里根本复现不出来,但到了真机上,脚本挂掉的方式能有一百种。
传统方案无非两条路。一条是写 UI Automator 或者 Espresso 脚本,硬编码控件 ID 和坐标,App 一改版就全废;另一条是用 Appium 这类跨端框架,靠 accessibility 树去定位元素,稳定性比硬编码好一些,但遇到 WebView、Flutter 自绘控件、游戏引擎渲染的界面,照样抓瞎。更麻烦的是,真机测试往往需要人工盯着,脚本报错了得有人判断"这是真 bug 还是定位失效",一来二去,自动化测试反而变成了"自动化 + 人工兜底"。
Google 开源的 ARTEMIS 想解决的正是这个断层。它不是又一个 UI 测试框架,而是一个让 AI Agent 直接"接管"Android 真机测试的系统。你给它一个测试目标,比如"验证登录流程在弱网下是否正常",它会自己看屏幕、自己点、自己判断结果,遇到异常还能自己调整策略。这背后的核心思路是:把"看屏幕—理解界面—决策操作—验证结果"这个闭环交给多模态大模型来做,而不是靠预先写死的选择器。
这篇文章我会从 ARTEMIS 的架构设计讲起,拆解它怎么通过 MCP 协议连接真机、AI Agent 怎么做界面理解和决策、实际部署时有哪些坑,最后给出一套可以照着搭的最小可用方案。不管你是测试工程师、Android 开发者,还是正在研究 AI Agent 落地的同学,应该都能从中拿到能直接用的东西。
2. ARTEMIS 的架构拆解:它到底怎么"接管"一台真机
2.1 从"脚本驱动"到"目标驱动"的范式转换
理解 ARTEMIS 的第一步,是搞清楚它和传统自动化测试的根本区别。传统脚本是过程驱动的:你先告诉机器"点这个按钮,等 2 秒,再点那个输入框,输入用户名",机器照做。ARTEMIS 是目标驱动的:你告诉它"完成登录并验证首页加载成功",它自己决定点哪里、等多久、怎么判断成功。
这个转换听起来简单,实现起来涉及三个层面的重构。第一层是感知层,Agent 需要实时获取设备屏幕的视觉信息和 UI 层级结构,不能只靠 accessibility 树,因为很多关键信息(比如自绘控件的文字、图片里的提示)在树里是拿不到的。第二层是决策层,Agent 要根据当前屏幕状态和目标,推理出下一步动作,这需要多模态模型同时理解截图和文本。第三层是执行层,Agent 要把决策翻译成具体的 adb 指令或者设备操作,并且处理执行失败后的重试和回退。
ARTEMIS 在这三层之上还加了一个记忆层,记录已经尝试过的操作和结果,避免在同一个死循环里打转。这个设计很关键,因为真机测试里最常见的失败模式就是"Agent 反复点同一个没反应的按钮",没有记忆机制的话,token 烧完了问题还在原地。
2.2 MCP 协议在中间扮演了什么角色
ARTEMIS 用 MCP(Model Context Protocol)来连接 AI Agent 和 Android 设备。MCP 本质上是一套标准化的"工具调用"协议,让模型能够以统一的方式调用外部能力。在 ARTEMIS 的场景里,Android 设备被封装成一个 MCP Server,对外暴露一系列工具方法,比如:
| 工具方法 | 作用 | 对应底层操作 |
|---|---|---|
get_screen | 获取当前屏幕截图和 UI 层级 | adb screencap + uiautomator dump |
tap | 点击指定坐标或元素 | adb shell input tap |
swipe | 滑动操作 | adb shell input swipe |
input_text | 输入文本 | adb shell input text |
get_package | 获取当前前台应用包名 | adb shell dumpsys |
launch_app | 启动指定应用 | adb shell am start |
这样设计的好处是解耦。AI Agent 不需要知道 adb 命令怎么写,它只需要知道"我有一个 tap 工具,参数是坐标"。换一台设备、换一个 Android 版本,只要 MCP Server 的实现适配好,Agent 的逻辑完全不用改。这也是为什么 ARTEMIS 能同时支持真机和模拟器——底层差异被 MCP 层屏蔽了。
提示:MCP 协议本身不绑定任何特定模型,你可以用 Gemini、GPT-4o,也可以接本地部署的多模态模型。ARTEMIS 默认配置用的是 Google 自家的模型,但接口是开放的。
2.3 多模态理解:Agent 的"眼睛"是怎么工作的
Agent 要操作界面,首先得"看懂"界面。ARTEMIS 的做法是把屏幕截图和 UI 层级树一起喂给多模态模型。截图提供视觉信息,UI 树提供结构信息,两者互补。
举个例子,一个登录页面,截图里能看到"用户名"和"密码"两个输入框,但模型不一定知道哪个是哪个。UI 树里会有resource-id、text、bounds这些属性,模型结合两者就能准确定位。ARTEMIS 在 prompt 里会把 UI 树转成一种紧凑的文本格式,类似:
[0] EditText text="用户名" bounds=[100,400,900,480] id="com.example:id/username" [1] EditText text="密码" bounds=[100,520,900,600] id="com.example:id/password" [2] Button text="登录" bounds=[100,640,900,720] id="com.example:id/login_btn"模型看到这个结构,就能推理出"要登录,先点 [0] 输入用户名,再点 [1] 输入密码,最后点 [2]"。如果某个元素没有 id 也没有 text(比如纯图标按钮),模型就靠截图里的视觉位置来判断。这种"结构 + 视觉"的双通道输入,是 ARTEMIS 比纯 accessibility 方案稳得多的原因。
2.4 决策循环:一次测试任务的生命周期
一个完整的 ARTEMIS 测试任务,大致经历这几个阶段:
- 任务解析:把自然语言描述的测试目标拆解成可执行的子步骤。比如"验证登录流程"会被拆成"打开 App → 输入用户名 → 输入密码 → 点击登录 → 检查是否进入首页"。
- 状态感知:调用
get_screen获取当前屏幕状态。 - 动作决策:模型根据当前状态和目标,输出下一步动作(调用哪个工具、参数是什么)。
- 执行与观察:执行动作,再次获取屏幕状态,判断动作是否生效。
- 结果验证:对照预期结果,判断测试通过还是失败。
- 异常处理:如果动作没生效或者出现意外弹窗,Agent 需要决定是重试、回退还是标记失败。
这个循环里最考验工程能力的是第 4 步和第 6 步。真机上经常出现"点了按钮但界面没变"的情况,可能是网络慢、可能是按钮被遮挡、也可能是点击坐标偏了。ARTEMIS 的处理策略是:动作执行后等待一个短时间,再次截图对比,如果界面没变化就重试,重试超过阈值就换策略(比如改用坐标点击而不是元素点击)。
3. 把 ARTEMIS 跑起来:环境搭建与最小可用配置
3.1 硬件和软件的前置条件
在动手之前,先把环境理清楚。ARTEMIS 对环境的要求不算苛刻,但有几个点容易踩坑。
硬件侧:一台 Android 真机(建议 Android 10 以上,太老的版本 uiautomator dump 会有兼容问题),一根质量靠谱的 USB 线。我强烈建议不要用那种几块钱的杂牌线,真机测试里因为线材导致 adb 断连的情况太常见了,排查起来还特别费劲。
软件侧:
- Android SDK Platform Tools(主要是 adb 和 fastboot)
- Python 3.10+(ARTEMIS 的 MCP Server 部分依赖 Python)
- 一个支持多模态的模型 API Key
- Node.js 18+(如果你要用 Playwright MCP 做 WebView 部分的测试)
设备侧:开发者选项打开,USB 调试打开,Stay awake打开(防止测试中途锁屏)。如果是小米、华为这类 ROM,还要额外打开"USB 调试(安全设置)"允许模拟点击,否则 adb 的 input 命令会被拦截。
3.2 adb 环境的三分钟自检
环境搭好后,先做一轮自检,别急着上 ARTEMIS。打开终端:
# 检查 adb 是否可用 adb version # 检查设备是否连接 adb devices # 如果能列出设备,测试一下截图 adb exec-out screencap -p > test.png # 测试 UI 层级导出 adb shell uiautomator dump /sdcard/ui.xml adb pull /sdcard/ui.xml .这四步能跑通,说明基础环境没问题。如果adb devices显示unauthorized,去手机上确认授权弹窗;如果显示offline,拔插一下 USB 或者adb kill-server && adb start-server。
注意:
uiautomator dump在某些 App 上会失败,报ERROR: could not get idle state。这是因为界面一直在动(比如有动画或者轮播),uiautomator 等不到空闲状态。解决办法是加--compressed参数,或者用adb shell uiautomator dump --compressed /sdcard/ui.xml。
3.3 MCP Server 的配置与启动
ARTEMIS 的 MCP Server 是连接 Agent 和设备的桥梁。配置的核心是告诉 Server:设备怎么连、暴露哪些工具、超时怎么设。
一个典型的配置长这样:
{ "mcpServers": { "android-device": { "command": "python", "args": ["-m", "artemis.mcp.android_server"], "env": { "ANDROID_SERIAL": "你的设备序列号", "SCREENSHOT_QUALITY": "80", "ACTION_TIMEOUT": "5000", "MAX_RETRY": "3" } } } }几个参数值得说明。ANDROID_SERIAL在多设备场景下必须指定,否则 adb 会随机选一台。SCREENSHOT_QUALITY控制截图压缩质量,设太高会让模型推理变慢(图片 token 多),设太低模型看不清小字,80 是个比较平衡的值。ACTION_TIMEOUT是单个动作的超时时间,真机上建议不要低于 5000ms,因为有些操作(比如启动大型 App)确实慢。
启动 Server 后,可以用 MCP 的调试工具测试一下工具是否正常暴露。如果用的是支持 MCP 的客户端,直接看工具列表里有没有get_screen、tap这些方法。
3.4 第一个测试任务:让 Agent 打开设置并读取系统版本
理论说再多不如跑一个。我们用一个最简单的任务验证整条链路:让 Agent 打开系统设置,找到"关于手机",读出 Android 版本号。
任务描述可以写成:
打开系统设置应用,进入"关于手机"页面,读取并报告 Android 版本号。Agent 的执行过程大致是:
- 调用
launch_app,包名com.android.settings - 调用
get_screen,看到设置首页 - 分析界面,找到"关于手机"入口(可能在"系统"二级菜单下)
- 调用
tap点击,如果没找到就滑动屏幕继续找 - 进入关于手机页面后,读取版本号文本
- 输出结果
这个任务看起来简单,但能验证感知、决策、执行、验证四个环节是否都通了。如果 Agent 卡在某一步,日志里会显示它当时的屏幕状态和决策理由,方便定位问题。
4. 真机测试里那些"只有踩过才知道"的坑
4.1 弹窗地狱:权限、更新、广告一个都躲不掉
真机测试和模拟器最大的区别之一,就是真机上会冒出各种模拟器里没有的弹窗。App 首次启动要权限、系统要更新、厂商 ROM 要推广告、输入法要切换——这些弹窗会打断 Agent 的操作流,让它"看不懂现在在哪"。
ARTEMIS 的处理方式是在决策循环里加一个"异常弹窗检测"步骤。每次get_screen之后,Agent 会先判断当前屏幕是不是预期页面,如果不是,就尝试识别弹窗类型并关闭。常见的弹窗处理策略:
| 弹窗类型 | 识别特征 | 处理动作 |
|---|---|---|
| 权限请求 | 包含"允许"、"拒绝"按钮 | 点击"允许"(测试场景下通常都允许) |
| 系统更新 | 包含"稍后"、"更新" | 点击"稍后" |
| 广告弹窗 | 有关闭图标,通常在右上角 | 点击关闭图标坐标 |
| 输入法切换 | 键盘弹出但输入框未聚焦 | 按返回键收起键盘 |
这里有个经验:不要试图用一套规则覆盖所有弹窗。厂商 ROM 的弹窗千奇百怪,硬编码规则永远追不上。更好的做法是让 Agent 自己判断——把弹窗截图喂给模型,问它"这是什么弹窗,应该怎么处理",让模型给出决策。ARTEMIS 默认就是这个思路,规则库只作为兜底。
4.2 元素定位失效:为什么"看得见"却"点不着"
这是真机测试里最让人抓狂的问题。屏幕上明明有个按钮,Agent 也识别出来了,但tap执行后没反应。原因通常有三类:
第一类,坐标偏移。uiautomator dump拿到的 bounds 是逻辑坐标,但adb shell input tap用的是物理坐标。如果设备有显示缩放或者导航栏,两者会对不上。解决办法是用adb shell wm size和wm density换算,或者直接用input tap的相对坐标。
第二类,点击被拦截。有些 App 会在按钮上盖一层透明 View 做埋点或者防抖,点击事件被这层 View 吃掉了。这种情况 Agent 需要改用input swipe模拟短滑动,或者用 accessibility 的performAction直接触发点击。
第三类,界面还没稳定。Agent 截图的时候按钮还在动画中,等它点击的时候按钮已经移走了。解决办法是在动作之间加一个"界面稳定检测"——连续两次截图差异小于阈值再执行动作。
实操心得:我一般会在 MCP Server 里加一个
wait_for_stable工具,Agent 在关键操作前先调用它。这个工具的实现逻辑是:每隔 500ms 截一次图,连续三次截图相似度超过 95% 就认为界面稳定了。这个简单的机制能消掉一大半的"点不着"问题。
4.3 Token 消耗:真机测试是个"烧钱"的活
多模态模型的调用成本不低,尤其是截图这种大 token 输入。一次完整的登录测试,如果每步都截图 + 推理,轻松烧掉几十万 token。ARTEMIS 在这方面做了几个优化:
- 截图压缩:默认把截图压到 720p 以下,模型看界面布局够用了,不需要 4K 原图。
- UI 树裁剪:只把可见区域的元素喂给模型,屏幕外的元素不传。
- 决策缓存:相同界面状态下的决策结果缓存起来,下次遇到同样界面直接复用,不再调模型。
- 分级模型:简单决策(比如"点这个明显的按钮")用小模型,复杂决策(比如"判断这个报错是什么意思")才用大模型。
实测下来,一个中等复杂度的测试用例(10 步左右),优化前大概消耗 15 万 token,优化后能压到 3 万以内。这个差距在批量跑测试的时候非常可观。
4.4 测试结果的可信度:Agent 说"通过"就真的通过了吗
这是 AI Agent 做测试最容易被质疑的点。Agent 判断测试通过,依据是"当前屏幕符合预期",但屏幕符合预期不等于功能真的正常。比如登录后进入了首页,但首页数据是缓存的旧数据,Agent 看不出来。
ARTEMIS 的应对是多维度验证。除了看屏幕,还会结合:
- 日志验证:抓
adb logcat里的关键日志,确认没有异常堆栈。 - 网络验证:抓包确认请求真的发出去了,返回码正常。
- 数据验证:如果测试涉及数据写入,直接查数据库或者调 API 确认数据落库了。
这三层验证加上屏幕验证,可信度就高多了。当然,配置起来也更复杂,需要根据具体测试场景来定。
5. 从单点测试到批量回归:ARTEMIS 的工程化落地
5.1 测试用例怎么组织才不会被 Agent "理解偏"
ARTEMIS 的测试用例是自然语言写的,这既是优点也是坑。优点是写起来快,不用学新语法;坑是自然语言有歧义,Agent 可能理解偏。
我的经验是,测试用例要遵循"目标明确、步骤可验证、边界清晰"三个原则。对比一下:
| 写法 | 问题 | 改进 |
|---|---|---|
| "测试登录功能" | 太笼统,Agent 不知道测什么 | "用正确账号密码登录,验证进入首页且用户名显示正确" |
| "输入用户名密码然后点登录然后看看" | 口语化,步骤不清晰 | "在用户名框输入 testuser,密码框输入 Test1234,点击登录按钮" |
| "验证各种异常情况" | 边界不清,Agent 会发散 | "分别用空用户名、错误密码、未注册账号登录,验证错误提示正确" |
另外,用例里最好显式写出预期结果,不要指望 Agent 自己推断。比如"点击登录后应在 3 秒内跳转到首页,首页顶部显示'欢迎回来'"就比"点击登录"好得多。
5.2 批量执行的调度与隔离
单个用例跑通了,接下来就是批量回归。这里有几个工程问题要解决:
设备隔离:多个用例并行跑的时候,每台设备要独立,不能互相干扰。ARTEMIS 支持通过ANDROID_SERIAL指定设备,配合设备池管理,可以做到用例级别的设备分配。
状态重置:每个用例开始前,设备要恢复到干净状态。常用的做法是adb shell pm clear <包名>清数据,或者用adb shell am force-stop强杀 App。如果测试涉及系统设置,可能还需要恢复出厂设置或者用快照回滚。
失败重试:真机测试的 flaky 率比模拟器高,同一个用例可能第一次失败第二次通过。ARTEMIS 支持配置重试次数,但要注意区分"真失败"和"flaky 失败"——如果重试三次都失败,基本可以判定是真 bug 了。
结果聚合:批量跑完要出一份报告,包含每个用例的通过/失败状态、失败时的截图和日志、Agent 的决策轨迹。这份报告是给开发看的,所以要尽量还原现场。
5.3 和 CI/CD 的对接方式
ARTEMIS 要真正发挥价值,得接进 CI/CD 流程。常见的对接方式有两种:
一种是定时触发,比如每晚跑一轮全量回归,第二天早上出报告。这种方式适合稳定期,测试用例变动不频繁。
另一种是提交触发,每次代码合并到主分支就跑一轮核心用例。这种方式反馈快,但对测试环境的稳定性要求高,设备得随时可用。
对接的技术细节上,ARTEMIS 本身是个命令行工具,CI 里直接调就行。关键是要处理好设备连接——CI 机器上插一堆真机不现实,通常用 USB Hub 或者网络 adb 来管理设备池。网络 adb 的配置是adb tcpip 5555然后adb connect <设备IP>:5555,但要注意设备 IP 可能会变,需要配合 DHCP 静态分配或者 mDNS 发现。
注意:网络 adb 的稳定性不如 USB,尤其是在 Wi-Fi 信号差的环境下。如果测试对稳定性要求高,还是老老实实用 USB 连接,配合 USB Hub 扩展。
5.4 成本与收益的平衡点在哪
最后聊一个现实问题:ARTEMIS 这套方案值不值得上。我的判断标准是看测试用例的维护成本和执行频率。
如果你的 App 界面经常改版,传统脚本每周都要修选择器,那 ARTEMIS 的价值就很大——它靠视觉和语义定位,改版后大部分用例不用改。如果你的 App 界面很稳定,传统脚本半年不用动,那 ARTEMIS 的模型调用成本反而是负担。
执行频率也是关键。一天跑一次和一天跑一百次,成本差两个数量级。高频场景下,建议把 ARTEMIS 用在核心链路上(登录、支付、下单),边缘用例还是用传统脚本或者人工。
从我的实际经验看,一个 20 人左右的测试团队,如果 App 有 200+ 回归用例,界面每月都有改动,上 ARTEMIS 大概能省掉 30%-40% 的用例维护时间,模型成本大概相当于一个初级测试工程师月薪的 1/5 到 1/3。这个账算下来是划算的,但前提是团队得有工程能力把 MCP Server 和设备池维护好,不然省下的维护时间又搭进环境问题里了。
6. 写在最后:AI Agent 做测试的边界在哪
跑了一段时间 ARTEMIS 之后,我对"AI Agent 接管真机测试"这件事有了更具体的认知。它确实能解决传统自动化测试的一部分痛点——尤其是界面改版导致的脚本失效、复杂界面的元素定位、以及需要"看一眼判断"的验证场景。但它不是银弹。
Agent 目前最不擅长的,是需要精确数值验证和长链路状态追踪的场景。比如"验证转账后余额精确减少 100 元",Agent 能操作界面,但让它去核对数字容易出错,这种还是得靠 API 层断言。再比如跨多个页面的状态流转,Agent 的记忆机制还不够可靠,容易在中途"忘记"之前做了什么。
我的建议是混合使用:核心链路的 UI 操作交给 ARTEMIS,数据验证和逻辑断言交给传统测试代码,两者通过测试框架串起来。这样既拿到了 AI 的灵活性,又保住了传统测试的确定性。ARTEMIS 的 MCP 接口是开放的,完全可以和现有的 pytest、JUnit 体系集成,不用推倒重来。
真机测试这个领域,短期内不会被 AI 完全替代,但会被 AI 深刻改变。会用 Agent 的测试工程师,和只会写脚本的测试工程师,效率差距会越拉越大。早点上手,比观望强。