Testing React Native Apps with Maestro: Tutorial

React Native apps can be tested with Maestro using simple automation workflows. Explore installation, writing tests, and running mobile test flows.

Written by Sarthak Sharma Sarthak Sharma
Reviewed by Bhumika Babbar Bhumika Babbar
Last updated: 14 August 2026 15 min read

Key Takeaways

  • Maestro uses readable YAML flows to automate React Native UI interactions across Android and iOS without requiring complex test code.
  • Use stable testID values, reusable flows, conditional logic, and state-based waits to reduce flaky tests and keep automation easier to maintain.
  • Test complete user journeys across platforms, but isolate Android and iOS differences only where permissions, navigation, or native behavior actually diverge.

React Native helps teams build Android and iOS apps from a shared codebase, but UI testing still has to account for platform differences, gestures, animations, asynchronous updates, and native components.

Maestro makes this easier by letting you define mobile test flows in readable YAML. You can launch the app, tap elements, enter text, scroll, and validate expected UI states across Android and iOS.

By the end, you will understand how to set up Maestro for React Native, write reliable test flows, run them, and handle common mobile UI testing scenarios.

What is Maestro Framework?

Maestro is an open-source automation framework designed to test mobile app UIs reliably on real devices. Unlike traditional mobile testing tools, it does not rely on emulators or fragile selectors. It interprets UI components directly from the app’s rendering layer, which reduces flakiness and improves stability.

Maestro uses YAML scripts to define test flows. Each step corresponds to an interaction or assertion, making test scripts human-readable while maintaining the rigor required for complex workflows. Its architecture supports parallel execution on multiple devices, enabling efficient cross-platform testing for React Native apps.

How Does Maestro Support React Native UI Testing?

Maestro automates React Native UI testing by interacting directly with app components and views. It abstracts platform-specific differences and simulates real user behavior across both Android and iOS.

Here are some more reasons to use Maestro for React Native UI testing.

  • Precise element identification: Targets components using accessibility labels, text, or view hierarchy. Avoids fragile selectors like XPath or CSS, which break easily with UI changes.
  • Dynamic content handling: Automatically waits for asynchronous rendering, animations, and state updates before performing actions. Ensures tests do not fail due to timing issues.
  • Complex gesture simulation: Supports taps, long presses, swipes, scrolls, and multi-touch gestures. Enables testing of interactive elements exactly as a real user would.
  • Structured test flows: YAML-based scripts allow branching, conditional logic, loops, and assertions. Supports full user journeys, from app launch to complex workflows.
  • Cross-platform execution: Executes the same flows on Android and iOS without rewriting scripts. Handles platform-specific differences internally to maintain consistency.
  • CI/CD readiness: Integrates with pipelines to automate test execution, monitor results, and capture failures in real time. Improves feedback loops and reduces manual testing effort.

Why Choose Maestro for React Native Testing?

Maestro provides a robust framework for testing React Native apps. It interacts directly with app components to ensure reliable, stable, and maintainable test flows.

  • Stable Tests with Less Maintenance: Reduces failures caused by minor UI changes, lowering the cost of maintaining test suites.
  • Reusable Test Flows: Single flows can cover multiple scenarios or apps, saving development time and avoiding duplicate effort.
  • Accurate User Simulation: Captures real gestures and interactions, producing results that reflect actual user behavior.
  • Faster Feedback Loops: Automated execution highlights regressions early, allowing teams to fix issues before releases.
  • Scalable Testing: Supports larger test suites and multiple devices without increasing complexity for testers.

Maestro vs Other React Native Testing Tools

Maestro stands out for its reliability and simplicity when testing React Native apps. Unlike Detox, Appium, and Jest, it interacts directly with UI components and works seamlessly on real devices.

FeatureMaestroDetoxAppiumJest + React Native Testing Library
Platform supportAndroid and iOS, same flowsAndroid and iOSAndroid and iOSMainly JavaScript logic, limited real-device UI testing
Element targetingAccessibility labels, text, view hierarchyNative selectors, can be brittleXPath, class names, IDsComponent-level queries, no gestures
UI gesturesTap, swipe, scroll, multi-touchTap, swipe, limited multi-touchTap, swipe, scrollNot supported
Dynamic content handlingWaits for async rendering, animations, and state updates automaticallyManual waits often neededManual waits or retries neededNot designed for dynamic UI
Test script readabilityYAML-based, human-readableJavaScriptJava, JavaScript, or PythonJavaScript, component-focused
CI/CD integrationBuilt-in support for pipelinesSupports CI/CDSupports CI/CDLimited to unit/integration pipelines

Installing Maestro for React Native Projects

Before writing a Maestro flow, set up the CLI and make sure your local device environment is ready. Maestro currently requires Java 17 or later.

Step 1: Install Maestro CLI

On macOS or Linux, install Maestro with:

curl -Ls "https://get.maestro.mobile.dev" | bash

If the maestro command is not available after installation, add Maestro to your PATH:

export PATH="$PATH:$HOME/.maestro/bin"

Then reload your terminal configuration.

Step 2: Verify the Installation

Check that the CLI is available:

