đŸŽ„ Guide Complet des Tests E2E

Ce guide centralise toutes les informations sur les tests End-to-End avec Playwright et la génération de vidéos de documentation vivante.

🎯 Introduction

Les tests E2E de KoproGo testent toute la stack :

  • ✅ Frontend (Astro + Svelte)

  • ✅ Backend (Rust + Actix-web)

  • ✅ Base de donnĂ©es (PostgreSQL)

  • ✅ API REST

  • ✅ PWA + Mode Offline

Chaque test peut enregistrer une vidéo de démonstration : générez-les localement, puis ajoutez les fichiers .webm dans docs/_static/videos/ pour enrichir la documentation.

🚀 DĂ©marrage Rapide

Installation (une seule fois)

# Installer les dépendances frontend
cd frontend
npm install

# Installer Playwright et Chromium
npx playwright install chromium

DĂ©marrer les services

# Depuis la racine du projet
make up

# Les services démarrent via Docker Compose + Traefik, sur des ports
# DÉCALÉS pour ne pas entrer en collision avec la dĂ©mo (ADR 0050) :
# Frontend: http://localhost:8090
# API:      http://localhost:8090/api/v1
# Traefik:  http://localhost:8091
# Postgres: localhost:15432    MinIO: localhost:19000 / 19001

Lancer les tests

# Le gate, Ă  la vitesse
make test-e2e

# La vitrine : le MÊME parcours, en cadence, narrĂ©, avec sa galerie.
# Harnais sĂ©parĂ© — il ne rend aucun verdict et ne touche pas au gate.
make vitrine

