test(r0.12): ESLint monorepo + parcours démo Playwright — R0.12 clos

- ESLint 10 flat config racine (typescript-eslint, react-hooks) ; règle
  ADR-001 codée : no-restricted-imports interdit « minio » partout sauf
  minio-storage.service.ts (violation vérifiée) ; jobs lint par paquet
- Playwright apps/web/e2e : redirection sans jeton, connexion démo 1 clic,
  coquille (rail actif, chip DÉMO), bascule de rôle chronométrée < 3 s
  (ADR-002), /design, déconnexion avec purge ; webServer api dist + vite,
  seed idempotent en globalSetup, PW_CHANNEL=chrome en local
- CI : jobs lint + e2e (services PostgreSQL/Redis, artefact
  playwright-report sur échec) ; vitest restreint à src/

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-15 22:36:17 +01:00
parent a9e51507aa
commit 36de93bb7e
15 changed files with 968 additions and 56 deletions

View File

@@ -0,0 +1,65 @@
import { expect, test } from '@playwright/test';
/**
* LE parcours de recette R0 : connexion démo 1 clic, coquille fidèle,
* bascule de rôle < 3 s (critère d'acceptation ADR-002), /design accessible.
*/
test('sans jeton, lapplication renvoie sur la connexion (fermée par défaut)', async ({
page,
}) => {
await page.goto('/');
await expect(page).toHaveURL(/\/connexion$/);
});
test('connexion démo → coquille → bascule de rôle < 3 s → /design', async ({
page,
}) => {
await page.goto('/connexion');
// Mode démonstration : 7 comptes seedés, chip d'avertissement
await expect(page.locator('.sep')).toHaveText('Mode démonstration');
await expect(page.locator('.demo-liste button')).toHaveCount(7);
await expect(page.locator('.chip-demo')).toContainText('DEMO_MODE=true');
// Connexion 1 clic (Dispatcher)
await page.getByRole('button', { name: /Salma Idrissi/ }).click();
await expect(
page.getByRole('heading', { name: 'Tableau de bord' }),
).toBeVisible();
await expect(page.locator('.topbar')).toContainText('Salma Idrissi');
await expect(page.locator('.topbar .chip-demo')).toHaveText('DÉMO');
// Rail safran : l'entrée active de la sidebar
await expect(page.locator('.nav a.actif')).toHaveText(/Tableau de bord/);
// Bascule de rôle via la topbar — critère ADR-002 : < 3 s
const debut = Date.now();
await page.locator('.compte').click();
await page.getByRole('menuitem', { name: /Ahmed Benali/ }).click();
await expect(page.locator('.topbar')).toContainText('Ahmed Benali');
await expect(page.locator('.topbar')).toContainText('Technicien');
expect(Date.now() - debut).toBeLessThan(3000);
// Référence vivante du design
await page.goto('/design');
await expect(
page.getByRole('heading', { name: /Design — référence vivante/ }),
).toBeVisible();
await expect(page.locator('.puce-c').first()).toBeVisible();
});
test('la déconnexion ramène à la connexion et ferme la session', async ({
page,
}) => {
await page.goto('/connexion');
await page.getByRole('button', { name: /Yasmine Alaoui/ }).click();
await expect(page.locator('.topbar')).toContainText('Yasmine Alaoui');
await page.locator('.compte').click();
await page.getByRole('menuitem', { name: 'Se déconnecter' }).click();
await expect(page).toHaveURL(/\/connexion$/);
// Retour direct sur / : toujours redirigé (le jeton est bien purgé)
await page.goto('/');
await expect(page).toHaveURL(/\/connexion$/);
});