Un même code d’interface iOS peut sembler correct sur la machine de développement, puis afficher des boutons tronqués, une mise en page comprimée par les polices dynamiques ou des couleurs incohérentes en mode sombre après sa fusion. Un contrôle de régression visuelle ne vise pas à remplacer les tests unitaires. Il doit répondre à une question précise dans des conditions de rendu fixes : ce commit a-t-il modifié les pixels réellement visibles par l’utilisateur ?
Un Mac cloud convient bien à l’exécution continue de ce type de tâche, à condition d’intégrer à la gestion de versions l’état du simulateur, les données de test et les règles de comparaison. Conserver quelques captures d’écran qui « semblent correctes » ne suffit pas à créer un contrôle fiable.
Commencer par définir une matrice de captures stable
Ne cherchez pas d’emblée à « couvrir tous les écrans ». Sélectionnez d’abord les interfaces à forte valeur, comme l’écran d’accueil avant connexion, la liste principale, la page de détail, l’état vide et l’état d’erreur. Fixez ensuite les dimensions suivantes pour chacune d’elles :
| Dimension | Valeur fixe recommandée | Traitement en cas de changement |
|---|---|---|
| Appareil | Un modèle de Simulator clairement défini | Créer un répertoire de référence distinct |
| Système | Un runtime disponible précisément défini | Réexaminer les images de référence |
| Apparence | Exécuter séparément les modes clair et sombre | Ne pas comparer des apparences différentes |
| Langue | Créer des captures séparées pour chaque langue cible | Inclure le code de langue dans le nom du fichier |
| Taille du texte | Commencer par la taille par défaut | Créer un groupe de tests distinct pour les grandes tailles |
| Données | Fixtures locales ou API de test | Interdire toute dépendance à du contenu aléatoire |
Les références peuvent être stockées sous Snapshots/<runtime>/<device>/<locale>/<appearance>/. Cette arborescence est certes un peu longue, mais elle permet d’identifier immédiatement l’environnement de comparaison en cas d’échec et évite de prendre une différence de rendu entre deux systèmes pour une régression du produit.
Une image de référence n’est pas une vérité permanente. C’est un contrat d’interface validé, dont chaque mise à jour doit être examinée en même temps que la modification du code correspondante.
Fixer le simulateur au lieu de réutiliser son état courant
La réutilisation prolongée d’un simulateur manipulé manuellement laisse subsister des demandes d’autorisation, des réglages de clavier, des caches et des comptes de test. Une méthode plus fiable consiste à réserver un appareil aux tests visuels, puis à l’éteindre, l’effacer et le redémarrer avant chaque exécution.
#!/usr/bin/env bash
set -euo pipefail
: "${DEVICE_UDID:?DEVICE_UDID is required}"
xcrun simctl shutdown "$DEVICE_UDID" 2>/dev/null || true
xcrun simctl erase "$DEVICE_UDID"
xcrun simctl boot "$DEVICE_UDID"
xcrun simctl bootstatus "$DEVICE_UDID" -b
xcrun simctl status_bar "$DEVICE_UDID" override \
--time 09:41 \
--batteryState charged \
--batteryLevel 100 \
--wifiBars 3 \
--cellularBars 4
rm -rf TestResults/Visual.xcresult
xcodebuild test \
-workspace Example.xcworkspace \
-scheme ExampleVisualTests \
-destination "id=$DEVICE_UDID" \
-resultBundlePath TestResults/Visual.xcresult
rm -rf TestResults/Attachments
xcrun xcresulttool export attachments \
--path TestResults/Visual.xcresult \
--output-path TestResults/Attachments
Ne dépendez pas de l’appareil booted au moment de l’exécution. Des tâches parallèles peuvent démarrer plusieurs simulateurs simultanément. Seul le passage explicite de l’UDID garantit que la personnalisation de la barre d’état, l’installation et les tests ciblent le même appareil.
Déclencher la capture à partir d’un état observable
L’erreur la plus courante dans un test visuel consiste à attendre systématiquement deux secondes après le lancement de l’application. Selon la charge de la machine ou l’état du réseau, ces deux secondes peuvent être excessives ou insuffisantes. XCUITest doit attendre l’apparition d’un élément accessible indiquant que « la page est prête à être comparée ».
func capture(_ name: String, readyIdentifier: String) {
let app = XCUIApplication()
app.launchArguments = ["-VisualTestMode", "1"]
app.launch()
let ready = app.descendants(matching: .any)[readyIdentifier]
XCTAssertTrue(ready.waitForExistence(timeout: 15))
let image = XCUIScreen.main.screenshot()
let attachment = XCTAttachment(screenshot: image)
attachment.name = name
attachment.lifetime = .keepAlways
add(attachment)
}
Le mode de test doit désactiver les carrousels, les animations de squelette de chargement et les curseurs clignotants. Il doit également injecter une date, un nom d’utilisateur et des données de liste fixes. Il s’agit ici de stabiliser les entrées, et non de remplacer l’interface testée par une autre implémentation. Si la page dépend de la fin d’une requête, exposez dans l’interface un identifiant stable signalant la fin du chargement au lieu d’allonger continuellement la durée d’attente.
Gérer les polices et les animations
Les polices doivent provenir du système ou être livrées avec l’application. Elles ne doivent pas dépendre d’une installation manuelle effectuée ponctuellement. Les animations peuvent être désactivées au moyen d’arguments de lancement. Avant la capture, vérifiez également que tout défilement est terminé. Pour les vidéos, cartes ou minuteurs en évolution permanente, privilégiez des fixtures de test déterministes. N’utilisez un masque que pour les très petites zones impossibles à contrôler.
Définir des seuils de pixels explicables
Une différence entre les octets de deux fichiers PNG ne signifie pas nécessairement que leur rendu visuel diffère. Une comparaison directe avec cmp est facilement influencée par les informations d’encodage. Le comparateur doit d’abord décoder les deux images dans les mêmes dimensions et en pixels sRGB, puis calculer les écarts entre les canaux pixel par pixel.
Il est recommandé d’utiliser simultanément deux conditions :
- Une tolérance maximale par canal, afin d’autoriser par exemple de légères variations d’anticrénelage.
- La proportion de pixels dépassant cette tolérance, qui ne doit pas excéder une fraction infime de l’image entière.
Une simple différence moyenne peut masquer un problème local grave : un bouton disparu peut n’occuper qu’une très petite partie de l’image. Le comparateur doit aussi produire une image mettant les écarts en évidence et consigner le nombre de pixels hors seuil, leur rectangle englobant et les seuils utilisés. Les écrans de confirmation critiques peuvent appliquer des règles strictes, tandis que les pages comportant des ombres ou des dégradés complexes peuvent avoir leur propre configuration. Il ne faut toutefois pas assouplir sans cesse le seuil global dans le seul but de « faire passer le test au vert ».
Transformer chaque échec en preuve vérifiable
Pour chaque échec, archivez au minimum l’image de référence, l’image obtenue, l’image des différences et le fichier .xcresult. Enregistrez également l’identifiant du commit, la version de Xcode, le runtime système, le modèle de l’appareil, la langue et l’apparence. Les noms de fichiers doivent combiner la suite de tests, l’état de la page et l’environnement afin d’éviter que des tâches parallèles n’écrasent mutuellement leurs résultats.
Lors du diagnostic, procédez dans cet ordre : vérifiez d’abord si les dimensions ou le runtime ont changé, contrôlez ensuite la stabilité des données de test, puis examinez le rectangle englobant des différences. Si les écarts couvrent tout l’écran, ils proviennent généralement d’une dérive des polices, de l’apparence ou de l’espace colorimétrique. S’ils se concentrent sur un seul contrôle, une modification de la mise en page devient plus probable.
La mise à jour des références doit elle aussi suivre une procédure distincte : générez les images candidates, faites examiner les différences par une personne, puis remplacez les références après confirmation de l’intention de la modification. La tâche de test ne doit jamais écraser automatiquement les références en cas d’échec, sous peine de valider une véritable régression dès l’exécution suivante.
Une fois en place, ce contrôle doit présenter trois caractéristiques : l’environnement peut être reconstruit, les seuils peuvent être expliqués et les échecs peuvent être reproduits. C’est à ces trois conditions que les tests par capture d’écran deviennent un véritable contrôle d’ingénierie, plutôt qu’un dépôt d’images nécessitant ponctuellement un nettoyage manuel.
Questions fréquentes
Faut-il exiger une égalité parfaite entre tous les pixels ?
Non dans la plupart des cas. Il vaut mieux limiter l’écart par canal et la proportion de pixels hors seuil, puis masquer uniquement les petites zones réellement dynamiques.
Pourquoi une même révision produit-elle des images différentes selon le simulateur ?
Le modèle d’appareil, la version du système, la police, la langue, le fuseau horaire et la barre d’état modifient le rendu. Chaque référence doit donc être liée à une combinaison précise.
Quels fichiers faut-il conserver après un échec ?
Conservez l’image de référence, l’image obtenue, l’image des différences, le résultat de test, l’identifiant de révision ainsi que les versions du simulateur et du système.
Déployez vos workflows validés sur un Mac cloud disponible en continu
Choisissez BookaMac M4 ou BookaMac M4 Pro selon votre usage, puis vérifiez la région, la durée de location et les options de stockage au moment de la commande.