Ratgeber

iOS-UI-Tests mit XCUITest auf einem dedizierten Mac automatisieren

Um XCUITest zu automatisieren, führen Sie xcodebuild test mit einem Simulator als Ziel und einem -resultBundlePath aus, auf einem Mac mit angemeldeter Benutzersitzung. Legen Sie mit xcrun simctl einen eigenen Simulator an. Fügen Sie -parallel-testing-enabled YES hinzu, um Tests auf Klone des Simulators zu verteilen, und -retry-tests-on-failure, um wacklige Tests zu wiederholen. Jeden Befehl hier haben wir auf einem Mac mit Xcode 26.6 und dem Simulator für iOS 26.5 ausgeführt.

Was Sie vorher brauchen

  • Einen Mac mit Xcode und mindestens einer Simulator-Runtime für iOS.
  • Ein UI-Test-Target in Ihrem Projekt und ein geteiltes Scheme, das es testet.
  • Eine angemeldete Desktop-Sitzung. UI-Tests steuern die Simulator-App, also muss alles, was sie startet, in dieser Sitzung laufen. Das heißt: ein CI-Agent, der als LaunchAgent gestartet wird, nicht als LaunchDaemon. Die plist-Vorlage von Buildkite sagt dasselbe: Der GUI-Modus erlaubt UI-Tests mit Xcode, braucht aber eine Anmeldung.

1. Xcode und die Runtimes prüfen

xcodebuild -version
sudo xcodebuild -runFirstLaunch
xcrun simctl list runtimes
xcrun simctl list devicetypes | grep iPhone

-runFirstLaunch installiert Pakete und akzeptiert die Lizenz. Führen Sie es nach jeder Installation und jedem Upgrade von Xcode aus.

2. Einen Simulator nur für die CI anlegen

Ein eigenes Gerät trennt die CI von allem, was ein Mensch von Hand geöffnet hat. bootstatus -b bootet es und wartet, bis es bereit ist.

UDID=$(xcrun simctl create "CI iPhone 17" "iPhone 17" com.apple.CoreSimulator.SimRuntime.iOS-26-5)
xcrun simctl bootstatus "$UDID" -b
echo "$UDID"

Nutzen Sie die UDID im Ziel: -destination "platform=iOS Simulator,id=$UDID". Ein Name funktioniert auch, etwa name=iPhone 17,OS=26.5. Aber zwei Geräte können denselben Namen haben.

3. Die UI-Tests mit Result Bundle ausführen

rm -rf build/TestResults.xcresult
xcodebuild test \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -derivedDataPath build/DerivedData \
  -resultBundlePath build/TestResults.xcresult

Das Result Bundle enthält jedes Testergebnis, jedes Log, jeden Screenshot und jeden Fehler. Eine Zusammenfassung lesen Sie auf der Kommandozeile:

xcrun xcresulttool get test-results summary --path build/TestResults.xcresult

Der Befehl gibt JSON mit den Zahlen bestandener, fehlgeschlagener und übersprungener Tests pro Gerät aus. Hängen Sie das Bundle als Artefakt an Ihren CI-Lauf, gezippt mit ditto -c -k --keepParent.

4. Einmal bauen, oft testen

Trennen Sie den Build vom Testlauf. Dann können Sie eine Teilmenge erneut ausführen, ohne neu zu kompilieren.

xcodebuild build-for-testing -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData

xcodebuild test-without-building -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData \
  -only-testing:MyAppUITests/LoginTests/testSignIn

-only-testing nimmt Target, Target/Class oder Target/Class/method. -skip-testing funktioniert genauso, nur umgekehrt.

5. Tests parallel ausführen

xcodebuild test -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -parallel-testing-enabled YES \
  -parallel-testing-worker-count 2 \
  -resultBundlePath build/TestResults.xcresult

Xcode klont den Simulator und verteilt Testklassen auf die Klone. Unser Log zeigte Tests auf Clone 1 of iPhone 17. -parallel-testing-enabled überschreibt die Einstellung im Scheme. Beginnen Sie auf einem Mac mit 16 GB mit 2 Workern und messen Sie, bevor Sie erhöhen. Jeder Klon ist ein vollständiger Simulator im Arbeitsspeicher. Um auf mehreren Gerätetypen gleichzeitig zu testen, geben Sie mehrere Ziele an und setzen -maximum-concurrent-test-simulator-destinations.

6. Wacklige Tests wiederholen

xcodebuild test -project MyApp.xcodeproj -scheme MyApp \
  -destination "platform=iOS Simulator,id=$UDID" \
  -retry-tests-on-failure \
  -test-iterations 3 \
  -resultBundlePath build/TestResults.xcresult

Laut xcodebuild -help wird ein fehlgeschlagener Test bis zur Zahl der Iterationen wiederholt. Ohne -test-iterations liegt das Maximum bei 3. Wir haben das mit einem Test geprüft, der einmal fehlschlägt und dann besteht. Der Lauf endete mit TEST SUCCEEDED. Die Zusammenfassung meldete 3 Testläufe für 2 Tests.

Ein Retry macht einen wackligen Test also grün, und das Problem bleibt verborgen. Lesen Sie bei jedem Lauf das Result Bundle und halten Sie fest, welche Tests einen Retry brauchten. Fügen Sie -test-repetition-relaunch-enabled YES hinzu, um jeden Versuch in einem frischen Prozess zu starten. Um einen wackligen Test aufzuspüren, nutzen Sie -run-tests-until-failure. Diese Option lässt sich nicht mit -retry-tests-on-failure kombinieren.

7. Hängenden Tests Grenzen setzen

-test-timeouts-enabled YES \
-default-test-execution-time-allowance 120 \
-maximum-test-execution-time-allowance 300

Fügen Sie diese Flags dem Testbefehl hinzu. Ein UI-Test, der ewig auf ein Element wartet, schlägt dann nach seinem Zeitbudget fehl. Er blockiert die Maschine nicht bis zum Timeout der CI.

8. Simulatoren zwischen Läufen sauber halten

xcrun simctl shutdown "$UDID"
xcrun simctl erase "$UDID"
xcrun simctl delete unavailable

erase setzt Inhalte und Einstellungen des Simulators zurück. delete unavailable entfernt Geräte, die das aktuelle Xcode nicht mehr unterstützt. Führen Sie es nach jedem Upgrade von Xcode aus.

Wie das Neustarts übersteht

Simulatoren, die Sie anlegen, bleiben über Neustarts erhalten. Zurückkommen muss der CI-Agent, der die Tests ausführt. Betreiben Sie ihn als LaunchAgent, schalten Sie die automatische Anmeldung ein und booten Sie den CI-Simulator zu Beginn jedes Jobs mit bootstatus -b. Der Befehl ist auch auf einem Gerät sicher, das schon gebootet ist.

Fehler, auf die wir gestoßen sind, und ihre Lösung

  • Unable to find a device matching the provided destination specifier. Der Name oder das OS existiert nicht. Prüfen Sie xcrun simctl list devices und die Liste der Runtimes.
  • xcodebuild: error: Existing file at -resultBundlePath. Löschen Sie das alte Bundle vor jedem Lauf.
  • Unable to erase contents and settings in current state: Booted. Fahren Sie den Simulator herunter, bevor Sie ihn zurücksetzen.

Warum ein dedizierter Mac hilft

UI-Tests verbringen viel Zeit vor dem ersten Tippen. Sie warten auf den Build, das Booten eines Simulators und die Installation der App. Eine Maschine, die eingeschaltet bleibt, hält DerivedData und einen gebooteten Simulator bereit. In unserem Benchmark haben wir die Wikipedia-iOS-App mit Xcode 26.6 gebaut. Ein Job nach einer kleinen Änderung dauerte auf einem warmen M6 27 Sekunden. Auf einem frischen gehosteten macos-26-Runner von GitHub waren es 269 Sekunden. Unser M6 hat 12 CPU-Kerne. Zwei parallele Klone lassen also noch Platz für den Build.

Wann gehostete CI reicht

Eine kleine Suite, die bei ein paar Pull Requests am Tag läuft, passt gut zu Minutenpreisen. Beim Tarif von GitHub von $0.062 pro macOS-Minute (geprüft im September 2026) rechnet sich ein Mac für $139 ab etwa 2,242 Minuten im Monat. Darunter bleiben Sie beim gehosteten Angebot. Müssen Sie auf vielen physischen iPhones testen, brauchen Sie eine Device Cloud. Ein Mac mini führt Simulatoren aus. Braucht Ihre parallele Suite mehr als 16 GB Arbeitsspeicher? Unsere Stufen mit M5 Pro haben 48 oder 64 GB. Sie sind aber eine Vorbestellung, rechnen Sie also mit etwa einer Woche Wartezeit.

Der Leitfaden zur iOS-CI/CD-Pipeline zeigt, wo UI-Tests in eine vollständige Pipeline passen. Der Kostenrechner rechnet Ihre eigenen Zahlen durch.

Häufige Fragen

Hat xcodebuild ein Flag, um fehlgeschlagene Tests zu wiederholen?

+

Ja. -retry-tests-on-failure wiederholt einen fehlgeschlagenen Test, bis zu -test-iterations Mal oder standardmäßig 3 Mal. Es lässt sich nicht mit -run-tests-until-failure kombinieren.

Wie führe ich XCUITest-Tests auf der Kommandozeile parallel aus?

+

Fügen Sie -parallel-testing-enabled YES hinzu und optional -parallel-testing-worker-count. Xcode klont den Simulator und verteilt die Testklassen auf die Klone.

Läuft XCUITest auf einem Headless-Mac?

+

Es braucht eine angemeldete Benutzersitzung, weil UI-Tests die Simulator-App steuern. Betreiben Sie den CI-Agent als LaunchAgent und schalten Sie die automatische Anmeldung ein.

Wie lese ich Testergebnisse von xcodebuild, ohne Xcode zu öffnen?

+

Übergeben Sie -resultBundlePath und führen Sie dann xcrun xcresulttool get test-results summary --path auf dem Bundle aus. Der Befehl gibt die Zahlen als JSON aus.

Passende Ratgeber