Dieselben iOS-UI-Tests laufen auf dem Entwicklungsrechner fehlerfrei, bleiben nach dem Umzug auf einen Cloud-Mac jedoch gelegentlich an einem Berechtigungsdialog hängen. Meist ist dafür kein zufälliger Fehler im Anwendungscode verantwortlich, sondern ein TCC-Berechtigungsstatus, den der Simulator aus einem vorherigen Auftrag übernommen hat. Wurde einmal „Erlauben“ gewählt, erscheint die erstmalige Abfrage in späteren Testfällen nicht mehr. Wurde „Nicht erlauben“ gewählt, wechseln Abläufe mit Zugriff auf Kontakte oder Fotos direkt in den Fehlerpfad. Für stabile Tests sind daher nicht häufigere Wiederholungen entscheidend, sondern eindeutig definierte Berechtigungsvoraussetzungen vor jedem Testfall.
Berechtigungsstatus als Testeingabe behandeln
Berechtigungstests müssen mindestens drei Zustände abdecken: noch nicht entschieden, erlaubt und abgelehnt. Jeder Zustand entspricht einem anderen Produktablauf. Sie dürfen daher nicht gemeinsam über einen einzigen Schritt zum Neuinstallieren der App behandelt werden.
| Zielzustand | simctl-Aktion | Zu prüfendes Verhalten |
|---|---|---|
| Noch nicht entschieden | reset |
Die App fordert Zugriff an und das System zeigt den Berechtigungsdialog |
| Erlaubt | grant |
Die Funktion wird ohne erneute Abfrage direkt fortgesetzt |
| Abgelehnt | revoke |
Die App zeigt eine eingeschränkte Alternative oder einen Hinweis auf die Einstellungen |
Prüfen Sie zunächst, ob die Zielversion der Runtime den gewünschten Dienst unterstützt. Welche Dienste steuerbar sind, kann je nach Systemversion variieren. Dienstnamen sollten deshalb nicht allein aus Erfahrung fest eingetragen und ungeprüft übernommen werden:
xcrun simctl privacy help
xcrun simctl list devices available
Zu den häufig verwendeten Diensten gehören contacts, photos, photos-add, location, microphone und calendar. Maßgeblich für das Testskript ist die Hilfeausgabe der simctl-Version, die mit dem aktuell verwendeten Xcode ausgeliefert wird.
Der Berechtigungsstatus gehört zum Simulatorgerät und nicht zum Code-Repository. Das Löschen des Build-Verzeichnisses entfernt keine TCC-Daten. Auch das Löschen und erneute Installieren der App ist keine zuverlässige Methode zum Zurücksetzen von Berechtigungen.
Dedizierten Simulator und App vorbereiten
In CI sollte booted nicht beliebig auf irgendein bereits gestartetes Gerät verweisen. Parallele Aufträge könnten dadurch denselben Simulator auswählen, sodass Installation, Start und Berechtigungsänderungen einander überschreiben. Zuverlässiger ist es, jedem Auftrag eine eindeutige UDID zuzuweisen und sie als Parameter an das Skript zu übergeben.
Die App muss bereits installiert sein, damit grant oder revoke für die tatsächliche Bundle Identifier wirksam werden. Die Bundle Identifier sollte auch nicht aus dem Namen des Scheme abgeleitet, sondern direkt aus dem Build-Artefakt gelesen werden:
APP_PATH="$1"
UDID="$2"
BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \
"$APP_PATH/Info.plist")
xcrun simctl boot "$UDID" 2>/dev/null || :
xcrun simctl bootstatus "$UDID" -b
xcrun simctl install "$UDID" "$APP_PATH"
printf '%s
' "$BUNDLE_ID"
Wird eine Extension getestet, besitzt sie eine eigene Bundle Identifier. Lesen Sie die jeweilige Info.plist der Extension separat aus, statt die Kennung der Haupt-App für alle Berechtigungsbefehle zu verwenden.
Drei definierte Ausgangszustände per Skript setzen
Werden alle Zustandswechsel in einer Funktion gebündelt, lassen sie sich leichter prüfen als über die gesamte Pipeline-Konfiguration verteilte Befehle. Beenden Sie die App vor jeder Berechtigungsänderung, damit der Prozess weder einen alten Status beibehält noch gerade einen Dialog anzeigt.
set -euo pipefail
UDID="$1"
BUNDLE_ID="$2"
SERVICE="$3"
STATE="$4"
xcrun simctl terminate "$UDID" "$BUNDLE_ID" 2>/dev/null || :
case "$STATE" in
undecided)
xcrun simctl privacy "$UDID" reset "$SERVICE" "$BUNDLE_ID"
;;
authorized)
xcrun simctl privacy "$UDID" grant "$SERVICE" "$BUNDLE_ID"
;;
denied)
xcrun simctl privacy "$UDID" revoke "$SERVICE" "$BUNDLE_ID"
;;
*)
printf 'Unknown permission state: %s
' "$STATE" >&2
exit 64
;;
esac
Starten Sie die App oder den Test erst nach Ausführung des Skripts. Für ein Szenario mit erlaubtem Kontaktzugriff kann beispielsweise zuerst das Skript aufgerufen und anschließend nur die zugehörige Testklasse ausgeführt werden:
./set-permission.sh "$UDID" "$BUNDLE_ID" contacts authorized
xcodebuild test \
-scheme PermissionTests \
-destination "platform=iOS Simulator,id=$UDID" \
-only-testing:PermissionUITests/ContactsAuthorizedTests \
-resultBundlePath Artifacts/ContactsAuthorized.xcresult
reset setzt den Status auf noch nicht entschieden zurück und bedeutet nicht abgelehnt. Behandelt der Anwendungscode beide Zustände gleich, sollte der Test dieses Entwurfsproblem unmittelbar sichtbar machen.
Systemdialog und Anwendungslogik getrennt testen
Der Berechtigungsdialog gehört zur Systemoberfläche. Die Lokalisierung seiner Schaltflächen kann durch Sprache, Runtime-Version und Reihenfolge der Dialoge beeinflusst werden. Deshalb sollten nicht alle Berechtigungstests vom Anklicken dieses Dialogs abhängen. Sinnvoller ist eine Aufteilung in Testebenen: Die meisten Tests prüfen die Anwendungslogik mit grant und revoke; nur wenige Testfälle validieren die erstmalige Anfrage.
Testfall für die erstmalige Anfrage
Führen Sie zuerst reset aus, starten Sie dann die App und lösen Sie die Berechtigungsanfrage aus. Der Testfall prüft lediglich, ob der Dialog erscheint und ob die App nach der Auswahl durch den Benutzer zur erwarteten Ansicht wechselt. Muss mit der Systemoberfläche interagiert werden, sollte dafür ein separates Systemanwendungsobjekt verwendet werden. Feste Bildschirmkoordinaten sind zu vermeiden.
Testfälle für erlaubten und abgelehnten Zugriff
In beiden Arten von Testfällen darf kein Berechtigungsdialog erscheinen. Im erlaubten Zustand wird geprüft, ob die Funktion Daten ordnungsgemäß lesen kann. Im abgelehnten Zustand muss sichergestellt sein, dass die App weder wiederholt neue Anfragen stellt noch in einem endlosen Ladezustand verbleibt. Gibt es einen Hinweis auf die Einstellungen, sollten nur die Schaltfläche und der Erklärungstext geprüft werden. Die Systemeinstellungen selbst sollten im automatisierten Auftrag nicht geändert werden.
Bei Standortberechtigungen müssen außerdem fachliche Unterschiede berücksichtigt werden, etwa der Zugriff während der App-Nutzung und die dauerhafte Freigabe. simctl privacy kann einen grundlegenden Status vorbereiten, ersetzt aber nicht jedes Verhalten eines realen Geräts. Entsprechende Ergebnisse müssen daher weiterhin auf echter Hardware abgenommen werden.
Isolation, Beweissicherung und Fehleranalyse
Nach jeder Testklasse kann für den zugehörigen Dienst reset ausgeführt werden. Wichtiger ist jedoch, dass jeder nachfolgende Test seinen benötigten Zustand selbst herstellt, statt auf eine erfolgreiche Bereinigung durch den vorherigen Test zu vertrauen. Bei paralleler Ausführung darf ein Simulator nur einem Auftrag zugewiesen sein. Für zusätzliche Parallelität müssen mehrere Geräte erstellt werden, statt dieselbe UDID gemeinsam zu verwenden.
Bei einem Fehler sollten mindestens .xcresult, Testprotokolle, Systemprotokolle des Simulators und die aktuelle Geräteliste gesichert werden. Wurde der erwartete Zustand nicht wirksam, prüfen Sie die folgenden Punkte in dieser Reihenfolge:
- Stammt die Bundle Identifier von der tatsächlich installierten App oder Extension?
- Unterstützt die aktuelle Runtime den angegebenen Dienstnamen?
- Wurde die App vor der Zustandsänderung beendet?
- Entspricht die Ziel-UDID dem von
xcodebuildverwendeten Gerät? - Greift ein weiterer paralleler Auftrag auf denselben Simulator zu?
- Verwendet der Testcode ein zwischengespeichertes, veraltetes Berechtigungsergebnis?
Das vollständige Löschen des Simulators sollte nicht als Standardlösung dienen. Es verlängert die Auftragsdauer erheblich und kann Fehler in der Zustandsverwaltung verdecken. Ein Gerät sollte nur dann neu erstellt werden, wenn dauerhaft nicht erklärbare Systemzustände auftreten. Die zuvor erfassten Protokolle müssen dabei erhalten bleiben.
Bei der Ausführung solcher Aufträge auf einem Cloud-Mac von MiniRent beeinflussen Modell und Knoten lediglich die Platzierung des Auftrags. Das Testskript muss die Umgebung weiterhin vollständig über UDID, Bundle Identifier, Dienstnamen und Zielzustand beschreiben. Die aktuell verfügbaren Konfigurationen sind in der Konsole zu prüfen. So lässt sich dieselbe Berechtigungsregression sowohl in temporären Debugging-Aufträgen reproduzieren als auch zuverlässig in die Continuous-Integration-Pipeline übernehmen.
Häufig gestellte Fragen
Ersetzt simctl privacy alle Tests des Systemdialogs?
Nein. Das Werkzeug bereitet reproduzierbare Zustände vor. Text, Schaltflächen und Rückkehrpfad des echten Systemdialogs sollten weiterhin durch wenige UI-Endtests geprüft werden.
Muss der Simulator vor jedem Berechtigungstest gelöscht werden?
In der Regel nicht. Beenden Sie die App und führen Sie reset, grant oder revoke für den betroffenen Dienst und die Bundle-ID aus.
Die nächste Entwicklungs- oder Build-Aufgabe auf einem Cloud-Mac mini ausführen
Wählen Sie zwischen zwei M4-Konfigurationen, vier Mietlaufzeiten und fünf verfügbaren Standorten. Maßgeblich ist der vom Control Panel in Echtzeit angezeigte Status.