ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ARTEMIS:AI Agent驱动的Android真机UI自动化测试框架

ARTEMIS:AI Agent驱动的Android真机UI自动化测试框架

做 Android 测试这些年,我最深的感受就是:UI 自动化脚本写起来一时爽,维护起来火葬场。今天登录按钮的 resourceId 变了,明天“确认支付”文案改成了“立即支付”,后天某个页面加载多了一个弹窗,脚本就直接在 CI 里红灯躺平。所以当我看到 Google 开源 ARTEMIS,把 AI Agent 这套思路真正落到 Android 真机测试上时,第一反应是“终于有人想治这个老毛病了”。

ARTEMIS 的核心思路并不神秘:它把传统测试里“写死”的控件选择器和操作步骤,换成自然语言描述的目标,然后由一个多智能体框架在真实设备上自主感知、规划、操作、反思,最终完成测试任务。你可以把它理解成一个“长了眼睛和手指”的测试助手:眼睛负责看屏幕,手指负责点按滑动,大脑负责判断下一步该干什么。这对三类人最有价值——被 Flaky Test 折腾到没脾气的 QA 工程师、需要快速验证 App 稳定性的研发团队,以及做智能体应用开发、想找个真实硬件场景落地练手的同学。

这篇文章我会从框架设计、核心原理、实操流程和排坑经验四个维度展开,尽量还原我在本地环境把 ARTEMIS 跑起来、让它自主完成一个登录测试的完整过程。文章里涉及的命令和参数都基于常见实践,你可以当作一份补全版的实操笔记来参考。

1. ARTEMIS 到底在解决什么问题

1.1 传统 UI 自动化的“阿喀琉斯之踵”

先聊聊我们这群老测试踩过的坑。传统 UI 自动化测试的基本逻辑是:找一个元素、做一次操作、断言一个结果。听起来没问题,但实际执行时你会遇到一连串噩梦。

第一,元素定位极不稳定。Android 布局复杂,同样的按钮在不同版本系统上 resourceId 可能不同,层级深一点的页面用 XPath 定位又慢又脆。早期市面上很多框架都靠坐标点硬点,这种方案最致命——屏幕尺寸一变、安全区一改、广告一插,坐标直接偏到姥姥家。

第二,测试用例的表达成本高。你为了验证“用户点击忘记密码后能跳转重置页”,要写十几行甚至是几十行代码:等待控件出现、判断是否可点、处理各种异常弹窗、设置超时重试。这些代码大部分都是在对抗环境不确定性,而不是在描述业务逻辑。

第三,真机和模拟器行为差异明显。模拟器上动画渲染快、网络环境干净、系统控件一致,但真机上会有厂商定制 ROM、第三方输入法弹窗、网络抖动、权限弹窗、系统降频等因素。很多测试在模拟器上跑得绿油油,一上真机就原形毕露。

ARTEMIS 的设计初衷就是把这些问题一次性推开:不再把测试写成“确定性的步骤脚本”,而是交给 Agent 根据当前屏幕真实状态去动态决策。它不断读屏幕、做规划、执行动作、看结果,直到完成目标或达到终止条件。这正是“AI Agent 接管 Android 真机测试”这句话背后的含义。

1.2 从“写脚本”到“描述意图”的范式转换

如果你用过 Android 自带的 uiautomator,或者再熟悉一点 Appium,你可能习惯了这种写法:

# 传统 UI 测试的典型代码 driver.find_element_by_id("com.example:id/login_btn").click() driver.find_element_by_id("com.example:id/username").send_keys("test_user") driver.find_element_by_id("com.example:id/password").send_keys("123456") driver.find_element_by_id("com.example:id/submit").click() assert driver.find_element_by_id("com.example:id/home_page").is_displayed()

这段代码最大的问题是“只认死理”:一旦某个 id 变化,或者布局从 LinearLayout 换成 ConstraintLayout 后层级多了几层,元素查找就会失败。而 ARTEMIS 这类方案会让用户这样描述任务:

