Retour au blog
Tests

PrestaFlow : consommer le results.json

PrestaEdit •
PrestaFlow : consommer le results.json

Le fichier oublié de la boucle CI

L’article 2 mentionne que l’Action officielle lance composer run prestaflow:json:file puis pousse le résultat à la plateforme. Une prochaine annexe, consacrée à l’historique cloud, montrera qu’on peut retélécharger ce même fichier depuis l’app.

Mais on n’a jamais dit ce qu’il y a dedans, ni ce qu’on peut faire avec en dehors de le regarder.

Ce fichier results.json est la sortie machine de PrestaFlow. Il contient ce qu’un humain voit dans la sortie console, dans un format que n’importe quel script sait consommer. Cette annexe montre son schéma et quatre usages pratiques pour brancher PrestaFlow au reste de votre outillage.

Comment le générer

Une seule commande :

composer prestaflow -- run --output=JSON --file ./tests/prestaflow
  • --output=JSON (ou -o json) — remplace le rapport CLI par du JSON structuré.
  • --file — écrit sur le disque plutôt que sur stdout. Le fichier atterrit toujours au même endroit : prestaflow/results.json, à la racine du projet (le dossier depuis lequel vous lancez la commande).

C’est très exactement ce que fait le script Composer prestaflow:json:file, celui que définit le module de démonstration psflowdemo et que l’Action officielle appelle :

"scripts": {
    "prestaflow": "./vendor-dev/prestaflow/php-library/bin/prestaflow",
    "prestaflow:json:file": "@prestaflow run --output=JSON ./tests --file"
}

Rien ne vous empêche de l’appeler à la main pour explorer. Sans --file, le JSON part sur stdout, mêlé à l’indicateur de progression : pas exploitable tel quel par jq.

Le schéma

Voici un extrait d’un vrai results.json produit par PrestaFlow v1.7.1 (champ steps retiré, noms adaptés au module fil rouge) :

{
  "suite": "Tests\\Suites\\UpdateTitle",
  "title": "Modification du titre depuis le BO",
  "stats": {
    "passes": 1,
    "failures": 1,
    "skips": 0,
    "skippeds": 1,
    "todos": 0,
    "assertions": 2,
    "time": 4213
  },
  "tests": [
    {
      "title": "enregistre le nouveau titre",
      "datasets": [],
      "dataset": 1,
      "warning": "",
      "state": "pass",
      "expect": {
        "pass": ["expected 'Settings updated' to contain 'updated'"]
      },
      "debug": [],
      "time": 2104,
      "visual": []
    },
    {
      "title": "affiche le nouveau titre sur la home",
      "datasets": [],
      "dataset": 1,
      "state": "fail",
      "warning": "",
      "screen": "error_16E9C8B963AB07ADED3089EF55D8FD13-1790260655.png",
      "attachments": [],
      "expect": {
        "pass": [],
        "fail": ["expected 'Bienvenue sur notre boutique' to contain 'Titre mis à jour'"]
      },
      "debug": [],
      "time": 1108,
      "visual": []
    },
    {
      "title": "conserve le titre après vidage du cache",
      "datasets": [],
      "dataset": 1,
      "state": "skipped",
      "expect": [],
      "debug": [],
      "time": 0,
      "visual": []
    }
  ],
  "warnings": ["", ""],
  "screens": ["error_16E9C8B963AB07ADED3089EF55D8FD13-1790260655.png"]
}

Les champs qui comptent en pratique :

  • stats.failures — zéro = suite verte. C’est le seul champ à regarder pour un check automatique de santé.
  • tests[].state — pass / fail / skip / skipped / todo. Ce sont les cinq états possibles vus dans l’annexe debug. Après un échec, les it suivants de la suite passent en skipped (comportement par défaut, désactivable avec ->skipWhenFailed(false)).
  • tests[].expect — un objet par état : pass liste les assertions réussies, fail contient le message de l’échec, du style “expected ‘Bienvenue sur notre boutique’ to contain ‘Titre mis à jour’”. La langue du message suit la locale de la suite. Quand aucune assertion n’a tourné (skipped, todo), c’est un tableau vide [].
  • tests[].screen — nom de fichier de la capture d’erreur, à retrouver dans prestaflow/screens/errors/<nom>. Présent seulement pour un it en fail.
  • tests[].time — durée en millisecondes.
  • tests[].dataset / tests[].datasets — le numéro du jeu de données (à partir de 1) et ses valeurs, quand le it était exécuté via ->with([...]) (voir l’annexe scénarios paramétrés). Sans ->with(), dataset vaut 1 et datasets est vide.
  • tests[].visual — résultats de régression visuelle pour ce it, si visualCheckpoint a été appelé ; tableau vide sinon.

Le reste (warning, debug, attachments, warnings, screens) est du bonus utile en debug mais rarement consommé par un script. steps est la closure du it, sérialisée en objet vide : ignorez-la.

Cas d’usage 1 : notification Slack riche

