Bridging the Gap Between Developers and QA Testers with Maestro
Tags: mobile, reactnative, flutter, testing
Published: 2023-10-29 14:04:45

[Maestro](https://maestro.mobile.dev/) is a mobile UI testing framework, very simple and effective. It's an alternative to other frameworks like Appium, Espresso, UIAutomator or XCTest and is built on top of the knowledge from its predecessors.

Maestro doesn't depend on any mobile framework, so you can run it in React Native apps, Native Android or iOS, Flutter, Ionic, Native Script, etc.
It doesn't need a special app package too, you can use it even in a production/release build.

The best points of Maestro are:

- Simple setup.
- Declarative syntax.
- Fast iteration.
- Built-in tolerance delay and flakiness.

## Installing

The Maestro documentation is complete and covers installing steps for the specific OS; [Macos](https://maestro.mobile.dev/getting-started/installing-maestro/macos), [Windows](https://maestro.mobile.dev/getting-started/installing-maestro/windows) and [Linux](https://maestro.mobile.dev/getting-started/installing-maestro/linux).

To install the Maestro CLI:
```sh
curl -Ls "https://get.maestro.mobile.dev" | bash
```
> Don't forget to add the Maestro to `PATH` variable.

Now we can check if the Maestro is successfully working:

```sh
maestro -v
```

## Creating a sample Flow

Start your Android or iOS emulator/simulator, create a new folder for store our files, and open it in your favorite code editor:

![Maestro Macos setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpzd3843c6uvmkmxhlur.png)

For this sample, let's write a simple flow to open the "Maps" app and search for a city, click to change the view from 2D to 3D, and change the map type to Satellite.

> Open the Maps before write/run this test, maybe you will be asked to give some permissions. I don't put this on the flow (but is possible) cause I want a clean example.

In your editor or terminal create a new file `maps-flow.yml` and let's start to write the instructions.

In the first line, we got:
```yml
appId: com.apple.Maps
```

> This `appId` is for iOS, if you are running android change it to `com.google.android.apps.maps`

This is the first line for every flow that we write, it tells Maestro what application we want to manipulate.
The first lines in the Maestro flow file are the configuration part, in fact, the configuration part is all the lines above the `---` marker(we will use this in the next part).
We can set the configuration in the flow file (like we see right now) or in a root `config.yml` file in the folder.

On the properties, we can for example run flow before the current in case we need any setup steps or somelike that.
You can check all the [properties here](https://maestro.mobile.dev/api-reference/configuration/flow-configuration).


So using the name and appId properties our config part looks like this:

```yml
appId: com.apple.Maps
name: Maps Test
---
```
Here we use the marker `---` to tell the Maestro that above is the config and below is your workflow.

Now let's write the commands flow, before any command in the app we need to open the app, and for that, we use the `launchApp` command.

> `Command` is the name of actions(the properties in the `YML`) that we got in Maestro.

```yml
appId: com.apple.Maps
name: Maps Test
---
  - launchApp
```

Note that have a space on the line of the command `launchApp`, this is how `YML` files work, we need indent to nesting. If it is not clear, you can [read about YML](https://www.redhat.com/en/topics/automation/what-is-yaml) before starts.

The `launchApp` command [accepts other params too](https://maestro.mobile.dev/api-reference/commands/launchapp), you can launch another app, clear the state, give or not permissions, and more. 
All the commands got a really good doc.

Now we can test the flow, in the terminal run:

```sh
maestro test maps-flow.yml
```

> Sometimes it takes some time to install the maestro-drive in the device in the first run.

If all is right we will see the following output:

![Maestro flow run](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xno3hc9j217xtsbkuopp.png)

Let's put one more command:

```yml
appId: com.apple.Maps
name: Maps Test
---
  - launchApp
  - tapOn: "Search Maps"
```

> 🚩 For Android emulator change the `tapOn` to "Search here".

The `tapOn` command tells the Maestro to tap/touch/click on a view/component/element that matches with the selector that we pass. By default we can pass a string as an argument, this means that Maestro will look for a view that has this EXACLTY text. It's very useful when you don't know or the application doesn't use things like `test-id`.
But like always we can pass any other option, in this case, other selectors. 

In Maestro we got a lot of selectors, `text`, `id`, `point` (relative position in the screen), and more. [All selectors are documented here](https://maestro.mobile.dev/api-reference/selectors).
Right now the `text` selector is the best option for us.

We tap on the search field, and putting some text is the next step, right?

```yml
appId: com.apple.Maps
name: Maps Test
---
  - launchApp
  - tapOn: "Search Maps"
  - inputText: "Salvador, Bahia, Brazil"
```

Now we type some text in the field, the `inputText` command has some random generators too, you can see it on the docs.
Now that we have typed the text let's confirm the search and seed the result:

```yml
appId: com.apple.Maps # android = com.google.android.apps.maps 
name: Maps Test 
---
  - launchApp
  - tapOn: "Search Maps" # android = Search here
  - inputText: "Salvador, Bahia, Brazil"
  - pressKey: Enter
```

![Maestro demo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx4g303dh4oyulddctw5.png)

If we want we can swipe the info modal, just add and test:

```yml
  - swipe:
      start: 50%, 80%
      end: 50%, 10%
```

## Using parameters

We are changing some values between OS, for iOS we using the `appId` "com.apple.Maps" and for Android "com.google.android.apps.maps". In this case, we can turn this flow generic for each OS using parameters.

With parameters is possible to change the fixed values in the file for variables that will be read from the CLI arguments or from environment variables. Just a refactor in our file:

```yml
appId: ${APP}
name: Maps Test 
---
  - launchApp
  - tapOn: ${FIELD}
  - inputText: "Salvador, Bahia, Brazil"
  - pressKey: Enter
  - swipe:
      start: 50%, 80%
      end: 50%, 10%
```

Now `appId` is the value of `APP` and `tapOn` is `FIELD`, to set this value just run the Maestro CLI this way:

For Android test:
```sh
maestro test -e APP=com.google.android.apps.maps -e FIELD="Search here"  maps-flow.yaml
```

For iOS test:
```sh
maestro test -e APP=com.apple.Maps -e FIELD="Search Maps"  maps-flow.yaml
```

We can use parameters for multiple things, if you have a secret (like a password) and don't want to store this in the `YML` file or if this value is dynamic and you need to get a new one every running for example.

## Recording and report

You may need to get some evidence that all tests are passing, maybe a video or a report or both. Maestro has a built-in solution for the two cases.

### Recording

Maestro records the mobile screen and creates a virtual window to show the test state and formats it to `mp4`, actually the rendering is done in the server and when the render is done you get a temporary URL to download the video.

To record the video is ultra simple, just change `test` to `record` in the CLI:

```sh
maestro record -e APP=com.google.android.apps.maps -e FIELD="Search here"  maps-flow.yaml
```
The output:
![Maestro record command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aqfa0xt5jna6b0cc9h3w.gif)

### Report

Maestro can generate reports for test suites, in this case, we just got one test but we can generate the report too.
Like the record, the report is very simple too, just add a new argument in the CLI:

```sh
 maestro test --format junit -e APP=com.google.android.apps.maps -e FIELD="Search Here"  maps-flow.yaml
```

The `--format junit` will generate an `xml` file in the root with the status of the tests, actually the only supported format is Junit. You can change the output path using the `--output` argument.


![Tests report in xml](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xy14r1jfnl7qct4ikpm8.png)

> Copy and past the `xml` [content here](https://lotterfriends.github.io/online-junit-parser/#suite.0)


## Conclusion 

Maestro is an awesome framework and we use it a lot at my current company, [GOK](https://www.gok.digital/?ref=tuliocalil).

This is just the first post of a series about Maestro, I will bring real-world examples and advanced concepts in the next posts, so [check my personal blog](https://tuliocalil.com/) and stay updated with new posts!