# 目标描述:在已安装的示例 App 上完成登录并进入首页 登录测试:使用用户名 test_user 和密码 123456 完成登录, 确保登录成功后能看到首页的主内容区域。

Agent 拿到这段描述后,会自己去识别当前屏幕上有哪些输入框和按钮,理解它们的语义,决定先点哪里、再输入什么、点击后会发生什么,并验证结果。本质上,你从一个命令行式的精确指令模式,切换到了“带人干活”的意图模式。指令模式下机器不懂变通,意图模式下机器会自己想办法。

这个转换对测试团队的意义很大:测试用例的维护成本被大幅压缩,你不再需要为“UI 调整后更新定位符”这件事花掉大量时间。当然,这不是说 AI Agent 可以完全替代人工设计用例,但它确实能把“重复执行、反馈验证”的脏活累活接过去。

1.3 为什么偏偏是“真机测试”

其实“真机”这个词不是营销包装。ARTEMIS 走的是真机或模拟器连接 Android 设备的方式,但特别强调真实设备环境的语义感知和操作真实感。

在真机环境中,你能拿到更真实的系统行为:真实渲染时机、真实网络栈、真实权限弹窗、真实厂商自定义组件。Agent 在面对这些状态时才会训练出更鲁棒的决策能力。如果在模拟器上过度过拟合,换到真机很容易立刻露馅。ARTEMIS 的框架在感知层面会实时获取 UI 层级和截图,在执行层面直接通过系统工具触发真实输入事件,这保证了它是“在真实系统里实际干活”,而不是在某种轻量化的 Mock 环境里自己跟自己玩。

2. 多 Agent 框架的核心设计

2.1 为什么把测试任务拆给多个 Agent

ARTEMIS 的框架结构一个核心特点就是多 Agent。初次接触的人会问:为什么不让一个 Agent 从头干到尾?问题在于,一个模型要把“看屏幕、做判断、动手操作、自我纠错”全都揽下来,上下文会迅速膨胀,而且很难分阶段调试。

你可以把多 Agent 理解成一个成熟的研发小组:产品经理负责拆解需求,开发负责写代码,QA 负责验证结果,组长负责最终拍板。每道工序独立,出问题能定位到具体环节。ARTEMIS 的做法也类似——感知、规划、执行、反思、评估各司其职,每个 Agent 只承担一部分职责,模型输出的解析和错误定位都更清晰。

这种设计也方便做“人在环路”的干预。比如执行 Agent 因为一个误判走到奇怪的页面,反思 Agent 会捕捉到异常并触发回退,不需要整个流程推倒重来。

2.2 感知 Agent 让模型真正“看见”屏幕

在真机测试里,模型要做的第一步是理解屏幕状态。Android 系统的 UI 层级可以用系统的 UI Automator 框架导出来,但原始 XML 非常臃肿:一个普通页面可能包含几百个节点,其中大量的是占位布局、不可见的 View、无语义的 FrameLayout。如果直接塞给大模型,既浪费 token,又容易让模型把注意力放在无关信息上。

所以 ARTEMIS 的感知 Agent 通常会做如下几步处理:

  • 利用adb exec-out uiautomator dump /dev/tty获取当前 UI 层级原始 XML。
  • 对 XML 做过滤去重,剔除不可见节点和冗余布局节点。
  • 把节点属性裁剪成紧凑的文本表示,例如只保留text、content-desc、resource-id、class和bounds这几个关键字段。
  • 把屏幕截图按比例压缩后作为图像上下文一起提供给视觉模型。

实际效果是:一个平常几百行甚至上千行的 XML,被压成一段只有几百字的结构化描述,上下文占用极大降低,模型注意力也集中了。

这一步是整个框架的地基。感知做得不够好,后面规划再厉害也是盲人摸象。我自己跑下来的体会是,压缩时一定要保留bounds,因为模型需要知道元素在屏幕上的位置,才能决定点击坐标;同时要标记不可见的节点和已离屏的节点,避免误操作。

