# How to Reuse or Import Maestro Flows: Complete Guide to Modular Test Automation

> Easily reuse Maestro flows with the runFlow command. Learn to import external YAML files and build modular test automation for your mobile apps effectively.

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

---

**Yes, Maestro flows can be reused and imported using the `runFlow` command, which loads external YAML files or inline command blocks and executes them within the current orchestration session without breaking the test context.**

The `mobile-dev-inc/Maestro` repository provides a powerful YAML-based DSL for mobile UI automation. Understanding how to reuse or import Maestro flows enables you to build maintainable test suites that eliminate duplication and support modular architecture through composition.

## The runFlow Command Architecture

Maestro implements flow reuse through the `runFlow` command, a first-class DSL element that functions as a dynamic loader for sub-flows. When the interpreter encounters this command, it parses the referenced content into a list of `MaestroCommand` objects and executes them sequentially as part of the current session.

### Parsing and Validation in YamlFluentCommand

The core implementation resides in [`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), specifically within the `runFlowCommand()` method (lines 511-545). This function validates the command structure, resolves the file path (or processes inline commands), merges environment variables, and constructs a `RunFlowCommand` object. The method handles both `runFlow.file` references and `runFlow.commands` blocks, converting YAML definitions into executable command objects.

### Execution in the Orchestra Engine

The actual execution occurs in [`maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt) via the `runFlow()` method (lines 164-170). This method receives the list of parsed commands, runs them sequentially through the orchestration engine, and returns a boolean success flag. Because the sub-flow executes within the existing session, all state—including the current screen and application context—persists across the boundary between parent and child flows.

## Flow Reuse Patterns and Implementation Details

### File-Based Flow Import with Hot Reloading

Flows can import external YAML files using relative paths. The `runFlow` command resolves these paths and establishes file watchers on imported flows, enabling automatic reload during development. This implementation detail appears in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) (lines 646-654), where each `runFlow` invocation configures its own watch files to detect changes in the dependency graph.

### Inline Command Blocks

For scenarios requiring encapsulated logic without separate files, `runFlow` accepts an inline `commands` list. The parser converts these entries into `MaestroCommand` objects directly within `runFlowCommand()`, treating them equivalently to file-based definitions while maintaining lexical scope.

### Parameterized Sub-flows Using Environment Variables

Sub-flows accept parameters through the `env` map, which `runFlowCommand` merges into the child flow's environment using `.withEnv(runFlow.env)`. This mechanism allows parent flows to inject dynamic values—such as credentials or configuration flags—into reusable components without modifying the underlying YAML files.

### Conditional Execution with Runtime Evaluation

The `when` clause enables conditional flow import. During parsing, `runFlow.when?.toCondition()` (lines 536-538) compiles the condition into a `Condition` object. At runtime, the orchestration engine evaluates this condition and only executes the sub-flow if the predicate returns true, allowing platform-specific or state-dependent reuse patterns.

## Practical Code Examples

### Importing an External Flow File

```yaml

# parent.yaml

- tapOn:
    text: "Open Settings"
- runFlow:
    file: "subflow.yaml"
    label: "Settings Navigation"

```

```yaml

# subflow.yaml

- tapOn:
    text: "Settings"
- scroll:
    direction: down
    distance: 400

```

The `file` parameter resolves relative to the parent YAML location, and the orchestration engine executes [`subflow.yaml`](https://github.com/mobile-dev-inc/Maestro/blob/main/subflow.yaml)'s commands as if they were inline.

### Using Inline Commands

```yaml
- runFlow:
    commands:
      - tapOn:
          text: "Profile"
      - swipe:
          direction: up
          distance: 300
    env:
      USER_MODE: "demo"

```

This pattern encapsulates transient logic without creating separate files, while still supporting environment variable injection.

### Conditional Flow Import

```yaml
- runFlow:
    file: "androidOnly.yaml"
    when: ${platform == "android"}

```

The JavaScript expression in `when` is evaluated at runtime, and the flow only imports when the condition evaluates to true.

### Passing Dynamic Variables

```yaml
- runFlow:
    file: "loginFlow.yaml"
    env:
      USERNAME: ${username}
      PASSWORD: ${password}

```

The `env` map merges with the sub-flow's environment, allowing parameterized reuse of authentication or setup routines across multiple test scenarios.

## Summary

- **Use `runFlow`** to import external YAML files or define inline command blocks within the current orchestration session.
- **Reference implementation** resides in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) (parsing) and [`Orchestra.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/Orchestra.kt) (execution), with specific logic for command validation, environment merging, and sequential execution.
- **Support nesting** arbitrarily deep, with each level maintaining its own file watchers for development hot-reloading.
- **Apply conditions** using the `when` clause to enable platform-specific or state-dependent flow selection.
- **Inject parameters** via the `env` map to create reusable, parameterized test components.

## Frequently Asked Questions

### Can Maestro flows be nested multiple levels deep?

Yes, Maestro supports arbitrary nesting of `runFlow` commands. Each nested invocation maintains its own context while preserving the parent session state. The file watcher implementation in [`YamlFluentCommand.kt`](https://github.com/mobile-dev-inc/Maestro/blob/main/YamlFluentCommand.kt) (lines 646-654) ensures that changes to any level of the dependency tree trigger appropriate reloads during development.

### How do environment variables behave when importing flows?

Environment variables flow downward through the `env` parameter. When you specify `env` in a `runFlow` invocation, the `runFlowCommand()` method merges these values into the sub-flow's environment using `.withEnv()`. The child flow can access these variables using standard interpolation syntax, but changes within the child do not propagate back to the parent scope.

### Does runFlow support conditional execution based on platform?

Yes, the `when` clause enables conditional import. The parser compiles the condition into a `Condition` object via `runFlow.when?.toCondition()` (lines 536-538), and the orchestration engine evaluates this at runtime. This allows you to import Android-specific or iOS-specific flows conditionally using JavaScript expressions like `${platform == "android"}`.

### What happens to the test session when a sub-flow fails?

Because `runFlow` executes within the existing orchestration session via `Orchestra.runFlow()` (lines 164-170), a failure in the sub-flow propagates to the parent flow immediately. The method returns a boolean success flag, and the test runner handles the failure according to the parent flow's error configuration—either stopping execution or continuing based on the `optional` parameter or retry logic defined in the parent.