🚀 Getting Started with Maestro UI for Mobile UI Testing

UI testing is a crucial part of any mobile app development workflow. Whether you're building with React Native, Flutter, Swift, or Kotlin, ensuring that your app's user interface behaves as expected is key to a smooth user experience.

In this article, we’ll explore how to use Maestro — a simple and powerful tool for automating UI tests for mobile apps.

Maestro is a UI testing framework specifically built for mobile apps. It's:

  • Easy to set up

  • Uses YAML files for test scripts

  • Supports both iOS and Android

  • Great for end-to-end (E2E) testing

Think of it as a simpler, lighter alternative to Appium or Detox — ideal for fast iterations and CI/CD pipelines.

Installation

I will guide you through how to install Maestro for different operating systems.

  • macOS

    If you use Homebrew, install Maestro with:

      "brew install mobile-dev-inc/tap/maestro"
    

    Once installed, verify it:

      "maestro --version"
    

    Make sure you have Java installed (brew install openjdk), as Maestro requires Java 11+ to run.

Windows

  • Install Java

    • Download and install Java 11+ from Adoptium or Oracle

    • Add Java to your system's PATH if needed.

  • Download Maestro CLI

    • Go to the official Maestro Releases Page

    • Download the latest .zip file for Windows

    • Extract it to a folder (e.g., C:\maestro)

    • Add that folder to your Environment Variables → PATH

      Verify Installation:

        "maestro --version"
      

      Linux

      1. Install Java 11+
    "sudo apt update"
    "sudo apt install openjdk-11-jdk"
  1. Download Maestro
    "curl -Ls "https://get.maestro.mobile.dev" | bash"
  1. Move to /usr/local/bin for global access:
    "sudo mv maestro /usr/local/bin/maestro"
  1. Check it works:
    "maestro --version"

Using Maestro in your code

Create a folder in your project for your test flows or on your terminal you can run:

"mkdir maestro-tests && cd maestro-tests"

In your component or screen that you would like to test, you have to add test ids to various buttons you want to be identified

Example (Login.tsx) in react native.

import React, { useState } from 'react';
import { View, Text, TextInput, TouchableOpacity, StyleSheet, Alert } from 'react-native';

const Login = ({ navigation }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = () => {
    if (email === 'user@example.com' && password === 'password123') {
      Alert.alert('Success', 'Welcome');
    } else {
      Alert.alert('Error', 'Invalid credentials');
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title} testID="login-title">Login</Text>

      <TextInput
        style={styles.input}
        placeholder="Email"
        value={email}
        onChangeText={setEmail}
        autoCapitalize="none"
        keyboardType="email-address"
        testID="email-input"
      />

      <TextInput
        style={styles.input}
        placeholder="Password"
        value={password}
        onChangeText={setPassword}
        secureTextEntry
        testID="password-input"
      />

      <TouchableOpacity style={styles.button} onPress={handleLogin} testID="submit-button">
        <Text style={styles.buttonText}>Submit</Text>
      </TouchableOpacity>
    </View>
  );
};

export default Login;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    backgroundColor: '#fff',
  },
  title: {
    fontSize: 28,
    marginBottom: 24,
    textAlign: 'center',
    fontWeight: 'bold',
  },
  input: {
    height: 50,
    borderWidth: 1,
    borderColor: '#ddd',
    paddingHorizontal: 12,
    marginBottom: 16,
    borderRadius: 8,
  },
  button: {
    backgroundColor: '#007bff',
    paddingVertical: 14,
    borderRadius: 8,
  },
  buttonText: {
    color: '#fff',
    textAlign: 'center',
    fontSize: 16,
  },
});

Since everything is up and running we can start testing, so let’s write our first flow. We are going to write commands to launch the app.

Step 1: Create a new file with login**.yaml** extension inside of the maestro-tests folder we initially created.

Steps 2: In your login.yaml file

appId: com.example.myapp

---
- launchApp

- tapOn: "email-input"
- inputText: "user@example.com"

- tapOn: "password-input"
- inputText: "password123"

- tapOn: "submit-button"

- assertVisible: "Welcome"

On your terminal run the command to carry out your test:

maestro test login_flow.yaml

Congratulation! You have successfully created a test case for your login screen.

Here are some other available Maestro commands you might find help.

- launchApp: # Launches the app under test
- stopApp: # Stops current application
- tapOn: # Taps on a selected element
- doubleTapOn: # Double taps on a selected element
- inputText: # Inputs text
- eraseText: # Removes characters from the currently selected text field
- assertVisible: # Asserts whether an element is visible
- assertNotVisible: # Asserts whether an element is not visible
- copyTextFrom: # Copies text from an element and saves it in-memory
- pasteText: # Pastes any text copied with copyTextFrom into the currently focused field
- startRecording: # Starts a screen recording
- stopRecording: # Stops a running screen recording
- scroll: # Does a vertical scroll
- scrollUntilVisible: # Scrolls towards a direction until an element becomes visible in the view hierarchy
- waitForAnimationToEnd: # Waits until an ongoing animation/video is fully finished, and the screen becomes static
- extendedWaitUntil: # Waits until an element becomes visible within a specified amount of time
- back: # Navigates the user to the previous screen
- pressKey: # Presses a set of special keys
- runFlow: # Runs commands from another file
- runScript: # Runs a provided JavaScript file
- setLocation: # Applies a mock geolocation to the device
- travel: # Mocks the motion of the user, by specifying a set of points and a speed
- clearState: # Clears the application state
- clearKeychain: # Clears the entire iOS keychain
- hideKeyboard: # Hides the software keyboard
- swipe: # Makes the swipe gesture
- addMedia: # Adds media to the device’s gallery
- takeScreenshot: # Saves a screenshot in a PNG file
- assertTrue: # Asserts whether the given value is either true or non-empty
- repeat: # Repeats set of commands N times
- openLink: # Opens a link on a device
- evalScript: # Allows specifying JavaScript directly in the Maestro flow
13
Subscribe to my newsletter

Read articles from Matthias Ehizojie directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matthias Ehizojie
Matthias Ehizojie