2.3 规划、执行与反思的闭环

ARTEMIS 的行动层由一个闭环组成:规划 Agent 把主目标拆成可执行的小步骤,执行 Agent 把步骤落成具体系统动作,反思 Agent 在执行后检查页面变化是否与预期一致。

步进过程大致如下:

  1. 规划 Agent 基于当前屏幕摘要生成下一步动作描述,例如“点击登录按钮”或“在用户名输入框中输入 test_user”。
  2. 执行 Agent 将动作映射为 Android 实际操作的参数集合——要操作的控件 ID、点击的坐标、输入的文本内容等。
  3. 执行后重新触发感知 Agent,获得新屏幕状态。
  4. 反思 Agent 对比前后状态,判断动作是否生效:如果按钮没反应、弹窗意外出现,就生成纠偏动作。

这个闭环解决了传统自动化里的“状态漂移”问题。传统脚本是线性的,执行完一步就去走下一步,根本不检查中间状态对不对。ARTEMIS 是“感知-决策-执行-验证”的循环,每一步都有反馈,碰到意外弹窗能做到随机应变。这一点在真机上尤其关键,因为真实 App 里你永远预测不到弹窗、广告、推送提醒会从哪个角度冒出来。

我印象很深的一次是:Agent 执行登录点击时,系统弹出“允许 XX 获取定位权限”的弹窗。传统脚本大概率直接卡死,但 ARTEMIS 的反思 Agent 在感知到新页面后立刻识别出这是一个权限弹窗,并自动规划出“点击允许”的动作,测试得以继续。虽然这只是一个很细的场景,但它充分说明了反馈闭环的价值。

2.4 评估 Agent 与终止条件

一个无限循环的 Agent 是不可接受的。ARTEMIS 设计了评估 Agent 来负责“何时收手”。评估的逻辑通常从两个维度出发:目标完成度和资源消耗。

目标完成度指当前屏幕状态是否满足任务描述里定义的验收条件。比如任务是“完成登录并进入首页”,那评估目标就是看首页的核心组件是否可见——这可能是一个特定的控件 ID,也可能是一段页面内容描述。资源消耗则包括步数上限和累计时长:系统会设定最大执行步数,超过步数仍然无法完成任务,就自动终止并返回失败报告。

终止后,生成一份总结,涵盖执行过程、关键截图、动作序列和失败原因。这份总结可以直接接入测试报告系统,也可以作为后续调优的输入。我建议你在实际项目中把 Agent 的完整“思考过程”也一并记录,因为当你需要排查为什么某次测试失败时,回看感知 Agent 当时“看到了什么”比一切日志都有说服力。

3. 技术链路细节与工具选型

3.1 连接与设备管理

ARTEMIS 操作 Android 真机,底层基本都绕不开 ADB。无论项目最终用 Python、Java 还是 Node.js 封装,第一步必然是把设备通过 ADB 建立连接。

# 查看当前连接的设备 adb devices # 在 USB 连接真机时的常用调试选项开关 # 开发者选项中开启 USB 调试 # 部分机型还需要关闭“仅充电模式下允许 ADB 调试”的开关

如果测试量上来了,我建议加一个设备管理模块,把多台真机、多个模拟器统一纳入队列调度。这个模块可以主动检查设备在线状态、App 安装状态、屏幕亮灭状态,避免测试跑到一半发现设备已经掉线了。实际操作中,手机息屏是高频事故,记得在执行前用adb shell input keyevent KEYCODE_WAKEUP唤醒屏幕,再用adb shell wm dismiss-keyguard解锁。

设备连接是整套环境最基础也最容易出问题的一环。很多人以为 AI Agent 能解决所有问题,实际上它只负责“思维决策”,手和脚还是需要 ADB 这套基础设施老老实实送上去。

3.2 从 UI XML 到语义摘要的加工流程

前面讲过感知 Agent 会做 XML 压缩,这里我展开说一下加工流程的细节,因为这一步决定了模型能不能准确理解屏幕。

