☰
Maestro test-android CI 失败诊断实战:基于 artifacts 的结构化故障分类指南
2026/10/2 8:20:47 网站建设 项目流程
  • 测试
  • 移动开发
  • 开发工具
  • CLI

【免费下载链接】Maestro

Painless E2E Automation for Mobile and Web

项目地址:https://gitcode.com/GitHub_Trending/ma/Maestro
点击查看免费下载

导读

本文面向在 CI(如 GitHub Actions)上运行 Maestrotest-android流程并希望快速定位失败根因的工程师与 Agent。它围绕仓库中 diagnose-maestro-failure.md 定义的诊断方法论展开:从本地 artifact 目录或 GitHub Actions URL 出发,通过commands-*.json、截图与maestro.log三者交叉验证,将每条新失败流程分类为「测试流程问题 / Maestro 驱动缺口 / App 侧问题 / 框架缺陷」,并识别级联失败组,最终输出结构化诊断报告。读完本文,你将掌握一套可复用的、低上下文开销的 CI 失败归因流程,并理解其背后的 artifact 生成原理。


一、诊断的输入与前提

诊断 Agent 接收两种输入中的一种:

  1. 本地 artifact 目录:例如/Users/.../maestro-root-dir-android或/tmp/maestro-android-<run_id>,目录下必须包含tests/子树。
  2. GitHub Actions URL:形如https://github.com/mobile-dev-inc/Maestro/actions/runs/<run_id>或.../runs/<run_id>/job/<job_id>。

若传入的是 URL,先提取run_id,再用gh命令将对应的 artifact 下载到本地:

RUN_ID=<extracted> DEST=/tmp/maestro-android-$RUN_ID gh run download "$RUN_ID" --repo mobile-dev-inc/Maestro \ --name maestro-root-dir-android -D "$DEST"

前置校验:如果目录下没有tests/demo_app/passing/子树(例如拿到的是 iOS artifact,或者任务在测试运行前就失败了),则直接报告该情况并停止——不存在可诊断的信号。

一个关键前提:passing/ 才是信号,failing/ 必须忽略

artifact 中同时存在passing/与failing/两套套件,但只有前者是信号源。failing/是「负路径」套件——其中命令的预期结果就是失败(例如断言元素不存在),因此它们的 FAILED 状态不构成回归证据,必须整体忽略。

cd <artifact_root>/tests/demo_app/passing grep -l '"FAILED"' commands-*.json

这条 grep 是唯一的候选流筛选器:它会返回所有在 JSON 中存在终结性FAILED状态的流程。


二、权威数据源:commands-*.json 的元数据结构

诊断的核心事实来源是commands-*.json,而非带 ❌ 标记的截图文件。每个 JSON 条目包含:

  • command:命令类型(command 对象下的单一键,如tapOn、assertVisible);
  • metadata.status:可选值COMPLETED(成功)与FAILED(终结性失败);
  • metadata.timestamp:毫秒级 epoch 时间戳;
  • metadata.sequenceNumber:命令在流程内的序号;
  • metadata.duration:命令执行耗时。

关于duration有一个高价值判读技巧:约 17000 ms 的耗时等于默认等待超时时间,这通常表明 UI 被阻塞(例如系统对话框挡住了元素查找),而不是崩溃——崩溃通常表现为快速失败或堆栈信息。

从源码看,这些字段由 CommandDebugMetadata 定义,状态枚举完整包含PENDING / RUNNING / COMPLETED / FAILED / WARNED / SKIPPED(见 CommandStatus.kt)。序列化写入由 TestOutputWriter.saveCommands() 完成——它逐条将「执行过的步骤」写入 JSON,并为每个步骤附带 metadata 包装;错误对象只序列化message与debugMessage两个字段,完整堆栈保留在maestro.log中。

元数据中的 depth 与「每次执行一条记录」

