How to Debug YAML Flows in Maestro: Capturing Debug Bundles and Artifacts
Enable the --debug-output flag when running maestro test to automatically capture UI hierarchies, screenshots, and JSON execution logs that pinpoint exactly why any command fails.
Maestro (mobile-dev-inc/Maestro) is an open-source mobile UI testing framework that executes flows written in YAML. When a flow fails, the framework produces a debug bundle containing full execution metadata, allowing you to inspect the exact UI state and command history without guessing what went wrong.
Enabling Debug Output
The CLI provides two flags to control debug artifact generation. Both are parsed in [TestCommand.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt) (lines 146-160).
| Flag | Purpose | Source Location |
|---|---|---|
--debug-output <path> |
Specifies the output directory (defaults to <home>/maestro/debug). |
TestCommand.kt lines 146-151 |
--flatten-debug-output |
Flattens the directory structure—writes all files directly to the output folder instead of nested subdirectories. Ideal for CI artifact collection. | TestCommand.kt lines 158-160 |
Run your flow with debug capture enabled:
maestro test my_flow.yaml --debug-output ./debug
This creates a directory structure at ./debug/my_flow/ containing debug.json, a screenshots/ folder, and hierarchy.xml (if a UI inspection failure occurred).
What Gets Recorded
The debug bundle aggregates four distinct artifact types produced by coordinated components across maestro-cli and maestro-orchestra.
Debug JSON (debug.json)
A complete execution log written after the flow finishes (success or failure). It contains ordered command metadata including status, evaluated expressions, and error messages. Generated by TestDebugReporter.saveFlow.
Screenshots
PNG files captured before and after each command depending on its status (pending, completed, failed, or warned). Produced by ScreenshotUtils.takeDebugScreenshot.
UI Hierarchy
An XML (or JSON) dump of the complete view hierarchy captured at the moment a UI-dependent command fails—such as when an element selector returns no matches. Implemented in [Orchestra.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt#L425-L435).
Command Metadata
In-memory state accumulated during execution within the FlowDebugOutput data class. This container tracks every command's lifecycle and any exceptions thrown. Defined in [TestDebugReporter.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt#L230-L242):
data class FlowDebugOutput(
val commands: MutableMap<Command, CommandDebugMetadata> = mutableMapOf(),
val screenshots: MutableList<ScreenshotMetadata> = mutableListOf(),
var exception: Exception? = null
)
How the Debug Bundle Is Built
The framework constructs the debug bundle through a five-stage pipeline involving the CLI, test runner, and orchestra modules.
-
CLI Bootstrapping —
TestCommand.call()installs the debug reporter with your specified path.TestDebugReporter.install( debugOutputPathAsString = debugOutput, flattenDebugOutput = flattenDebugOutput, printToConsole = parent?.verbose == true, )Source: [
TestCommand.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/command/TestCommand.kt) lines 46-51. -
Flow Initialization —
TestRunner.runFlow()instantiates a freshFlowDebugOutputcontainer for each flow execution.val debugOutput = FlowDebugOutput()Source: [
TestRunner.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/runner/TestRunner.kt) lines 56-58. -
Command Execution —
MaestroCommandRunnerruns each command and immediately records screenshots to thedebugOutputobject, updating status fromPENDINGto final state.ScreenshotUtils.takeDebugScreenshotByCommand(maestro, debugOutput, CommandStatus.PENDING) // ... execution ... debugOutput.commands[command] = CommandDebugMetadata(...)Source:
MaestroCommandRunner.ktlines 101-113, 125-128. -
Failure Handling — When a command fails,
Orchestracaptures the view hierarchy and enriches the error message with debugging guidance.val hierarchyRoot = maestro.viewHierarchy().root debugMessage = "Element with $description not found. Check the UI hierarchy in debug artifacts ..."Source: [
Orchestra.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt) lines 425-436. -
Persistence — After completion,
TestDebugReporter.saveFlowwrites the JSON file and creates the directory structure.Files.createDirectories(debugOutput) TestDebugReporter.saveFlow(flowName, debugOutput, debugOutputPath)Source: [
TestDebugReporter.kt](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-cli/src/main/java/maestro/cli/report/TestDebugReporter.kt) lines 183-194.
Inspecting the Debug Artifacts
Analyze the generated files to diagnose failures without reproducing the issue manually.
debug.json
Open this file in any JSON viewer to see an ordered list of commands with their statuses, evaluated arguments, duration in milliseconds, and error messages. Each command entry includes a debugMessage field that often contains the specific selector or expression that failed.
screenshots/
Browse PNG files named by command index (e.g., 0_failed.png, 1_completed.png). Compare the screenshot taken before the command executed with the one taken after to visualize UI transitions and timing issues.
hierarchy.xml
Import this file into Android Studio's Layout Inspector or view it in any XML editor to see the complete UI tree at the exact moment of failure. Use this to verify that element selectors in your YAML match actual view attributes.
Quick Inspection Workflow
# Run the flow
maestro test ./flows/checkout.yaml --debug-output ./debug
# Navigate to the generated bundle
cd ./debug/checkout
# View the UI hierarchy (macOS example)
open hierarchy.xml
# Examine the failing command's screenshot
open screenshots/3_failed.png
# Query the JSON log for failed commands
cat debug.json | jq '.commands[] | select(.status == "FAILED")'
Common Debugging Scenarios
Element Not Found Errors
When tapOn or assertVisible fails, open hierarchy.xml to verify the element exists and check the debugMessage in debug.json for the exact selector that failed. The message explicitly references the UI hierarchy artifact for verification.
Timing and Flakiness
Check the durationMs field in debug.json for each command. If a command fails after a very short duration (e.g., < 500ms), the UI likely hadn't settled. Add explicit waitFor commands or increase retry counts in your YAML.
Screenshot Assertion Mismatches
When using assertScreenshot, compare the expected baseline with the actual screenshot saved in screenshots/. The debug bundle also stores a diff image highlighting pixel differences when the assertion fails.
Unexpected Crashes
The exception field in debug.json contains the full stack trace of any unhandled exception, while the commands map shows exactly which instruction was executing when the crash occurred.
Summary
- Enable debug mode with
--debug-output <path>to automatically capture comprehensive failure data. - Key artifacts include
debug.json(execution log),screenshots/(visual state), andhierarchy.xml(UI tree). - Source coordination involves
TestCommand,TestDebugReporter,ScreenshotUtils, andOrchestraworking together to build the bundle. - Inspect hierarchies to validate selectors, check screenshots to verify visual state, and query
debug.jsonfor precise error messages and timing data. - Use
--flatten-debug-outputin CI pipelines to simplify artifact archival.
Frequently Asked Questions
Where does Maestro store debug output by default?
If you omit the --debug-output flag, Maestro writes debug bundles to <home>/maestro/debug/<flow-name>/. You can override this path by providing a specific directory to the flag, such as --debug-output ./ci-artifacts.
Can I view the debug bundle on CI without downloading files?
Yes. Use --flatten-debug-output to ensure all files sit in a single directory, then configure your CI system (GitHub Actions, GitLab CI, etc.) to upload that folder as a pipeline artifact. Most CI UIs allow browsing PNG screenshots and JSON files directly in the browser.
Why is hierarchy.xml missing from my debug bundle?
The UI hierarchy is only captured when a command that inspects the UI fails—typically an element lookup like tapOn or assertVisible. If your flow fails due to a JavaScript error, network timeout, or assertion unrelated to UI elements, the hierarchy file may not be generated because the view state was never queried.
How do I correlate a screenshot filename with a specific command in my YAML?
Screenshots are named using the command's zero-based index in the flow execution sequence followed by its final status (e.g., 2_failed.png means the third command failed). Match this index against the commands array in debug.json, which preserves the execution order and includes the command name and arguments for each step.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →