Régression visuelle : pourquoi comparer pixel par pixel laisse passer de vrais changements

Régression visuelle : pourquoi comparer pixel par pixel laisse passer de vrais changements

La plupart des outils de régression visuelle open-source — BackstopJS, Wraith, ou les vérifications de captures d'écran de Playwright et Cypress — reposent sur la même idée : prendre une capture d'écran avant et après, puis compter les pixels qui ont changé. Le moteur est presque toujours le même : comparer les deux images pixel par pixel.

C'est simple, robuste, et parfait pour attraper un gros cassage de mise en page. Mais cette approche a un compromis structurel que peu de gens mesurent. Nous l'avons fait, chiffres à l'appui — et sur des cas choisis pour l'exposer, le résultat est net.

Deux réglages à ne pas confondre

La confusion est partout, alors clarifions. Ce type d'outil a deux réglages distincts :

  1. La tolérance par pixel : à partir de quel écart de couleur on considère qu'un pixel a « vraiment » changé. C'est un curseur de sensibilité à la couleur, appliqué pixel par pixel.
  2. Le seuil de décision : une fois les pixels différents comptés, on regarde quel pourcentage de l'image a changé pour décider si le test passe ou échoue. L'outil de référence BackstopJS le fixe par défaut à 0,1 % des pixels.

Notre test suit exactement cette logique : on compte les pixels différents (outil réglé sur sa sensibilité par défaut), puis on applique le seuil de décision de 0,1 % pour dire passe ou échoue.

Le dilemme du seuil