Le mail “Job PrestaFlow failed” de GitHub Actions ne dit rien du pourquoi. Un message Slack riche, généré depuis le results.json, dit précisément quelle suite a cassé et sur quelle assertion.

Step à ajouter après Run PrestaFlow dans le workflow (GitHub Actions, adaptable ailleurs) :

- name: Notify Slack on failure
  if: failure()
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
  run: |
    RESULTS=prestaflow/results.json

    FAILED=$(jq -r '[(.suites // [.])[] | select(.stats.failures > 0)] | length' "$RESULTS")

    SUMMARY=$(jq -r '[(.suites // [.])[] |
      select(.stats.failures > 0) |
      .title as $suite |
      .tests[] |
      select(.state == "fail") |
      "• *\($suite)* — \(.title)\n  ↳ `\(.expect.fail[0] // "pas de message")`"
    ] | join("\n")' "$RESULTS")

    PAYLOAD=$(jq -n --arg failed "$FAILED" --arg summary "$SUMMARY" \
      '{text: "❌ *\($failed) suite(s) PrestaFlow en échec*\n\n\($summary)"}')

    curl -X POST -H 'Content-Type: application/json' \
      -d "$PAYLOAD" "$SLACK_WEBHOOK_URL"

jq -n --arg construit le payload : les guillemets, apostrophes et retours à la ligne des messages d’assertion sont échappés correctement, ce qu’une chaîne JSON assemblée à la main en shell ne garantit pas.

Rendu Slack :

❌ 1 suite(s) PrestaFlow en échec

• Modification du titre depuis le BO — affiche le nouveau titre sur la home
  ↳ expected 'Bienvenue sur notre boutique' to contain 'Titre mis à jour'

Bien plus utile que le lien vers l’onglet Actions. On sait quoi débugger avant même d’ouvrir le job.

Cas d’usage 2 : badge README dynamique

Shields.io sait générer un badge à partir d’un endpoint JSON qu’on héberge soi-même — un gist, un fichier statique sur S3, un endpoint de projet. On peut publier après chaque run CI un mini-JSON avec le résumé.

Extraction du résumé :

jq '{
  schemaVersion: 1,
  label: "prestaflow",
  message: "\([(.suites // [.])[].stats.passes] | add) passing / \([(.suites // [.])[].stats.failures] | add) failing",
  color: (if ([(.suites // [.])[].stats.failures] | add) == 0 then "brightgreen" else "red" end)
}' prestaflow/results.json > badge.json

Publiez badge.json où vous voulez (un gist GitHub public suffit), puis dans votre README :

![PrestaFlow](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/vous/xxx/raw/badge.json)

Le badge se met à jour à chaque run vert / rouge, dès que votre CI republie badge.json. Utile pour un module open-source qui veut signaler visuellement sa couverture E2E.

Cas d’usage 3 : dashboard multi-projets

Quand vous maintenez 10 modules ou plus, un tableau agrégé qui montre l’état de chacun vaut mieux que des workflows GitHub Actions à surveiller un par un.

Pattern :

  • Chaque projet CI, après son run, POST son results.json (ou juste le stats extrait) vers un endpoint interne
  • Un backend léger (Node/PHP/Python, une table SQL) stocke l’entrée avec project_id + timestamp + stats
  • Un frontend HTML minimal affiche la table avec un statut vert / rouge par projet, dernière date de run, taux de succès sur 7 jours

C’est ce que la plateforme prestaflow.io fait déjà pour vous, mais un dashboard maison a l’avantage de la personnalisation (filtres, groupements, KPI custom, intégration avec vos autres outils internes).

Cas d’usage 4 : auto-création d’issue GitHub

Sur la branche main, une régression PrestaFlow devrait automatiquement créer une issue GitHub avec le contexte du bug. Step CI :

- name: Create issue on regression
  if: failure() && github.ref == 'refs/heads/main'
  env:
    GH_TOKEN: ${{ github.token }}
  run: |
    RESULTS=prestaflow/results.json

    TITLE=$(jq -r '[(.suites // [.])[] |
      select(.stats.failures > 0) |
      "PrestaFlow : \(.title)"
    ] | .[0]' "$RESULTS")

    BODY=$(jq -r '[(.suites // [.])[] |
      select(.stats.failures > 0) |
      .title as $suite |
      .tests[] | select(.state == "fail") |
      "### \($suite) — \(.title)\n\n**Assertion :** `\(.expect.fail[0] // "n/a")`\n"
    ] | join("\n---\n")' "$RESULTS")

    gh issue create --title "$TITLE" --body "$BODY" --label "regression,e2e"

À réserver au CI de main (via le github.ref check) — sinon toutes vos feature branches génèrent des issues à chaque it qui n’a pas fini d’être stabilisé. Le job doit avoir la permission issues: write, et les labels regression et e2e doivent exister dans le dépôt, sinon gh issue create échoue.

Notes

Dans la Série PrestaFlow — article 15 sur 23