How to Manage Environments in Maestro YAML: CLI Injection, Dynamic Variables, and Scope Isolation

Maestro injects environment variables into YAML flows using the ${VAR} syntax, supporting CLI flags, dynamic definitions, and isolated sub-flow scopes as implemented in Orchestra.kt and RunFlowTool.kt.

The mobile-dev-inc/Maestro repository provides a robust environment management system that allows you to parameterize test flows without hardcoding values. By leveraging the ${NAME} substitution syntax and the engine's scoping mechanisms, you can manage secrets, configuration flags, and runtime data across single flows and complex multi-flow hierarchies.

Injecting Environment Variables from the CLI

The most common method to manage environments in Maestro YAML is passing a JSON map via the command line when executing maestro run. The CLI parses this map in maestro-cli/src/main/java/maestro/cli/mcp/tools/RunFlowTool.kt (lines 69-106) and injects the values into the flow's execution context before any commands execute.


# Run a flow and provide two variables

maestro run -e '{"USERNAME":"test_user","PASSWORD":"s3cr3t"}' my_flow.yml

Inside your YAML file, reference these values using the ${VARIABLE_NAME} syntax:


# my_flow.yml

- launchApp: com.example.app
- tapOn: "login_button"
- inputText: "${USERNAME}"
- inputText: "${PASSWORD}"
- tapOn: "submit_button"

The substitution occurs after the YAML file is parsed but before the command is sent to the device, ensuring sensitive data never appears in your version-controlled test files.

Dynamic Variable Definition with DefineVariables

For values generated at runtime—such as timestamps, random IDs, or API tokens—you can create or overwrite variables dynamically using the DefineVariables command. This command evaluates a JavaScript/Kotlin snippet and stores the result in the environment map via jsEngine.putEnv, as implemented in maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt at line 536.


# flow.yml

- launchApp: com.example.app
- defineVariables:
    outputVariable: "authToken"
    script: |
      // Kotlin/JS snippet; returns a token string
      return "token_" + System.currentTimeMillis()
- tapOn: "settings"
- assertVisible: "Token: ${authToken}"

The DefineVariablesCommand data class (defined in maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt) drives this behavior, allowing any subsequent command in the flow to reference the newly created ${authToken} variable.

Variable Scoping and Isolation

Maestro guarantees environment isolation between flows and sub-flows through the EnvironmentScope mechanism. When entering a sub-flow, Orchestra.kt (line 1037) creates a new scope that isolates variables per execution branch, preventing leaks between parallel flows or nested hierarchies.

Overriding Variables in Sub-flows

You can override environment variables for a specific sub-flow without affecting the parent context or other parallel executions using the env block on a runFlow command:


# Parent flow runs with global environment

maestro run -e '{"ENV":"prod"}' parent.yml

# parent.yml

- runFlow:
    file: subflow.yml
    env:
      ENV: "staging"   # overrides the CLI value just for this sub-flow

# subflow.yml

- assertVisible: "Running in ${ENV} mode"

This per-flow scoping is backed by GraalJS isolation behavior documented in the repository's CHANGELOG.md (line 109), ensuring that a variable set in one flow never leaks into another, even during parallel test execution.

Default Environment Variables

Maestro automatically injects several built-in variables useful for CI/CD pipelines and test sharding, documented in CHANGELOG.md (line 21):

  • MAESTRO_DEVICE_UDID – The unique device identifier of the device under test
  • MAESTRO_SHARD_ID – The total number of shards when splitting flows across machines
  • MAESTRO_SHARD_INDEX – The current shard index (0-based) for distributed execution

Access these directly in your YAML without any CLI configuration:

- assertVisible: "Device UDID: ${MAESTRO_DEVICE_UDID}"
- assertVisible: "Shard ${MAESTRO_SHARD_INDEX} of ${MAESTRO_SHARD_ID}"

Substitution Rules and Edge Cases

Understanding how Maestro processes the ${VAR} syntax ensures reliable environment management:

  • Case-sensitivity – Variable names are case-sensitive; ${username} and ${USERNAME} reference different values
  • Undefined variables – If a referenced variable is undefined, the placeholder remains unchanged in the output and Maestro logs a warning, rather than failing silently or throwing an error
  • String applicability – Substitution works for any string value within commands, including inputText, tapOn selectors, and assertVisible labels

Summary

Managing environments in Maestro YAML relies on a three-layer architecture:

  • CLI injection via -e/--env flags parsed in RunFlowTool.kt for external secrets and configuration
  • Dynamic creation via the DefineVariables command in Orchestra.kt for runtime-generated values
  • Scope isolation via EnvironmentScope in Orchestra.kt to prevent variable leakage between sub-flows and parallel executions

Combine these mechanisms with built-in variables like MAESTRO_DEVICE_UDID to create flexible, secure, and scalable mobile UI testing pipelines.

Frequently Asked Questions

How do I pass environment variables when running a Maestro flow from the command line?

Use the -e or --env flag followed by a JSON object when executing maestro run. For example: maestro run -e '{"API_KEY":"12345"}' flow.yml. The CLI tool parses this JSON in RunFlowTool.kt and injects the values into the flow's execution context, making them available via ${API_KEY} syntax throughout the YAML file.

What happens if I reference an undefined environment variable in Maestro YAML?

If you reference a variable that has not been defined via CLI, DefineVariables, or parent scope, Maestro leaves the ${VAR} placeholder unchanged in the command string and logs a warning. The flow continues execution rather than failing immediately, allowing you to identify missing configuration without breaking the entire test suite.

Can I override environment variables for specific sub-flows only?

Yes. When using the runFlow command, include an env block to specify variables that apply only to that sub-flow execution. These local overrides take precedence over CLI-provided values but do not affect the parent flow's environment or other parallel sub-flows, thanks to the isolated EnvironmentScope created in Orchestra.kt for each sub-flow entry.

Are environment variables shared between parallel Maestro flows?

No. Maestro enforces strict isolation between parallel flows through per-flow EnvironmentScope instances. As documented in the CHANGELOG.md regarding GraalJS isolation, variables set in one parallel execution branch cannot be accessed or modified by another, ensuring test reliability and preventing race conditions in concurrent test runs.

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 →