Files
siop2/apps/web/e2e/parcours-r2.spec.ts
pr-daaif 6a2dae1710 feat(r2.3): écrans web exploitation — OT, demandes, préventif, compteurs, urgence
- liste OT (filtres, strie rouge, « immédiat ») ; fiche OT : transitions
  pilotées par allowedTransitions, clôture grisée avec la garde expliquée,
  bilan codé (6 selects sur référentiels), checklist Fait→N-A→à faire,
  activité + commentaires, assignation, annulation motivée
- nouvel OT : interrupteur « personne bloquée » qui force la priorité
- demandes : table + panneau d'approbation (priorité, assignation,
  approuver → fiche OT), rejet en modale à motif obligatoire, signalement
  interne ; statut « Résolue » dérivé de l'OT lié
- préventif : tuiles réelles, générer + résumé (« regénérer ne double
  rien »), gabarits administrables ; compteurs : saisie + historique
- tableau de bord réel : bandeau urgence cliquable, KPIs, OT par statut,
  interventions récentes ; accueil dédié aux rôles sans exploitation
- urgence traversante : chip topbar pulsante (60 s), badges de nav
- GET /assets/options (auth seule) : le Demandeur peut désigner l'appareil
  qu'il signale — trou débusqué par l'e2e (49 opérations au contrat)
- génération préventive durcie : collision de référence RETENTÉE (plus de
  saut silencieux), P2003 toléré ; 3 runs Jest complets consécutifs verts
- 9 tests Playwright (recette R2 officielle rejouée intégralement),
  50 tests API (94,6 % / 78,9 %)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:13:20 +01:00

118 lines
6.1 KiB
TypeScript

