Mobile app testing easier with Maestro
Languages
- 🇻🇳 Tiếng Việt
View full on my Github: https://github.com/tuantvk/rnmaestro
I used Detox to test React Native apps. At that time, Detox was so "cool", saving both the time and effort of the dev team and the tester team. However, later on, I saw the complexity, as well as the "difficulty" with new team members, and that's when Maestro came to me as a savior. I know Maestro through an article on dev.to, but my team's "noob" is true 😂 (but my teammates don't believe it).
Table of Contents
- What is Maestro?
- Setting up the development environment and initializing the app
- Installing Maestro
- Test flows
- Maestro studio
- Test case
- Interaction with a component by testID
- External parameters
- runFlow
- Recording your flow
- Tags
- Maestro Cloud
- Videos testing of Maestro
What is Maestro?
Maestro is the simplest and most effective mobile UI testing framework. Maestro is built on learnings from its predecessors (Appium, Espresso, UIAutomator, XCTest) and allows you to easily define and test your Flows.
What are Flows? Think of Flows as parts of the user journey in your app. Login, Checkout and Add to Cart are three examples of possible Flows that can be defined and tested using Maestro. Declarative yet powerful syntax and write your tests in a yaml
or yml
file. Read more Why Maestro?
Platform Support:
Platform | Support |
---|---|
iOS | ✅ |
Android | ✅ |
React Native | ✅ |
Flutter | ✅ |
Web Views | ✅ |
I like using Maestro for mobile app testing. Installing and writing tests is also very easy for all those who do not know how to use the Casio FX-570 calculator.
In this example, I will guide you to install and write some common test cases. I use Mac OS and a simple app written in React Native.
Setting up the development environment and initializing the app
First, you must have the application to be tested. To create a React Native project, you can refer to the full steps in Setting up the development environment.
Assuming you already have the environment, proceed with initializing the application:
npx react-native init RNMaestro
After initialization, notice the applicationId of Android (android/app/build.gradle -> applicationId
) and iOS (Project in Xcode -> Signing & Capabilities -> Bundle Identifier
). You can optionally edit them for later use, in this tutorial, I edit Android and iOS to com.rnmaestro
.
In the App.tsx
file of the project, copy & paste the code below.
// App.tsx
import React, { useState } from 'react';
import {
View,
Alert,
SafeAreaView,
TextInput,
Button,
FlatList,
} from 'react-native';
const TASKS = Array.from({ length: 25 }, (_, i) => ({ title: 'Task ' + i }));
interface Item {
title: string;
}
const App = () => {
const [title, setTitle] = useState('');
const [tasks, setTasks] = useState(TASKS);
const addTask = () => {
if (title?.trim()?.length === 0) {
Alert.alert('Title is required');
} else {
const newTasks = [...tasks];
newTasks.unshift({ title });
setTasks(newTasks);
setTitle('');
}
};
const renderItem = ({ item }: { item: Item }) => (
<Button title={item?.title} onPress={() => Alert.alert(item?.title)} />
);
const keyExtractor = (item: Item, idx: number) => `${idx}`;
return (
<SafeAreaView>
<FlatList
data={tasks}
renderItem={renderItem}
keyExtractor={keyExtractor}
ListHeaderComponent={
<View>
<TextInput
value={title}
placeholder="Enter your title"
onChangeText={setTitle}
/>
<Button testID="btn_add_task" title="Add task" onPress={addTask} />
</View>
}
/>
</SafeAreaView>
);
};
export default App;
Or you can use my project (skip the step below if you don't use it):
- Clone repository
git clone https://github.com/tuantvk/rnmaestro.git
- Installing packages
cd rnmaestro; yarn install
- Pod (Only for iOS)
npx pod-install
Installing Maestro
For installation information for Windows or other environments, please refer to the official documentation Installing Maestro
.
Run the following command to install Maestro on Mac OS, Linux:
curl -Ls "https://get.maestro.mobile.dev" | bash
You can check if maestro is installed by checking the version:
maestro -v
It should print the version number vi.xxx.com
(E.g: 1.27.0).
In case zsh: command not found: maestro
, restart your terminal please.
Running flows on iOS Simulator requires installation of Facebook IDB:
brew tap facebook/fb
brew install idb-companion
- Xcode recommended version is 14 or higher.
- Maestro can't interact with real iOS devices yet. Only Simulator is supported at the moment. (May 2023)
After completing the above steps, the installation is complete. Start writing test cases.
Test flows
Based on the functionality of the current application, our workflow should look like this:
- Start the app
- Press
Add task
button - Check if the empty message is visible
- Enter
title
- Press
Add task
button - Check if the new task is visible
Maestro studio
Use Maestro Studio to instantly discover the exact commands needed to interact with your app.
maestro studio
Run the command above to launch Maestro Studio in your browser, default is http://localhost:9999
.
Video demo on Github - Maestro Studio
Test case
Create file .maestro/app.yaml
in the root folder of the project.
# .maestro/app.yaml
appId: com.rnmaestro # applicationId
---
- launchApp
# Check if "Title is required" is visible
- tapOn: "Add task"
- assertVisible: "Title is required"
- tapOn: "OK"
# Check if new task is visible
- tapOn: "Enter your title"
- inputText: "Task from maestro"
- hideKeyboard # Note 1
- tapOn: "Add task"
- assertVisible: "Task from maestro"
Note 1:
On iOS,hideKeyboard
is done with help of scrolling up and down from the middle of the screen since there is no native API to hide the keyboard. If using this command doesn't hide the keyboard we recommend clicking on some non-tappable region withtapOn
points command, similarly to how a user would hide the keyboard when interacting with your app. Read more iOS implementation caveat.
Run the associated Flow using the maestro test
command.
# run single flow
maestro test .maestro/app.yaml
# or
# run all flows in a directory
maestro test .maestro/
Video demo on Github - Test case
In terminal looks similar to the image below:
The test will be automatically restarted whenever you make a change to the test file. This is particularly convenient when writing a new test from ground up. Run with -c
argument.
maestro test -c .maestro/app.yaml
Commands
assertVisible | assertNotVisible | assertTrue | back |
clearKeychain | clearState | copyTextFrom | evalScript |
eraseText | extendedWaitUntil | hideKeyboard | inputText |
launchApp | openLink | pressKey | pasteText |
repeat | runFlow | runScript | scroll |
scrollUntilVisible | setLocation | stopApp | swipe |
takeScreenshot | tapOn | travel | waitForAnimationToEnd |
Read more Maestro - Commands.
Interaction with a component by testID
In the example above, I was instructed to write the flow by calling the contents of the screen directly. However, there will be many testing parts whose content changes after each operation, so you need to use testID
to identify like: View, Button, Text, Image.
Example:
# .maestro/app.yaml
appId: com.rnmaestro # applicationId
---
- launchApp
# Check if "Title is required" is visible
- tapOn:
id: "btn_add_task" # testID here
- assertVisible: "Title is required"
- tapOn: "OK"
External parameters
There might be cases where you don't want to store certain values in a test file itself, you can pass parameters to Maestro:
maestro test -e APP_ID=com.rnmaestro .maestro/app.yaml
And then refer to them in your flow using ${name}
notation:
# .maestro/app.yaml
appId: ${APP_ID} # applicationId
---
- launchApp
Constants can be declared at the flow file level, in key env
, above the ---
marker:
# .maestro/app.yaml
appId: ${APP_ID} # applicationId
env:
APP_ID: com.rnmaestro
---
- launchApp
If you want to run tests from scripts
of package.json
you can config:
{
"scripts": {
"test": "$HOME/.maestro/bin/maestro test",
"test-dev": "yarn test -e APP_ID=com.rnmaestro.dev",
"test-prod": "yarn test -e APP_ID=com.rnmaestro"
}
}
In case, I have 2 environments dev
and production
.
-
com.rnmaestro.dev
is for dev environments -
com.rnmaestro
is for production environments
Run test:
yarn run test-prod .maestro/app.yaml
runFlow
If you'd like to avoid duplication of code or otherwise modularize your Flow files, you can use the runFlow
command to run commands from another file. Example:
# Login.yaml
appId: com.example.app
---
- launchApp
- tapOn: Username
- inputText: Test User
- tapOn: Password
- inputText: Test Password
- tapOn: Login
# Settings.yaml
appId: com.example.app
---
- runFlow: Login.yaml # Run commands from `Login.yaml`
- tapOn: Settings
- assertVisible: Switch to dark mode
Read more Maestro - runFlow.
Recording your flow
Simply run the command below:
maestro record .maestro/app.yaml
After testing is complete, maestro renders a beautiful mp4
video recording of the entire process.
Currently, Maestro versions
CLI 1.26.0
,CLI 1.26.1
,CLI 1.27.0
, the record feature does not work on iOS, but it has been fixed at commit 2bd380d, but no release yet. If you are using the above versions, it is possible that the screen recording feature will not work (Updated date: 2023-05-09).
Tags
There is a couple of different use cases for this, but this is especially useful when you want to run some Flows at Pull Request time, and other Flows before a version release. The --include-tags
will look for all flows containing the provided tag; it doesn't matter if those Flows also have other tags. On the other hand, the --exclude-tags
parameter will remove from the list of Flows run any Flow that contains the provided tags. Example:
# flowA.yaml
appId: com.example.app
tags:
- dev
- pull-request
# flowB.yaml
appId: com.example.app
tags:
- dev
maestro test --include-tags=dev --exclude-tags=pull-request workspaceFolder/
In the scenario above:
- If they use
--include-tags=dev
, flowA and flowB will run. - If they use
--include-tags=dev,pull-request
, both flows will run. - If they use
--exclude-tags=pull-request
, only flowB will run. - If they use
--exclude-tags=dev
none Flow will run. - If they use
--include-tags=dev --exclude-tags=pull-request
, only flowB will run.
Read more Maestro - Tags.
Maestro Cloud
The easiest way to test your Flows in CI is to run your Flows on Maestro Cloud. Since your flows run in the cloud there's no need to configure any simulators or emulators on your end. Check out the Maestro Cloud Documentation.
CI Support:
CI Platform | Support via CLI | Native Integration |
---|---|---|
GitHub Actions | ✅ | ✅ |
Bitrise | ✅ | ✅ |
Bitbucket | ✅ | ✅ |
CircleCI | ✅ | ✅ |
GitLab CI/CD | ✅ | 🚧 |
TravisCI | ✅ | |
Jenkins | ✅ | |
All other CI platforms | ✅ |
Videos testing of Maestro
Resources
- Maestro: https://maestro.mobile.dev/
- Maestro Cloud: https://cloud.mobile.dev/
- Facebook IDB: https://github.com/facebook/idb
- Best tips & tricks for E2E Maestro with React Native: https://dev.to/retyui/best-tips-tricks-for-e2e-maestro-with-react-native-2kaa
- Test your React Native app with Maestro: https://dev.to/b42/test-your-react-native-app-with-maestro-5bfj
- Streamline Your React Native Testing with Maestro: https://viniciuspetrachin.medium.com/streamline-your-react-native-testing-with-maestro-bc279586125f
Maestro is also very new to the mobile application testing community, there are many issues to fix and upgrade. However, it is well deserved 1 star on the Maestro Github for the development team.
🎉 🎉 🎉 Hope the article is useful to everyone! Thanks! 🎉 🎉 🎉
Contributions
Any comments and suggestions are always welcome. Please make Issues or Pull requests for me.
Top comments (1)
What are the specific benefits of Maestro when compared to Detox?