值得注意的实现细节是:executedSteps采用每次执行一条记录(per-execution)的语义,depth字段记录嵌套层级——顶层为 0,每进入一次runFlow/repeat/retry递增 1。这意味着同一个命令在一次流程中被重复执行时,会生成多条 JSON 记录,这也直接支撑了下文关于 retry 恢复场景的判读。


三、retry 恢复场景:为什么 ❌ 截图不一定是失败证据

文档以commands-(retry).json为典型范例,展示了retryCommand指令下的行为。该流程中:一次 tap 失败 → Maestro 自动重试 → 重试成功 → 流程整体通过。对应的 artifact 表现为:

  • 所有条目(包括最终恢复成功的tapOnElement)的metadata.status均为COMPLETED——Maestro 对恢复成功的命令只写入最终结果,不写入失败的尝试;
  • 仍存在一张screenshot-❌-<ts>-(retry).png,它拍摄于失败尝试发生、但重试尚未成功的那一刻;
  • 因此上述 grep(grep -l '"FAILED"')不会匹配该文件——JSON 中没有任何"FAILED"字符串。

从实现上验证:retryCommand的执行逻辑位于 Orchestra.kt,maxRetries默认取 YAML 中配置的值(缺省为 1,见 YamlFluentCommand.kt),并封顶MAX_RETRIES_ALLOWED。重试针对的失败类型是MaestroException(元素找不到、断言失败等测试级 flaky 问题),而驱动传输失败、JS 求值 bug、CancellationException等会直接向上传播,不做重试。

结论:不要把 ❌ 截图单独当作失败证据。若某个流程的 JSON 中没有任何FAILED条目,它内部的 ❌ 截图来自被恢复的重试,与回归无关,无需调查。


四、逐流程调查的标准步骤

对 grep 返回的每个候选流程:

  1. 打开commands-(<flow>).json,定位metadata.status == "FAILED"的条目——这就是终结性失败。记录:命令类型、错误消息、metadata.timestamp、metadata.sequenceNumber、metadata.duration。
  2. 打开时间戳与该 FAILED 条目匹配的screenshot-❌-<timestamp>-(<flow>).png。这是信号最强的 artifact——当系统对话框对 JSON 和日志不可见时,只有截图能暴露真相,必须始终读取。同一文件内其他时间戳的 ❌ 截图是恢复的重试,忽略。
  3. 在maestro.log中 grep 流程名与时间窗,寻找周边的 driver 活动、gRPC 错误与堆栈信息。

截图与流程的配对关系在 ArtifactsGenerator.kt 中建立:每个执行的步骤在开始时记录 metadata,若命令「可见」(即对屏幕产生操作,见 StepArtifactNaming.capturesScreenshot())则抓取步骤截图;命令结束时回写最终状态与耗时。截图文件命名规则为step-<NNN>-<slug>,NNN 为 1 基的序号(最小宽度 3),slug 由命令类型与关键参数(如 appId、选择器、坐标)生成,保证截图与层级文件在「截图叠层级」视图中成对出现。


五、失败分类表与修复落点

失败时的屏幕分类修复落点
预期的 App 界面,但选择器匹配不到测试流程问题(Test flow issue)e2e/demo_app/.maestro/**/*.yaml
Android / GMS 系统对话框挡住了 AppMaestro 驱动缺口(缺少 auto-grant / auto-dismiss)maestro-client/src/main/java/maestro/drivers/AndroidDriver.kt或.github/workflows/test-e2e.yaml(模拟器预配置)
Pixel 启动器出现在系统对话框后面同左——对话框在launchApp/clearState/stopApp之间抢占了焦点同左
App 崩溃对话框 / 空白屏幕App 侧问题超出范围——仅报告
maestro.log中出现来自maestro.*的堆栈Maestro 框架缺陷maestro-client//maestro-orchestra//maestro-android/

分类为driver gap或framework bug时,应派遣Explore子代理进入相应模块定位有问题的代码路径,在提出行号之前必须验证,禁止猜测。例如 Android 侧驱动实现位于 AndroidDriver.kt,测试流程 YAML 位于 e2e/demo_app/.maestro,CI 工作流配置可参考仓库中的.github/workflows/test-e2e.yaml(本文仅列出路径,行号以 Explore 验证结果为准)。


