用 Maestro 做移动端自动化测试:从入门到 MCP 实战
最近在项目中引入移动端自动化测试,调研了一圈选定了 Maestro。用了一段时间下来,整体体验不错,尤其是搭配 MCP 让 AI 自动跑测试这块,虽然速度比不上手动,但胜在可以脱手不管。这里把安装配置、日常使用和一些踩坑经验整理一下。
介绍
Maestro 是一个用 YAML 声明式语法驱动 app 测试的框架,支持 Android 和 iOS,支持录制和报告。
它还有一个 MCP 服务器,可以让 AI 直接操作手机,实现修复 → 测试 → 验证的全自动闭环。个人用下来速度比人慢一点,但可以脱手不管,让它自己跑。模型强的话效果还是不错的。
- 我做了一个 skill,包含使用过程中踩过的坑,让 AI 测试时准确率更高。感兴趣可以看看
npx skills@latest add junjie-z666/skills - github 地址
和 Appium、Playwright 的对比(现在用 AI 做格式转换也很方便):
| 维度 | Maestro | Appium | Playwright |
|---|---|---|---|
| 原生层操作 | ✅ 完整支持 | ✅ 完整支持 | ❌ 不支持 |
| WebView H5 操作 | ✅ 和原生一样支持 | ✅ 真正 context 切换 + 完整 DOM | ❌ 只支持 web,不支持 app 上的 webview |
| 跨平台一致性 | ✅ 同一 YAML 双平台运行 | ⚠️ 需两套定位策略 | — |
| 上手难度 | ⭐ 极低(YAML 声明式),有 GUI | ⭐⭐⭐⭐⭐ 很高 | ⭐⭐ 低 |
| 维护成本 | 低 | 高 | 中 |
| 模块化 | ✅ runFlow 子流程嵌套 | ✅ 代码级自由组织(POM 等) | ✅ 代码级组织 |
| 流程复用 | ✅ 流程文件嵌套组合 | ✅ 完全灵活 | ✅ fixture / test.step |
| 参数化 | ✅ env 环境变量注入 | ✅ 完全灵活 | ✅ 完全灵活 |
| 环境配置 | 简单 | 复杂 | 中等 |
安装
Maestro Studio
图形化界面,适合手动编写测试流程。
- 编写时有命令提示,不用死记语法
- 可以直接选中界面上的组件进行添加
- 下载地址
Maestro CLI
命令行工具,适合配合 CI/CD 使用。MCP 服务器依赖 CLI 安装。
- 安装后可以启动调试视图,实时查看测试过程(包含测试命令和手机镜像)
- 安装说明
注意:Studio 和 CLI 是两套独立工具,按需安装。
Studio 使用
- 打开 Maestro Studio,选择设备,创建 YAML 文件
- 选中目标组件,插入点击操作并运行
- 添加断言,验证 count 是否为 1
- 插入文本输入操作
- 切换选择器检查 toast。示例中用的是文字匹配,也可以换成 id 匹配(准确率更高)。服务端报错时一般会弹 toast
- 完整运行整个测试流程
MCP 实战
配置
安装 Maestro CLI 后,给 Claude 添加 MCP 服务器:
claude mcp add maestro -- maestro mcp
记得把 maestro 加到 PATH 里
使用流程
- 让 agent 执行
open the maestro viewer,成功后打开 http://localhost:9999/ 即可看到调试界面 - 用自然语言描述你要验证的功能,让 AI 帮你操作
- AI 自动执行测试过程
推荐搭配 Skill
npx skills@latest add junjie-z666/skills
这个是我写的一个 skill 加了提示词规则,让 AI 在需要 app 测试时自动调用 Maestro MCP,不然它大概率不会主动用。
- 触发词:「测试一下」「运行看看」「验证效果」「调试验证」等
- 如果输入完没有自动加载,可以直接用命令触发:
/maestro
常用技巧
id 定位
在ReactNative中,需要对 View 设置 testId 才能通过 id 定位,release 包也生效。
- Android:映射为
android:id - iOS:
testID会映射为accessibilityIdentifier,release 包默认保留。但如果开启了 Xcode 的 "Strip Accessibility Identifiers" 优化就会被移除,这种情况下同时设置accessibilityLabel即可
环境变量
debug 包和 release 包配置不同时(比如 appId 不一样),可以在 Run Test 旁边的 env 里配置环境变量。
比如配一个 appId=com.demo,YAML 里直接用 ${appId} 引用。
tapOn 执行慢
添加最长等待时间可以缓解:
- tapOn:
text: "xxx"
retryTapIfNoChange: false # 取消重试
waitToSettleTimeoutMs: 2000 # 最长等待时间,按需调短
条件分支
当某个元素不确定是否出现时,可以用 runFlow + when 做条件判断:
# 看到 screen1 就执行 commands
- runFlow:
when:
visible: "screen1"
commands:
- tapOn: "next"
# 也支持引用封装好的 yaml 文件
- runFlow:
when:
visible: "screen1"
file: tap_test.yaml
toast 检测
搭配 extendedWaitUntil,5 秒内检测到 toast 就判定为异常:
注意:
visible里用 id + text 的组合选择器不知道为啥会识别失败,只用单个选择器就行。tapOn等命令就可以。建议把这段抽成独立文件方便复用。
# 最多等 5 秒,有就立即执行 runFlow 报错,没有的话 runFlow 条件也不会通过
- extendedWaitUntil:
visible:
id: "toast-container"
timeout: 5000
optional: true
- runFlow:
when:
visible:
id: "toast-container"
commands:
- assertTrue:
condition: ${false}
常见问题
screenshot 不够准确
Agent 默认会用 screenshot 观察页面状态,但有些模型不支持图像会报错,而且准确性不如 inspect_screen。
解决: 明确告诉 AI 优先使用 inspect_screen,只有 inspect_screen 拿不到信息时再用 screenshot。
键盘弹起导致点击偏移
点击输入框时键盘弹起或收起,输入框的实际坐标会变化,导致后续点击打偏。
解决: 键盘状态变化后,用 inspect_screen 重新获取元素的 bounds,再计算点击位置。
无法输入方向键
Maestro 的 pressKey 只支持:enter、home、lock、backspace、volume up/down、back、power,不支持方向键。
解决: 通过 adb shell input keyevent 发送方向键事件。
以上问题的规则也收录在我的 skill 中:
npx skills@latest add junjie-z666/skills