xhub.io
A Digital Service by BeeBack UG
BlogÜber uns
Zurück zum Blog
Softwareentwicklung

Mobile-E2E-Tests mit Maestro: von null auf grüne Flows

Maestro beschreibt Mobile-E2E-Tests als YAML statt als Code. Wie Installation, erster Flow, Flow-Organisation und CI-Anbindung aussehen — plus drei Learnings aus dem Testen unserer eigenen Apps.

Autor
Torsten Link
Datum
5. August 2026
Lesezeit
8 Min. Lesezeit
Mobile-E2E-Tests mit Maestro: von null auf grüne Flows

Warum E2E-Tests auf Mobile so unangenehm sind

Auf dem Web ist End-to-End-Testing ein gelöstes Problem. Ein Browser, ein DOM, ein Selektor — Playwright oder Cypress hängen sich dran und laufen los.

Auf Mobile ist davon nichts wahr. Es gibt zwei Plattformen mit zwei völlig verschiedenen UI-Toolkits. Es gibt keinen stabilen DOM, sondern eine View-Hierarchie, die je nach Renderer anders aussieht. Es gibt Animationen, die 300 Millisekunden brauchen, und Netzwerk-Requests, die mal 80 und mal 2.000 Millisekunden brauchen. Es gibt Systemdialoge für Benachrichtigungen, Kamera und Standort, die sich über die App legen und die niemand im Testcode vorgesehen hat. Und es gibt den Simulator, der beim zweiten Durchlauf einfach anders reagiert als beim ersten.

Das Ergebnis kennt jedes Team, das es mit Appium oder Detox versucht hat: eine Testsuite, die halb so schnell wächst wie die App und doppelt so oft rot ist wie berechtigt. Irgendwann schaut niemand mehr hin, und dann ist die Suite tot.

Der eigentliche Fehler liegt selten in der Testlogik. Er liegt in der Menge an Code, die man schreiben muss, bevor man den ersten Testfall überhaupt formulieren kann.

Was Maestro anders macht

Maestro ist ein Open-Source-Testframework für mobile Apps, und es trifft drei Entscheidungen, die den Unterschied ausmachen.

Tests sind YAML, kein Code. Ein Flow ist eine Liste von Kommandos. Kein Test-Runner, keine Page Objects, keine Promise-Ketten. Wer die App bedienen kann, kann den Flow lesen — auch Product Owner und QA-Kolleginnen ohne JavaScript-Hintergrund.

Black-Box statt Instrumentierung. Maestro spricht mit der App von außen, über die Accessibility-Schicht des Betriebssystems. Es muss nichts in den App-Code eingebaut werden, kein Test-Build, keine Bridge. Der Flow läuft gegen denselben Build, der auch in den Store geht.

Eine Sprache für iOS und Android. Derselbe Flow läuft auf beiden Plattformen, solange die Texte und Accessibility-IDs übereinstimmen. Das ist bei React-Native- und Flutter-Apps fast immer der Fall.

Dazu kommt der Punkt, der im Alltag am meisten wiegt: Maestro wartet von selbst. Jedes Kommando hat implizite Waits eingebaut. Man schreibt nicht waitForElement, weil es die Regel ist, sondern nur dort, wo eine echte Verzögerung existiert. Das eliminiert die häufigste Flake-Ursache überhaupt — den vergessenen Wait.

Installation

Maestro installiert sich als CLI, ohne Projekt-Setup:

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

Ab hier braucht es nur noch einen laufenden Simulator, Emulator oder ein per USB angeschlossenes Gerät mit installierter App. Ein Flow startet mit:

maestro test .maestro/smoke/app-launch.yaml

Praktisch für den Einstieg ist außerdem maestro studio: ein Inspector, der die aktuelle View-Hierarchie anzeigt und die passenden Selektoren gleich als YAML ausgibt. Damit findet man heraus, ob ein Element per id, per text oder per label erreichbar ist, ohne zu raten.

Der erste Flow

Ein Flow besteht aus einem Header — App-ID, optional Tags und Name — und darunter, getrennt durch ---, der Kommandoliste.

# Login mit Testzugangsdaten
appId: com.example.app
tags:
  - auth
  - smoke
name: "Login with test credentials"
---
- launchApp:
    clearState: true
    permissions:
      notifications: allow

- tapOn:
    id: "email-input"
- eraseText: 50
- inputText: "qa-user@example.com"

- tapOn:
    text: "Ihr Passwort"
- inputText: "Beispiel-Passwort!1"

- tapOn:
    text: "Anmelden"

- assertVisible:
    text: "Dashboard"

Das ist der komplette Testfall. Bemerkenswert sind drei Details:

clearState: true setzt die App vor dem Start auf Werkszustand zurück. Das ist der Unterschied zwischen einem Test, der immer gleich läuft, und einem Test, der davon abhängt, was der letzte Durchlauf hinterlassen hat. permissions beantwortet den Systemdialog für Benachrichtigungen vorab — ein Dialog weniger, der den Flow blockiert.

Selektoren gibt es in drei Geschmacksrichtungen. id greift auf die Accessibility-ID zu und ist am stabilsten — wenn die App sie setzt. text matcht sichtbaren Text, inklusive Platzhaltern in Eingabefeldern, und akzeptiert reguläre Ausdrücke. label greift auf das Accessibility-Label zu, was bei reinen Icon-Buttons oft der einzige Weg ist.

Für mehrsprachige Apps ist die Regex-Fähigkeit von text Gold wert. Statt zwei Flows pro Sprache zu pflegen, schreibt man einen:

- extendedWaitUntil:
    visible: "Dashboard|Übersicht|Overview"
    timeout: 15000

extendedWaitUntil ist der explizite Wait für die Fälle, in denen die impliziten nicht reichen: der erste Start nach der Installation, ein Sync gegen eine langsame API, ein Cold Start auf einem kalten Simulator.

Assertions, Screenshots und Flow-Organisation

Für Prüfungen gibt es assertVisible und assertNotVisible. Beide akzeptieren optional: true — dann führt ein fehlendes Element nicht zum Fehlschlag, sondern zu einer Warnung. Das klingt nach einem Feigenblatt und ist bei bestimmten Testfällen genau richtig: Ein Flow, der einen Zustand prüft, den nicht jede Testumgebung hat, soll übersprungen werden statt die ganze Suite rot zu färben. Zusammen mit label wird aus der Warnung eine lesbare Aussage:

- assertVisible:
    text: "Noch keine Importe vorhanden|No imports yet"
    optional: true
    label: "Empty state expected when no imports recorded"

takeScreenshot schreibt einen Screenshot an einen frei wählbaren Pfad. Das ist mehr als ein Debugging-Hilfsmittel: Wer die Store-Screenshots ohnehin für mehrere Sprachen und Gerätegrößen braucht, erzeugt sie mit denselben Flows, die auch die Tests fahren. Der Nebeneffekt ist, dass die Screenshots nie veralten.

- takeScreenshot: docs/screenshots/01-login

Der wichtigste Hebel für eine Suite, die auch nach einem Jahr noch lebt, ist aber die Organisation. Bewährt hat sich eine Struktur aus thematischen Ordnern plus einem Ordner für wiederverwendbare Bausteine:

.maestro/
├── flows/          # Bausteine: _launch-app, _ensure-logged-in, _logout
├── smoke/          # App startet, Grundnavigation
├── auth/           # Login, Registrierung, Logout
├── navigation/     # Tab-Wechsel, Deep Links
├── import/         # fachlicher Bereich
├── export/         # fachlicher Bereich
└── screenshots/    # Store-Assets pro Sprache

Die Dateien in flows/ beginnen mit einem Unterstrich, weil sie keine eigenständigen Tests sind, sondern Bausteine. Eingebunden werden sie mit runFlow:

- runFlow: ../flows/_launch-app.yaml
- runFlow: ../flows/_ensure-logged-in.yaml

runFlow kann auch bedingt ausführen, was für Setup-Schritte praktisch ist:

- runFlow:
    when:
      visible: "Dashboard"
    file: ../flows/_logout.yaml

Damit steht der Launch-Ablauf — inklusive aller Dialoge, die auf dem Weg zur ersten Seite auftauchen können — genau einmal im Repo. Ändert sich das Onboarding, ändert man eine Datei statt vierzig.

Über tags lassen sich Flows quer zur Ordnerstruktur gruppieren: smoke, regression, requires-login. In der Pipeline läuft dann pro Trigger die passende Auswahl.

CI-Anbindung

Das Grundprinzip ist einfach: Maestro ist ein CLI-Aufruf gegen ein laufendes Gerät. Der Aufwand in der Pipeline liegt nicht bei Maestro, sondern beim Gerät.

Auf einem macOS-Runner startet man einen iOS-Simulator headless, installiert den Build und ruft die Suite auf:

maestro test .maestro/smoke .maestro/auth
maestro test .maestro/import

Für Android gilt dasselbe mit einem Emulator. Der Exit-Code entscheidet über den Build-Status, JUnit-Reports lassen sich für die CI-Oberfläche ausgeben.