import { expect, test } from '@playwright/test';
/**
* Recette R2 (plan de releases) : demande gardien → approbation → OT →
* intervention → bilan codé → clôture → suivi demandeur ; génération du
* préventif du mois (idempotente) ; compteur croissant.
*/
const suffix = Date.now().toString(36);
test('recette R2 : demande → OT → bilan codé → clôture → suivi demandeur', async ({
page,
}) => {
// 1 · Karim (demandeur) signale depuis l'app
await page.goto('/connexion');
await page.getByRole('button', { name: /Karim Doukkali/ }).click();
await expect(page.getByRole('heading', { name: 'Mes signalements' })).toBeVisible();
await page.getByRole('link', { name: /Demandes/ }).click();
await page.getByRole('button', { name: '+ Signaler' }).click();
await page.getByLabel('Équipement *').selectOption({ index: 1 });
await page.getByLabel('Décrivez le problème *').fill(`E2E ${suffix} — porte qui claque très fort`);
await page.getByRole('button', { name: 'Envoyer le signalement' }).click();
await expect(page.getByRole('cell', { name: new RegExp(suffix) })).toBeVisible();
// 2 · Salma (dispatcher) approuve → OT créé, assigné à Ahmed
await page.locator('.compte').click();
await page.getByRole('menuitem', { name: /Salma Idrissi/ }).click();
await page.getByRole('link', { name: /Demandes/ }).click();
await page.getByRole('cell', { name: new RegExp(suffix) }).click();
await page.getByLabel('Assigner à').selectOption({ index: 1 }); // Ahmed Benali
await page.getByRole('button', { name: "Approuver → créer l'OT" }).click();
// → fiche OT
await expect(page.getByRole('heading', { name: new RegExp(suffix) })).toBeVisible();
await expect(page.locator('.entete-page .st')).toHaveText('Ouvert');
// 3 · Démarrer, tenter de clôturer (bloqué : bilan), remplir le bilan, clôturer
await page.getByRole('button', { name: 'Démarrer' }).click();
await expect(page.locator('.entete-page .st')).toHaveText('En cours');
const boutonCloture = page.getByRole('button', { name: "Clôturer l'intervention" });
await expect(boutonCloture).toBeDisabled(); // garde visible dans l'UI
await expect(page.locator('.avertissement')).toContainText('bilan incomplet');
await page.getByLabel(/État des portes/).selectOption({ label: 'Fonctionnement normal' });
await page.getByLabel(/Action réalisée/).selectOption({ label: 'Réglage' });
await page.getByLabel(/Élément concerné/).selectOption({ label: 'Portes' });
await expect(boutonCloture).toBeEnabled();
await boutonCloture.click();
await expect(page.locator('.entete-page .st')).toHaveText('Terminé');
// 4 · Karim suit : sa demande est Résolue
await page.locator('.compte').click();
await page.getByRole('menuitem', { name: /Karim Doukkali/ }).click();
await page.getByRole('link', { name: /Demandes/ }).click();
const ligne = page.locator('tr', { hasText: suffix });
await expect(ligne.locator('.st')).toHaveText('Résolue');
});
test('préventif : générer le mois — puis regénérer ne double rien', async ({ page }) => {
await page.goto('/connexion');
await page.getByRole('button', { name: /Yasmine Alaoui/ }).click();
await page.getByRole('link', { name: 'Préventif' }).click();
await expect(page.getByRole('heading', { name: 'Préventif' })).toBeVisible();
await expect(page.getByText('Essai du parachute')).toBeVisible();
await page.getByRole('button', { name: /Générer la grille de/ }).click();
await expect(page.locator('.gen-info')).toContainText('regénérer ne double rien');
// Second appel : 0 créé
await page.getByRole('button', { name: /Générer la grille de/ }).click();
await expect(page.locator('.gen-info')).toContainText('0 OT créés');
});
test('urgence « personne bloquée » : interrupteur → chip topbar + bandeau + tête de liste', async ({
page,
}) => {
await page.goto('/connexion');
await page.getByRole('button', { name: /Salma Idrissi/ }).click();
await page.getByRole('link', { name: /Ordres de travail/ }).click();
await page.getByRole('button', { name: '+ Nouvel OT' }).click();
await page.getByLabel('Objet *').fill(`E2E ${suffix} — personne bloquée cage B`);
await page.getByLabel('Équipement *').selectOption({ index: 1 });
await page.getByRole('switch', { name: 'Personne bloquée' }).click();
await page.getByRole('button', { name: "Créer l'OT (statut Ouvert)" }).click();
await expect(page.getByRole('heading', { name: new RegExp(suffix) })).toBeVisible();
// chip topbar (pulse) présent et bandeau sur le tableau de bord
await expect(page.locator('.topbar .chip-urgence')).toContainText('personne');
await page.getByRole('link', { name: 'Tableau de bord' }).click();
await expect(page.locator('.bandeau-urgence').first()).toBeVisible();
// tête de liste OT
await page.getByRole('link', { name: /Ordres de travail/ }).click();
await expect(page.locator('tbody tr').first()).toHaveClass(/ligne-bloque/);
// nettoyage : annuler l'OT d'urgence créé (motif via prompt)
await page.locator('tbody tr').first().click();
page.once('dialog', (d) => d.accept('Test e2e — nettoyage'));
await page.getByRole('button', { name: 'Annuler', exact: true }).click();
await expect(page.locator('.entete-page .st')).toHaveText('Annulé');
});
test('compteurs : relevé croissant accepté, régression refusée', async ({ page }) => {
await page.goto('/connexion');
await page.getByRole('button', { name: /Ahmed Benali/ }).click();
await page.getByRole('link', { name: 'Ascenseurs' }).click();
await page.getByRole('cell', { name: 'A1', exact: true }).click();
await page.getByRole('button', { name: 'Compteurs' }).click();
await expect(page.getByRole('heading', { name: /Compteurs — Ascenseur A1/ })).toBeVisible();
const saisie = page.getByLabel('Nouveau relevé — Heures de marche');
await saisie.fill('99999999');
await page.getByRole('button', { name: 'Enregistrer le relevé' }).first().click();
await expect(page.locator('.chrono').first()).toContainText('99 999 999');
await saisie.fill('12');
await page.getByRole('button', { name: 'Enregistrer le relevé' }).first().click();
await expect(page.locator('.erreur-form')).toContainText('ne redescend pas');
});