# Maestro YAML File Structure: A Complete Guide to Mobile Test Flows

> Deconstruct Maestro YAML file structure for mobile test flows. Learn how to declare your appId and define sequential commands for efficient automation.

- Repository: [Maestro/Maestro](https://github.com/mobile-dev-inc/Maestro)
- Tags: how-to-guide
- Published: 2026-03-20

---

**Maestro test flows are plain-text YAML files that declare an `appId` and execute a sequential list of commands parsed into `YamlFluentCommand` objects.**

The Maestro framework by mobile-dev-inc uses these YAML files to define automated UI tests for Android and iOS applications. Each flow file describes the target application and the exact steps Maestro should execute on a device or simulator, parsed by the orchestration layer into executable commands.

## Top-Level Metadata and the Document Separator

Every Maestro YAML file begins with **metadata** that identifies the application under test. The only mandatory field is `appId`, which specifies the Android package name or iOS bundle identifier.

```yaml
appId: com.example.myapp
name: Optional Flow Name
---

```

The three hyphens (`---`) serve as a **document separator** that distinguishes metadata from the command list. Any keys in the metadata section that the parser does not recognize are silently ignored, making the header extensible for future configuration options.

## Command List Structure and Syntax

After the separator, the file contains a **YAML array** where each element represents a single command. According to the source code in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) ([source](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt)), commands map to specific data class properties and fall into two expression forms:

**Scalar shortcuts** for simple commands:

```yaml
- back
- clearState
- stopApp

```

**Maps with parameters** for commands requiring arguments:

```yaml
- tapOn:
    text: "Login Button"
    label: "Tap login button"
- inputText: "hello@example.com"

```

The `YamlCommandReader` ([source](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt)) validates the file and converts the list of `YamlFluentCommand`s into `MaestroCommand` objects by selecting the **first non-null property** defined in the command map.

## Advanced Constructs and Control Flow

Maestro supports complex logic through nested data structures represented by auxiliary classes in the YAML module.

**`runFlow`** inlines external flow files or command lists:

```yaml
- runFlow:
    file: "./subflows/login.yaml"
    env:
      USERNAME: "test@example.com"

```

**`repeat`** and **`while`** create loops over command sub-lists, implemented via `YamlRepeatCommand`.

**`if`** and **`when`** enable conditional execution using `YamlCondition` predicates based on platform detection or element visibility:

```yaml
- if:
    platform: android
    commands:
      - tapOn: "Android-specific menu"

```

**`config`** sets global `MaestroConfig` options affecting the entire flow execution, such as default timeouts.

## Error Handling and Validation

When parsing fails, `YamlCommandReader` catches internal `FlowParseException` instances and throws a user-friendly `SyntaxError` that includes the file path, line number, and contextual code snippet ([source](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt#L78-L87)). This validation occurs before any commands execute on the target device.

## Complete Code Examples

### Minimal Flow (Hello-World)

```yaml
appId: com.example.app
---
- launchApp
- tapOn: "Accept"
- inputText: "hello world"
- assertVisible:
    text: "Welcome"

```

This flow starts the application, taps an acceptance button, enters text, and asserts that a welcome message appears.

### Advanced Flow with Sub-flow and Loop

```yaml
appId: com.example.app
---
- launchApp
- setPermissions:
    permissions: ["READ_CONTACTS", "WRITE_CONTACTS"]
- repeat:
    times: 3
    commands:
      - tapOn: "Add Contact"
      - inputText:
          label: "First name"
          text: "John"
      - inputText:
          label: "Last name"
          text: "Doe"
      - tapOn: "Save"
- runFlow:
    file: "./login_flow.yaml"
    env:
      PASSWORD: "s3cr3t"

```

The `repeat` block executes its nested `commands` exactly three times, while `runFlow` injects environment variables into the external file.

### Conditional Execution

```yaml
appId: com.example.app
---
- if:
    visible:
      text: "Special Offer"
    commands:
      - tapOn: "Claim Offer"
- if:
    platform: ios
    commands:
      - tapOn: "iOS Settings"

```

Conditions evaluate before executing their inner command lists, enabling platform-specific or state-dependent test paths.

## Key Source Files

Understanding the Maestro YAML file structure requires referencing these specific files in the mobile-dev-inc/Maestro repository:

- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt)** — Defines the data class mirroring every possible YAML command property, including `tapOn`, `launchApp`, `runFlow`, and `evalScript`.
- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlCommandReader.kt)** — Contains the `readCommands()` logic that validates YAML and transforms `YamlFluentCommand` objects into runtime `MaestroCommand` instances.
- **[`maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/yaml/MaestroFlowParser.kt)** — Implements low-level parsing that builds the command tree from raw YAML strings.
- **`maestro-test/src/test/resources/*.yaml`** — Houses real-world sample flows used by the test suite, serving as canonical reference implementations for all supported commands.

## Summary

- Maestro flows require a mandatory `appId` in the metadata header, followed by `---` separating configuration from commands.
- Commands follow a sequential YAML array structure using either scalar shortcuts or parameterized maps.
- The `YamlFluentCommand` class maps each YAML entry to typed command objects via `YamlCommandReader`.
- Advanced features include `runFlow` for composition, `repeat`/`while` for iteration, and `if`/`when` for conditional logic.
- Invalid syntax triggers descriptive `SyntaxError` messages with line numbers parsed by `YamlCommandReader`.

## Frequently Asked Questions

### What is the mandatory field in a Maestro YAML file?

The `appId` field is required in the top-level metadata section. It must contain the Android package name or iOS bundle identifier of the application under test. Without this field, `YamlCommandReader` cannot associate the test flow with a specific target app.

### How does Maestro parse conditional commands in YAML?

Maestro converts conditional blocks into `YamlCondition` objects that wrap nested command lists. The `if` key accepts predicates such as `platform`, `visible`, or `notVisible`. If the predicate evaluates to true during execution, Maestro runs the associated `commands` array; otherwise, it skips the block entirely.

### Can I include another flow file inside a Maestro YAML test?

Yes, using the `runFlow` command. This construct accepts a `file` parameter pointing to an external YAML path and optionally accepts an `env` map to inject environment variables. The parser loads and validates the referenced file recursively before converting the entire tree into `MaestroCommand` objects.

### What happens if my Maestro YAML syntax is invalid?

`YamlCommandReader` catches parsing errors internally and throws a `SyntaxError` containing the file path, exact line number, and surrounding context snippet. This occurs during the validation phase before any UI interaction begins, preventing partial test execution against malformed flow definitions.