第一步是去除噪音节点。Android 的 View 层级里有很多纯容器节点,比如FrameLayout、LinearLayout,它们只做布局排布,没有交互语义,完全可以丢弃。还有一些节点虽然存在,但实际不可见,比如宽高为 0、透明度为 0、或者被移出屏幕外,也需要过滤。

第二步是字段裁剪。原始 XML 每个节点包含一堆属性:text、content-desc、resource-id、class、package、checkable、clickable、focusable、enabled、bounds等等。对 LLM 来说,很多布尔属性价值不大,可以省去,只保留对决策最有用的:文本内容、描述信息、资源 ID、控件类型和边界坐标。

第三步是转成紧凑文本。我习惯输出这样的格式:

[0] Button 登录 (id: com.example:id/btn_login, bounds: [720,1600][960,1760], clickable=true) [1] EditText 用户名 (id: com.example:id/et_username, bounds: [360,900][1080,1100], focused=true)

每个节点一行,前面带索引,后续执行时直接引用索引即可。这个格式的好处是模型不必解析复杂的嵌套 XML,只要看一行行的结构化文本就能建立屏幕心智模型。视觉模型如果支持图像输入,就把截图一起送进去,某些时候文本和图像互相印证,能显著提升识别准确率。

3.3 行动投递:Agent 如何把意图变成手指

规划 Agent 生成的是自然语言动作,比如“点击登录按钮”。但设备需要的是具体系统指令。这里就是执行 Agent 和 Android 底层交互的接口。

常见动作投递方式有三种:

  • adb shell input tap x y:直接按坐标点击,最简单,但依赖感知阶段给出的 bounds 坐标。
  • UIAutomator 框架:通过UiDevice.findObject(By.res(...))等方式按控件属性操作,稳定性更高。
  • AccessibilityService 无障碍服务:可以读取更丰富的节点信息,并模拟全局操作,适合处理跨 App 场景。

ARTEMIS 风格的框架通常会同时保留坐标点击和语义定位两种渠道,并设置降级策略:优先按资源 ID 定位,定位失败再退回到坐标点击。这个降级逻辑非常实用,因为有些三方自定义控件根本不暴露 ID,你只能靠坐标。

输入文本时要注意输入法干扰。建议先设置 ADB 层面的 IME(输入法),或用adb shell input text配合处理中文文本时的转义问题。中文字符输入在部分系统上会有编码坑,稳妥做法是使用剪贴板方案:先把文本写入剪贴板,再通过长按粘贴触发。我在实际项目中遇到过不少中文输入失败的问题,改剪贴板方案后基本绝迹。

3.4 与 LLM 交互的策略与底线

ARTEMIS 这类项目,核心大脑是 LLM。实际操作中要关注几个策略问题。

第一,模型选择。视觉理解任务对模型要求不低,建议使用具备视觉能力的模型,因为截图里的视觉信息密度的确比纯文本 UI 树更高。如果团队对成本敏感,也可以考虑把任务拆开:感知阶段用视觉模型,规划阶段用纯文本模型,降低整体费用。

第二,输出格式约束。让模型返回 JSON 可以显著提升解析稳定性,例如统一要求它输出“action”、“target”、“params”三个字段。如果模型偶尔返回了不合法 JSON,需要设计解析容错机制,比如用正则兜底提取动作类型,而不是直接让整个流程崩溃。

第三,上下文窗口管理。长时间执行数百步后,对话历史会变得非常长。ARTEMIS 在实际设计中会做历史摘要:让反思 Agent 定期把前面若干步关键信息压缩成一段摘要,替换掉冗长的原始历史。这个技巧对防止长任务失控至关重要,否则模型到后面可能已经被陈年信息刷到忽略当前屏幕状态。

第四,安全边界。Agent 在真机上自动操作是件危险事——万一它点进了支付流程、点到了删除按钮,后果不堪设想。所以框架要定义“高危险动作清单”,比如涉及支付、删除、系统设置变更等操作,一律要求二次确认或直接禁止。在测试环境里跑可以大胆,但生产环境的真机大规模部署一定要有这层护栏。

4. 实操复盘:跑一个登录测试用例

4.1 测试目标与初始环境

为了讲清楚整个执行链路,我拿一个典型场景来复盘:在示例 App 上完成一次登录操作,要求用户名输入test_user,密码输入123456,点击登录后进入首页,并验证首页顶部标题可见。

测试环境:一台 Android 12 真机,ADB 已连接,示例 App 已安装。我用的模型方案是云端的视觉多模态模型,并在本地跑起 ARTEMIS 框架的组件编排。

任务描述直接写成自然语言:

目标:在示例 App 中完成登录。 步骤要求:用户名输入 test_user,密码输入 123456,点击登录按钮。 验收条件:登录成功后首页顶部出现“首页”标签或等价的核心界面元素。

4.2 第一次感知:Agent 看到了什么

感知 Agent 执行了 UI 层级 dump,压缩之后,页面摘要大致长这样:

[0] TextView 欢迎使用示例App (bounds: [360,400][1080,600]) [1] EditText 用户名 (id: com.example.sample:id/username, bounds: [180,800][900,1000], focused=true) [2] EditText 密码 (id: com.example.sample:id/password, bounds: [180,1050][900,1250]) [3] Button 登录 (id: com.example.sample:id/login_btn, bounds: [180,1400][900,1580], clickable=true)

这段摘要虽然简单,但足够让规划 Agent 做出判断:页面有两个输入框和一个登录按钮,目标明确,不需要扩张页面或切换 Tab。

这里有个细节值得注意:感知阶段如果发现目标应用还没启动,Agent 会自动用adb shell am start或monkey唤起应用。ARTEMIS 框架一般允许你预设一个“启动策略”,指定包名和入口 Activity,避免 Agent 每次从桌面开始盲找应用图标,浪费大量步数。

4.3 执行循环:从规划到动作落地的每一步

第一次规划 Agent 的输出是:

{ "action": "input_text", "target": "1", "params": {"text": "test_user"} }

执行 Agent 拿到这个 JSON,会在 UI 摘要里找到索引为 1 的节点,也就是用户名输入框,先点击它获得焦点,再通过 ADB 输入方法写入了test_user。随后感知阶段刷新,确认输入框内容已更新。

第二步类似地输入密码。这个阶段最大的坑是密码输入框的text字段不会显示明文,因为系统为保护密码输入,UI 层级里通常只有password=true而不回填真实字符。这时候依赖文本反推是不靠谱的,必须信任执行 Agent 的写入动作是否成功,或者干脆用节点里的focused状态和截图来判断。

第三步是点击登录按钮。点击后,感知 Agent 立刻重新 dump UI,发现页面正在跳转。刷新后页面变成了首页,顶部出现了一个标题“首页”。反思 Agent 将这前后状态对比,确认登录动作生效,不再需要额外操作。最后评估 Agent 判定“验收条件满足”,整个任务结束,耗时几十秒,执行步数只有几步。

这个流程看起来好像也没什么神奇的,但关键是:整个过程中没有任何一个步骤是靠写死的定位符完成的。页面结构变化后,只要语义信息还在,Agent 依然能通过文本、组件类型或上下文理解来完成任务。

4.4 复盘的几点产出

跑完这个用例,我提炼了几条经验,值得所有想用这类框架的人参考:

  • 任务描述越清晰,Agent 效率越高。特别是验收条件,不能只写“登录成功”,要写清楚“首页顶部出现标题”,否则 Agent 可能逛到别的页面也误判成功。
  • 感知频率要控制。每执行一步就重新 dump 整个 UI 很耗时,真机上通常需要一两秒。可以在关键动作之后(点击、输入、返回)再触发感知,而不是无脑每一步都刷。
  • 截图配合文本能救场。有些时候 UI 树里没有足够语义信息(比如 Canvas 绘制的自定义图表、图片按钮),这时候视觉模型的截图理解能力比纯文本可靠得多。我实测下来,多模态方案对复杂页面的理解成功率明显更高。

