AccueilClientsExpertisesBlogOpen SourceJobsContact

1 août 2023

Tests end to end avec Maestro

7 minutes de lecture

Tests end to end avec Maestro

Dans l'ecosystème React Native, les outils d'instrumentation de tests end to end (E2E) sont très peu nombreux. En réalité jusqu'à il y a un an, on pouvait compter uniquement Detox et React-Native-Owl (ce dernier est utilisé à des fins de tests de régression notamment). En juillet 2022, un nouveau challenger est arrivé : Maestro. Partons aujourd'hui à la découverte de cet outil en testant une application réalisée à l'aide de React Native.

Maestro, c'est quoi ?

Tout comme Detox et Owl, Maestro est un outil d'instrumentation de tests. L'objectif est d'exécuter une suite d'instructions (clic, scroll, input, etc.) sur un appareil mobile (simulateur ou téléphone physique) et de vérifier que le résultat obtenu à l'écran est bien celui attendu. Contrairement à Detox et Owl, ces instructions seront écrites dans des fichiers YAML, là où les deux autres outils utilisent du JavaScript.

Installation

Maestro ne nécessite que l'installation d'un CLI. Nous allons ici l'installer pour Mac, mais il est également disponible pour Windows et Linux.

curl -Ls "https://get.maestro.mobile.dev" | bash

Et voilà, Maestro est installé. Nous allons maintenant créer notre application de test. Pour cela nous utiliserons Expo.

yarn create expo-app

Nous allons créer un simple formulaire d'authentification. À des fins de tests, nous allons gérer la validité de la combinaison identifiant / mot de passe de manière très simple, mais évidemment dans un cas concret cela serait géré par un serveur.

const login = (username: string, password: string) => {
  return username === 'admin' && password === 'admin';
};

