Automatiser les tests UI iOS avec XCUITest sur un Mac dédié
Pour automatiser XCUITest, lancez xcodebuild test avec une destination simulateur et un -resultBundlePath, sur un Mac avec une session utilisateur ouverte. Créez un simulateur dédié avec xcrun simctl. Ajoutez -parallel-testing-enabled YES pour répartir les tests sur des clones du simulateur, et -retry-tests-on-failure pour relancer les tests instables. Nous avons lancé chaque commande de cette page sur un Mac avec Xcode 26.6 et le simulateur iOS 26.5.
Ce qu’il vous faut d’abord
- Un Mac avec Xcode et au moins un runtime de simulateur iOS.
- Une cible de tests UI dans votre projet et un scheme partagé qui la teste.
- Une session de bureau ouverte. Les tests UI pilotent l’app Simulator, donc ce qui les lance doit vivre dans cette session. Cela veut dire un agent CI démarré comme LaunchAgent, pas comme LaunchDaemon. Le modèle de plist de Buildkite dit la même chose : le mode GUI permet les tests UI Xcode, mais demande une session ouverte.
1. Vérifiez Xcode et les runtimes
xcodebuild -version sudo xcodebuild -runFirstLaunch xcrun simctl list runtimes xcrun simctl list devicetypes | grep iPhone
-runFirstLaunch installe des paquets et accepte la licence. Lancez-le après chaque installation ou mise à jour de Xcode.
2. Créez un simulateur réservé à la CI
Un appareil dédié sépare la CI de tout ce qu’une personne a ouvert à la main. bootstatus -b le démarre et attend qu’il soit prêt.
UDID=$(xcrun simctl create "CI iPhone 17" "iPhone 17" com.apple.CoreSimulator.SimRuntime.iOS-26-5) xcrun simctl bootstatus "$UDID" -b echo "$UDID"
Utilisez l’UDID dans la destination : -destination "platform=iOS Simulator,id=$UDID". Un nom fonctionne aussi, comme name=iPhone 17,OS=26.5, mais deux appareils peuvent porter le même nom.
3. Lancez les tests UI avec un result bundle
rm -rf build/TestResults.xcresult xcodebuild test \ -project MyApp.xcodeproj \ -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath build/DerivedData \ -resultBundlePath build/TestResults.xcresult
Le result bundle contient chaque résultat de test, log, capture d’écran et échec. Lisez un résumé en ligne de commande :
xcrun xcresulttool get test-results summary --path build/TestResults.xcresult
Il affiche du JSON avec le nombre de tests réussis, échoués et ignorés par appareil. Joignez le bundle à votre run CI comme artefact, compressé avec ditto -c -k --keepParent.
4. Compilez une fois, testez plusieurs fois
Séparez le build de l’exécution des tests. Vous pouvez alors relancer une partie des tests sans recompiler.
xcodebuild build-for-testing -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData xcodebuild test-without-building -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" -derivedDataPath build/DerivedData \ -only-testing:MyAppUITests/LoginTests/testSignIn
-only-testing accepte Target, Target/Class ou Target/Class/method. -skip-testing fonctionne de la même façon, à l’inverse.
5. Lancez les tests en parallèle
xcodebuild test -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -parallel-testing-enabled YES \ -parallel-testing-worker-count 2 \ -resultBundlePath build/TestResults.xcresult
Xcode clone le simulateur et répartit les classes de test sur les clones. Notre log montrait des tests sur Clone 1 of iPhone 17. -parallel-testing-enabled remplace le réglage du scheme. Sur un Mac de 16 Go, commencez avec 2 workers et mesurez avant d’augmenter. Chaque clone est un simulateur complet en mémoire. Pour tester sur plusieurs types d’appareils à la fois, listez plusieurs destinations et réglez -maximum-concurrent-test-simulator-destinations.
6. Relancez les tests instables
xcodebuild test -project MyApp.xcodeproj -scheme MyApp \ -destination "platform=iOS Simulator,id=$UDID" \ -retry-tests-on-failure \ -test-iterations 3 \ -resultBundlePath build/TestResults.xcresult
xcodebuild -help indique qu’un test en échec est relancé jusqu’au nombre d’itérations. Sans -test-iterations, le maximum est 3. Nous l’avons vérifié avec un test qui échoue une fois puis réussit. L’exécution s’est terminée par TEST SUCCEEDED. Le résumé indiquait 3 exécutions de test pour 2 tests.
Une relance fait donc passer un test instable au vert, et l’instabilité se cache. Lisez le result bundle à chaque exécution, et notez quels tests ont eu besoin d’une relance. Ajoutez -test-repetition-relaunch-enabled YES pour relancer chaque tentative dans un processus neuf. Pour traquer un test instable, utilisez -run-tests-until-failure. Cette option ne peut pas être combinée avec -retry-tests-on-failure.
7. Limitez les tests bloqués
-test-timeouts-enabled YES \ -default-test-execution-time-allowance 120 \ -maximum-test-execution-time-allowance 300
Ajoutez ces options à la commande de test. Un test UI qui attend un élément sans fin échoue alors après son délai. Il ne bloque plus la machine jusqu’au timeout de la CI.
8. Gardez les simulateurs propres entre les exécutions
xcrun simctl shutdown "$UDID" xcrun simctl erase "$UDID" xcrun simctl delete unavailable
Erase réinitialise le contenu et les réglages du simulateur. Delete unavailable supprime les appareils que le Xcode actuel ne prend plus en charge. Lancez-le après chaque mise à jour de Xcode.
Comment tout cela survit aux redémarrages
Les simulateurs que vous créez persistent après un redémarrage. C’est l’agent CI qui lance les tests qui doit revenir. Faites-le tourner comme LaunchAgent, activez la connexion automatique, et démarrez le simulateur de CI au début de chaque job avec bootstatus -b. Cette commande ne pose pas de problème sur un appareil déjà démarré.
Les erreurs rencontrées et leurs solutions
Unable to find a device matching the provided destination specifier. Le nom ou l’OS n’existe pas. Vérifiezxcrun simctl list deviceset la liste des runtimes.xcodebuild: error: Existing file at -resultBundlePath. Supprimez l’ancien bundle avant chaque exécution.Unable to erase contents and settings in current state: Booted. Éteignez le simulateur avant de l’effacer.
Pourquoi un Mac dédié aide
Les tests UI passent beaucoup de temps avant le premier tap. Ils attendent le build, le démarrage d’un simulateur et l’installation de l’app. Une machine qui reste allumée garde le DerivedData et un simulateur démarré, prêts à servir. Dans notre benchmark, nous avons compilé l’app iOS Wikipedia avec Xcode 26.6. Un job après un petit changement a pris 27 secondes sur un M6 déjà chaud. Il a pris 269 secondes sur un runner macos-26 neuf hébergé par GitHub. Notre M6 a 12 cœurs CPU, donc deux clones en parallèle laissent encore de la place au build.
Quand la CI hébergée suffit
Une petite suite lancée sur quelques pull requests par jour convient aux minutes facturées. Au tarif GitHub de $0.062 la minute macOS (vérifié en septembre 2026), un Mac à $139 est rentabilisé après environ 2,242 minutes par mois. En dessous, restez sur l’hébergé. Si vous devez tester sur de nombreux iPhone physiques, il vous faut un cloud d’appareils. Un Mac mini fait tourner des simulateurs. Votre suite parallèle a besoin de plus de 16 Go de mémoire ? Nos modèles M5 Pro ont 48 ou 64 Go, mais ils sont en précommande. Prévoyez donc environ une semaine d’attente.
Le guide du pipeline CI/CD iOS montre où se placent les tests UI dans un pipeline complet. Le calculateur fait le calcul avec vos propres chiffres.
Questions fréquentes
xcodebuild a-t-il une option pour relancer les tests en échec ?
+
Oui. -retry-tests-on-failure relance un test en échec, jusqu’à -test-iterations fois, ou 3 fois par défaut. Elle ne peut pas être combinée avec -run-tests-until-failure.
Comment lancer des tests XCUITest en parallèle en ligne de commande ?
+
Ajoutez -parallel-testing-enabled YES, et éventuellement -parallel-testing-worker-count. Xcode clone le simulateur et répartit les classes de test sur les clones.
XCUITest peut-il tourner sur un Mac headless ?
+
Il lui faut une session utilisateur ouverte, car les tests UI pilotent l’app Simulator. Faites tourner l’agent CI comme LaunchAgent et activez la connexion automatique.
Comment lire les résultats de xcodebuild test sans ouvrir Xcode ?
+
Passez -resultBundlePath, puis lancez xcrun xcresulttool get test-results summary --path sur le bundle. Il affiche les compteurs en JSON.