1. 移动端自动化测试的现状与ARTEMIS的切入点
移动端自动化测试这件事,做过的人都懂——它不像后端接口测试那样干净利落,也不像Web端那样有成熟的DOM结构可以依赖。一台真机摆在面前,屏幕分辨率、系统版本、厂商定制ROM、输入法弹窗、权限弹窗、网络切换、来电打断……任何一个变量都能让一条昨天还跑得好好的用例今天直接挂掉。更别提现在App里大量使用自绘UI、Flutter、React Native、游戏引擎渲染,传统的基于控件树的定位方式经常抓瞎。
而大语言模型(LLM)的介入,给这个领域带来了一个全新的思路:不再依赖精确的控件ID和层级路径,而是让模型像人一样“看”屏幕、“理解”界面、“决定”下一步操作。这个方向这两年涌现了不少探索性工作,ARTEMIS就是其中一个比较有代表性的尝试。它做的事情,简单说就是:把移动端GUI自动化的“感知-决策-执行”闭环,用LLM重新组织了一遍,并且提出了一个双模式架构来应对不同复杂度的任务场景。
这篇文章我会从架构层面把ARTEMIS拆开来讲,重点放在它的双模式设计到底解决了什么问题、两种模式各自的适用边界在哪里、以及如果我们要自己复现或借鉴这套思路,需要注意哪些工程细节。适合已经在做移动端自动化、想了解LLM如何落地到GUI操作场景的测试开发同学,也适合对多模态Agent感兴趣但还没找到具体切入点的工程师。
2. 为什么移动端GUI自动化需要“双模式”
2.1 单模式架构的三个死结
在ARTEMIS出现之前,大多数LLM驱动的GUI Agent都采用单一模式:把当前屏幕截图丢给多模态模型,模型输出一个动作(点击坐标、输入文本、滑动方向),执行后再截图,循环往复。这个模式听起来很直接,但实际跑起来会撞上三堵墙。
第一堵墙是推理成本与延迟。每一步都调用一次多模态大模型,一次截图编码加上推理,动辄几秒。一个包含20步操作的测试用例,光模型推理就要花掉一两分钟。如果测试套件里有上百条用例,这个时间成本是不可接受的。而且很多步骤其实是“傻瓜操作”——比如点击一个明确的按钮、在输入框里填一段文本,这些根本不需要大模型来“思考”。
第二堵墙是长程任务的上下文爆炸。移动端一个完整的业务流程可能涉及几十步操作,如果每一步都把完整截图和历史动作序列塞进上下文,token消耗会迅速膨胀。更麻烦的是,当上下文过长时,模型对早期步骤的记忆会衰减,容易出现“忘记自己已经登录过了”“重复点击同一个按钮”这类问题。
第三堵墙是错误恢复能力弱。单模式架构下,如果某一步点击偏了、弹窗没关掉、页面加载慢了,模型往往缺乏足够的“元认知”来判断当前状态是否异常。它只会基于当前截图继续输出下一个动作,而不是回头检查“我上一步的操作到底生效了没有”。
2.2 双模式的核心设计哲学:分而治之
ARTEMIS的双模式架构,本质上是对任务复杂度做了一次分层。它把操作分成两类:一类是确定性操作,比如“点击坐标(x,y)”“输入文本‘hello’”“按下返回键”,这些动作的语义明确、执行结果可预期;另一类是探索性操作,比如“找到登录入口”“判断当前页面是否加载完成”“决定下一步该点哪里”,这些需要理解界面语义、做判断和规划。
对应地,ARTEMIS设计了两条路径:
- 快速模式:面向确定性操作,走轻量级的执行链路,不调用重型多模态模型,或者只调用一个小的视觉定位模型来完成“元素定位”这一件事。
- 深度模式:面向探索性操作,调用完整的多模态LLM,进行界面理解、任务规划和异常判断。
这个设计的精妙之处在于,它不是一个静态的开关,而是一个动态路由机制。系统会根据当前任务的状态、历史执行情况、界面变化程度,来决定这一步走快速模式还是深度模式。简单页面、明确目标,走快速通道;页面结构复杂、目标模糊、或者前一步执行失败,自动升级到深度模式。
2.3 两种模式的边界划分与切换逻辑
那具体怎么判断该走哪条路?ARTEMIS的切换逻辑大致基于几个信号:
| 判断信号 | 走快速模式 | 走深度模式 |
|---|---|---|
| 当前步骤是否有明确的目标元素描述 | 是,且元素在屏幕中可见 | 否,或元素不可见需要滚动查找 |
| 上一步操作是否成功执行 | 成功且页面已稳定 | 失败、超时或页面未变化 |
| 当前页面是否出现过 | 是,已有缓存的元素定位信息 | 否,首次遇到该页面 |
| 任务剩余步骤是否明确 | 是,有预定义的步骤序列 | 否,需要模型动态规划 |
| 界面变化幅度 | 小,局部刷新 | 大,整页跳转或弹窗覆盖 |
这个表格是我根据ARTEMIS论文和类似系统的常见实践整理的,实际实现中每个信号的权重和阈值需要根据具体App和测试场景调优。但核心思想是一致的:能用便宜方式解决的,绝不调用昂贵模型;只有真正需要“理解”的时候,才升级到深度模式。
注意:快速模式和深度模式的划分不是绝对的。有些系统会把“元素定位”也交给一个小型的视觉模型(比如基于YOLO或Grounding DINO的检测器),这样快速模式本身也具备一定的视觉理解能力,只是不做任务级规划。
3. ARTEMIS双模式架构的模块拆解
3.1 感知层:截图、控件树与视觉特征的融合
ARTEMIS的感知层不是单纯依赖截图,也不是单纯依赖控件树,而是做了多源信息的融合。这一点很关键,因为移动端自动化的一个核心痛点是:控件树经常拿不到有效信息(自绘UI、WebView、Flutter),而纯视觉方案又缺乏精确的文本和层级信息。
它的感知层大致输出三类数据:
- 原始截图:用于多模态模型的视觉理解,以及视觉定位模型的输入。
- 控件树(Accessibility Tree):通过系统提供的无障碍服务获取,包含控件的类名、文本、content-desc、bounds等属性。这部分信息在原生控件上非常可靠,但在自绘UI上可能为空或只有最外层容器。
- 视觉特征向量:对截图做轻量级处理(如边缘检测、文本区域检测、图标检测),提取出候选的可交互区域。这部分用于在控件树失效时做兜底。
这三类数据会一起送入后续的决策模块。快速模式下,系统优先使用控件树中的bounds信息来定位元素;如果控件树中找不到目标,则退而使用视觉特征向量做匹配。深度模式下,截图和控件树摘要会一起送给LLM,让模型综合判断。
3.2 决策层:快速模式与深度模式的调度器
决策层是ARTEMIS的大脑,也是双模式架构的核心实现处。它包含一个调度器和两个执行器。
调度器的输入是当前任务状态(剩余步骤、当前步骤描述、历史执行记录)和感知层输出。它输出一个决策:这一步走快速模式还是深度模式。调度器的实现可以是一个规则引擎,也可以是一个轻量级分类模型。ARTEMIS论文中采用的是规则+启发式的方案,因为规则引擎的可解释性更强,调试起来也方便。
快速执行器的职责很聚焦:给定一个目标元素描述(比如“登录按钮”“用户名输入框”),在感知层输出中找到对应的元素,返回其坐标或操作句柄。它不负责理解页面整体语义,也不做任务规划。实现上,它可以是一个基于控件树文本匹配的查找器,也可以是一个小的视觉定位模型。
深度执行器的职责则宽泛得多:它接收完整截图和任务上下文,输出一个结构化的动作指令,可能包含“先滚动找到某个元素”“点击某个区域”“输入文本”“等待页面加载”等。它本质上是一个多模态Agent,需要具备一定的推理和规划能力。
3.3 执行层:动作原语与反馈采集
执行层负责把决策层输出的动作指令转化为真实的设备操作。ARTEMIS定义了一套动作原语,包括:
tap(x, y):点击坐标tap_element(element_id):点击某个已定位的元素input_text(text):在焦点输入框中输入文本swipe(direction, distance):滑动back():返回键home():Home键wait(ms):等待assert(condition):断言
执行层在完成动作后,会采集反馈信息:操作是否成功执行、页面是否发生变化、是否有弹窗出现、当前前台Activity是什么。这些反馈会回传给调度器,用于判断下一步的模式选择。
实操心得:动作执行后的“页面是否变化”判断,不能简单用截图像素差。因为有些页面有动画、有倒计时、有视频播放,像素差会一直很大。更可靠的方式是结合控件树的结构变化和前台Activity的变化来判断。如果控件树根节点的子节点数量或关键属性变了,或者Activity切换了,才认为页面发生了实质性变化。
3.4 记忆层:短期上下文与长期经验库
ARTEMIS还有一个容易被忽略但很重要的模块:记忆层。它分两部分:
- 短期记忆:当前测试用例的执行历史,包括每一步的截图摘要、动作、结果。这部分用于深度模式下的上下文构建,让模型知道“我已经做了什么”。
- 长期记忆:跨用例积累的页面元素定位信息、常见弹窗处理策略、页面跳转关系。这部分用于加速快速模式,比如某个页面的“登录按钮”坐标已经被记录过,下次直接查表即可,不需要重新定位。
长期记忆的引入,让ARTEMIS在重复执行相似测试用例时,速度会越来越快。这也是它区别于一次性Agent的重要特征。
4. 快速模式的实现细节与性能优化
4.1 元素定位的三种策略
快速模式的核心任务是“给定描述,找到元素”。ARTEMIS实现了三种定位策略,按优先级依次尝试:
策略一:控件树精确匹配。在Accessibility Tree中查找text、content-desc、resource-id与目标描述匹配的节点。匹配支持精确匹配、包含匹配和正则匹配。这是最快、最准的方式,但前提是App的无障碍信息完整。
策略二:控件树模糊匹配。当精确匹配失败时,对目标描述做分词和同义词扩展,然后在控件树中做模糊匹配。比如目标描述是“登录按钮”,而控件树中有一个按钮的text是“登 录”,模糊匹配可以命中。
策略三:视觉模板匹配。当控件树中完全找不到时,使用预存的元素截图模板,在当前屏幕截图中做模板匹配。这种方式适合图标按钮、自绘控件。模板可以来自长期记忆库,也可以由深度模式在首次遇到时生成并存入。
三种策略的耗时依次递增,但都远低于调用多模态LLM。实测下来,策略一通常在50ms内完成,策略二在200ms内,策略三在500ms左右。而一次多模态LLM调用通常需要2-5秒。
4.2 缓存机制与失效策略
快速模式之所以快,缓存功不可没。ARTEMIS的缓存分两级:
- 页面级缓存:以“前台Activity + 页面结构哈希”为key,缓存该页面上所有已定位元素的坐标。下次进入同一页面时,直接查缓存。
- 元素级缓存:以“元素描述 + 页面key”为key,缓存元素的定位策略和坐标。当页面结构发生小幅度变化时,元素级缓存可以配合相对坐标做微调。
缓存失效的策略也很重要。ARTEMIS在以下情况会主动清除或更新缓存:
- 页面结构哈希发生显著变化(控件树节点数量变化超过30%)
- 元素定位连续失败两次
- 前台Activity切换
- 屏幕方向或分辨率变化
注意:缓存不能过度依赖。我见过一些实现为了追求速度,缓存了过于激进的坐标信息,结果App一次小版本更新后,所有缓存全部失效,反而导致大量误点击。建议缓存时同时记录元素的多个属性(text、content-desc、相对位置),失效判断综合多个信号。
4.3 快速模式的适用边界与误判处理
快速模式不是万能的。它的适用边界很明确:目标元素在当前屏幕可见,且描述足够具体。如果目标元素需要滚动才能看到,或者描述本身模糊(比如“那个提交按钮”,页面上有三个提交按钮),快速模式就会失效或误判。
误判的处理机制是:快速模式执行动作后,调度器会检查执行结果。如果发现点击后页面没有按预期变化,或者出现了异常弹窗,调度器会将这一步标记为“快速模式失败”,然后自动升级到深度模式重新执行。这个降级机制保证了系统的鲁棒性。
5. 深度模式的推理链路与Prompt工程
5.1 多模态输入的组织方式
深度模式的核心是把“当前屏幕状态”和“任务上下文”组织成多模态LLM能理解的输入。ARTEMIS的输入组织方式大致如下:
- 系统提示词:定义模型的角色(移动端GUI操作助手)、可用的动作空间、输出格式要求。
- 任务描述:当前测试用例的目标,比如“完成登录流程并进入首页”。
- 执行历史:最近N步的动作和结果摘要,N通常取5-10,避免上下文过长。
- 当前截图:经过压缩和标注的屏幕截图。标注包括:在截图上叠加控件树的bounds框和编号,方便模型引用。
- 控件树摘要:对控件树做剪枝,只保留可交互节点和文本节点,以结构化文本形式呈现。
这种多源输入的组织方式,让模型既能“看”到视觉信息,又能“读”到结构信息,比纯截图输入的效果好很多。
5.2 动作空间的约束与输出解析
深度模式的输出必须严格约束在预定义的动作空间内,否则解析会非常麻烦。ARTEMIS要求模型输出JSON格式的动作指令,比如:
{ "action": "tap", "target": {"type": "element", "id": 3}, "reason": "点击登录按钮进入登录页面" }或者:
{ "action": "swipe", "direction": "up", "reason": "向上滚动查找更多选项" }输出解析器会校验action字段是否在允许列表内,target是否有效。如果解析失败,会触发重试或降级到规则兜底。
实操心得:Prompt里一定要给模型“拒绝执行”的选项。有些时候当前页面状态异常(比如网络错误页、崩溃弹窗),模型强行操作只会让情况更糟。让模型可以输出
{"action": "abort", "reason": "..."},然后由上层决定是重试还是标记用例失败。
5.3 长程任务的上下文压缩策略
深度模式在长程任务中最大的挑战是上下文长度。ARTEMIS采用了两种压缩策略:
策略一:滑动窗口 + 摘要。只保留最近K步的完整信息,更早的步骤压缩成一段文字摘要,比如“已完成:打开App、点击我的、点击登录、输入用户名”。摘要由模型自己生成,或者用规则模板生成。
策略二:关键帧保留。对于页面发生重大跳转的步骤,保留其截图和控件树摘要作为关键帧;对于同一页面内的连续操作,只保留最后一步的完整信息。
这两种策略结合使用,可以把一个50步的任务上下文控制在模型窗口的合理范围内。
6. 双模式协同的工程实现与调优
6.1 模式切换的触发条件与优先级
模式切换的触发条件需要仔细设计,否则会出现“该快的时候不快,该深的时候不深”的问题。ARTEMIS的触发逻辑大致是:
默认走快速模式:如果当前步骤有明确的目标元素描述,且该元素在缓存或控件树中可定位,直接走快速模式。
以下情况强制走深度模式:
- 当前步骤是任务的第一步或最后一步(需要理解整体状态)
- 上一步快速模式执行失败
- 当前页面首次出现,且缓存中没有该页面的任何信息
- 任务描述中包含“判断”“确认”“如果”等需要推理的关键词
- 检测到异常弹窗或页面状态异常
以下情况优先走快速模式:
- 当前页面已有缓存,且缓存未失效
- 当前步骤是重复性操作(如连续输入多个表单字段)
- 上一步深度模式已经明确了当前页面的操作路径
6.2 性能与准确率的平衡调参
双模式架构的调参目标是在保证准确率的前提下,尽可能提高快速模式的占比。ARTEMIS论文中给出的经验值是:在典型测试用例中,快速模式占比70%-85%是比较健康的区间。如果快速模式占比过低,说明缓存和定位策略需要优化;如果过高但准确率下降,说明快速模式的判定条件太宽松。
调参时建议关注几个指标:
| 指标 | 含义 | 健康范围 |
|---|---|---|
| 快速模式占比 | 快速模式执行步数 / 总步数 | 70%-85% |
| 快速模式成功率 | 快速模式中无需降级的比例 | >90% |
| 深度模式调用频率 | 每用例平均深度模式调用次数 | 3-8次 |
| 平均单步耗时 | 从截图到动作执行完成 | 快速<1s,深度<6s |
| 用例通过率 | 端到端测试通过率 | >95% |
这些指标需要在实际项目中持续监控和调整。
6.3 异常场景下的降级与恢复
异常场景是移动端自动化的常态。ARTEMIS在双模式框架下,设计了几种降级和恢复策略:
- 快速模式定位失败:自动升级到深度模式,由模型重新理解页面并给出操作。
- 深度模式输出无效:重试一次,如果仍无效,则回退到规则兜底(比如点击屏幕中心、按返回键)。
- 连续多步失败:暂停当前用例,截图保存现场,标记为“需人工介入”,然后重置App状态继续下一条用例。
- 弹窗干扰:检测到系统弹窗(权限、更新、广告)时,优先走快速模式中的弹窗处理规则库,如果规则库中没有匹配,再交给深度模式判断。
注意:弹窗处理是移动端自动化最烦人的问题之一。建议在长期记忆库中专门维护一个弹窗规则库,记录常见弹窗的关闭按钮位置和文案特征。这个库越丰富,快速模式的处理能力就越强。
7. 常见问题与排查技巧实录
7.1 快速模式定位不准的排查思路
快速模式定位不准,通常有几个原因:
- 控件树信息缺失:App使用了自绘UI或Flutter,Accessibility Tree中只有最外层容器。排查方法是打印控件树,看目标元素是否存在。如果不存在,需要依赖视觉定位策略。
- 文本匹配规则太严格:目标描述和控件文本存在大小写、空格、标点差异。排查方法是打开模糊匹配日志,看匹配得分。
- 缓存过期:页面结构已变化,但缓存未失效。排查方法是检查页面结构哈希的计算逻辑,看是否覆盖了关键属性。
- 多元素匹配冲突:页面上有多个相似元素,定位到了错误的那个。排查方法是输出所有匹配元素的坐标和属性,人工确认。
7.2 深度模式“胡说八道”的抑制方法
深度模式偶尔会输出看似合理但实际错误的操作,比如点击一个不存在的元素、输入错误的文本。抑制方法包括:
- 加强输出约束:在Prompt中明确要求模型只能引用截图中标注过的元素编号,不能凭空捏造。
- 后置校验:对模型输出的坐标做范围校验,对元素编号做存在性校验。
- 多步一致性检查:如果模型连续两步的操作逻辑矛盾(比如先输入文本又立刻清空),触发人工确认。
- 温度参数调低:深度模式的temperature建议设置在0.1-0.3,减少随机性。
7.3 双模式切换频繁抖动的解决
模式切换抖动是指系统在快速模式和深度模式之间反复横跳,导致性能下降。解决方法:
- 增加切换冷却时间:一次深度模式调用后,至少接下来2步优先尝试快速模式。
- 引入滞后判断:不要因为单次快速模式失败就立刻升级,而是连续两次失败才升级。
- 页面级模式锁定:对于已知结构稳定的页面,强制走快速模式;对于已知复杂的页面,强制走深度模式。
7.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 点击无反应 | 坐标偏移、元素被遮挡 | 截图标注点击位置 | 校准坐标、增加等待 |
| 输入文本失败 | 输入框未聚焦、输入法拦截 | 检查焦点状态 | 先点击输入框再输入 |
| 页面加载判断错误 | 动画干扰、异步加载 | 检查控件树变化 | 改用Activity+关键元素判断 |
| 弹窗未处理 | 规则库缺失 | 截图记录弹窗 | 补充弹窗规则 |
| 深度模式超时 | 模型响应慢、上下文过长 | 检查token数 | 压缩上下文、换小模型 |
| 用例间状态污染 | App未重置 | 检查App状态 | 每用例前重启App或清数据 |
8. 从ARTEMIS架构看LLM移动端自动化的落地建议
如果你打算在自己的项目中借鉴ARTEMIS的双模式思路,我的建议是:不要一上来就追求全链路LLM化。先把快速模式做扎实——控件树解析、元素定位、缓存机制、弹窗规则库,这些是地基。地基打好了,深度模式只需要处理那些真正需要“理解”的少数场景,整体系统的稳定性和性能都会好很多。
另一个体会是,长期记忆库的价值被很多人低估了。一个持续积累的页面元素库和弹窗规则库,能让快速模式的覆盖率随着时间推移不断提升。我见过一个团队,他们的自动化系统跑了三个月后,快速模式占比从最初的40%提升到了85%,靠的就是长期记忆库的积累。
最后,双模式架构的调试比单模式复杂,因为你要同时关注两条路径的行为和它们之间的切换。建议在早期就建立完善的日志和可视化工具,把每一步的模式选择、定位结果、执行反馈都记录下来。这样出问题时,你能快速定位是快速模式的锅还是深度模式的锅,还是调度器的判断逻辑有问题。