首页 / 资讯中心 / 文章详情

ARTEMIS双模式架构:LLM移动端自动化测试的脚本与代理协同实践

ARTEMIS双模式架构:LLM移动端自动化测试的脚本与代理协同实践 ★ FEATURED ARTICLE
1. 移动端自动化测试的现状与ARTEMIS的切入点移动端自动化测试这件事做过的人都懂——写起来容易跑起来难跑稳了更难。尤其是这两年大模型能力下放到端侧之后测试场景从传统的UI点击流一下子扩展到了对话生成、意图识别、多轮上下文保持这些非确定性领域。传统的Appium、Espresso、XCUITest那一套面对LLM驱动的移动端应用基本处于半失灵状态你没法用assertEquals去断言一句自然语言回复是否正确也没法用固定坐标去点击一个每次位置都可能变化的推荐卡片。ARTEMIS这个项目就是在这个背景下进入视野的。它不是一个通用测试框架而是一个专门针对LLM移动端应用设计的自动化测试系统。我第一次接触它的时候最吸引我的不是它支持多少种设备而是它提出的双模式架构——这个设计思路直接决定了它能不能同时处理确定性操作和非确定性输出这两类截然不同的测试需求。简单来说ARTEMIS的双模式架构把测试执行分成了两条路径一条是脚本驱动模式走传统的确定性指令流适合登录、导航、表单填写这类操作另一条是代理驱动模式由LLM代理根据当前屏幕状态和测试目标自主决策下一步动作适合探索性测试、对话流验证、异常路径发现这类场景。两条路径共享同一套设备抽象层和状态管理机制但在决策层完全解耦。这个设计解决了一个很实际的问题纯脚本方案维护成本高得离谱UI一改全量重写纯代理方案又太不稳定每次跑出来的路径都不一样没法做回归。ARTEMIS的做法是让两者共存按测试用例的粒度选择模式甚至在同一条用例里混合使用。适合谁来参考这篇文章如果你正在做移动端测试架构选型或者手头有LLM相关的App需要做质量保障又或者你单纯对怎么让AI代理操作手机这件事感兴趣那接下来的内容应该能给你一些可以直接抄作业的思路。我会从架构设计、核心模块拆解、实操配置、踩坑记录几个维度展开尽量把每个设计决策背后的为什么讲清楚。2. 双模式架构的整体设计与选型逻辑2.1 为什么不是单一模式两种测试需求的本质冲突在拆解ARTEMIS之前先得把问题定义清楚。移动端LLM应用的测试需求本质上可以分成两类而这两类需求对测试系统的要求是矛盾的。第一类是确定性验证。比如点击设置按钮后是否跳转到设置页、输入手机号后获取验证码按钮是否在60秒后置灰、支付流程中金额计算是否正确。这类测试的特点是输入确定、预期输出确定、执行路径确定。传统自动化框架就是为这类需求设计的用脚本写死每一步操作和断言跑一千遍结果都一样。第二类是非确定性验证。比如用户问帮我推荐一家附近的餐厅LLM返回的文本是否合理、是否包含餐厅名称和距离信息、多轮对话后是否还记得用户之前说的我不吃辣。这类测试的特点是输入是自然语言、输出是自然语言、执行路径取决于模型当前状态。你没法用精确匹配去断言只能用语义评估、关键词覆盖、意图一致性这些模糊判断。问题来了如果用同一套机制处理这两类需求要么确定性测试被拖慢、变得不稳定要么非确定性测试被限制得太死、失去探索能力。ARTEMIS的选择是——不强行统一而是在架构层面做分离。这里有个常见的误区很多人觉得双模式就是两套代码库。实际上ARTEMIS的双模式共享了设备控制层、截图采集层、日志记录层只在决策层分叉。这个设计很关键后面会详细说。2.2 脚本驱动模式确定性操作的基石脚本驱动模式Script-Driven Mode是ARTEMIS的默认执行路径。它的工作方式和传统自动化框架类似测试用例被写成结构化的指令序列每条指令包含操作类型点击、输入、滑动、等待、目标元素定位方式resource-id、text、xpath、视觉特征、以及可选的断言条件。但ARTEMIS在这个基础上做了两个关键改进。第一个改进是元素定位的多级回退机制。移动端UI的脆弱性在于同一个按钮在不同版本、不同机型、不同语言下定位方式可能完全不同。ARTEMIS允许为同一个目标元素配置多个定位策略按优先级依次尝试target: 登录按钮 locators: - type: resource-id value: com.example.app:id/btn_login - type: text value: 登录 - type: text value: Log In - type: visual template: templates/login_btn.png threshold: 0.85这个配置的意思是先找resource-id找不到就找中文登录再找不到就找英文Log In最后用图像模板匹配兜底。实测下来这个机制能把因UI微调导致的脚本失效率降低70%以上。第二个改进是操作前的状态校验。传统脚本经常因为上一步还没加载完就点下一步而失败。ARTEMIS在执行每条指令前会先检查当前屏幕状态是否满足预期。比如点击登录按钮前会确认按钮存在且可点击enabled属性为true如果条件不满足会进入等待重试循环而不是直接报错。2.3 代理驱动模式让LLM自己决定怎么测代理驱动模式Agent-Driven Mode是ARTEMIS真正有意思的地方。在这个模式下测试用例不再是一堆写死的指令而是一个测试目标加上一组约束条件。系统会把当前屏幕截图、可交互元素列表、以及测试目标一起发给LLM代理由代理决定下一步做什么。举个例子测试目标是验证用户能够成功修改昵称约束条件是最多执行15步操作。代理收到这个目标后会自己规划路径先找到我的页面入口点击进入找到编辑资料点击找到昵称输入框清空原有内容输入新昵称点击保存验证昵称是否更新。这个过程中代理每一步都会重新观察屏幕状态根据实际情况调整策略。如果发现我的页面不在底部导航栏而是在侧边栏它会自己切换路径。这种灵活性是脚本模式做不到的。但代理模式也有明显的代价执行速度慢每步都要调用LLM、结果不可复现同样的目标可能走不同路径、成本高token消耗。所以ARTEMIS的定位很明确代理模式用于探索性测试和脚本生成不用于日常回归。2.4 两种模式的协同关系双模式不是二选一而是有明确的协同流程。ARTEMIS推荐的实践路径是这样的新功能上线时先用代理模式做一轮探索性测试让LLM代理自由操作发现潜在问题和关键路径。代理执行过程中系统会自动记录每一步的操作和屏幕状态生成操作轨迹。从操作轨迹中提取出稳定的路径自动转换成脚本模式的测试用例。后续回归测试用脚本模式跑快速且稳定。当UI发生较大变化导致脚本失效时重新用代理模式探索更新脚本。这个流程的核心价值在于用代理模式的灵活性来生成和维护脚本用脚本模式的稳定性来保证回归效率。两者不是竞争关系而是上下游关系。实操心得代理模式生成的脚本不要直接用于回归一定要人工审核一遍。我遇到过代理为了抄近路直接调用内部API绕过UI的情况这种脚本在真实用户场景下是不成立的。3. 核心模块拆解与关键技术细节3.1 设备抽象层一套接口控制Android和iOSARTEMIS的设备抽象层Device Abstraction Layer是整个系统的地基。它的设计目标是上层测试逻辑不关心底层是Android还是iOS只调用统一的接口。这个抽象层定义了以下几类核心操作操作类别接口方法Android实现iOS实现应用管理launchApp(package)am startxcrun simctl launch元素查找findElement(locator)UiAutomatorXCUITest点击操作tap(element)click()tap()文本输入inputText(element, text)setText()typeText()屏幕截图captureScreen()screencapscreenshot手势操作swipe(from, to)swipe()swipe()状态获取getScreenState()dumpWindowHierarchydebugDescription这个抽象层的难点不在于接口定义而在于行为一致性。比如Android的setText()会直接替换输入框内容而iOS的typeText()是追加输入。ARTEMIS在iOS实现里做了封装先清空再输入保证两端行为一致。另一个坑是截图时机。Android的screencap是异步的调用后立即读取可能拿到旧帧。ARTEMIS在截图后加了一个帧同步检查对比连续两帧的哈希值确认画面稳定后才返回。这个细节在代理模式下特别重要因为代理是基于截图做决策的拿到旧帧会导致误判。3.2 屏幕理解模块从像素到结构化信息代理模式的核心能力是看懂屏幕。ARTEMIS的屏幕理解模块做了三层处理第一层是原始截图采集。通过设备抽象层获取当前屏幕的PNG图像分辨率根据设备实际屏幕决定。这里有个优化ARTEMIS会把截图缩放到固定宽度通常是1080px高度按比例缩放目的是减少后续LLM调用的token消耗。第二层是UI树解析。通过Android的UiAutomator或iOS的XCUITest获取当前界面的元素树提取每个元素的类型、文本、位置、可点击状态等信息。这一层输出的是结构化数据比纯图像更精确。第三层是视觉-结构融合。把UI树中的元素边界框叠加到截图上生成带标注的图像。同时把元素列表整理成文本描述格式如下[1] Button 登录 at (540, 1200) size (200, 80) clickabletrue [2] EditText 手机号 at (540, 800) size (600, 100) clickabletrue text [3] TextView 忘记密码 at (540, 1400) size (150, 60) clickabletrue这个融合结果会同时以图像和文本两种形式发给LLM代理。图像提供视觉上下文比如布局关系、颜色暗示文本提供精确的元素信息。实测下来这种双通道输入比纯图像或纯文本的决策准确率高出不少。注意事项UI树解析在Flutter、React Native这类跨平台框架上经常拿不到完整的元素信息。ARTEMIS的应对方案是降级到纯视觉模式用OCR提取文本用图像检测识别可点击区域。这个降级逻辑是自动触发的当UI树元素数量少于阈值比如5个时启用。3.3 代理决策引擎Prompt设计与动作空间定义代理决策引擎是ARTEMIS最核心也最复杂的模块。它的输入是当前屏幕状态和测试目标输出是下一步动作。这个模块的设计质量直接决定了代理模式能不能用。动作空间定义是第一个关键决策。ARTEMIS定义的动作类型包括tap(element_id)点击指定元素input(element_id, text)在指定元素输入文本swipe(direction, distance)向指定方向滑动wait(seconds)等待指定时间assert(condition)断言某个条件成立finish(reason)结束测试并说明原因动作空间不能太大否则LLM容易选错也不能太小否则表达能力不够。ARTEMIS经过多轮迭代最终保留了这6种基本动作覆盖了移动端测试的绝大多数场景。Prompt设计是第二个关键决策。ARTEMIS的代理Prompt包含以下几个部分角色定义告诉LLM它是一个移动端测试代理目标是在最少步骤内完成测试任务。当前状态屏幕截图base64编码 元素列表文本描述。测试目标自然语言描述的目标比如验证用户能够成功修改昵称。历史动作之前执行过的动作序列帮助LLM避免重复操作。约束条件最大步数、禁止操作比如不能删除数据、必须验证的点。输出格式要求LLM以JSON格式返回下一步动作。这个Prompt的设计有几个细节值得注意。第一历史动作要包含执行结果成功/失败否则LLM可能反复尝试同一个失败的操作。第二约束条件要明确我见过代理为了完成修改昵称的目标直接把账号注销了重新注册这种操作必须提前禁止。第三输出格式要严格ARTEMIS用了JSON Schema约束解析失败会触发重试。3.4 状态管理与轨迹记录无论是脚本模式还是代理模式ARTEMIS都会记录完整的执行轨迹。每条轨迹包含时间戳当前屏幕截图压缩后存储UI树快照执行的动作动作执行结果代理的决策理由代理模式下这些轨迹数据有几个用途。第一是问题复现当测试失败时可以回放整个操作序列定位是哪一步出了问题。第二是脚本生成从成功的代理轨迹中提取操作序列转换成脚本模式的用例。第三是模型优化用轨迹数据微调代理的决策能力不过这是进阶用法大多数团队用不到。轨迹存储用的是SQLite每条记录包含截图路径而不是直接存二进制避免数据库膨胀。截图文件按日期分目录存储定期清理超过30天的数据。4. 实操部署与核心环节实现4.1 环境准备与依赖安装ARTEMIS的部署不算复杂但依赖项比较多建议用Docker统一环境。以下是我实际搭建时用的步骤。首先是基础环境。ARTEMIS需要Python 3.10以上版本推荐3.11因为用到了asyncio.TaskGroup这个特性。Android测试需要安装Android SDK和platform-toolsiOS测试需要macOS环境和Xcode Command Line Tools。# 创建虚拟环境 python3.11 -m venv artemis-env source artemis-env/bin/activate # 安装核心依赖 pip install artemis-test-framework pip install uiautomator2 # Android设备控制 pip install facebook-wda # iOS设备控制仅macOS pip install openai # LLM代理调用 pip install pillow # 图像处理设备连接方面Android设备通过ADB连接确保adb devices能看到设备。iOS设备通过WebDriverAgent连接需要先编译并安装WDA到设备上。实操心得Android模拟器和真机的行为差异比想象中大。模拟器的UI渲染有时序问题截图经常拿到半渲染的帧。如果条件允许代理模式的测试尽量用真机跑脚本模式可以用模拟器。4.2 配置文件编写与参数调优ARTEMIS的配置文件是YAML格式分为设备配置、模式配置、代理配置三个部分。以下是一个完整的配置示例device: platform: android serial: emulator-5554 app_package: com.example.llmapp screenshot_scale: 0.5 frame_sync: true frame_sync_retries: 3 mode: default: script script: element_timeout: 10 retry_count: 2 retry_interval: 1 agent: max_steps: 20 step_timeout: 30 llm_model: gpt-4o temperature: 0.1 max_tokens: 500 agent: action_space: [tap, input, swipe, wait, assert, finish] forbidden_actions: [delete_account, clear_data] require_confirmation: [payment, logout]几个关键参数的解释temperature设为0.1而不是0是因为完全确定性会导致代理在遇到相同状态时永远选同一个动作陷入死循环。0.1的微小随机性给了代理换个思路的机会。max_steps设为20是经验值。大多数移动端测试任务在15步内能完成超过20步通常意味着代理迷路了继续跑下去只是浪费token。frame_sync开启后每次截图会等待画面稳定。这个参数在代理模式下必须开启脚本模式下可以关闭以提升速度。4.3 脚本模式用例编写实战脚本模式的用例用YAML描述以下是一个登录流程的完整示例name: 用户登录流程 mode: script steps: - action: launch app: com.example.llmapp - action: wait_for locator: type: resource-id value: com.example.llmapp:id/et_phone timeout: 15 - action: input locator: type: resource-id value: com.example.llmapp:id/et_phone text: 13800138000 - action: input locator: type: resource-id value: com.example.llmapp:id/et_password text: Test123456 - action: tap locator: type: resource-id value: com.example.llmapp:id/btn_login - action: wait_for locator: type: text value: 首页 timeout: 10 - action: assert condition: element_exists locator: type: resource-id value: com.example.llmapp:id/tv_username expected: 138****8000这个用例的执行逻辑很直白启动App、等待手机号输入框出现、输入手机号和密码、点击登录、等待首页出现、断言用户名显示正确。关键点在于wait_for的使用。很多新手会直接用sleep等待这是大忌。sleep时间设短了会失败设长了会拖慢整个测试套件。wait_for是条件等待元素一出现就继续效率高得多。4.4 代理模式用例编写与执行代理模式的用例更简洁因为不需要写具体步骤name: 验证昵称修改功能 mode: agent goal: 验证用户能够成功修改昵称从测试用户改为新昵称001 constraints: max_steps: 15 must_verify: - 修改后昵称显示为新昵称001 - 修改后返回首页昵称同步更新 forbidden: - 不能退出登录 - 不能修改其他个人信息 context: initial_state: 已登录位于首页执行时ARTEMIS会启动代理循环采集当前屏幕状态构建Prompt发给LLM解析LLM返回的动作执行动作检查是否满足结束条件重复1-5直到完成或达到max_steps代理执行过程中每一步的决策理由都会被记录。比如代理可能输出这样的决策{ action: tap, element_id: 5, reason: 当前在首页需要进入我的页面才能修改昵称。元素5是底部导航栏的我的按钮点击它。, confidence: 0.92 }这个reason字段在调试时非常有用能看出代理的思路是否正确。4.5 混合模式在同一条用例中切换ARTEMIS支持在一条用例中混合使用两种模式。典型场景是用脚本模式完成登录确定性操作然后切换到代理模式做探索性验证。name: 登录后探索个人中心 steps: - mode: script actions: - action: launch app: com.example.llmapp - action: login username: 13800138000 password: Test123456 - mode: agent goal: 探索个人中心的所有功能入口记录每个入口的名称和位置 constraints: max_steps: 25 output_format: list这种混合模式的价值在于把稳定的前置操作交给脚本把需要灵活性的探索交给代理各取所长。5. 常见问题与排查技巧实录5.1 代理模式下的典型故障与解决代理模式虽然灵活但故障模式比脚本模式复杂得多。以下是我在实际使用中遇到的高频问题。问题一代理陷入死循环。表现是代理反复执行同一个动作比如反复点击同一个按钮因为点击后页面没有变化代理以为没点到。排查方法是查看轨迹记录如果发现连续3步以上动作相同且屏幕状态未变基本可以确认是死循环。解决方案有两个。第一是在Prompt里加入如果连续两次执行相同动作且屏幕无变化请尝试其他方式的指令。第二是在系统层面加检测当检测到死循环时强制中断并报错。ARTEMIS默认开启了第二种配置项是loop_detection: true。问题二代理看不懂自定义控件。很多App会用Canvas或WebView绘制界面UI树里拿不到元素信息。代理面对这种屏幕时只能靠视觉理解准确率会下降。应对方案是启用OCR增强。ARTEMIS集成了Tesseract OCR当UI树元素数量少于阈值时自动启用。OCR提取的文本会作为补充信息加入Prompt。实测下来对于文字类控件OCR的识别准确率能达到90%以上基本够用。问题三代理执行了危险操作。我遇到过代理为了验证退出登录功能真的把测试账号退出了导致后续用例全部失败。虽然配置了forbidden_actions但代理有时会用其他方式绕过。根本的解决方案是权限隔离。ARTEMIS支持配置操作白名单只有白名单内的操作才被允许执行。白名单外的操作会被拦截并记录。这个机制比黑名单可靠得多因为黑名单永远列不全。5.2 脚本模式的稳定性优化脚本模式的问题相对传统主要是元素定位失败和时序问题。元素定位失败的排查思路先用uiautomatorviewer或Appium Inspector查看当前页面的元素树确认目标元素的属性。如果resource-id是动态生成的比如包含时间戳就需要改用其他定位方式。ARTEMIS的多级回退机制在这里很有用配置2-3个定位策略基本能覆盖大多数情况。时序问题的典型表现是偶发失败同样的脚本跑10次失败1-2次。排查方法是开启详细日志记录每步操作的耗时和屏幕状态。常见原因是动画未结束就执行了下一步操作。解决方案是在关键操作后加wait_for_idle等待页面稳定。实操心得脚本模式下的retry机制要慎用。重试能提高单次通过率但会掩盖真正的问题。我的做法是重试次数设为1且重试时必须记录日志。如果某个用例频繁触发重试说明脚本本身有问题需要修复而不是靠重试掩盖。5.3 性能与成本优化代理模式的主要成本是LLM调用。每次决策都要发一次请求包含截图和元素列表token消耗不小。以下是我总结的优化手段。截图压缩是最直接的优化。ARTEMIS默认把截图缩放到宽度1080px我实测发现缩放到720px对决策准确率影响很小但token消耗能降低40%左右。配置项是screenshot_scale: 0.5。元素列表裁剪是第二个优化点。屏幕上可能有几十个元素但大多数是无关的。ARTEMIS支持只发送可交互元素clickabletrue或focusabletrue能把元素数量减少60%以上。决策缓存是第三个优化点。对于相同的屏幕状态和测试目标代理的决策应该是相同的。ARTEMIS用屏幕截图的感知哈希pHash作为key缓存代理的决策结果。当遇到相同屏幕时直接复用缓存跳过LLM调用。这个优化在回归测试场景下效果显著能降低50%以上的token消耗。5.4 常见问题速查表问题现象可能原因排查方法解决方案代理反复点击同一元素页面未响应或元素不可点击查看轨迹中屏幕是否变化开启loop_detectionPrompt加提示元素定位失败resource-id动态变化用Inspector查看元素属性配置多级定位策略截图拿到旧帧截图异步未同步对比连续帧哈希开启frame_sync代理执行危险操作约束条件不完整查看决策理由配置操作白名单脚本偶发失败动画未结束查看失败步骤的耗时加wait_for_idletoken消耗过高截图过大或元素过多统计每次请求的token数压缩截图、裁剪元素列表代理决策准确率低Prompt信息不足查看决策理由是否合理增加视觉标注、补充上下文iOS输入文本异常typeText是追加模式检查输入框原有内容先清空再输入6. 双模式架构的扩展与个人实践体会ARTEMIS的双模式架构不是终点而是一个可扩展的基础。我在实际项目里基于它做了几个延伸效果还不错这里分享一下思路。第一个延伸是代理模式的批量探索。与其让代理跑单个测试目标不如给它一个功能区域让它自由探索。比如探索设置页面的所有选项代理会自己遍历每个设置项记录每个选项的功能和状态。这种用法适合新功能上线时的快速摸底能发现一些人工测试容易遗漏的边界情况。第二个延伸是脚本的自动修复。当脚本模式因为UI变化而失败时自动触发代理模式重新探索该路径从代理的成功轨迹中提取新的元素定位方式自动更新脚本。这个机制能把脚本维护的工作量降低一半以上不过需要小心处理自动更新的脚本必须经过人工审核才能合入。第三个延伸是跨应用测试。ARTEMIS的设备抽象层支持应用切换代理模式可以处理从App A跳转到App B完成支付再跳回来这类跨应用场景。这个能力在测试支付流程、分享流程时特别有用。我个人在实际操作中的体会是双模式架构的价值不在于两种模式而在于它强迫你把测试需求按确定性程度分类。这个分类过程本身就是一种测试设计优化。很多团队把所有测试都写成脚本结果维护成本高得离谱另一些团队盲目上AI代理结果测试结果不可复现。ARTEMIS的思路是——该确定的地方确定该灵活的地方灵活两者通过轨迹数据打通。最后分享一个小技巧代理模式的Prompt里把测试目标写成用户故事格式作为一个用户我想要...以便于...比写成技术描述验证昵称修改功能的效果更好。原因是用户故事格式包含了意图和上下文LLM更容易理解为什么要做这个操作从而做出更合理的决策。这个技巧是我在调试了几十个用例后偶然发现的实测决策准确率能提升10-15个百分点。
阅读完成 · 觉得有帮助?
咨询建站