5. 落地使用时的常见问题与排查速查

5.1 设备连接不稳与离线问题

真机跑久之后,ADB 偶尔会掉线,表现就是adb devices找不到设备,或者unauthorized。排查顺序如下:先重新插拔 USB,或执行adb kill-server && adb start-server重置服务;再检查手机上“允许 USB 调试”的授权弹窗是否被误点拒绝;如果用的是无线调试,注意同一局域网和端口是否变更。大规模跑测建议写一个设备健康检查脚本,每轮任务开始前都检查设备状态。

5.2 UI 层级过大导致模型注意力分散

某些复杂页面 dump 出来的 XML 有上千个节点,压缩后仍然很长。建议多轮过滤:第一轮去掉纯布局容器,第二轮去掉 bounds 面积过小的节点,第三轮去掉text、content-desc和resource-id全部为空的白板节点。如果页面确实复杂但必要信息集中在某个区域,可以考虑让感知 Agent 只截取屏幕半区,减少上下文压力。

5.3 模型输出不规则导致的解析失败

LLM 经常会在 JSON 输出外面加说明文字,或者把布尔值写成字符串。我建议所有动作指令都走一个“宽容解析器”:先尝试标准 JSON 解析,失败后用正则把action、target等字段捞出来。如果解析连续失败三次,就触发“重新规划”而不是直接失败,因为有时候是模型在表达歧义,换个思路重新生成一条动作即可。

5.4 输入框焦点与输入法干扰

自动化输入最常见的坑是点了输入框但没有真正获得焦点,导致文本写不进去。常见对策是在输入动作前先执行一次adb shell input tap命中输入框坐标,等 200 毫秒,再执行文本输入;中文文本使用剪贴板方案可避免编码问题。此外,App 里如果自定义了输入面板,可能需要先关闭系统输入法弹窗再截图,否则输入法遮挡了下半屏,感知 Agent 容易漏掉部分控件。

5.5 权限弹窗和系统级中断

真机测试时权限弹窗、Wi-Fi 提示、系统更新提醒都可能随时打断测试。我建议框架内置一个“异常弹窗守卫”:当感知 Agent 识别到非目标页面的 Dialog 时,先尝试用系统返回键或点击“取消/允许”按钮来处理。但是“允许”还是“拒绝”不能一刀切,需要根据弹窗文案做语义判断,比如定位权限弹窗对测试目标无碍时可以点“仅在使用中允许”。

5.6 超时与步数上限设置建议

步数上限设太短,Agent 还没完成探索就被判死刑;设太长,失败任务会白白耗掉大量 token。我自己的习惯是:普通页面流程上限设为 20 到 30 步,多页面复杂流程设为 50 步左右,同时加一个全局执行时长上限,防止单个测试无限挂机。每次失败后把 Agent 的执行轨迹存下来,观察它是卡在感知还是规划还是执行,再做针对性调优。

这类 AI 驱动的测试框架还有个特点:它不是一次性就能稳定的,需要跑几轮积累失败样本,人工看几遍执行轨迹,调整提示词和判断规则,效果才会越来越好。第一轮跑通的成功率可能只有六成,等到你把它喂过几十个真实测试场景后,成功率会明显上一个台阶。

把 ARTEMIS 接入团队的日常测试流程,不需要一下子铺到全量用例。我个人建议从最痛的那部分开始,比如兼容性冒烟测试、关键路径回归测试、或者以前最容易因为 UI 变动而红的用例。跑通一个再扩一个,比一把梭要稳得多。真机上的状态复杂度摆在那里,AI Agent 不是银弹,但它确实把“能不能自动跑”这件事的门槛大幅降低了。你可以先下载开源代码,拿一台测试手机,从最简单的登录用例开始感受一下整个感知-规划-执行闭环,再决定在哪个场景深入落地。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进