Un pipeline iOS peut réussir entièrement la compilation, puis révéler une structure de paquet incorrecte seulement lors de l’installation dans le simulateur, ou encore voir l’application se fermer immédiatement après son lancement. Placer les tests d’interface complets en première ligne ralentit le retour d’information et transforme souvent des défaillances élémentaires en simples délais d’expiration. Une approche plus fiable consiste à ajouter, sur le Mac cloud, un contrôle rapide de deux à trois minutes : utiliser un environnement d’exécution déterminé, installer le fichier .app qui vient d’être généré, vérifier que le processus cible reste actif, puis conserver les journaux de lancement.
À quelles questions le contrôle doit-il répondre ?
Ce contrôle ne valide aucune fonctionnalité métier. Il répond uniquement à quatre questions binaires : le simulateur démarre-t-il correctement, l’artefact peut-il être installé, le Bundle ID demandé peut-il être lancé et le processus reste-t-il actif pendant la période d’observation ? Si l’une de ces vérifications échoue, il ne faut pas continuer à mobiliser des ressources pour les tests d’interface suivants.
| Point de contrôle | Condition de réussite | Données à conserver en cas d’échec |
|---|---|---|
| Démarrage | bootstatus se termine normalement |
UDID de l’appareil, version de l’environnement d’exécution |
| Installation | simctl install renvoie 0 |
Chemin de l’app, sortie de la commande |
| Lancement | simctl launch renvoie un PID |
Bundle ID, code de sortie |
| Processus actif | Le processus reste interrogeable après la période d’observation | Journaux système, rapports de plantage |
L’intérêt de ce contrôle rapide n’est pas d’élargir la couverture, mais de distinguer clairement deux conclusions : « l’artefact existe » et « l’artefact peut s’exécuter ».
Il est recommandé de transmettre explicitement l’UDID du simulateur à la tâche au lieu de dépendre de booted. Sur un Mac cloud partagé, des appareils lancés par d’autres tâches peuvent encore être actifs. Une cible ambiguë risque alors de mélanger les journaux et les résultats d’installation. Les configurations concrètement disponibles sur les nœuds NowMini doivent être vérifiées dans la console ; le pipeline lui-même dépend uniquement de Xcode déjà installé et de l’environnement d’exécution du simulateur correspondant.
Fixer d’abord l’artefact de compilation et l’appareil cible
Commencez par regrouper la sortie de compilation dans le répertoire de la tâche afin de ne pas avoir à deviner son chemin dans DerivedData. Le Scheme, la configuration, le Bundle ID et l’UDID doivent tous être fournis au moyen de variables d’environnement et vérifiés au début de la tâche.
set -euo pipefail
: "${SIM_UDID:?SIM_UDID is required}"
: "${APP_BUNDLE_ID:?APP_BUNDLE_ID is required}"
WORK_DIR="${RUNNER_TEMP:-$PWD/.smoke}"
APP_PATH="$WORK_DIR/build/Sample.app"
LOG_DIR="$WORK_DIR/logs"
rm -rf "$WORK_DIR"
mkdir -p "$LOG_DIR"
xcodebuild \
-workspace Sample.xcworkspace \
-scheme Sample \
-configuration Debug \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$SIM_UDID" \
CONFIGURATION_BUILD_DIR="$WORK_DIR/build" \
build
N’utilisez pas find pour sélectionner le premier fichier .app. Un projet peut générer simultanément l’app hôte, l’hôte des tests et des extensions, sans garantir leur ordre. Lorsque le nom du produit est connu, construisez directement le chemin et validez Info.plist avant l’installation :
test -d "$APP_PATH"
BUILT_ID=$(/usr/libexec/PlistBuddy -c \
"Print :CFBundleIdentifier" "$APP_PATH/Info.plist")
test "$BUILT_ID" = "$APP_BUNDLE_ID"
Cette étape permet de détecter en amont un Scheme incorrect, un mauvais remplacement de configuration de compilation ou une commande d’installation qui aurait récupéré l’hôte des tests.
Démarrer, installer et vérifier le processus
Pour démarrer l’appareil, utilisez bootstatus -b afin d’attendre que le système soit réellement disponible. Exécuter uniquement boot, puis installer immédiatement l’application, peut provoquer des échecs intermittents lorsque la charge est élevée et que les services ne sont pas encore prêts.
xcrun simctl boot "$SIM_UDID" 2>/dev/null || true
xcrun simctl bootstatus "$SIM_UDID" -b
xcrun simctl uninstall "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl install "$SIM_UDID" "$APP_PATH"
LAUNCH_OUTPUT=$(xcrun simctl launch \
"$SIM_UDID" "$APP_BUNDLE_ID" \
-SmokeTestMode YES)
printf '%s
' "$LAUNCH_OUTPUT"
PID=$(printf '%s
' "$LAUNCH_OUTPUT" | awk -F': ' 'NF == 2 {print $2}')
test -n "$PID"
Les arguments de lancement peuvent permettre à l’app d’ignorer l’assistant initial, de désactiver les animations ou d’utiliser des fixtures locales. Ce comportement doit toutefois être explicitement implémenté dans le code de l’application : il ne faut pas supposer qu’un argument arbitraire sera pris en compte. Après le lancement, attendez une courte période d’observation, puis interrogez le processus depuis le simulateur. Vérifier uniquement le code de retour de launch ne permet pas de détecter une application qui se ferme dans la seconde suivant son démarrage.
sleep 8
xcrun simctl spawn "$SIM_UDID" launchctl print \
"gui/$(id -u)/$APP_BUNDLE_ID" >/dev/null
Pour certaines applications, le libellé du processus ne correspond pas exactement au Bundle ID. Dans ce cas, recherchez le nom de processus connu ou faites écrire par l’application, au démarrage, un indicateur d’état prêt réservé aux tests. N’essayez pas de masquer un état instable en augmentant simplement un délai fixe.
Conserver des éléments de diagnostic lisibles en cas d’échec
Le nettoyage est important, mais les éléments de diagnostic doivent être enregistrés auparavant. Le jeu minimal comprend la sortie de compilation, la description de l’appareil, les sorties d’installation et de lancement, ainsi que les journaux unifiés associés au processus cible. La période couverte par les journaux doit correspondre à la tâche en cours, plutôt que d’inclure tout l’historique de la machine hôte.
xcrun simctl list devices -j > "$LOG_DIR/devices.json"
xcrun simctl spawn "$SIM_UDID" log show \
--style compact \
--last 2m \
--predicate "process == 'Sample'" \
> "$LOG_DIR/sample-launch.log" 2>&1 || true
Une erreur fréquente consiste à n’archiver que la sortie d’erreur standard. Lorsqu’une application se ferme dès son lancement, la cause réelle peut se trouver dans les journaux système du simulateur : échec de chargement d’une bibliothèque dynamique, chemin de ressource incorrect ou exception non gérée, par exemple. Si les journaux contiennent des jetons, des en-têtes de requête ou des données utilisateur, ces informations doivent être expurgées avant l’archivage, et l’accès aux artefacts du pipeline doit être restreint.
Distinguer les défaillances de l’environnement de celles de l’application
Un échec de bootstatus indique généralement un problème lié à l’environnement de l’appareil. Si install échoue, vérifiez en priorité la structure du paquet, la compatibilité avec l’environnement d’exécution et l’espace disque. Si launch réussit mais que le processus disparaît, consultez d’abord les journaux de l’application. Décidez de relancer la tâche seulement après avoir classé l’échec. Lorsqu’une application échoue plusieurs fois de la même manière, elle ne doit pas être relancée automatiquement à répétition, car cela ne ferait que retarder le retour d’information réel.
Nettoyer l’état et intégrer le contrôle au pipeline
Que la tâche réussisse ou échoue, l’application doit être arrêtée et le simulateur utilisé pour cette exécution doit être éteint. Un trap shell permet de garantir l’exécution du nettoyage final :
cleanup() {
xcrun simctl terminate "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl uninstall "$SIM_UDID" "$APP_BUNDLE_ID" \
2>/dev/null || true
xcrun simctl shutdown "$SIM_UDID" \
2>/dev/null || true
}
trap cleanup EXIT
Si la tâche exige un état initial strictement identique, vous pouvez exécuter simctl erase après l’arrêt. Cette opération prend toutefois plus de temps et supprime également les données préchargées. Le compromis le plus courant consiste à désinstaller l’app, nettoyer le répertoire de la tâche et attribuer un appareil distinct à chaque pipeline. Lorsque des autorisations sont concernées, réinitialisez explicitement les services visés.
Enfin, placez ce contrôle après la compilation et avant les tests unitaires ou les tests d’interface. Le script doit renvoyer un code de sortie non nul explicite, tandis que l’étape d’archivage des journaux doit toujours s’exécuter. Ainsi, un échec d’installation ne sera pas présenté comme un délai d’expiration de test, et une fermeture immédiate ne monopolisera pas inutilement tout un groupe d’exécuteurs. Chaque échec laissera suffisamment d’éléments pour permettre aux développeurs de le reproduire avec le même environnement d’exécution et le même artefact.
Questions fréquentes
Pourquoi un build Xcode réussi ne suffit-il pas ?
Un build réussi confirme la production du binaire, mais pas son installation ni son lancement. Un bundle mal assemblé, une version minimale incompatible ou une ressource absente peut encore provoquer un échec.
Quelles preuves conserver après un échec du contrôle ?
Conservez la sortie de xcodebuild, l’UDID et la version du simulateur, le chemin de l’app, les codes de sortie de simctl install et launch, ainsi que les journaux du processus autour de l’échec.
Lancez votre prochaine tâche sur un nœud physique dédié
Utilisez un Mac dans le cloud équipé d’une puce M4, de 16 Go de RAM et d’un SSD de 256 Go, avec un nœud à Singapour, Tokyo, Séoul ou Hong Kong selon la durée de votre tâche.