Engineering-Praxis

iOS-Push-Benachrichtigungen mit simctl reproduzierbar testen

iOS-Push-Benachrichtigungen mit simctl reproduzierbar testen

Die Remote-Pipeline hat bereits eine installierbare Version der iOS-App erzeugt, dem Team steht jedoch kein dauerhaft verbundenes Testgerät zur Verfügung. In dieser Situation werden meist nicht Build-Fehler übersehen, sondern Probleme im Zusammenspiel von Mitteilungsberechtigungen, der Darstellung im Vordergrund, Deep-Link-Parametern und der Notification Service Extension. Mit simctl push lassen sich Payloads im APNs-Format direkt in den iOS Simulator einspeisen. Damit eignet sich der Befehl für schnelle, reproduzierbare Prüfungen vor einem Commit auf einem Cloud-Mac.

Ein Test der tatsächlichen Zustellung über APNs lässt sich damit nicht ersetzen. Viele clientseitige Fehler können jedoch bereits während des Builds erkannt werden. Entscheidend ist, nicht nur den Exit-Code des Befehls auszuwerten, sondern zugleich den App-Zustand, Screenshots und das Unified Logging zu sichern.

Zuerst den Prüfumfang festlegen

simctl push prüft, wie die bereits gebaute App einen Mitteilungs-Payload verarbeitet. Abdecken lassen sich die Auswertung des Mitteilungsinhalts, Delegate-Aufrufe im Vorder- und Hintergrund, Aktionsschaltflächen von Kategorien, das Deep-Link-Routing sowie das Ergebnis der Verarbeitung durch eine Notification Service Extension.

Nicht geprüft werden die serverseitige Authentifizierung, die Gültigkeit des Geräte-Tokens, die Zustellung über externe Netzwerke, APNs-Limits oder die Energiesparmechanismen eines echten Geräts. Wenn der Simulator einen Payload empfängt, bedeutet das daher nicht, dass die Push-Kette in der Produktion funktioniert.

Ein erfolgreicher Befehl bestätigt lediglich die lokale Einspeisung. Er belegt nicht, dass ein Benutzer die Mitteilung erhalten hat. Das Prüfergebnis muss sich aus der Benutzeroberfläche, den App-Protokollen und dem Routing-Ergebnis ergeben.

Vor dem Test muss sichergestellt sein, dass die App die Mitteilungsfunktion deklariert und die Berechtigung im jeweiligen Testablauf erteilt wurde. simctl push bestätigt den Systemdialog nicht im Namen der App. Die erstmalige Autorisierung kann durch einen UI-Test erfolgen oder auf einem dedizierten Testsimulator vorbereitet werden. Der Test darf sich jedoch nicht auf den Zustand eines seit Langem verwendeten Simulators verlassen.

Simulator und App-Eingaben fest vorgeben

Verwenden Sie in parallelen Jobs nicht direkt booted. Sind mehrere Simulatoren gleichzeitig gestartet, kann der Payload sonst beim falschen Job ankommen. Legen Sie für jeden Job eine eindeutige UDID fest und übergeben Sie den App-Pfad, die Bundle ID und das Artefaktverzeichnis als Eingaben.

set -euo pipefail

: "${SIMULATOR_UDID:?Set SIMULATOR_UDID}"
: "${APP_PATH:?Set APP_PATH}"
: "${BUNDLE_ID:?Set BUNDLE_ID}"

ARTIFACTS="${ARTIFACTS:-$PWD/.ci/push-check}"
mkdir -p "$ARTIFACTS"

xcrun simctl bootstatus "$SIMULATOR_UDID" -b
xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH"
xcrun simctl launch "$SIMULATOR_UDID" "$BUNDLE_ID"
xcrun simctl get_app_container "$SIMULATOR_UDID" "$BUNDLE_ID"

bootstatus -b wartet, bis der Systemstart tatsächlich abgeschlossen ist, und ist damit zuverlässiger als eine feste Wartezeit von einigen Sekunden. get_app_container dient als kostengünstige Installationsprüfung: Ist die Bundle ID falsch oder die App nicht installiert, schlägt der Job fehl, bevor eine Mitteilung gesendet wird.

