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 visualizationoptional: When set totrue, 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→LaunchAppCommandtapOn→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:
- No parameters:
- launchAppuses default values defined in the data class - Scalar argument:
- tapOn: "Accept"passes a single string value - Map of arguments: Complex commands accept key-value pairs for precise control
- Universal fields: Any command can include
labelandoptionalfields 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:
- Create a data class in
Commands.ktimplementingCommand(orCompositeCommandfor nested flows) - Register it in the
MaestroCommandsealed hierarchy - Add test cases in
YamlCommandReaderTestto 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.ktimplementing theCommandinterface - The
labelfield provides human-readable descriptions for debugging, whileoptional: trueenables non-critical path execution - Commands support scalar, map, and composite syntax forms depending on required parameters
- New commands require implementation in
maestro-orchestra-modelsand 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →