Expo(React Native)にe2eテストのMaestroを導入してみる

ここ最近React Nativeアプリのe2eテスト導入を試行錯誤しているんだけども、Detox, Appiumに続きMaestroを試してみる。

- [Expo + DetoxでE2Eテストが動くまで](https://qiita.com/harada4atsushi/items/7ca0e1b6d3fb0dd2c8b9)
- [React Native(Expo) + Appiumでe2eテスト環境を構築する（テストが通るところまで）](https://qiita.com/harada4atsushi/items/2752663b3f5dd5821713)

Maestroは比較的新しいテストツールのようで、React Nativeの公式でも３番目に紹介されていた。あまり期待はせずにとりあえず試してみる。

## まずはブランクプロジェクトが動くように

例によって、Expoのブランクプロジェクトが動くところまでセットアップする

```
$ expo init ExpoMaestro
$ expo eject
$ npx pod-install
$ npx react-native run-ios --simulator="iPhone 14"
```

起動できるところまで確認。

![Simulator Screen Shot - iPhone 14 - 2023-07-05 at 20.26.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47677/08a04250-59ba-cbc0-7ef5-6f089b4e7440.png)

こちらの記事を参考に、テストの挙動を確かめるための簡単なデモ画面を作成する。
https://dev.to/b42/test-your-react-native-app-with-maestro-5bfj

```App.tsx
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
import { StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';

export default function App() {
  const [username, setUsername] = useState<string>("");
  const [password, setPassword] = useState<string>("");
  const [valid, setValid] = useState(false);

  return (
    <View style={styles.container}>
      <View style={styles.inputGroup}>
        <Text style={styles.label}>Username</Text>
        <TextInput
          style={styles.input}
          placeholder="Username"
          onChange={(e) => setUsername(e.nativeEvent.text)}
          testID="usernameInput"
        />
      </View>
      <View style={styles.inputGroup}>
        <Text style={styles.label}>Password</Text>
        <TextInput
          style={styles.input}
          placeholder="Password"
          secureTextEntry
          onChange={(e) => setPassword(e.nativeEvent.text)}
          testID="passwordInput"
        />
      </View>
      <TouchableOpacity
        style={styles.button}
        onPress={() => {
          if (username.length > 0 && password.length > 0) {
            setValid(true);
          } else {
            setValid(false);
          }
        }}
        testID="signInButton"
      >
        <Text style={styles.buttonText}>Sign in</Text>
      </TouchableOpacity>
      {valid ? <Text style={styles.successText}>Sign in successfully</Text> : null}
      <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 24,
    backgroundColor: "#fff",
    justifyContent: "center",
  },
  inputGroup: {
    marginBottom: 16,
  },
  label: {
    fontSize: 12,
    fontWeight: "bold",
    marginBottom: 6,
  },
  input: {
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "gray",
    padding: 12,
    width: "100%",
    fontSize: 18,
  },
  button: {
    borderRadius: 8,
    padding: 12,
    backgroundColor: "black",
    alignItems: "center",
    marginBottom: 8,
  },
  buttonText: {
    fontSize: 16,
    fontWeight: "bold",
    color: "white",
  },
  successText: {
    fontSize: 16,
    color: "green",
    alignSelf: 'center',
  },
});
```

こんな感じのログイン画面っぽいものが出来る。

![Simulator Screen Shot - iPhone 14 - 2023-07-05 at 20.30.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47677/a891603f-653b-7a50-d892-5fca628208e6.png)


## Maestroの導入

この辺の記事を参考にMaestroをインストールする。
https://dev.to/b42/test-your-react-native-app-with-maestro-5bfj

```
$ curl -Ls "https://get.maestro.mobile.dev" | bash
$ brew tap facebook/fb
$ brew install facebook/fb/idb-companion
```

Mac OSX Ventura 13.4.1で作業しているが、javaが入っていないらしい。

```
$ maestro -v
The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.
```

Homebrewでjavaをインストールする。けっこう時間かかる。

```
$ brew install java
```

インストールしたのにまだ同じエラーが出る。どうやらパスが通ってないらしい。

```
$ java --version
The operation couldn’t be completed. Unable to locate a Java Runtime.
Please visit http://www.java.com for information on installing Java.
```

ので、シンボリックリンクを作る。

```
$ sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk.jdk                  ✘ 130
```

よろしい。

```
$ java --version
openjdk 20.0.1 2023-04-18
OpenJDK Runtime Environment Homebrew (build 20.0.1)
OpenJDK 64-Bit Server VM Homebrew (build 20.0.1, mixed mode, sharing)
```

maestroコマンドも実行できるようになった。

```
$ maestro -v
1.29.0
```

`maestro studio`はGUIから操作をレコードしてエクスポート出来るツールっぽいけど、いまいち操作方法がわからんので、今回はあまり深掘りしないでおこう。

```
$ maestro studio
```

![Screenshot 2023-07-05 at 20.57.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47677/01f75cc5-dc28-8bad-8556-2e17e5304e32.png)

適当にテストを書いてみる。この程度ならとてもシンプルな記述で済む。

```e2e/login.yml
appId: com.a.harada.ExpoMaestro
---
- launchApp

- tapOn:
    id: "usernameInput"
- inputText: "userhogehoge"

- tapOn:
    id: "passwordInput"
- inputText: "password"

- tapOn: "Sign in"
- assertVisible: "Sign in successfully"
```

シミュレータを起動してテストを実行。

```
$ npx react-native run-ios --simulator="iPhone 14"
$ maestro test e2e/login.yml
Running on iPhone 14 - iOS 16.2 - A2DAC637-2858-4DA1-B49C-6A35FE539845
Running on iPhone 14 - iOS 16.2 - A2DAC637-2858-4DA1-B49C-6A35FE539845

 ║
 ║  > Flow
 ║
 ║    ✅  Launch app "com.a.harada.ExpoMaestro"
 ║    ✅  Tap on id: usernameInput
 ║    ✅  Input text userhogehoge
 ║    ✅  Tap on id: passwordInput
 ║    ✅  Input text password
 ║    ✅  Tap on "Sign in"
 ║    ✅  Assert that "Sign in successfully" is visible
```

![Untitled.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/47677/2e80d917-e106-21d1-788a-6889754a62da.gif)
 
 
よっしゃ、簡単なテストが動いた。他のテストフレームワークに比べると圧倒的に導入が簡単だったけど、npmとかでインストールするわけではないし、javaを入れなきゃいけないので、チームで使うには環境構築のハードルがちょっと上がる気はする。

次はこれでちょっと実践のテストコードを書いてみる。