六、级联失败检测:一次根因,一个修复

级联检测是诊断的关键环节:单个系统对话框可能让其后许多流程接连失败,因为clearState/launchApp/stopApp并不会自动关闭系统级浮层。

操作方法:按时间戳升序列出所有 FAILED 条目;若下游流程的截图显示同一个浮层,则将它们归入根触发点(最早弹出对话框的那个流程)的分组之下。修复时对每个根只提一个修复方案,而不是为级联中的每个流程各提一个修复。

时间轴(示意) 流程 A FAILED —— 弹出 GMS 对话框(根触发) 流程 B FAILED —— 同一对话框仍挡在界面上(级联成员) 流程 C FAILED —— 同一对话框(级联成员) → 只针对流程 A 提出一个修复

七、结构化输出格式

诊断完成后必须返回可被调用方解析的固定结构,逐字保留以下框架:

## Summary <一行:多少流程失败、多少个根因、多少个级联组> ## Root causes (caller acts on each) ### 1. <flow-or-group-label> — <classification> - **Trigger:** <什么引发了该问题> - **Affected flows:** <flow1>, <flow2>, ... (N flows) - **Cascade?** yes/no - **Proposed fix:** - File: `<exact path>:<line range>` - What it does: <一句话> - Diff (unified or pseudo): ``` - old line + new line ``` - Side effects: <若有则写,否则 none> - Verified via Explore? <yes — line numbers confirmed | no — fix is in CI yaml or test flow yaml, line numbers from direct read> ### 2. ... ## Out of scope - `<flow>` — <原因,例如 app crash、环境问题> ## Notes <调用方应知道但不属于修复建议的内容>

约束清单

  • 只诊断,不改动:不得编辑、写入或提交任何文件。
  • 不逐成员提修复:级联组成员共享一个根因修复。
  • 不猜测行号:Kotlin 行号必须经 Explore 子代理验证后才写入报告。
  • 不把 failing/ 当失败:负路径套件的 FAILED 不是回归。
  • 不单独信任 ❌ 截图:grep(grep -l '"FAILED"' commands-*.json)才是判断哪个流程存在终结性失败的唯一权威;JSON 无FAILED条目的流程内的 ❌ 截图来自被恢复的重试(典型范例即commands-(retry).json)。
  • 始终读截图:即使 JSON + 日志看起来已指向明确原因,也要读截图——系统浮层只有截图能暴露。

八、何时结束并交还控制权

只要满足以下条件就立即交还,不做循环、重试或尝试应用修复:passing 套件中的每条终结性 FAILED 命令,要么已被分类并附上修复建议,要么已被归类为带原因的 out-of-scope。诊断 Agent 的职责边界是产出结构化报告,修复的同意权与应用动作归调用方。


结语

这套诊断方法论的根基是 Maestro 的 artifact 生成机制:commands-*.json记录每次执行的命令与 metadata(TestOutputWriter.kt、FlowDebugOutput.kt),截图与流程序号一一配对(ArtifactsGenerator.kt、StepArtifactNaming.kt),retry 的最终结果写入由 Orchestra.kt 保证。理解了这些底层规则,你就能在 CI 失败洪流中快速分清「真失败」「恢复的重试」与「级联噪音」,把每条失败归因到正确的修复落点。

  • 测试
  • 移动开发
  • 开发工具
  • CLI

【免费下载链接】Maestro

Painless E2E Automation for Mobile and Web

项目地址:https://gitcode.com/GitHub_Trending/ma/Maestro
点击查看免费下载

相关推荐

上一篇:Tinycast 剪贴板历史(Clipboard History)深度解析:捕获、存储、检索与安全设计
下一篇:Ant Design Switch 尺寸详解:size 参数、小号开关与样式源码实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询