Ce seuil en pourcentage enferme la comparaison de l'image entière dans un compromis qu'elle ne peut pas gagner :

  • Avec un seuil en pourcentage (réglage par défaut, 0,1 %) → on laisse passer les petits changements réels. Un bouton qui change de couleur, une bordure qui s'arrondit, un statut de cellule « OK » → « ERREUR » : tout ça ne pèse qu'une fraction minuscule des pixels. Sous 0,1 %, le test passe au vert alors que la page a visiblement changé. Un vrai changement passe inaperçu.
  • Sans aucune tolérance (l'autre extrême, le réglage par défaut de Playwright) → on échoue au moindre pixel d'écart, y compris le simple lissage des contours — ces pixels semi-transparents au bord des lettres et des formes, qui varient légèrement d'un affichage à l'autre. Résultat : des fausses alertes en pagaille, et l'équipe finit par ignorer l'outil.

Les vrais outils offrent des garde-fous — masquer des zones, ignorer des régions, recadrer. Ils sont efficaces, mais il faut les configurer à l'avance, à la main, zone par zone. La sensibilité localisée n'est pas automatique.

Le test en conditions réelles

Nous avons comparé, sur exactement la même page (rendue par un navigateur, figée de façon reproductible, largeur d'écran de 1280 px, capture de la page entière), deux approches :

  • Delta-QA : notre comparateur, qui compare élément par élément (il associe les éléments entre les deux versions et ne compare les pixels qu'au niveau le plus fin) ;
  • la comparaison de l'image entière : on compare les deux captures pixel par pixel, puis on applique le seuil de décision de 0,1 %.

Cinq cas, choisis pour exposer l'angle mort :

Cas Changement Delta-QA (élément par élément) Comparaison image entière (% de pixels modifiés, verdict au seuil 0,1 %)
Déplacement un triangle se déplace dans la page 2 signaux : disparu + apparu sur l'élément 0,005 % → NON DÉTECTÉ
Réordonnancement cartes et menu réordonnés 13 signaux localisés (quelles cartes, quels items) 0,63 % → échoue, mais bloc diffus, sans rien préciser
Changement fin bordure arrondie de 12 px à 30 px 1 signal sur la carte concernée 0,011 % → NON DÉTECTÉ
Couleur localisée en-tête d'une carte vert → violet le bon en-tête (intensité 0,996) + 2 signaux parents faibles 5,03 % → échoue, mais bloc diffus, sans rien préciser
Cellule de tableau statut d'une ligne FAIL → WARN 2 signaux forts sur les bonnes lignes 0,036 % → NON DÉTECTÉ

Sur ces cinq cas, trois changements réels — un déplacement d'élément, un arrondi de bordure, un statut de cellule — passent sous le seuil de décision standard de 0,1 %. Un outil configuré par défaut les déclare « pas de changement ». Ce sont pourtant exactement les régressions qu'un test visuel est censé attraper.

Pour les deux cas que la comparaison pixel par pixel « détecte » (réordonnancement, couleur), elle ne dit qu'une chose : « X % de pixels ont changé quelque part ». Aucune idée de quel élément, ni de quelle nature. Delta-QA, lui, nomme l'élément exact et qualifie le changement (déplacé, ajouté, supprimé, modifié).

Pourquoi le niveau élément change la donne

Delta-QA ne compare pas une grande image. Il :

  1. reconstruit l'arborescence des éléments de la page ;
  2. associe chaque élément entre les deux versions (par son contenu, puis sa position) ;
  3. ne compare les pixels qu'au niveau le plus fin, et repère les changements propres à un bloc en ignorant les zones de ses sous-éléments déjà modifiés ;
  4. écarte le lissage des contours du comptage des pixels vraiment différents.

Conséquence : il peut être très sensible (attraper une bordure d'1 px sur un grand bloc) sans se noyer dans les variations de lissage, parce que ce bruit est écarté et que chaque signal est rattaché à un élément précis. Un déplacement n'est pas un « bloc rouge » : c'est un élément signalé disparu à l'ancienne place et apparu à la nouvelle. La sensibilité localisée est automatique, sans masque à préparer d'avance.

Méthodologie — et ses limites

Nous tenons à la rigueur, alors voici la part d'ombre :

  • La même page pour les deux. Les deux approches partent exactement de la même page, rendue et figée — aucun biais d'affichage.
  • Chiffres vérifiés contre l'outil de référence. Notre banc de test recalcule l'écart de couleur de la même façon que l'outil de comparaison pixel par pixel le plus utilisé. Nous avons recoupé les 5 cas avec cet outil officiel : sur le changement de couleur franc, les deux donnent 5,036 % contre 5,034 % — quasi identiques. Sur les autres cas, l'outil de référence compte encore moins de pixels (il ignore le lissage des contours) — il est donc encore plus enclin à laisser passer les petits changements. Les chiffres du tableau sont les siens.
  • Delta-QA sur-signale (et on l'assume). Sur le changement de couleur, il émet 3 signaux : le vrai (l'en-tête, intensité 0,996) + 2 signaux parents très faibles (0,005 et 0,001). C'est volontaire : on remonte tout, et le curseur de sensibilité de l'interface masque ces signaux faibles par défaut. Mais soyons clairs : le compte brut n'est pas « 1 changement = 1 signal ».
  • Un seul contexte de test. Ces mesures sont faites à une seule taille d'écran, page au repos, sur des pages de test contrôlées. Nous ne prétendons rien sur plusieurs tailles d'écran, les états interactifs (survol, focus) ou de vraies pages bruitées — d'autres chantiers.
  • Réordonnancement. Delta-QA a classé les cartes réordonnées comme « modifiées » plutôt que « déplacées », mais localisées par élément — ce qui reste très au-dessus d'un bloc diffus.

Et soyons justes : comparer l'image entière est simple, ne demande qu'une capture par page, reste excellent pour un gros cassage, et ses masques de zones fonctionnent. Le problème n'est pas que ce soit mauvais — c'est que ça vous force à arbitrer entre laisser passer le fin et crier au bruit, et à régler la finesse à la main.

Ce qu'il faut retenir

Si votre test de régression visuelle repose sur une comparaison de l'image entière avec un seuil en pourcentage au réglage par défaut, il laisse probablement passer des changements que vos utilisateurs voient — déplacements, couleurs localisées, micro-changements de style. Baisser le seuil les rattrape, mais réveille les fausses alertes ; les masques aident, mais se configurent zone par zone, à l'avance.

Comparer élément par élément n'est pas un réglage : c'est une autre architecture, qui rend à la fois la sensibilité et la précision — avec, en prime, le nom de l'élément et la nature du changement.

Pour aller plus loin


Test reproductible : la comparaison « image entière » a été produite avec l'outil open-source de référence (le package pixelmatch, Node/npm), réglé sur sa sensibilité par défaut, puis avec un seuil de décision à 0,1 % comme BackstopJS — sur exactement la même page figée que Delta-QA.