Le pipeline distant produit déjà une application iOS installable, mais l’équipe ne dispose d’aucun appareil de test connecté en permanence. Dans ce contexte, les problèmes les plus faciles à manquer ne sont pas les erreurs de compilation, mais les interactions entre les autorisations de notification, l’affichage au premier plan, les paramètres de liens profonds et la Notification Service Extension. simctl push permet d’injecter directement une charge utile au format APNs dans l’iOS Simulator, ce qui convient parfaitement à des validations rapides, reproductibles et exécutées avant chaque soumission sur un Mac dans le cloud.
Cette commande ne remplace pas un test de distribution via les véritables services APNs, mais elle permet de détecter de nombreux problèmes côté client dès la phase de build. L’essentiel est de ne pas se limiter au code de sortie de la commande et de conserver également l’état de l’application, les captures d’écran et les journaux unifiés.
Définir d’abord ce que ce test permet de valider
simctl push vérifie comment une application déjà compilée traite une charge utile de notification. Le test peut couvrir l’analyse du contenu, les callbacks du delegate au premier plan et en arrière-plan, les actions associées aux catégories, le routage des liens profonds et le résultat du traitement par la Notification Service Extension.
Il ne permet pas de valider l’authentification côté serveur, la validité du jeton de l’appareil, la distribution sur un réseau externe, la limitation de débit d’APNs ni les stratégies d’économie d’énergie d’un appareil réel. La réception de la charge utile par le simulateur ne prouve pas non plus que la chaîne de push en production fonctionne de bout en bout.
Une commande réussie signifie uniquement que l’injection locale a abouti, pas que l’utilisateur a reçu la notification. La validation doit reposer sur l’interface, les journaux de l’application et le résultat du routage.
Avant de commencer, vérifiez que l’application déclare la capacité de notification et que l’autorisation est accordée dans le parcours testé. simctl push ne clique pas à la place de l’application dans la boîte de dialogue d’autorisation du système. La première autorisation peut être gérée par un test d’interface ou accordée à l’avance sur un simulateur réservé aux tests, mais elle ne doit pas dépendre de l’état d’un simulateur utilisé depuis longtemps.
Fixer le simulateur et les entrées de l’application
N’utilisez pas directement booted dans des tâches parallèles. Si plusieurs simulateurs démarrent simultanément, la charge utile risque d’être envoyée à celui d’une autre tâche. Enregistrez un UDID précis pour chaque job et fournissez comme entrées le chemin de l’application, son Bundle ID et le répertoire des artefacts.
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 attend que le démarrage du système soit réellement terminé, ce qui est plus fiable qu’une pause d’une durée fixe après le lancement. get_app_container fournit quant à lui une vérification d’installation peu coûteuse : si le Bundle ID est incorrect ou si l’application n’est pas installée, la tâche échoue avant l’envoi de la notification.
Pour retester la première demande d’autorisation, créez ou effacez un simulateur dédié à cette tâche au lieu de réinitialiser sans distinction tous les réglages de confidentialité d’un appareil partagé. Vous éviterez ainsi que des tests parallèles modifient mutuellement les autorisations de l’appareil photo, des photos ou des notifications.
Générer et valider la charge utile APNs
La charge utile est stockée dans un fichier JSON. Ajoutez de préférence un identifiant de test interrogeable afin de relier les captures d’écran et les journaux de l’application au job du pipeline correspondant.
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"
La vérification de la limite de 4 Ko permet de repérer rapidement une charge utile anormale, mais le simulateur ne reproduit pas intégralement toutes les validations effectuées côté serveur. L’application doit également contrôler le type de route, des identifiants et des champs personnalisés. Une conversion forcée n’est pas acceptable sous prétexte que la charge utile de test est fiable.
Si vous utilisez une extension de service de notification, ajoutez "mutable-content": 1 et faites écrire à l’extension des journaux structurés dans le système unifié. Le temps de traitement de l’extension et son interruption par le système doivent néanmoins être traités comme des scénarios d’échec : ne partez pas du principe qu’elle terminera toujours le téléchargement ou la réécriture.
Établir une matrice de validation selon l’état de l’application
Une même charge utile doit au minimum être testée lorsque l’application est au premier plan, en arrière-plan et arrêtée. Au premier plan, l’affichage d’une bannière dépend généralement d’une méthode du delegate. L’absence de bannière peut donc correspondre au comportement prévu du produit comme à un callback manquant. Le résultat attendu doit être explicite pour chaque état.
| Scénario | Action | Résultats à vérifier |
|---|---|---|
| Premier plan | Injecter après le lancement de l’application | Callback du delegate, indication dans l’interface, événements en double |
| Arrière-plan | Injecter après le retour à l’écran d’accueil | Contenu de la bannière, badge, routage après un appui |
| Application arrêtée | Injecter après l’arrêt de l’application | Point d’entrée du démarrage à froid, analyse des paramètres, écran cible |
| Extension de contenu | Injecter une charge utile contenant mutable-content |
Journaux de l’extension, contenu réécrit, repli après expiration du délai |
| Champs incorrects | Omettre la route ou fournir un type de champ incorrect | Dégradation sûre, absence de plantage, journaux exploitables |
Enregistrez une capture d’écran après l’envoi afin que le pipeline ne conserve pas uniquement une conclusion vide indiquant que la commande a réussi.
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"
Le nom du processus n’est pas nécessairement identique au Bundle ID. Une méthode plus fiable consiste à utiliser un subsystem de journalisation fixe dans l’application et l’extension, puis à filtrer les journaux sur ce subsystem. Consignez uniquement l’identifiant de test, l’état et le type d’erreur, jamais le texte intégral de la notification, les jetons ou les données utilisateur.
Intégrer les critères d’échec au pipeline
Une tâche fiable de validation des notifications ne doit pas se contenter d’exécuter des commandes shell. Elle doit également vérifier que les artefacts existent, que l’application n’a pas planté et que la route attendue a bien été enregistrée. Dans les builds de test, l’application peut écrire le trace_id traité et son résultat dans un fichier dédié. Il suffit ensuite de localiser le conteneur avec simctl get_app_container pour lire ce fichier. Cette interface de test doit être activée uniquement dans la configuration de build interne et obligatoirement désactivée dans la configuration de publication.
Parmi les faux résultats fréquents figurent la réutilisation d’un simulateur déjà autorisé, qui empêche de tester la première boîte de dialogue ; l’emploi de booted, qui mélange les tâches parallèles ; l’absence de bannière au premier plan, interprétée à tort comme un échec d’injection ; la vérification de la seule capture d’écran sans contrôler le routage après un appui ; et une plage de recherche trop large dans les journaux, qui récupère les entrées d’une tâche précédente.
Avant la soumission, finalisez la validation dans l’ordre suivant :
- Fixez les versions de Xcode et du runtime, le modèle d’appareil et l’UDID du simulateur.
- Installez l’artefact produit par le build en cours sans réutiliser un ancien conteneur d’application.
- Validez la syntaxe JSON, le nombre d’octets et les champs obligatoires.
- Exécutez séparément les scénarios au premier plan, en arrière-plan, application arrêtée et charge utile incorrecte.
- Archivez la charge utile, les captures d’écran, les journaux de l’application et l’identifiant de test unique.
- Présentez séparément les résultats de la validation sur simulateur et ceux du test APNs réel de bout en bout.
Une fois ces étapes terminées, la logique cliente des notifications push n’est plus une fonctionnalité contrôlée uniquement par observation manuelle. Elle devient une vérification d’ingénierie reproductible, documentée par des preuves et capable d’identifier précisément l’étape à laquelle un échec se produit.
Questions fréquentes
Un succès de simctl push garantit-il une livraison APNs réelle ?
Non. Il confirme uniquement l’injection locale dans le simulateur. L’authentification du serveur, le jeton de l’appareil, le réseau et les limites APNs doivent être testés séparément sur un appareil contrôlé.
Pourquoi aucune bannière n’apparaît-elle malgré une commande réussie ?
Vérifiez l’autorisation des notifications, le contenu de aps et l’état de l’application. Au premier plan, l’application doit explicitement demander la présentation via son délégué de notifications.
Peut-on utiliser booted dans une tâche CI parallèle ?
Il vaut mieux utiliser l’UDID attribué à la tâche. Avec plusieurs simulateurs démarrés, booted peut cibler un autre appareil et mélanger notifications, captures et journaux.
Choisissez un Mac dans le cloud pour votre prochain pipeline de développement
Comparez trois configurations fixes et quatre nœuds en Asie, puis choisissez la durée de location adaptée à votre charge de travail. Chaque location correspond à une machine physique dédiée, et non à une machine virtuelle.