export default function App() {
  const [success, setSuccess] = useState<boolean | undefined>();
  const [username, setUsername] = useState<string>('');
  const [password, setPassword] = useState<string>('');

  const handleLogin = () => {
    setSuccess(login(username, password));
  };

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Username"
        onChangeText={setUsername}
        autoCapitalize="none"
        autoCorrect={false}
        testID="username-input"
      />
      <TextInput
        style={styles.input}
        placeholder="Password"
        secureTextEntry
        onChangeText={setPassword}
        onSubmitEditing={handleLogin}
        testID="password-input"
      />
      <TouchableOpacity
        style={styles.button}
        activeOpacity={0.6}
        onPress={handleLogin}
        testID="login-button"
      >
        <Text style={{ color: 'black' }}>Connexion</Text>
      </TouchableOpacity>
      {success !== undefined && (
        <Text style={{ color: success ? 'green' : 'red' }}>
          {success ? 'Connexion réussie' : 'Connexion échouée'}
        </Text>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    justifyContent: 'center',
    paddingHorizontal: 20,
    gap: 16,
  },
  input: {
    borderWidth: 1,
    borderColor: 'lightgray',
    borderRadius: 8,
    height: 44,
    paddingHorizontal: 16,
  },
  button: {
    backgroundColor: 'skyblue',
    height: 44,
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Ici un formulaire très simple : un champ texte pour l'identifiant et un autre pour le mot de passe ainsi qu'un bouton pour soumettre le formulaire. Un texte s'affiche en dessous pour indiquer si la connexion a réussi ou non.

Ecritures de tests

Nous allons maintenant écrire nos tests. Pour cela, nous allons créer un dossier e2e à la racine du projet. Créons ensuite un fichier login.yaml à l'intérieur de ce dossier. Ces fichiers sont, pour Maestro, des flows. Un flow est une suite de commandes à exécuter sur une application. Nous pouvons lui donner plusieurs attributs : un identifiant d'application, un nom, un tag, des variables d'environnement ainsi que des callbacks exécutés avant et après l'exécution du flow. Nous allons ici ajouter uniquement l'identifiant d'application, qui est obligatoire.

appId: com.maestro.test
---

Indiquons ensuite à Maestro d'ouvrir l'application.

- launchApp

Nous allons maintenant ajouter des instructions pour remplir le formulaire et le soumettre. Pour cela, nous allons utiliser les attributs testID que nous avons ajouté au niveau des composants de notre application. Ils serviront de sélecteur pour Maestro.

- tapOn:
    id: username-input
- inputText: admin
- pressKey: Enter
- tapOn:
    id: password-input
- inputText: bad
- pressKey: Enter

Maintenant nous allons valider la présence du message de connexion échouée.

- assertVisible:
    text: 'Connexion échouée'

Nous pouvons reproduire à la suite de ça les mêmes instructions pour tester une connexion réussie.

Néanmoins on remarque que cette répétition peut s'avérer exhaustive. Dans le cas d'une application plus importante, impliquant de nombreux champs ou bien tout simplement des étapes à répéter pour chaque flow comme par exemple un onboarding. Il serait intéressant de pouvoir regrouper ces instructions dans un fichier à part. Maestro nous permet de faire cela grâce à la commande runFlow. Créons un dossier subflows et ajoutons-y à l'intérieur un fichier input.yaml.

appId: com.maestro.test
---
- tapOn:
    id: ${INPUT_ID}
- inputText: ${TEXT}
- pressKey: Enter

Ce flow devra donc recevoir deux variables d'environnement : INPUT_ID et TEXT. Il est important de conserver la configuration appId afin d'indiquer l'identifiant d'application où les commandes doivent être exécutées. Nous pouvons maintenant l'utiliser dans notre flow principal.

appId: com.maestro.test
---
- launchApp
- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: username-input
      TEXT: admin
- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: password-input
      TEXT: bad

- assertVisible:
    text: 'Connexion échouée'

- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: password-input
      TEXT: admin

- assertVisible:
    text: 'Connexion réussie'

Nous avons ici écrit nos identifiants en dur dans les tests, mais il peut-être intéressant de les stocker à part, par exemple dans un fichier JavaScript où on peut ajouter de la logique. Dans notre cas, ce fichier sera très simple. Créons un dossier scripts et ajoutons-y un fichier login.js.

output.login = {
  username: 'admin',
  password: 'admin',
}

output est une variable exposée par Maestro, que l'on peut par la suite utiliser dans notre test. Pour cela, nous devons d'abord exécuter le script via la commande runScript

- runScript: scripts/login.js

Nous pouvons maintenant utiliser nos différentes variables dans la suite de notre flow:

- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: username-input
      TEXT: ${output.login.username}
- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: password-input
      TEXT: bad

- assertVisible:
    text: 'Connexion échouée'

- runFlow:
    file: subflows/input.yaml
    env:
      INPUT_ID: password-input
      TEXT: ${output.login.password}

- assertVisible:
    text: 'Connexion réussie'

Il est maintenant temps d'exécuter notre test. Dans un premier temps, il sera nécessaire de produire un build en mode release de l'application, puis l'installer sur notre simulateur. Nous pouvons ensuite exécuter la commande suivante :

maestro test e2e/login.yaml

Si tout se passe bien, nos tests devraient s'afficher en vert.

Il est possible de lancer nos tests directement sur Expo Go. Pour cela, notre appId doit devenir host.exp.Exponent, la commande launchApp devient openLink: exp://127.0.0.1:19000, enfin au moment du lancement des tests il faut s'assurer que notre bundler soit lancé via yarn start.

Maestro Studio

Maestro propose une fonctionnalité appelée Maestro Studio. Il s'agit d'une interface web présentant une recopie de l'écran de notre simulateur afin de pouvoir y sélectionner des éléments et d'en obtenir les différentes commandes à utiliser dans nos flows. Cela peut s'avérer très pratique pour les applications complexes où il peut être difficile de trouver les identifiants des éléments à tester. On peut aussi y effectuer une suite de tests directement dans l'interface.

maestro-studio maestro-studio-selection

Pour exécuter Maestro Studio :

maestro studio

Vidéos de tests

Il est possible d'obtenir un enregistrement de nos tests, néanmoins ce n'est possible que pour un seul flow à la fois :

maestro record e2e/login.yaml

Le test va être exécuté, converti en vidéo et enregistré sur les serveurs de Maestro pour une durée limitée.

Intégration dans Github Actions

L'intérêt principal d'avoir des tests E2E est évidemment de pouvoir les exécuter dans le cadre d'une intégration continue. Maestro propose son propre service de test, Maestro Cloud, mais nous allons ici intégrer cela directement dans une CI Github Actions. Ici nous lancerons nos tests uniquement sur iOS, mais l'idéal est de les lancer sur toutes les plateformes couvertes par notre application.

name: iOS e2e maestro

on:
  push:
    branches: [main]

jobs:
  test:
    name: E2E test
    runs-on: macos-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: yarn
      - name: Install IDB
        run: |
          brew tap facebook/fb
          brew install idb-companion

      - name: Install dependencies
        run: yarn
      - name: Install Maestro
        run: |
          curl -Ls "https://get.maestro.mobile.dev" | bash
          echo "$HOME/.maestro/bin" >> $GITHUB_PATH

      - name: Build & install app
        run: |
          npx expo prebuild -p ios
          gem install xcpretty
          yarn expo prebuild -p ios
          xcodebuild -workspace ios/maestrotest.xcworkspace -configuration Release -sdk iphonesimulator -derivedDataPath ios/build -scheme maestrotest
          open -a Simulator
          sleep 10
          xcrun simctl install booted ios/build/Build/Products/Release-iphonesimulator/maestrotest.app

      - name: Run Maestro
        run: maestro test e2e
        env:
          MAESTRO_DRIVER_STARTUP_TIMEOUT: 100000
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: results
          path: ~/.maestro/tests

Et voilà ! Vous pourrez trouver le résultat de tout ce que nous avons écrit sur ce repository.

Conclusion

Maestro est un outil de test E2E qui se veut bien plus simple d'utilisation que Detox, son principal concurrent, notamment de par sa simplicité de configuration. On notera notamment que Detox nécessite une configuration supplémentaire dans le code natif de notre application Android, ce qui n'est pas le cas de Maestro. L'écriture des tests en YAML peut être un frein au début notamment car nous pouvons souvent être en train de nous référer à la documentation lors de l'écriture de nos tests, néanmoins la présence de Studio simplifie grandement cela. Maestro bénéficie d'énormément de fonctionnalités que nous n'avons pas abordées ici, donc n'hésitez pas à consulter la documentation.

À vos tests !

18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub
Twitter
Flux RSS

Naviguez à vue