maestro --version

You should also have the platform tooling required to run your React Native app. For Android, this usually means the Android SDK and an emulator or connected device. For iOS, you need Xcode and an iOS Simulator.

Step 3: Start Your React Native App

Build and launch the app on the emulator, simulator, or device you plan to test. Maestro works through the app’s accessibility layer, so React Native apps do not require Maestro-specific instrumentation in the application code.

Once the app is running, you can create a YAML flow and execute it with:

maestro test flow.yaml

maestro test is the current CLI command for executing a Maestro flow.

At this point, Maestro is ready. The next step is preparing your React Native UI so that important elements can be targeted reliably in test flows.

Setting Up React Native Apps for Maestro Testing

Maestro can interact with visible text, but relying on text alone makes tests easier to break when labels change or the app is localized. For React Native apps, Maestro recommends using testID for elements you need to target consistently.

Step 1: Add Stable Test IDs

Assign a testID to important interactive components such as buttons, inputs, and navigation controls.

<TextInput

  testID="email-input"

  placeholder="Email"

/>




<Button

  testID="login-button"

  title="Log in"

  onPress={handleLogin}

/>

You can then target those IDs directly in Maestro:

- tapOn:

    id: "email-input"

- inputText: "tester@example.com"

- tapOn:

    id: "login-button"

Maestro maps React Native testID values to identifiers that can be used in flows. This is generally more reliable than depending on visible text that may change later.

Step 2: Keep IDs Consistent

Treat test IDs as part of your testing contract. Avoid generating them dynamically or renaming them during routine UI changes unless the underlying user action has changed.

Use IDs that describe the element’s purpose, such as checkout-button or search-input, rather than its screen position or styling.

With stable identifiers in place, you can start building Maestro flows around actual user journeys.

Creating Your First Maestro Test Flow

A Maestro flow is a YAML file that combines the app identifier with the actions and checks you want to perform.

For example, a basic login flow could look like this:

appId: com.example.myapp

---

- launchApp



- tapOn:

    id: "email-input"

- inputText: "tester@example.com"



- tapOn:

    id: "password-input"

- inputText: "password123"



- tapOn:

    id: "login-button"



- assertVisible: "Welcome"

Here, launchApp starts the application, tapOn selects an element, inputText enters data, and assertVisible confirms that the expected UI appears. Maestro automatically retries assertVisible while waiting for the target element, so you usually do not need to add a fixed delay before the assertion.

Save the flow as something descriptive, such as:

.maestro/login.yaml

Then run it against a connected device, emulator, or simulator:

maestro test .maestro/login.yaml

The maestro test command executes the YAML flow against the target device.

Start with one complete user journey rather than trying to automate the entire app at once. Once the login flow is stable, you can split common actions into reusable flows and build larger scenarios from them.

Essential Maestro Commands for React Native

Maestro provides a set of CLI commands that testers rely on to manage projects, execute test flows, monitor results, and troubleshoot issues. Knowing these commands ensures smooth setup, efficient test execution, and reliable debugging during React Native UI testing.

CommandSyntaxDescription
Verify Installationmaestro –versionConfirms that the Maestro CLI is installed and accessible on your system.
Initialize Projectmaestro initSets up Maestro configuration files and generates a sample test flow in your project.
Run Test Flowmaestro runExecutes a specific YAML test flow on connected devices. Replace with your flow file name.
List Connected Devicesmaestro devicesDisplays all devices currently available for testing, including real devices and simulators/emulators.
Check Environmentmaestro doctorValidates configuration, dependencies, and device connectivity. Highlights any setup issues.
View Test Logsmaestro logsShows detailed logs of a test flow run, helping identify failed steps or errors.
Dry Run a Flowmaestro run –dry-runPerforms a simulation of the test flow without executing actions on devices. Useful for validation.
Validate YAMLmaestro validateChecks the YAML syntax and structure of your test flow to prevent errors before execution.
Stop Running Flowmaestro stopStops a currently running test flow on connected devices. Useful for long or stuck tests.

Advanced Maestro Testing Techniques

Once your basic flows are stable, you can use Maestro’s flow-control features to reduce duplication and handle UI states that vary between runs.

1. Run Steps Conditionally

Some screens do not appear every time. Onboarding, permission prompts, and promotional pop-ups are common examples. Maestro supports conditional execution with when.

- runFlow:

    when:

      visible: "Skip"

    commands:

      - tapOn: "Skip"

The commands run only when the specified UI condition is met.

2. Create Reusable Subflows

Actions such as login, logout, or opening a common screen should not be copied into every test. Move them into separate YAML files and call them with runFlow.

- runFlow: flows/login.yaml

- tapOn: "Profile"

- assertVisible: "Account Settings"

You can also pass values into a subflow, which lets the same login flow work with different users or test data.

3. Repeat Actions with Loops

Use repeat when a scenario requires the same action several times. Maestro can also combine loops with variables and reusable flows for data-driven scenarios.

- repeat:

    times: 3

    commands:

      - tapOn: "Add Item"

This is useful when you need repeated interactions without maintaining several copies of the same commands.

4. Pass Test Data Through Variables

