Engineering-Praxis

Unzuverlässige XCUITest-Läufe auf einem Cloud-Mac beheben

Unzuverlässige XCUITest-Läufe auf einem Cloud-Mac beheben

Dieselben XCUITests können lokal wiederholt erfolgreich durchlaufen, auf einem Cloud-Mac jedoch sporadisch eine Schaltfläche nicht finden, in ein Timeout laufen oder auf dem Startbildschirm hängen bleiben. Dahinter steckt meist mehr als nur eine „langsame Maschine“. Häufiger sind ein wechselndes Ausführungsziel, hinterlassener Zustand aus einem vorherigen Test, Wartezeiten mit fester Dauer oder fehlende Diagnosedaten nach einem Fehler die Ursache. Die Probleme sollten in dieser Reihenfolge behoben werden: zuerst die Eingaben festlegen, dann implizite Abhängigkeiten beseitigen und erst danach über Wiederholungen nachdenken.

Eingaben für jeden Lauf fest vorgeben

Geben Sie Xcode, Testplan, Simulatorziel und Build-Verzeichnis ausdrücklich im Befehl an, statt sich auf die zuletzt in der grafischen Oberfläche ausgewählten Einstellungen zu verlassen. Ein Cloud-Mac kann langfristig unterschiedliche Aufgaben ausführen, und dasselbe Arbeitsverzeichnis kann von mehreren Pipelines verwendet werden. Daher benötigt jeder Lauf eigene DerivedData und ein eigenes Ergebnispaket.

RUN_ID="${CI_RUN_ID:-local-$(date +%s)}"
ARTIFACTS="$PWD/artifacts/$RUN_ID"
DERIVED="$PWD/.derived/$RUN_ID"

mkdir -p "$ARTIFACTS" "$DERIVED"

xcodebuild test \
  -workspace Sample.xcworkspace \
  -scheme SampleUITests \
  -testPlan Smoke \
  -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
  -derivedDataPath "$DERIVED" \
  -resultBundlePath "$ARTIFACTS/Smoke.xcresult" \
  -parallel-testing-enabled NO

Deaktivieren Sie bei der ersten Stabilisierung unzuverlässiger Tests zunächst die parallele Ausführung, um eine serielle Baseline zu schaffen. Sobald diese stabil ist, können Sie die Parallelisierung schrittweise für einzelne Gruppen aktivieren. Ändern sich mehrere Simulatoren, Testdaten und Hintergrundaufgaben gleichzeitig, lässt sich nur schwer feststellen, ob ein Fehler vom Test selbst oder von der Ablaufsteuerung verursacht wurde.

Minimale Prüfung vor dem Start durchführen

Protokollieren Sie vor dem Lauf die Ausgabe von xcodebuild -version und prüfen Sie mit xcrun simctl list devices available, ob das Ziel tatsächlich vorhanden ist. Geben Sie nicht nur den Gerätenamen ohne Systembedingung an, und lassen Sie verschiedene Runner nicht dasselbe DerivedData-Verzeichnis verwenden.

Feste Wartezeiten durch beobachtbare Zustände ersetzen

Thread.sleep wirkt unkompliziert, ersetzt aber die Bedingung „Die Seite ist bereit“ durch „Einige Sekunden sind vergangen“. Bei einer schnellen Netzwerkantwort kostet das unnötig Zeit; dauern eine Animation oder ein Festplattenzugriff etwas länger, endet die Pause dagegen zu früh.

let app = XCUIApplication()
app.launchArguments += ["--ui-testing", "--reset-state"]
app.launch()

let continueButton = app.buttons["onboarding.continue"]
let ready = continueButton.waitForExistence(timeout: 12)

XCTAssertTrue(ready, "Continue button did not appear")
XCTAssertTrue(continueButton.isHittable)
continueButton.tap()

Elemente sollten über stabile accessibility identifier angesprochen werden, nicht über lokalisierte Texte oder ihre Position in der View-Hierarchie. Ist eine Schaltfläche bereits vorhanden, aber nicht antippbar, sollten Sie überlagernde Elemente, Animationen und die Scrollposition prüfen, statt lediglich das Timeout zu verlängern.

Ein Timeout definiert die Fehlergrenze und ist kein Leistungsversprechen. Es sollte normale Schwankungen abdecken, bei einem echten Stillstand aber möglichst schnell fehlschlagen und den Zustand für die Diagnose sichern.

Jeden Test mit einem bekannten Zustand beginnen

Sporadische Fehler werden häufig vom vorherigen Test verursacht: Der Willkommensbildschirm wurde bereits übersprungen, Testdaten sind noch vorhanden, ein Dialog wird nur einmal angezeigt oder ein Hintergrundprozess wurde nicht beendet. Am zuverlässigsten ist es, wenn die App bei entsprechenden Testargumenten eine eigene Rücksetzlogik ausführt und für jede Testidentität eindeutige Daten erzeugt werden.

Die Zustände lassen sich nach folgenden Grenzen behandeln:

Zustandstyp Empfohlene Vorgehensweise Nicht empfohlene Vorgehensweise
App-Einstellungen Testspezifisches Zurücksetzen über Startargumente auslösen Davon ausgehen, dass der Zustand nach der Installation immer leer ist
Lokale Datenbank Definierte Fixtures importieren oder die Testdatenbank neu erstellen Von Daten abhängen, die der vorherige Test geschrieben hat
Serverseitige Daten Einen eindeutigen Namensraum für den aktuellen Lauf verwenden Feste Datensätze gemeinsam für mehrere Aufgaben nutzen
Systemberechtigungen Vor der Suite einheitlich vorbereiten und prüfen Spontan in beliebigen Tests behandeln
App-Prozess Nach dem Test ausdrücklich beenden Davon ausgehen, dass er sich nach einem Absturz automatisch erholt