Le tĂ©moin d’interruption backend (#880)

Le backend de la pile de recette tourne sous cargo-watch (backend/Dockerfile.dev:71) : une Ă©dition de fichier Rust pendant une campagne recompile et coupe le service ~90s. Sans tĂ©moin, ces Ă©checs sont indiscernables d’une rĂ©gression — 83 des 95 Ă©checs du 2026-09-13 visaient un backend mort (cf. issue #880).

make test-e2e encadre dĂ©sormais la commande Playwright rĂ©elle de scripts/e2e-guarded.sh, qui surveille l’empreinte (PID + heure de dĂ©marrage) du process backend pendant toute la campagne (scripts/e2e-backend-watch.sh) :

exit <code Playwright>   aucun redĂ©marrage dĂ©tectĂ© — verdict inchangĂ©
exit 75 (EX_TEMPFAIL)    backend redémarré pendant la campagne :
                         campagne NON MESURÉE, quel qu'ait Ă©tĂ© le rĂ©sultat
                         brut — voir frontend/test-results/campaign-verdict.json
                         et frontend/test-results/backend-restarts.jsonl

Si l’empreinte est indisponible (pas de dĂ©mon Docker, banc distant), le code de sortie de la commande encadrĂ©e est transmis tel quel, mais campaign-verdict.json porte "monitoring": "unavailable" et "backend_restarts": null — jamais un zĂ©ro silencieux qui prĂ©tendrait avoir vĂ©rifiĂ©.

Tests du tĂ©moin lui-mĂȘme (4 catĂ©gories, sans docker rĂ©el — fixtures rejouant une sĂ©quence d’empreintes) :

make e2e-guard-test

đŸ“č Enregistrer de Nouveaux Tests

MĂ©thode 1 : Playwright Codegen (⭐ RecommandĂ©)

Enregistrement interactif - Playwright génÚre le code automatiquement !

# Assurer que l'app tourne
make up

# Lancer l'enregistrement
cd frontend
npm run codegen

# OU pour mobile
npm run codegen:mobile

# Alternative depuis la racine
make codegen
make codegen DEVICE=mobile

Ce qui se passe :

  1. Un navigateur s’ouvre sur http://localhost:8090

  2. Une fenĂȘtre “Playwright Inspector” s’ouvre Ă  cĂŽtĂ©

  3. Vous naviguez dans l’app (clic, remplissage de formulaires, etc.)

  4. Le code du test apparaĂźt en temps rĂ©el dans l’Inspector

  5. Vous copiez le code et le collez dans un fichier .spec.ts

Sauvegarder le test :

// frontend/tests/e2e/mon-test.spec.ts
import { test, expect } from '@playwright/test';

test('Mon scénario de test', async ({ page }) => {
  await page.goto('/login');
  await page.fill('input[type="email"]', 'test@test.com');
  await page.fill('input[type="password"]', 'test123');
  await page.click('button[type="submit"]');
  await expect(page.locator('text=Dashboard')).toBeVisible();
});

Lancer le test :

npm run test:e2e -- mon-test.spec.ts

La vidéo sera dans frontend/test-results/ !

MĂ©thode 2 : Écrire le Test Manuellement

Si vous préférez écrire le code directement :

# Créer le fichier
nano frontend/tests/e2e/mon-test.spec.ts

# Écrire le test (voir exemple ci-dessus)

# Lancer
npm run test:e2e -- mon-test.spec.ts

🎬 CrĂ©er des VidĂ©os Lisibles — la vitrine

Les vidĂ©os du gate sont enregistrĂ©es Ă  la vitesse des tests : elles servent au diagnostic d’un Ă©chec, pas Ă  raconter le produit. Pour une vidĂ©o qu’un humain suit, on passe par la vitrine, qui est un harnais sĂ©parĂ© :

make vitrine

Elle rejoue le parcours de rĂ©fĂ©rence en cadence, incruste la narration dans la page pendant l’enregistrement, et assemble une galerie autonome :

frontend/tests/e2e/journeys/vitrine/index.html

Ce qui se passe :

  1. enregistrer-vitrine.mjs rejoue tests/e2e/journeys/parcours.ts à raison d’une action par seconde (CADENCE_MS, dans scene.ts)

  2. Chaque Ă©tape est racontĂ©e Ă  l’écran, donc visible dans la vidĂ©o

  3. assembler-vitrine.mjs produit la galerie et les chapitres horodatés (videos/<parcours>.json), pour sauter à une étape au lieu de regarder le film en entier

  4. La CI publie le tout dans l’artefact vitrine (ci.yml, #873)

Changer la cadence :

VITRINE_CADENCE_MS=2000 make vitrine

Warning

Il a existĂ© jusqu’au 2026-09-12 un make test-e2e-slow qui modifiait les fichiers du gate pour y insĂ©rer des pauses, puis les restaurait. La cible, les deux scripts et leurs commandes n’existent plus : muter les specs du gate pour enregistrer confie au gate une responsabilitĂ© qui n’est pas la sienne, et la MĂ©thode Foyer le nomme comme l’anti-patron Ă  Ă©viter (#876). Le harnais de valeur ne touche Ă  aucun fichier du gate.

📚 Ajouter les VidĂ©os dans la Documentation

Une fois les tests exécutés en local, synchronisez et versionnez les vidéos :

# Copie les vidéos + génÚre la page RST automatiquement
make docs-sync-videos

# Générer la documentation Sphinx
make docs-sphinx

# Vérifier le rendu localement
open docs/_build/html/e2e-videos.html

Les fichiers copiĂ©s dans docs/_static/videos/ doivent ĂȘtre commitĂ©s dans Git. Ils seront ensuite publiĂ©s automatiquement par le workflow documentation (.github/workflows/docs.yml).

Le dossier docs/_build/ reste local (ignoré par Git) : ne le commitez pas.

Les vidĂ©os validĂ©es sont listĂ©es dans la page đŸŽ„ VidĂ©os Tests E2E (Documentation Vivante).

🎬 Commandes Disponibles

Commandes npm (depuis frontend/)

# Enregistrement interactif
npm run codegen              # Desktop
npm run codegen:mobile       # iPhone 13

# Tests
npm run test:e2e             # Tous les tests (headless)
PLAYWRIGHT_BASE_URL=http://localhost:8090 npm run test:e2e -- AdminDashBoard.improved.spec.ts  # Suite admin
npm run test:e2e -- mon-test.spec.ts  # Un test spécifique
npm run test:e2e:ui          # Mode UI (interface graphique)
npm run test:e2e:headed      # Voir le navigateur
npm run test:e2e:debug       # Mode debug pas Ă  pas

# Rapports
npm run test:e2e:report      # Ouvre le rapport HTML avec vidéos

Commandes make (depuis la racine)

# Tests E2E
make test-e2e                # Le gate, Ă  la vitesse
make vitrine                 # La preuve de valeur : parcours narré + galerie

# Documentation
make docs-sync-videos        # Copier vidéos + générer RST (local)
make docs-with-videos        # Helper local pour générer vidéos + doc
make docs-sphinx             # Générer doc Sphinx seule
make codegen                 # Playwright codegen (DEVICE=mobile pour iPhone 13)

Les cibles make test-e2e et make docs-with-videos exportent PLAYWRIGHT_BASE_URL=$(RECETTE), dont le dĂ©faut est http://localhost:8090 — la pile de recette, jamais la dĂ©mo. Pour viser ailleurs, surchargez la variable plutĂŽt que d’éditer la cible :

make test-e2e RECETTE=http://localhost:3000

Danger

Ne visez jamais ``http://localhost`` sur un hÎte qui porte la démo.

Le port 80 y est tenu par le Traefik de la dĂ©mo, qui route vers Host(api.koprogo.com). La suite e2e Ă©crit — 480 appels POST/PUT/ DELETE — et make seed-reset POSTe sur /seed/scenario/world pendant que make reset-db annonce « SUPPRIME TOUTES LES DONNÉES ».

Ce n’est pas un risque Ă  entourer de prĂ©cautions : la prĂ©condition de reproductibilitĂ© de la recette est exactement ce qui dĂ©truirait la dĂ©mo. S’y ajoutent 130 connexions sur /auth/login contre une limite de 5/minute, qui ont dĂ©jĂ  valu un bannissement CrowdSec de l’adresse source le 2026-09-01.

C’est l’objet de l’ADR 0050 et de l’issue #872.

Viser un hĂŽte distant : deux variables obligatoires

Contre localhost, rien Ă  faire : la campagne amorce sa propre base et le mot de passe de repli du seed (admin123) y est lĂ©gitime. C’est aussi le cas en CI.

Contre un hĂŽte que la campagne n’amorce pas — la dĂ©mo, une prĂ©production — le superadministrateur est créé par seed_superadmin, qui fait un upsert Ă  chaque dĂ©marrage depuis l’environnement de cet hĂŽte. Ce que vaut le repli dĂ©pend donc de ce que l’exploitant a posĂ© lĂ -bas, et vous ne pouvez pas le deviner : sans choix explicite, la connexion rend 401 Invalid credentials, un message qui parle d’identifiants lĂ  oĂč le dĂ©faut est de configuration.

export KOPROGO_SUPERADMIN_EMAIL=...
export KOPROGO_SUPERADMIN_PASSWORD=...
export KOPROGO_CONFIRME_HOTE_DISTANT=1
PLAYWRIGHT_BASE_URL=https://koprogo.com npm run test:e2e

Sans les deux premiĂšres variables, la suite s’arrĂȘte avant la premiĂšre requĂȘte et nomme celle qui manque (tests/e2e/helpers/identifiants.ts). C’est dĂ©libĂ©rĂ© : le 2026-09-10, le 401 muet a coĂ»tĂ© une demi-journĂ©e d’enquĂȘte sur un dĂ©faut produit qui n’existait pas (#870).

Le garde regarde si la variable est posĂ©e, pas ce qu’elle contient. Choisir une valeur faible en connaissance de cause est une dĂ©cision d’exploitation, et le serveur l’avertit dĂ©jĂ  de son cĂŽtĂ© au dĂ©marrage. Ce qu’il refuse, c’est de partir vers un hĂŽte distant sans que personne n’ait choisi.

Danger

La troisiĂšme variable existe pour le cas inverse et plus dangereux (#872) : un identifiant qui FONCTIONNE. Elle se nomme KOPROGO_CONFIRME_HOTE_DISTANT.

Un mot de passe correct contre un hĂŽte distant ne rend aucun 401 — il laisse la campagne enchaĂźner ses Ă©critures, son seed-reset, son reset-db, sur des donnĂ©es vivantes. Le garde ne peut pas savoir si le mot de passe est correct sans l’essayer, et l’essayer est justement l’action qu’il doit empĂȘcher. Il exige donc une confirmation sĂ©parĂ©e de l’identifiant, que ce dernier soit correct ou non.

Warning

Corriger le mot de passe en base ne tient pas : l’upsert repart de l’environnement au prochain dĂ©marrage et l’écrase. C’est KOPROGO_SUPERADMIN_PASSWORD de l’hĂŽte qu’il faut changer, pas la ligne.

Note

Sur la dĂ©mo (api.koprogo.com), la variable porte admin123 depuis le 2026-09-12, par dĂ©cision d’exploitation : le mot de passe survit dĂ©sormais aux redĂ©ploiements, au prix d’ĂȘtre celui que le dĂ©pĂŽt publie. Le backend l’annonce Ă  chaque dĂ©marrage — « SÉCURITÉ : le superadmin utilise le mot de passe par dĂ©faut, lisible dans le dĂ©pĂŽt public ».

Note

Une campagne complĂšte contre la production a dĂ©jĂ  provoquĂ© un bannissement CrowdSec de l’adresse source. Une exception existe depuis le 2026-09-02, mais /api/v1/auth/login reste limitĂ© Ă  5 requĂȘtes par minute et par adresse : commencer par un parcours Ă©troit, pas par la suite entiĂšre.

📂 Structure des Fichiers

Tests E2E

frontend/tests/e2e/
├── config.ts                        # Helper pour construire les endpoints
└── AdminDashBoard.improved.spec.ts  # Suite complùte admin (org/users/buildings)

VidĂ©os GĂ©nĂ©rĂ©es

frontend/test-results/
├── admin-dashboard-tour-test-chromium/
│   ├── video.webm              # ← VidĂ©o du test
│   ├── trace.zip               # Trace Playwright
│   └── test-failed-1.png       # (si Ă©chec)
└── autre-test-chromium/
    └── video.webm

Documentation VidĂ©os

docs/_static/videos/
├── admin-dashboard-tour.webm
├── login-success.webm
└── *.webm                      # Toutes vos vidĂ©os

docs/e2e-videos.rst             # Page auto-générée

⚙ Configuration Playwright

Le fichier frontend/playwright.config.ts configure :

  • Enregistrement vidĂ©o : video: { mode: 'on', size: { width: 1280, height: 720 } }

  • Base URL : baseURL: 'http://localhost:3000'

  • WebServer : DĂ©marre automatiquement npm run dev

  • Timeouts : 10s par action, 30s par page

  • Screenshots : Uniquement en cas d’échec

🐛 Debugging

Mode UI (RecommandĂ©)

cd frontend
npm run test:e2e:ui

Cela ouvre une interface graphique oĂč vous pouvez :

  • ✅ Voir tous vos tests

  • ✅ Les lancer un par un

  • ✅ Voir les vidĂ©os/screenshots

  • ✅ Inspecter chaque Ă©tape

  • ✅ Voir les timings

Mode Debug

npm run test:e2e:debug

Le test s’arrĂȘte Ă  chaque Ă©tape, vous pouvez :

  • Inspecter le DOM

  • ExĂ©cuter du code dans la console

  • Avancer pas Ă  pas

Mode Headed (Voir le navigateur)

npm run test:e2e:headed

Le navigateur s’affiche pendant l’exĂ©cution des tests.

🆘 ProblĂšmes Courants

❌ Les navigateurs ne s’installent pas

# Sans dépendances systÚme (si pas de sudo)
npx playwright install chromium

# Avec dépendances (si sudo disponible)
npx playwright install chromium --with-deps

❌ L’app n’est pas accessible

# VĂ©rifier que les services tournent — sur le port de la RECETTE.
# Un curl sur le port 80 nu interrogerait la démo et répondrait 200,
# ce qui ferait croire que votre pile tourne alors qu'elle est éteinte.
curl http://localhost:8090
curl http://localhost:8090/api/v1/health

# Si pas de réponse, démarrer :
make up

❌ Timeout lors des tests

Augmentez les timeouts dans playwright.config.ts :

use: {
  actionTimeout: 20000,        // 20s au lieu de 10s
  navigationTimeout: 60000,    // 60s au lieu de 30s
}

❌ Les vidĂ©os ne sont pas gĂ©nĂ©rĂ©es

Vérifiez dans playwright.config.ts :

video: {
  mode: 'on',  // Doit ĂȘtre 'on', pas 'retain-on-failure'
}

❌ “Target page has been closed”

Votre app redirige trop vite. Ajoutez des attentes :

await page.click('button');
await page.waitForURL('/dashboard');

📊 Best Practices

  1. Noms de tests explicites

    // ✅ Bon
    test('Login admin et navigation vers dashboard organisations', ...)
    
    // ❌ Mauvais
    test('test', ...)
    
  2. Utiliser les rĂŽles ARIA

    // ✅ Bon (plus robuste)
    await page.getByRole('button', { name: 'Se connecter' }).click();
    
    // ❌ Éviter (fragile)
    await page.click('.btn-login');
    
  3. Attentes explicites

    // ✅ Bon
    await expect(page.getByText('Dashboard')).toBeVisible();
    
    // ❌ Éviter
    await page.waitForTimeout(5000);
    
  4. One test, one scenario

    Chaque test doit tester UN scénario utilisateur complet.

  5. Vidéos lisibles

    Utilisez make test-e2e-slow pour créer des vidéos de documentation.

🔗 IntĂ©gration CI/CD

Les vidĂ©os ne sont plus gĂ©nĂ©rĂ©es dans la CI : elles doivent provenir d’un run local fiable, puis ĂȘtre ajoutĂ©es au dĂ©pĂŽt. Le workflow .github/workflows/docs.yml se charge ensuite de publier la documentation Sphinx (et toutes les vidĂ©os dĂ©jĂ  prĂ©sentes dans docs/_static/videos/) vers GitHub Pages.

📚 Ressources


đŸ€– Guide maintenu avec Claude Code

KoproGo ASBL - Tests E2E et Documentation Vivante

Multi-rĂŽles E2E (post-FE1 cookie HttpOnly)

Depuis WP-FE1 (PR #543), l’authentification Playwright n’injecte plus de token dans localStorage. Le flow est alignĂ© prod : cookie HttpOnly rĂ©el posĂ© par le backend + silent-refresh Ă  la navigation.

Helper injectAuth (chokepoint unique)

frontend/tests/e2e/helpers/auth.ts :

  1. L’appelant fait un page.request.post(/auth/register) rĂ©el (ou /auth/login) — le cookie Set-Cookie: koprogo_refresh est stockĂ© dans le cookie jar partagĂ© avec le contexte navigateur.

  2. injectAuth pose koprogo_user via page.addInitScript (cache d’affichage non sensible, avant tout script de page).

  3. UNE seule navigation dashboard → authStore.init() → silent-refresh via le cookie HttpOnly → access token mĂ©moire frais.

Anti-course de rotation : on Ă©vite goto("/login") prĂ©alable (LoginForm dĂ©clencherait son propre authStore.init() ⇒ un 1er refresh qui rote le cookie, puis un 2e refresh au goto dashboard avec le cookie rĂ©voquĂ© → 401). Une seule navigation = un seul refresh.

PrĂ©-requis env (E2E sur http://localhost:8090)

  • COOKIE_SECURE=false cĂŽtĂ© backend (sinon le navigateur rejette le cookie hors HTTPS). Cf. docker-compose.yml et .env.example (dĂ©faut prod = true).

  • CORS supports_credentials() activĂ©, origines explicites (jamais * — validate_cors_origins le rejette).

  • Job CI Playwright : COOKIE_SECURE: "false" dans .github/workflows/ci.yml step “Build and start backend”.

Helpers existants (rĂ©utiliser, ne pas dupliquer)

  • loginAsSyndic(page, prefix) — admin login, crĂ©e org, register syndic, injectAuth syndic. Retourne { token, adminToken, orgId, email, userId }.

  • loginAsSyndicWithBuilding / loginAsSyndicWithUnit / loginAsSyndicWithMeeting / loginAsSyndicWithExpense / loginAsSyndicWithOwner / loginAsSyndicWithLinkedOwner — composent au-dessus en crĂ©ant les ressources via page.request.

  • loginAsAdmin(page) — variante superadmin.

Pattern silent-refresh single-flight (frontend)

(Voir aussi docs/backend/JWT_REFRESH_TOKENS.md §”Amendment 2026-05-19”.)

frontend/src/stores/auth.ts coalesce les appels concurrents à refreshAccessToken() via une promesse partagée au scope du module :

let inflightRefresh: Promise<boolean> | null = null;

refreshAccessToken: async (): Promise<boolean> => {
  if (inflightRefresh) return inflightRefresh;  // dedup
  inflightRefresh = doRefresh();
  try { return await inflightRefresh; }
  finally { inflightRefresh = null; }
}

Pourquoi c’est critique : RouteGuard.svelte et Navigation.svelte s’hydratent comme deux Ăźlots Astro client:load parallĂšles ; sans dedup, deux POST /auth/refresh concurrents rĂ©utilisent le mĂȘme cookie → la rotation backend en rĂ©voque un → 401 → clearSession() → dĂ©connexion. C’est un bug prod rĂ©el (#550), pas seulement de test.

Spec Playwright de rĂ©fĂ©rence (4-cat)

frontend/tests/e2e/smoke/AuthCookie.spec.ts :

  • @security access token absent de localStorage + cookie illisible document.cookie (HttpOnly).

  • @edge attributs du cookie (HttpOnly, SameSite=Strict, Path, Secure).

  • @happy reload conserve la session via silent-refresh cookie.

  • @negative sans cookie → redirige /login.

À utiliser comme modĂšle pour toute nouvelle spec testant un flow authentifiĂ©.