Retour au blog
Tests

PrestaFlow : debug d'un scénario qui casse

PrestaEdit •
PrestaFlow : debug d'un scénario qui casse

Décor

Un test qui passe s’écrit facilement. Un test qui casse à un moment imprévisible, ou qui échoue sur un message obscur, consomme dix fois plus de temps qu’il n’en faut si on l’aborde sans méthode.

Cette annexe est la boîte à outils pour ces moments-là : les cinq leviers que PrestaFlow met à votre disposition pour localiser la cause d’un échec rapidement, dans l’ordre où on les mobilise en pratique.

Le triangle d’or : trois artefacts, systématiquement

À chaque échec, PrestaFlow vous laisse trois choses. Consultez-les toujours dans cet ordre :

  • Le message d’échec dans la sortie CLI (ou dans la vue résultat de l’app, cf. article 3) : le it marqué FAIL, suivi du message de l’assertion ou de l’exception.
  • La capture d’erreur — prestaflow/screens/errors/error_<id>-<timestamp>.png. Prise automatiquement par Expect::that() quand une assertion tombe, après un court délai (voir l’étape 2). Une exception levée hors d’une assertion (un timeout de navigation, par exemple) ne déclenche pas de capture.
  • Les assertions déjà passées — listées sous chaque PASS en mode verbeux (le défaut), et dans expect.pass du results.json si vous lancez avec -o json --file.

Le message d’échec vous dit où. La capture d’erreur vous dit quoi. Les assertions passées vous disent comment on en est arrivé là.

Étape 1 : commencer par la capture d’erreur, pas les logs

Réflexe naturel de développeur : lire le message d’erreur, chercher la ligne, tenter de reproduire. Sur du E2E, c’est souvent une perte de temps.

Le message d’échec vous dira que getTextContent('.newsletter-message') a renvoyé une chaîne vide. Il ne vous dira pas pourquoi. La capture, elle, le montre en une seconde :

  • un bandeau cookies qui n’a pas été fermé et qui masque le formulaire ;
  • un modal RGPD qui s’ouvre au-dessus du bloc newsletter ;
  • une redirection inattendue vers la page maintenance ;
  • un message d’erreur PrestaShop en rouge en haut de page ;
  • une page 404 parce qu’un slug friendly a changé.

Ces cinq cas de figure représentent, à la louche, la majorité des échecs qu’on voit sur un scénario mûr.

# Ouvre tout le dossier des captures d'erreur du dernier run
open prestaflow/screens/errors/          # macOS
xdg-open prestaflow/screens/errors/      # Linux
explorer.exe prestaflow/screens/errors/  # Windows / WSL

Étape 2 : voir Chrome à l’œuvre

Quand la capture ne suffit pas — parce que la scène qui a mené à l’erreur est plus intéressante que l’instantané final — on regarde Chrome exécuter le scénario en direct.

Dans votre .env local (pas celui de la CI), passez :

PRESTAFLOW_HEADLESS=false
PRESTAFLOW_SCREENSHOT_DELAY=5

PRESTAFLOW_HEADLESS=false ouvre une fenêtre Chrome visible. PRESTAFLOW_SCREENSHOT_DELAY=5 porte à 5 secondes (3 par défaut) l’attente avant chaque capture d’erreur : quand une assertion tombe, la page reste figée sous vos yeux le temps de la regarder, avant la capture. Ce délai ne concerne que les captures d’erreur : un run sans échec n’attend jamais.

Étape 3 : isoler l’étape fautive avec skip() et todo()

Un scénario de 10 étapes qui casse à la 9ème vous fait attendre les 8 premières à chaque tentative. À raison de 5 à 30 secondes par étape, c’est une éternité.

PrestaFlow expose skip() et todo() en méthodes sœurs de it(), à enchaîner après describe() :

$this
->describe('Modification du titre depuis le BO')
->skip('se connecte au BO', function () use ($backOfficeLoginPage) {
    // ne s'exécute pas
})
->skip('met à jour le titre du bloc', function () use ($modulesPsflowdemoConfigurationPage) {
    // ne s'exécute pas non plus
})
->it('affiche le nouveau titre sur la home', function () use ($modulesPsflowdemoHomePage) {
    // seule cette étape s'exécute
});