Soll die erstmalige Berechtigungsabfrage erneut getestet werden, sollte für diesen Job ein dedizierter Simulator neu erstellt oder gelöscht werden. Setzen Sie nicht wahllos alle Datenschutzeinstellungen eines gemeinsam genutzten Geräts zurück. Andernfalls verändern parallele Tests gegenseitig die Berechtigungen für Kamera, Fotos oder Mitteilungen.

APNs-Payload erzeugen und validieren

Die Payload-Datei verwendet JSON. Ergänzen Sie eine durchsuchbare Testkennung, damit sich Screenshots und App-Protokolle dem jeweiligen Pipeline-Job zuordnen lassen.

PAYLOAD="$ARTIFACTS/foreground.apns"
TRACE_ID="push-check-${CI_RUN_ID:-local}"

cat > "$PAYLOAD" <<JSON
{
  "Simulator Target Bundle": "$BUNDLE_ID",
  "aps": {
    "alert": {
      "title": "Build completed",
      "body": "Open the result for details"
    },
    "sound": "default",
    "badge": 1,
    "category": "BUILD_RESULT"
  },
  "route": "build/result",
  "trace_id": "$TRACE_ID"
}
JSON

plutil -lint "$PAYLOAD"
PAYLOAD_BYTES="$(wc -c < "$PAYLOAD" | tr -d ' ')"
test "$PAYLOAD_BYTES" -le 4096
xcrun simctl push "$SIMULATOR_UDID" "$BUNDLE_ID" "$PAYLOAD"

Die Prüfung auf 4KB hilft, ungewöhnlich große Payloads frühzeitig zu erkennen. Der Simulator bildet jedoch nicht alle serverseitigen Validierungen vollständig nach. Auch die App muss daher die Typen von route, Kennungen und benutzerdefinierten Feldern prüfen. Ein vertrauenswürdiger Test-Payload ist kein Grund, Werte mit erzwungenen Typumwandlungen zu verarbeiten.

Wird eine Notification Service Extension eingesetzt, ergänzen Sie "mutable-content": 1 und lassen Sie die Erweiterung strukturierte Einträge in das Unified Logging schreiben. Ihre Ausführungszeit und eine mögliche Beendigung durch das System müssen dennoch als Fehlerpfade berücksichtigt werden. Der Ablauf darf nicht davon abhängen, dass ein Download oder eine Änderung des Inhalts immer abgeschlossen wird.

Prüfmatrix nach App-Zustand aufbauen

Derselbe Payload muss mindestens im Vordergrund, im Hintergrund und bei nicht laufender App geprüft werden. Ob im Vordergrund ein Banner erscheint, wird in der Regel durch eine Delegate-Methode gesteuert. Ein fehlendes Banner kann daher dem vorgesehenen Produktverhalten entsprechen oder auf einen nicht ausgeführten Callback hindeuten. Für jeden Zustand muss das erwartete Ergebnis ausdrücklich festgelegt werden.

Szenario Aktion Unbedingt zu prüfen
Vordergrund App starten und Payload einspeisen Delegate-Callback, Hinweis innerhalb der App, doppelte Ereignisse
Hintergrund Zum Home-Bildschirm wechseln und Payload einspeisen Bannerinhalt, Badge, Routing nach dem Antippen
Nicht ausgeführt App beenden und Payload einspeisen Einstieg beim Kaltstart, Auswertung der Parameter, Zielansicht
Inhaltserweiterung Payload mit mutable-content einspeisen Erweiterungsprotokoll, geänderter Inhalt, Rückfallverhalten bei Zeitüberschreitung
Fehlerhafte Felder Route weglassen oder falschen Feldtyp verwenden Sichere Degradierung, kein Absturz, aussagekräftige Diagnoseprotokolle

Speichern Sie nach dem Senden einen Screenshot. Andernfalls bleibt in der Pipeline womöglich nur die inhaltsleere Aussage zurück, dass der Befehl erfolgreich war.

xcrun simctl io "$SIMULATOR_UDID" screenshot \
  "$ARTIFACTS/notification.png"

xcrun simctl spawn "$SIMULATOR_UDID" log show \
  --last 2m \
  --style compact \
  --predicate "process == '$BUNDLE_ID'" \
  > "$ARTIFACTS/application.log"

Der Prozessname entspricht nicht zwangsläufig der Bundle ID. Zuverlässiger ist es, in App und Erweiterung ein festes Logging-Subsystem zu verwenden und anschließend nach diesem Subsystem zu filtern. Protokollieren Sie nur die Testkennung, den Zustand und den Fehlertyp, nicht jedoch den vollständigen Mitteilungstext, Tokens oder Benutzerdaten.

Fehlerbedingungen in der Pipeline verankern

Ein zuverlässiger Job zur Prüfung von Mitteilungen darf nicht nur Shell-Befehle ausführen. Er muss außerdem kontrollieren, ob die Artefakte vorhanden sind, die App abgestürzt ist und das erwartete Routing protokolliert wurde. In einem Test-Build kann die App die verarbeitete trace_id und das Ergebnis in eine eigene Datei schreiben. Der Container lässt sich anschließend mit simctl get_app_container ermitteln und die Datei daraus lesen. Diese Testschnittstelle darf nur in internen Build-Konfigurationen aktiviert sein und muss in der Release-Konfiguration deaktiviert werden.

Typische Fehlbewertungen sind: Ein bereits autorisierter Simulator wird wiederverwendet, sodass die erstmalige Abfrage ungetestet bleibt; booted vermischt parallele Jobs; ein im Vordergrund absichtlich fehlendes Banner wird als fehlgeschlagene Einspeisung interpretiert; es wird nur der Screenshot geprüft, nicht aber das Routing nach dem Antippen; oder der Abfragezeitraum der Protokolle ist so groß, dass Einträge des vorherigen Jobs einbezogen werden.

Schließen Sie die Prüfung vor dem Commit in dieser Reihenfolge ab:

  1. Xcode-Version, Laufzeit, Gerätemodell und Simulator-UDID fest vorgeben.
  2. Das Artefakt des aktuellen Builds installieren und keinen alten App-Container wiederverwenden.
  3. JSON-Syntax, Byteanzahl und Pflichtfelder validieren.
  4. Testfälle für Vordergrund, Hintergrund, nicht laufende App und fehlerhafte Payloads getrennt ausführen.
  5. Payloads, Screenshots, App-Protokolle und die eindeutige Testkennung archivieren.
  6. Die Simulatorprüfung und den End-to-End-Test mit echten APNs getrennt ausweisen.

Nach diesen Schritten ist die clientseitige Push-Logik keine Funktion mehr, deren Prüfung von manueller Beobachtung abhängt. Sie wird zu einer reproduzierbaren technischen Kontrolle, die Nachweise speichert und die Fehlerphase präzise eingrenzt.

Häufig gestellte Fragen

Beweist ein erfolgreicher simctl push die echte APNs-Zustellung?

Nein. Er bestätigt nur die lokale Übergabe an den Simulator. Serverauthentifizierung, Gerätetoken, Netzwerkzustellung und APNs-Grenzen müssen separat mit einem kontrollierten Gerät geprüft werden.

Warum erscheint trotz erfolgreichem Befehl kein Banner?

Prüfen Sie die Benachrichtigungsberechtigung, das aps-Objekt und den App-Zustand. Im Vordergrund muss die App die Banneranzeige über ihren Notification Delegate ausdrücklich anfordern.

Sollte CI booted oder eine feste Simulator-UDID verwenden?

Verwenden Sie die dem Auftrag zugewiesene UDID. Bei parallelen Läufen kann booted den falschen Simulator treffen und Nutzdaten, Screenshots sowie Protokolle vermischen.

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