Hardcoding usernames, app IDs, or environment-specific values makes flows harder to reuse. Maestro lets you pass parameters from the CLI or define values that can be referenced inside the flow.

For example:

- tapOn:

    id: "email-input"

- inputText: ${TEST_EMAIL}

You can provide the value when running the test:

maestro test -e TEST_EMAIL=tester@example.com login.yaml

This is especially useful when the same flow runs against development, staging, or different test accounts.

5. Handle Dynamic Screens Without Fixed Delays

For elements outside the viewport, scrollUntilVisible keeps scrolling until the target appears or the operation times out.

- scrollUntilVisible:

    element:

      id: "checkout-button"

    direction: DOWN



- tapOn:

    id: "checkout-button"

For content that takes longer to load, extendedWaitUntil lets you specify a longer timeout instead of adding an arbitrary sleep.

- extendedWaitUntil:

    visible: "Order confirmed"

    timeout: 15000

This makes the flow depend on the actual UI state rather than device or network speed.

6. Use JavaScript for Complex Test Logic

YAML is enough for most UI flows, but some tests require data manipulation or logic that becomes difficult to express declaratively. Maestro supports JavaScript through inline expressions, evalScript, and runScript.

Use JavaScript selectively for cases such as generating dynamic data, processing values returned during a flow, or handling logic that would otherwise require several duplicated YAML steps.

The goal is not to make every Maestro flow complex. Use these techniques where they remove duplication or make a flow respond more reliably to changing application state.

Handling React Native Specific Scenarios

React Native introduces a few testing cases that deserve extra attention, especially when the same UI behaves differently on Android and iOS. Maestro works through the accessibility layer, so most of these cases can still be handled from the test flow without adding Maestro-specific code to the app.

1. Testing FlatList and ScrollView Content

Items rendered by FlatList or ScrollView may not exist in the visible UI until you scroll to them. Use scrollUntilVisible instead of assuming a fixed number of swipes.

- scrollUntilVisible:

    element:

      id: "product-42"

    direction: DOWN



- tapOn:

    id: "product-42"

This is useful when list position changes because of screen size, loaded data, or other content above the target.

2. Handling Android and iOS Differences

A React Native screen can behave differently across platforms because of navigation, permissions, or native UI. Maestro conditions let you keep the common journey in one flow while isolating the steps that are genuinely platform-specific.

- runFlow:

    when:

      platform: Android

    commands:

      - tapOn: "Allow"



- runFlow:

    when:

      platform: iOS

    commands:

      - tapOn: "Continue"

Avoid duplicating the entire test just because one or two interactions differ.

3. Working with Different Android and iOS App IDs

Android and iOS builds often use different application identifiers. Instead of maintaining two versions of the same flow, parameterize appId. Maestro specifically recommends external parameters when app identifiers differ across platforms.

appId: ${APP_ID}

---

- launchApp

- assertVisible: "Home"

Then pass the correct value when running the test:

maestro test -e APP_ID=com.example.android flow.yaml

The same flow can then be reused for the iOS bundle identifier.

4. Testing Native Permissions

Features such as the camera, microphone, location, contacts, and notifications can trigger operating-system permission handling. Maestro supports permission configuration for both Android and iOS.

For example, you can launch an app with a known permission state instead of depending on what happened in an earlier test:

- launchApp:

    clearState: true

    permissions:

      camera: allow

      location: allow

Controlling the starting permission state makes permission-dependent scenarios easier to reproduce.

5. Handling Asynchronous React Native Screens

React Native screens often update after API requests, state changes, or delayed rendering. Avoid assuming that the next element will be available immediately after an action.

- tapOn: "Load Orders"



- extendedWaitUntil:

    visible: "Your Orders"

    timeout: 15000



- assertVisible:

    id: "orders-list"

Base the next step on an observable UI state rather than adding a fixed delay. This keeps the test less dependent on device or network speed.

These cases are worth testing explicitly because a shared React Native codebase does not guarantee identical runtime behavior on Android and iOS.

Conclusion

Maestro gives React Native teams a practical way to automate complete mobile UI flows using readable YAML. Stable testID values, reusable subflows, conditional logic, and state-based waits help keep tests reliable as the app grows.

Start with critical user journeys such as login, checkout, or onboarding, then expand coverage where UI regressions carry the most risk. Run the same flows across Android and iOS, while keeping platform-specific steps isolated only where the app behavior actually differs.

Version History

  1. Aug 14, 2026 Current Version

    Updated the selected sections with stronger technical detail, practical examples, and clearer explanations focused on real testing use cases.

    Bhumika Babbar
    Reviewed by Bhumika Babbar Principal Engineer
Tags
Automated UI Testing Automation Frameworks Mobile App Testing Real Device Cloud
Sarthak Sharma
Sarthak Sharma

Senior Software Development Engineer

Sarthak Sharma is a Senior Software Development Engineer with 9+ years of experience in software testing and customer engineering. He specializes in helping teams adopt effective automation practices and maximize the value of their testing infrastructure.

App Bugs Vary Across Devices?
Execute automated tests across real Android and iOS devices.