Le pattern est visible dans le code de test de la lib elle-même — FirstTest.php mélange it, skip, todo pour illustrer les trois statuts possibles.

Corollaire : ce jeu de skip() est un outil de debug local, pas quelque chose à committer. Une fois la cause identifiée, retirez-les.

Étape 4 : mode debug — PRESTAFLOW_DEBUG=true

Quand le scénario semble tourner mais l’assertion tombe sur une donnée dont on ne comprend pas la provenance, il faut voir ce que la lib a résolu comme contexte.

PRESTAFLOW_DEBUG=true

La sortie CLI affiche alors, en tête de chaque suite, la locale résolue (Debug: Locale: fr). C’est tout ce qu’ajoute ce mode en v1.7.1 : il n’affiche ni la version PS ni d’autres détails. Pour le reste, $this->log() dans un it écrit une ligne Debug: sous son résultat (et dans le champ debug du results.json), mode debug ou non :

->it('affiche le nouveau titre sur la home', function () use ($modulesPsflowdemoHomePage) {
    $this->log('Locale: ' . $this->getLocale() . ', PS: ' . $this->getPatchVersion());
    // ...
});

Cas typique où ça sauve : un test qui passe en 8.1.7 et casse en 9.0.0, où la sortie affiche noir sur blanc “Locale: fr, PS: 9.0.0” — et vous découvrez que votre catalogue de traductions n’a rien pour v9 alors que v8 l’avait (cf. le mécanisme de merge de l’annexe scénarios multi-locales).

Étape 5 : le dump temporaire, dernière ligne de défense

Quand rien de tout ce qui précède ne vous a mené à la cause, restez pragmatique. Dans une méthode de Page ou dans un it :

->it('lit la valeur', function () use ($page) {
    $value = $page->getTextContent('.some-selector');
    var_dump($value);            // ← temporaire
    var_dump($page->translationsCatalog);  // ← catalogue de la Page, null tant qu'aucune traduction n'a été demandée
    Expect::that($value)->contains('foo');
});

Bricolage assumé, mais qui coupe court à toutes les hypothèses. Contrairement au stack trace ou aux captures, il vous donne l’accès direct à la structure de données que la lib manipule à l’instant t.

Le cas particulier du flake

Un test qui passe parfois et échoue parfois n’est pas un bug fonctionnel. C’est un bug de scénario, et il en existe trois causes classiques :

  • Wait manquant — l’assertion est faite avant que l’action asynchrone n’ait fini de rendre son résultat. Parade : clickAndWaitReload($sel) pour un clic qui recharge la page, waitVisible($sel) ou waitForText('…', 5000, $sel) avant de lire un contenu injecté par JS, waitUntil() pour une condition sur mesure.
  • Ordre d’exécution / état résiduel — un it dépend d’un état laissé par le précédent, qui échoue silencieusement dans certaines conditions (session expirée, cookie perdu). Rendre chaque it idempotent quand c’est possible.
  • Données polluées — vu dans l’annexe tester un module tiers : le second run crée l’inscription que le premier a laissée. Suffixe unique par run (time(), uuid) ou cleanup en fin de suite.

Diagnostic : relancez le scénario 5 fois de suite. Si l’échec est intermittent, c’est un flake — traitez-le comme un bug de test, pas comme un bug de code métier. Un flake ignoré finit toujours par masquer une vraie régression.

Notes

Pour aller plus loin

  • Un rapport à lire en profondeur : voir la vue résultat de l’application, qui présente les mêmes données en plus interactif.
  • Une capture visuelle qui diffère de la baseline sans que l’assertion ne tombe : c’est le sujet de l’annexe régressions visuelles.
  • Un échec qui n’apparaît que sur une version PS ou une locale : cf. les annexes multi-versions et multi-locales pour comprendre ce que la lib a résolu comme contexte.

Dans la Série PrestaFlow — article 14 sur 23