Bei einer Bereinigung auf Suite-Ebene kann die App beendet werden. Eine Deinstallation sollte nur erfolgen, wenn tatsächlich der Ablauf nach einer Erstinstallation geprüft werden muss. Das häufige vollständige Löschen des Simulators verlängert die Laufzeit und kann Umgebungsprobleme hinter einem vermeintlichen „nach der Bereinigung erfolgreich“ verbergen.

xcrun simctl terminate booted "$APP_BUNDLE_ID" 2>/dev/null || true

Wiederholungen als Klassifizierungswerkzeug nutzen

Automatische Wiederholungen dürfen kein Ersatz für ein echtes Erfolgskriterium sein. Eine sinnvolle Strategie besteht darin, einen fehlgeschlagenen Test genau einmal zu wiederholen und attempt-1.xcresult sowie attempt-2.xcresult getrennt zu speichern. Schlagen beide Versuche fehl, sollte das Problem vorrangig als reproduzierbarer Defekt behandelt werden. Schlägt der erste Versuch fehl und ist der zweite erfolgreich, wird der Test als unzuverlässig markiert; anschließend müssen Wartebedingungen, gemeinsam genutzter Zustand und Ressourcenkonflikte weiter untersucht werden.

Nicht nur den endgültigen Exit-Code aufbewahren

Erfassen Sie bei jedem Fehler mindestens Folgendes:

  1. Ein separates Ergebnispaket und die Testprotokolle;
  2. Einen Screenshot und die UI-Hierarchie des fehlgeschlagenen Schritts;
  3. Die Xcode-Version, den Zielsimulator und die Startargumente;
  4. Die Kennung des Laufs, den Testnamen sowie Start- und Endzeit;
  5. Den Beendigungsstatus der App und relevante Ausschnitte aus den Systemprotokollen.

Der Pfad des Ergebnispakets muss die Laufkennung enthalten, damit parallele Aufgaben einander nicht überschreiben. Tokens, Zugangsdaten und Geschäftsdaten in Protokollen müssen vor dem Hochladen anonymisiert oder maskiert werden.

Vom reproduzierten Einzeltest zur stabilen Abnahme

Führen Sie den betroffenen Test zunächst mit -only-testing mehrfach aus, um irrelevante Variablen auszuschließen. Testen Sie anschließend die zugehörige Testklasse und kehren Sie erst danach zur vollständigen Suite zurück. Tritt der Fehler nur in der vollständigen Suite auf, deutet das normalerweise auf eine Abhängigkeit von der Ausführungsreihenfolge, gemeinsam genutzte Daten oder Ressourcenkonflikte hin.

xcodebuild test \
  -workspace Sample.xcworkspace \
  -scheme SampleUITests \
  -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
  -only-testing:SampleUITests/CheckoutTests/testSubmit \
  -parallel-testing-enabled NO

Für die Abnahme reicht es nicht, dass der Test „einmal erfolgreich“ war. Führen Sie mehrere Durchläufe hintereinander aus, prüfen Sie, ob keine Abhängigkeit von einer festen Reihenfolge besteht, und kontrollieren Sie, ob übermäßig hohe Wartegrenzen die Gesamtdauer deutlich verlängern. Nach der erneuten Aktivierung paralleler Tests muss außerdem sichergestellt werden, dass verschiedene Shards weder denselben Testdatensatz noch dasselbe Ausgabeverzeichnis verwenden.

Das Ziel besteht nicht darin, die Fehlerquote hinter Wiederholungen zu verbergen. Stattdessen muss jeder Lauf klar definierte Eingaben, eindeutige Wartebedingungen, isolierten Zustand und vollständige Diagnosedaten besitzen. Erst wenn diese vier Voraussetzungen erfüllt sind, werden XCUITests auf einem Cloud-Mac von Tests, die „manchmal durchlaufen“, zu einem verlässlichen technischen Signal für Merge- und Release-Entscheidungen.

Häufig gestellte Fragen

Soll ein fehlgeschlagener XCUITest automatisch wiederholt werden?

Eine einzige Wiederholung hilft bei der Einordnung, darf den ersten Fehler aber nicht verbergen. Bewahren Sie beide Ergebnisdateien auf und markieren Sie Tests, die nur beim zweiten Lauf bestehen, als instabil.

Warum sind feste sleep-Aufrufe in UI-Tests problematisch?

Eine feste Pause beweist nicht, dass die Oberfläche bereit ist, und verlängert schnelle Läufe unnötig. Warten Sie mit einem klaren Zeitlimit auf einen beobachtbaren Zustand.

Exklusiver physischer Mac mini

Wählen Sie einen Cloud-Mac für Ihre nächste Entwicklungs-Pipeline

Vergleichen Sie drei feste Konfigurationen an vier asiatischen Standorten und wählen Sie die Mietdauer passend zu Ihrer tatsächlichen Arbeitslast. Jede Miete umfasst einen exklusiven physischen Rechner, keine virtuelle Maschine.

Konfiguration auswählen und mieten