Suppose you are developing Android and iOS apps and want to write UI tests for both using the Maestro UI testing framework. Maestro is a versatile UI testing framework ideal for **Android** and **iOS** applications.

In this blog, we will focus on using Maestro’s key features, such as `runFlow`, `runScript`, and external parameters, to demonstrate the best practices for **cross-platform testing**. We will use a **NASA wallpaper app** built with Jetpack Compose for Android and SwiftUI for iOS as an example. However, the methodology we cover is applicable to any Maestro-supported frameworks, such as Flutter, React Native, UIKit, Android View and more!

The apps will have similar UI with minor differences, we will go through all of them as we UI test!

### Prerequisites

If you want to follow along or want to try out running the test commands yourself, please download the code and run it!

**Source Code**: [https://github.com/thekharche/maestro-nasa-wallpaper-kmm](https://github.com/thekharche/maestro-nasa-wallpaper-kmm)

Instructions to run the Android and iOS apps are in the README file of the GitHub code.

**_Let’s take a closer look at our Android and iOS apps!_**

Please refer to the app screenshots above when reading the flow files to have a better understanding of the UI being tested!

## Setting App ID Using Environment Variables

If you need to run the same flow for apps with different app IDs, you can pass an external parameter for `appId`. Provide the `APP_ID` parameter to Maestro as follows:

```
maestro test -e APP_ID=com.wallpapers.androidapp .maestro/wallpapers/ListDetailWallpaper.yaml
```

In our case for the iOS app, we need to use `use com.wallpapers.iosapp`

Then, refer to it in your flow by using `${APP_ID}`:

```
appId: ${APP_ID}
---
- launchApp
```

## Handling Different Element IDs with runScript

Consider a scenario where you want to tap an image to enter the details screen, but the element IDs for the image on Android and iOS are different. For instance, Android uses `wallpaperImage` and iOS uses `imageItem`. To have one Maestro flow file test both IDs without modifying the source code, you can use the `runScript` feature.

**Conditional**`runScript` is available from version **1.28.0** of Maestro!

```
- runScript:
    when:
      platform: iOS
    file: ios/init.js

- runScript:
    when:
      platform: Android
    file: android/init.js

- tapOn:
    id: ${output.wallpapers.wallpaperItem}
    index: 0
```

`android/init.js`

```
output.wallpapers = {
    wallpaperItem: 'wallpaperImage',
}
```

`ios/init.js`

```
output.wallpapers = {
    wallpaperItem: 'imageItem',
}
```

The `tapOn` command utilizes the `id` from the output of the respective JavaScript file.

## Conditional Execution with runFlow

In a scenario where Android does not display a date in the detail view but iOS does, you can use `runFlow` to conditionally test the date requirements for the iOS platform.

```
- runFlow:
    when:
      platform: iOS
    file: Date.yaml
```

The `Date.yaml` flow file will only be invoked when the platform is iOS.

`Date.yaml`

```
appId: ${APP_ID}
---
- assertVisible: " .*2023-.* "
```

## Using Inline Commands with runFlow

Assume the iOS app does not yet support setting a wallpaper image, but the Android app does. You can use `runFlow` with inline commands to conditionally test this feature on the Android platform.

```
- runFlow:
    when:
      platform: Android
    commands:
      - assertVisible:
          id: "setWallpaperIconButton"
      - tapOn:
            id: "setWallpaperIconButton"
```

## Testing Platform-specific Features using runFlow

Both Android and iOS have different methods for displaying the Share drawer or bottom sheet. Using `runFlow`, you can test the behaviour of the share feature on both platforms.

```
- runFlow:
    when:
      platform: iOS
    file: ios/Share.yaml

- runFlow:
    when:
      platform: Android
    file: android/Share.yaml
```

`ios/Share.yaml`

```
appId: ${APP_ID}
---
- tapOn:
    id: "shareIconButton"
- assertVisible: ".*Copy.*"
- assertVisible: ".*Print.*"
```

`android/Share.yaml`

```
appId: ${APP_ID}
---
- tapOn:
    id: "shareIconButton"
- assertVisible: ".*Nearby.*"
- assertVisible: ".*Edit.*"
```

### Complete Maestro Flow File

**On GitHub**: [https://github.com/thekharche/maestro-nasa-wallpaper-kmm/blob/main/.maestro/wallpapers/ListDetailWallpaper.yaml](https://github.com/thekharche/maestro-nasa-wallpaper-kmm/blob/main/.maestro/wallpapers/ListDetailWallpaper.yaml)

`ListDetailWallpaper.yaml`

```
appId: ${APP_ID}
---
- launchApp

- runFlow: Date.yaml

- runScript:
    when:
      platform: iOS
    file: ios/init.js

- runScript:
    when:
      platform: Android
    file: android/init.js

- tapOn:
    id: ${output.wallpapers.wallpaperItem}
    index: 0

- assertVisible: ".*alien.*"

- runFlow:
    when:
      platform: iOS
    file: Date.yaml

- runFlow:
    when:
      platform: Android
    commands:
      - assertVisible:
          id: "setWallpaperIconButton"
      - tapOn:
          id: "setWallpaperIconButton"

- runFlow:
    when:
      platform: iOS
    file: ios/Share.yaml

- runFlow:
    when:
      platform: Android
    file: android/Share.yaml
```

## Conclusion

By following these best practices, you can effectively test your apps using the Maestro UI testing framework. These cross-platform UI testing techniques ensure that your tests remain maintainable, scalable, and compatible with both Android and iOS platforms.

## References

**runFlow:**

[runFlow  API Reference  Maestro Docs](https://docs.maestro.dev/api-reference/commands/runflow)

**runScript:**

[runScript  API Reference  Maestro Docs](https://docs.maestro.dev/api-reference/commands/runscript)

**external parameters:**

[Parameters and constants  Flows  Maestro Docs](https://docs.maestro.dev/advanced/parameters-and-constants)

**NASA Wallpaper App Source Code**:

[GitHub - thekharche/maestro-nasa-wallpaper-kmm](https://github.com/thekharche/maestro-nasa-wallpaper-kmm)
