How to Define Commands in Maestro YAML: Syntax, Structure, and Examples

Maestro uses a human-readable YAML syntax where commands are defined as a list of automation actions separated from optional configuration by three hyphens (---), with each command mapping to a Kotlin data class in Commands.kt that implements the sealed Command interface.

Maestro is an open-source mobile UI testing framework by mobile-dev-inc that allows developers to define test flows using declarative YAML files. When you define commands in Maestro YAML, you are essentially creating a sequence of instructions that the Maestro orchestrator converts into Kotlin data classes to drive automation on iOS and Android devices.

Anatomy of a Maestro Flow File

Every Maestro flow consists of two distinct sections divided by the --- separator. The optional configuration block sets up the test environment, while the required command list contains the actual automation steps.

Configuration Block

The top section defines environment parameters such as appId, url, or device. These fields are optional but recommended for targeting specific applications.

Command List

Below the separator, commands appear as a YAML array. Each entry represents a single automation action that maps to a specific implementation in the codebase.

appId: com.example.myapp
---
- launchApp
- tapOn: "Login"
- assertVisible:
    selector: "Welcome"
    timeout: "10s"

How YAML Commands Map to the Codebase

When Maestro parses a flow, the YAML command reader (tested in YamlCommandReaderTest under maestro-orchestra/src/test/resources) converts each list item into an instance of a command data class. All command definitions reside in maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt.

The Command Interface

Every command implements the sealed Command interface:

sealed interface Command {
    @get:JsonIgnore val originalDescription: String
    fun description(): String = label ?: originalDescription
    fun evaluateScripts(jsEngine: JsEngine): Command
    fun visible(): Boolean = true
    val label: String?
    val optional: Boolean
}

Key properties include:

  • label: Provides a human-readable name for CLI output and Maestro Studio visualization
  • optional: When set to true, allows the flow to continue with a warning status if the command fails (see CHANGELOG line 312)

Common Command Mappings

Specific YAML entries correspond to distinct Kotlin classes in Commands.kt:

  • launchApp → LaunchAppCommand
  • tapOn → TapOnCommand (fields: selector, waitToSettleTimeoutMs)
  • assertVisible → AssertVisibleCommand (fields: selector, timeout)
  • scrollUntilVisible → ScrollUntilVisibleCommand (fields: direction, visibilityPercentage)
  • swipe → SwipeCommand (fields: startPoint, endPoint, duration)

Supported Command Syntax Forms

Maestro supports multiple YAML syntax patterns depending on command complexity:

  1. No parameters: - launchApp uses default values defined in the data class
  2. Scalar argument: - tapOn: "Accept" passes a single string value
  3. Map of arguments: Complex commands accept key-value pairs for precise control
  4. Universal fields: Any command can include label and optional fields for metadata and error handling

Practical Code Examples

Basic Authentication Flow

appId: com.example.myapp
---
- launchApp
- tapOn: "Login"
- inputText: "user@example.com"
- tapOn: "Submit"
- assertVisible:
    selector: "Welcome"
    timeout: "8s"

Optional Commands with Labels

Use label for documentation and optional: true to handle intermittent UI elements:

url: https://example.com
---
- launchApp:
    label: "Open the website"
- tapOn:
    selector: "Accept Cookies"
    optional: true
    label: "Handle cookie banner"
- scrollUntilVisible:
    selector: "Feature Section"
    direction: down
    visibilityPercentage: 80
    timeout: "12s"

Composite Commands and Subflows

The subflow command is a CompositeCommand that nests another YAML file:

appId: com.example.myapp
---
- launchApp
- subflow:
    flow: "login_subflow.yaml"
    label: "Perform login"

Advanced Swipe Configuration

appId: com.example.myapp
---
- launchApp
- swipe:
    direction: left
    startRelative: "center"
    endRelative: "leftEdge"
    duration: 600
    waitToSettleTimeoutMs: 1500
    label: "Swipe left on carousel"

Extending Maestro's Command Set

To add new functionality to Maestro, developers must:

  1. Create a data class in Commands.kt implementing Command (or CompositeCommand for nested flows)
  2. Register it in the MaestroCommand sealed hierarchy
  3. Add test cases in YamlCommandReaderTest to verify YAML parsing

According to the contribution guide in CONTRIBUTING.md, this workflow ensures new commands integrate properly with the orchestrator's command evaluation engine.

Summary

  • Maestro flows use a two-part structure: an optional configuration header and a command list separated by ---
  • Each YAML command maps to a Kotlin data class in Commands.kt implementing the Command interface
  • The label field provides human-readable descriptions for debugging, while optional: true enables non-critical path execution
  • Commands support scalar, map, and composite syntax forms depending on required parameters
  • New commands require implementation in maestro-orchestra-models and registration in the sealed class hierarchy

Frequently Asked Questions

What is the purpose of the --- separator in Maestro YAML?

The three-hyphen separator divides the optional configuration block (containing appId, url, etc.) from the required command list. This distinction allows Maestro to initialize the test environment before executing the sequence of automation actions defined below the separator.

How do I make a command optional so the flow continues if it fails?

Add optional: true to any command's parameter map. According to the source code in Commands.kt, this property marks the command as non-critical, causing Maestro to log a warning instead of failing the entire flow when the command cannot execute successfully.

Where are command definitions stored in the Maestro codebase?

All command data classes reside in maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt. This file contains the sealed Command interface and implementations like TapOnCommand, AssertVisibleCommand, and SwipeCommand that correspond to YAML directives.

Can I add custom labels to commands for better logging?

Yes. Every command accepts a label field that overrides the default description in CLI output and Maestro Studio. As implemented in the Command interface's description() method, the label value takes precedence over originalDescription when generating execution logs.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →