Auf demselben Cloud-Mac kann xcodebuild build über SSH problemlos funktionieren, während test noch vor dem Start des Test-Runners hängen bleibt: Kein Testfall beginnt, das Simulatorfenster erscheint nicht, und im Protokoll finden sich lediglich Fehler beim App-Start oder beim Aufbau der Sitzung. Löschen Sie in diesem Fall nicht vorschnell DerivedData. Die Build-Kette funktioniert bereits; zu prüfen ist vielmehr, ob der Auftrag in einer nutzbaren grafischen macOS-Sitzung ausgeführt wird.
Build-Umgebung und grafische Sitzung getrennt betrachten
XCUITest kompiliert nicht nur ein Test-Bundle. Es muss außerdem die Ziel-App, den Test-Host und die Simulatordienste starten und die beteiligten Prozesse über die grafische Sitzung des aktuellen Benutzers koordinieren. Eine erfolgreiche SSH-Anmeldung belegt lediglich, dass die Remote-Shell verfügbar ist. Sie garantiert nicht, dass sich diese Shell in derselben launchd-GUI-Domäne befindet wie der am Desktop angemeldete Benutzer.
Erfassen Sie zunächst vier Fakten, anstatt sofort sämtliche Prozesse neu zu starten:
| Prüfpunkt | Akzeptables Ergebnis | Hinweis auf ein Problem |
|---|---|---|
| Konsolenbenutzer | Der Benutzer, unter dem die Automatisierung tatsächlich ausgeführt wird | root oder loginwindow |
| GUI-Startdomäne | gui/<uid> kann abgefragt werden |
Domäne fehlt oder ist nicht zugänglich |
| WindowServer | Prozess ist vorhanden | Anmeldesitzung wurde noch nicht aufgebaut |
| Zielsimulator | Status lautet Booted |
Verbleibt dauerhaft in Booting oder Shutdown |
Ein vorhandener WindowServer ist nur eine der notwendigen Voraussetzungen. Wird der Testauftrag von einem anderen Benutzer oder einem systemweiten Hintergrundprozess gestartet, kann der Zugriff auf die grafische Sitzung des Zielbenutzers trotzdem fehlen.
Zustand mit einem Preflight-Skript erfassen
Das folgende Skript verändert den Systemzustand nicht und eignet sich daher für den Anfang jedes Testauftrags. Es protokolliert den ausführenden Benutzer, den Konsolenbenutzer, die GUI-Domäne und die verfügbaren Geräte. Schlägt eine der Prüfungen fehl, wird der Auftrag sofort beendet.
#!/bin/zsh
set -euo pipefail
RUNNER_USER="$(id -un)"
RUNNER_UID="$(id -u)"
CONSOLE_USER="$(stat -f '%Su' /dev/console)"
echo "runner_user=${RUNNER_USER}"
echo "runner_uid=${RUNNER_UID}"
echo "console_user=${CONSOLE_USER}"
if [[ "${CONSOLE_USER}" == "root" || "${CONSOLE_USER}" == "loginwindow" ]]; then
echo "No interactive console user"
exit 21
fi
CONSOLE_UID="$(id -u "${CONSOLE_USER}")"
if [[ "${RUNNER_UID}" != "${CONSOLE_UID}" ]]; then
echo "Runner and console user differ"
exit 22
fi
if ! launchctl print "gui/${CONSOLE_UID}" >/dev/null 2>&1; then
echo "GUI launch domain is unavailable"
exit 23
fi
if ! pgrep -x WindowServer >/dev/null; then
echo "WindowServer is unavailable"
exit 24
fi
xcrun simctl list devices available
Behandeln Sie unterschiedliche Benutzer nicht als harmlose Warnung und setzen Sie die Ausführung nicht fort. Spätere Fehler führen meist nur zu unklareren XCTest-Meldungen und verdecken damit die wertvollsten Hinweise auf die eigentliche Ursache.
Einen bestimmten Simulator statt eines unscharfen Namens verwenden
Sobald die GUI-Sitzung verfügbar ist, können Sie die Geräteauswahl prüfen. Eine Angabe wie name=iPhone wird schnell mehrdeutig, wenn mehrere Runtimes installiert sind. Erstellen oder wählen Sie zunächst ein dediziertes Gerät, speichern Sie dessen UDID und warten Sie anschließend, bis der Start vollständig abgeschlossen ist:
DEVICE_UDID="${IOS_TEST_DEVICE_UDID:?missing device udid}"
xcrun simctl boot "${DEVICE_UDID}" 2>/dev/null || true
xcrun simctl bootstatus "${DEVICE_UDID}" -b
xcodebuild test \
-workspace App.xcworkspace \
-scheme AppUITests \
-destination "platform=iOS Simulator,id=${DEVICE_UDID}" \
-resultBundlePath "$PWD/artifacts/ui-tests.xcresult"
Die Rückkehr von simctl boot bedeutet noch nicht, dass das System bereits Tests ausführen kann. Der tatsächliche Synchronisationspunkt ist bootstatus -b. Parallele Aufträge sollten außerdem unterschiedliche Geräte-UDIDs verwenden, damit nicht zwei Aufträge um dasselbe Simulator-Datenverzeichnis konkurrieren. Nach Abschluss eines Auftrags kann das Gerät heruntergefahren werden. Führen Sie jedoch keine globale Gerätebereinigung durch, solange noch andere Jobs auf dem Rechner laufen.
Drei Ebenen von Diagnosedaten aufbewahren
Bewahren Sie für die Fehlersuche mindestens drei Arten von Daten auf: die Ausgabe des Preflight-Skripts, das unveränderte xcodebuild-Protokoll und die .xcresult-Datei. Falls der Test-Host nicht startet, ist es außerdem hilfreich, die folgenden Prozesse zu erfassen:
ps -axo user,pid,ppid,command | \
grep -E 'XCTest|Simulator|CoreSimulator' | \
grep -v grep > artifacts/ui-processes.txt
Anhand dieser Informationen lässt sich feststellen, ob der Test den Simulator überhaupt erreicht hat, statt lediglich den endgültigen Fehlschlag des Befehls zu dokumentieren.
Auftrag aus der GUI-Domäne des Benutzers ausführen
Ein stabiler Ansatz besteht nicht darin, UI-Tests direkt aus jeder SSH-Sitzung zu starten. Stattdessen sollte ein LaunchAgent in der Domäne des angemeldeten Benutzers die Aufträge entgegennehmen. SSH schreibt lediglich die Auftragsbeschreibung oder stößt die Warteschlange an. Der LaunchAgent führt das Skript dann mit einem festgelegten Benutzer, einem festen Arbeitsverzeichnis und definierten Umgebungsvariablen aus.
Im LaunchAgent sollte WorkingDirectory ausdrücklich gesetzt werden. Standardausgabe und Standardfehlerausgabe gehören in das Verzeichnis des jeweiligen Auftrags. Die Werkzeugpfade dürfen nicht von den Startdateien einer interaktiven Shell abhängen. Setzen Sie PATH explizit im Skript und protokollieren Sie mit xcode-select -p das aktuell ausgewählte Entwicklerwerkzeugverzeichnis. So werden „Ausführung über SSH funktioniert“ und „GUI-Automatisierung funktioniert“ zu zwei voneinander unabhängigen Voraussetzungen.
Es ist nicht empfehlenswert, XCUITest direkt über einen systemweiten LaunchDaemon zu starten oder nohup als Weg in eine GUI-Sitzung zu betrachten. Beide Methoden können Prozesse am Leben halten, ändern aber nicht automatisch deren Startdomäne.
Mindestprüfung vor der Auftragsannahme einrichten
Bevor ein Knoten UI-Tests annimmt, kann er die folgenden Zulassungsprüfungen der Reihe nach ausführen:
- Konsolenbenutzer und Auftragsbenutzer stimmen überein.
gui/<uid>kann vom aktuellen Auftrag abgefragt werden.- WindowServer ist vorhanden.
xcode-select -pverweist auf das erwartete Werkzeugverzeichnis.- Die UDID des Zielsimulators ist vorhanden, und
bootstatuskann erfolgreich abgeschlossen werden. - Das Artefaktverzeichnis ist beschreibbar, und der aktuelle Auftrag verwendet einen eigenen Pfad.
- Ein schlanker Test, der nur die App startet und ein Element auf dem ersten Bildschirm prüft, wird ausgeführt.
Falls der siebte Schritt fehlschlägt, archivieren Sie zuerst den aktuellen Diagnosezustand und entscheiden Sie erst danach, ob der dedizierte Simulator neu erstellt werden muss. Das Löschen aller Geräte und Leeren sämtlicher Caches sollte keine standardmäßige Wiederherstellungsmaßnahme sein. Dadurch können Probleme mit der Startdomäne verdeckt und andere Aufträge auf demselben Rechner beeinträchtigt werden.
Wird die Prüfung der grafischen Sitzung an den Anfang der Pipeline gestellt, ist ein XCUITest-Fehler wie „Start nicht möglich“ kein unspezifisches Problem mehr. Der Auftrag meldet eindeutig, ob der Fehler beim Konsolenbenutzer, in der GUI-Domäne, bei WindowServer oder am Simulator auftritt. Die weitere Diagnose kann sich anschließend auf denselben Datensatz stützen.
Häufig gestellte Fragen
Warum funktioniert der SSH-Build, während XCUITest nicht startet?
Der Build benötigt vor allem Kommandozeilenwerkzeuge. Ein UI-Test braucht zusätzlich einen angemeldeten Konsolenbenutzer, eine launchd-GUI-Domäne, WindowServer und einen gestarteten Simulator.
Reicht ein laufender WindowServer-Prozess als Freigabe aus?
Nein. Prüfen Sie außerdem, dass der Konsolenbenutzer nicht loginwindow ist, gui/UID erreichbar ist, der Test unter demselben Benutzer läuft und der Simulator vollständig gestartet wurde.
Wie startet die Automatisierung in der richtigen GUI-Sitzung?
Lassen Sie einen LaunchAgent in der Domäne des angemeldeten Benutzers die Aufträge übernehmen. Starten Sie UI-Tests nicht direkt aus einem Systemdienst oder einer beiläufigen SSH-Shell.
Konfigurieren Sie für die nächste Entwicklungsaufgabe einen Cloud-Mac
Wählen Sie Oak M4, Oak M4 Plus oder Oak M4 Pro und konfigurieren Sie ein Mietmodell an einem von fünf physischen Standorten entsprechend dem Standort Ihres Teams.