In der Praxis bewährt sich eine Staffelung: Smoke-Flows bei jedem Push, die fachlichen Ordner nachts. Mobile-E2E-Tests sind langsam — ein Flow braucht je nach Umfang zwischen 20 Sekunden und drei Minuten. Wer die komplette Suite an jeden Commit hängt, macht die Pipeline unbenutzbar und schafft damit den nächsten Grund, sie abzuschalten.

Zwei Dinge, die in Maestro selbst nicht enthalten sind, gehören trotzdem zur Rechnung. Erstens Testdaten: Ein Flow, der einen Import prüft, braucht eine Datei mit definiertem Inhalt — genau der Fall, für den wir Nanook gebaut haben, weil systematische Testdaten sich schlecht von Hand pflegen lassen. Zweitens Zeit: Alles, was von Datum, Abrechnungszeitraum oder Ablauffristen abhängt, lässt sich per UI-Interaktion nicht sinnvoll prüfen. Dafür gibt es zeitgesteuertes E2E-Testing wie bitdiver, das auf einer anderen Ebene ansetzt als der Flow auf dem Gerät.

Drei ehrliche Learnings

Wir testen unsere eigenen Apps — TankKosten und LadeKosten — mit Maestro. Drei Dinge haben wir dabei teurer gelernt, als nötig gewesen wäre.

Erstens: Der Launch ist der schwierigste Teil des Flows. Nicht der Testfall. Zwischen launchApp und der ersten wirklich prüfbaren Seite liegen Systemdialoge, Debug-Overlays im Development-Build, Onboarding-Screens und Animationen. Wir haben lange versucht, das in jedem Flow einzeln zu behandeln. Richtig ist ein einziger Launch-Baustein, der jede dieser Hürden mit optional: true abräumt — und dann in jedem Flow per runFlow steht.

Zweitens: optional: true ist ein Werkzeug, kein Pflaster. Wir haben es anfangs verwendet, um rote Tests grün zu bekommen. Das Ergebnis war eine Suite, die immer grün war und nichts mehr aussagte. Die Regel, die daraus wurde: Optional darf ein Schritt sein, wenn er von einem Umgebungszustand abhängt, den der Test nicht kontrolliert. Die eigentliche Assertion des Testfalls ist nie optional.

Drittens: Textselektoren brechen bei jeder Übersetzung. Unsere Apps laufen in mehreren Sprachen, und jede neue Locale hat Flows gekippt, die auf deutschen Beschriftungen aufgebaut waren. Wer von Anfang an Accessibility-IDs für die wichtigen Elemente setzt, spart sich das. Für alles andere ist der Regex-Selektor mit allen Sprachvarianten die zweitbeste Lösung — und die pragmatischste, wenn die App schon existiert.

Fazit

Maestro löst nicht alle Probleme des Mobile-Testings. Es löst das Problem, das die meisten Suiten scheitern lässt: die Hürde zwischen "wir sollten das testen" und "der Test steht im Repo". Ein Flow ist zwanzig Zeilen YAML. Das schreibt man auch dann, wenn der Sprint eng ist.

Wenn Sie E2E-Testing für Ihre App aufbauen wollen — von der ersten Suite bis zur CI-Anbindung — finden Sie auf unserer Seite zu Mobile E2E Testing, wie wir dabei vorgehen.

xhub.io

Digitale Innovation seit 25 Jahren

in𝕏⌗

Services

  • KI-Strategie
  • Custom Development
  • SaaS Solutions
  • IT Consulting
  • Digital Strategy
  • Cloud Migration

Produkte

  • invoice-api.xhub.io
  • Mobile Apps
  • nanook.xhub.io
  • bitdiver.xhub.io

Unternehmen

  • Über uns
  • Blog
  • Kontakt
Über den Autor
TL
Torsten Link
CTO & Mitgründer, xhub.io

CTO und Mitgründer von xhub.io. Software-Architekt mit Erfahrung aus Großprojekten (u.a. Deutsche Bahn) und Spezialist für skalierbare Backend-Systeme sowie CI/CD-Automatisierung.

LinkedIn-Profil
#Maestro#E2E-Testing#Mobile#React Native#Testautomatisierung
Artikel teilen

Newsletter abonnieren

Erhalten Sie monatlich praxisnahe Tipps zur Digitalisierung

Wir respektieren Ihre Privatsphäre. Jederzeit abbestellbar.

Inhalt

  • Warum E2E-Tests auf Mobile so unangenehm sind
  • Was Maestro anders macht
  • Installation
  • Der erste Flow
  • Assertions, Screenshots und Flow-Organisation
  • CI-Anbindung
  • Drei ehrliche Learnings
  • Fazit

Rechtliches

  • Impressum
  • Datenschutzerklärung
  • AGB

© 2026 xhub.io is a Digital Service by BeeBack UG. All rights reserved.