# Tests Playwright instables sur les images WebP ou AVIF : le vrai coupable

> Un décodage d'image plus lent que prévu fait échouer par intermittence des assertions visuelles, un défaut facile à confondre avec un test simplement mal écrit.

- Auteur : Clément Hadrot
- Publié le : 2025-02-14
- Mis à jour le : 2025-02-14
- Catégorie : Tests
- URL : https://wpmoderne.dev.wordpress-developpement.fr/tests/tests-playwright-instables-images-webp-avif/

## L’essentiel

- Le symptôme ressemble à un test flaky classique
- La cause réelle est un décodage d'image asynchrone non attendu
- Le correctif attend un événement de chargement précis, pas un délai fixe

**Symptôme.** Un test Playwright vérifiant l'affichage correct d'une galerie produit échouait environ une fois sur six, toujours sur la même assertion : une capture d'écran de comparaison visuelle montrait une image manquante ou partiellement décodée, alors que le test réussissait parfaitement dans la majorité des exécutions. L'équipe a d'abord traité ce cas comme un test « flaky » ordinaire et a ajouté un `page.waitForTimeout(1000)` avant la capture, ce qui a réduit la fréquence de l'échec sans jamais l'éliminer complètement.

## Diagnostic : reproduire l'échec de façon déterministe

Ajouter un délai fixe pour masquer un problème d'attente est le signal qu'on n'a pas identifié la vraie cause. En instrumentant le navigateur avec l'API de performance pour tracer le moment exact de décodage de chaque image, le motif est apparu clairement : les images au format WebP et AVIF prenaient un temps de décodage variable selon la charge du runner CI, parfois supérieur à la seconde utilisée comme délai fixe, alors que les mêmes images au format JPEG classique se décodaient de façon quasi instantanée.

```
await page.evaluate(() => {
  performance.mark('avant-decodage');
  const img = document.querySelector('.produit-galerie img');
  return img.decode().then(() => {
    performance.mark('apres-decodage');
    performance.measure('decodage-image', 'avant-decodage', 'apres-decodage');
    return performance.getEntriesByName('decodage-image')[0].duration;
  });
});
// résultat observé : entre 40 ms et 1 340 ms selon la charge du runner
```

Le format AVIF, plus récent et au décodage plus coûteux en calcul, explique la majeure partie de la variance observée : sur un runner CI partagé et donc à charge CPU imprévisible, ce décodage pouvait dépasser largement la seconde de marge choisie arbitrairement.

## Correctif : attendre l'événement de décodage, jamais un délai fixe

> L'essentiel à retenir : Le symptôme ressemble à un test flaky classique ; La cause réelle est un décodage d'image asynchrone non attendu ; Le correctif attend un événement de chargement précis, pas un délai fixe

La méthode `HTMLImageElement.decode()`, exposée par le DOM standard, renvoie une promesse résolue précisément quand l'image est décodée et prête à être peinte à l'écran. C'est cet événement qu'il faut attendre avant toute capture, plutôt qu'un délai arbitraire :

```
test('galerie produit affiche toutes les images décodées', async ({ page }) => {
  await page.goto('/produit/chaise-scandinave/');

  await page.waitForFunction(() => {
    const images = Array.from(document.querySelectorAll('.produit-galerie img'));
    return Promise.all(images.map(img => img.decode().catch(() => false)))
      .then(() => true);
  });

  await expect(page.locator('.produit-galerie')).toHaveScreenshot('galerie-chaise.png');
});
```

Après ce correctif, le test est passé de un échec sur six exécutions à zéro échec observé sur plus de deux cents exécutions consécutives en CI, sur plusieurs semaines.

## Prévention : ne jamais accepter un délai fixe comme correctif définitif

- Un `waitForTimeout` qui « réduit » la fréquence d'un échec sans l'éliminer signale presque toujours un problème d'attente mal ciblée plutôt qu'un vrai aléa.
- Préférer systématiquement `waitForFunction`, `waitForLoadState('networkidle')` ciblé, ou un événement DOM précis à un délai arbitraire.
- Sur un projet servant des images modernes via la balise `<picture>` avec plusieurs sources, vérifier que `decode()` est appelé sur l'élément `<img>` réellement rendu, pas sur une source alternative ignorée par le navigateur.

## Un cas voisin : le lazy-loading natif

Le même symptôme peut apparaître avec l'attribut `loading="lazy"`, natif depuis WordPress 5.5, si le test capture une zone avant que le navigateur n'ait déclenché le chargement différé de l'image concernée. La solution est similaire : scroller explicitement jusqu'à l'élément avec `locator.scrollIntoViewIfNeeded()` puis attendre son décodage, plutôt que de désactiver le lazy-loading dans l'environnement de test, ce qui masquerait justement le comportement réel des utilisateurs.

## En résumé

Un test qui échoue occasionnellement sur une comparaison visuelle liée aux images mérite une instrumentation précise du décodage avant d'être qualifié de « flaky » et corrigé à coups de délais grandissants. Ce billet ne traite pas de la conversion des formats d'image côté serveur, seulement de la façon de fiabiliser un test qui en dépend, une fois les formats modernes déjà en place.
