Files
siop2/apps/web/e2e/parcours-r1.spec.ts
pr-daaif 9778629827 feat(r1.2): écrans web du référentiel — 7 écrans fidèles à maquette-r1
- Sites : liste + carte Leaflet/OSM réelle (pins maquette, positions
  PostGIS), création en modale avec position posée au clic sur la carte
- Fiche site : arbre site→zones, appareils rattachés, ajout de zone
- Ascenseurs : table dense, filtres site/statut, strie rouge à l'arrêt ;
  fiche appareil (organes ± ajout/retrait, statut, QR réel) ; création
  identité → rattachement → organes ; étiquette A6 imprimable (objet
  papier, window.print n'imprime qu'elle)
- Personnes & équipes : invitation → lien d'activation affiché à copier
  (pas d'email en R1, décision explicite), renvoi de lien, équipes ;
  page /activation (choix du mot de passe, connexion directe)
- Catégories : ajout, renommage inline, désactivation
- navigation et actions pilotées par la matrice (usePermissions) —
  l'API re-vérifie chaque requête
- e2e Playwright : recette R1 officielle rejouée intégralement (site →
  zone → appareil+organe → étiquette → invitation → activation) +
  parcours « la matrice pilote l'UI » ; purge idempotente en globalSetup
- alias Vite @siop/shared → source TS (exports nommés CJS ↯ workspace lié)

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

97 lines
5.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, test } from '@playwright/test';
/**
* Recette R1 (plan de releases) : créer un site → un ascenseur → ses organes
* → imprimer l'étiquette QR → inviter un technicien (activation incluse).
*/
const suffix = Date.now().toString(36);
test('recette R1 complète, de la création du site à lactivation de linvité', async ({
page,
}) => {
// Connexion démo (Administrateur — création tous azimuts)
await page.goto('/connexion');
await page.getByRole('button', { name: /Yasmine Alaoui/ }).click();
await expect(page.getByRole('heading', { name: 'Tableau de bord' })).toBeVisible();
// 1 · Sites : le parc seedé est là, on crée un site
await page.getByRole('link', { name: 'Sites' }).click();
await expect(page.getByRole('cell', { name: /Tour Atlas/ })).toBeVisible();
await page.getByRole('button', { name: '+ Nouveau site' }).click();
await page.getByLabel('Nom du site *').fill(`Site E2E ${suffix}`);
await page.getByLabel('Ville').fill('Casablanca');
await page.getByRole('button', { name: 'Créer le site' }).click();
// → fiche du site créé ; on lui ajoute une zone
await expect(page.getByRole('heading', { name: `Site E2E ${suffix}` })).toBeVisible();
await page.getByRole('button', { name: '+ Ajouter un emplacement' }).click();
await page.getByLabel('Nom de la zone *').fill(`Hall E2E ${suffix}`);
await page.getByRole('button', { name: 'Ajouter', exact: true }).click();
await expect(page.locator('.arbre')).toContainText(`Hall E2E ${suffix}`);
// 2 · Nouvel ascenseur avec un organe
await page.getByRole('button', { name: '+ Nouvel ascenseur' }).click();
await page.getByLabel('Référence *').fill(`E2E-${suffix}`);
await page.getByLabel('Marque *').fill('Otis');
await page.getByLabel('Catégorie *').selectOption({ label: 'Ascenseur électrique' });
await page.getByLabel('Emplacement dans le site *').selectOption({ label: `Hall E2E ${suffix}` });
await page.getByLabel("Type de l'organe 1").selectOption({ label: 'Portes cabine / palières' });
await page.getByLabel("Désignation de l'organe 1").fill('Fermator 40/10');
await page.getByRole('button', { name: "Créer l'ascenseur (QR généré)" }).click();
// → fiche appareil : identité, organe, QR
await expect(
page.getByRole('heading', { name: new RegExp(`Ascenseur E2E-${suffix}`) }),
).toBeVisible();
await expect(page.getByText('Portes cabine / palières')).toBeVisible();
// 3 · Étiquette QR imprimable
await page.getByRole('button', { name: 'Étiquette QR' }).click();
await expect(page.locator('.etiquette')).toContainText(`Ascenseur E2E-${suffix}`);
await expect(page.locator('.etiquette .consigne')).toContainText('Scannez ce code');
// 4 · Inviter un technicien → récupérer le lien d'activation
await page.getByRole('link', { name: 'Personnes' }).click();
await page.getByRole('button', { name: '+ Inviter' }).click();
await page.getByLabel('Nom complet *').fill('Recrue E2E');
await page.getByLabel('Email professionnel *').fill(`recrue-${suffix}@spelev.ma`);
await page.getByLabel('Rôle *').selectOption({ label: 'Technicien' });
await page.getByRole('button', { name: "Envoyer l'invitation" }).click();
const lien = await page.getByTestId('lien-activation').textContent();
expect(lien).toContain('/activation?token=');
await page.getByRole('button', { name: 'Fermer' }).click();
await expect(page.getByRole('cell', { name: /Recrue E2E/ })).toBeVisible();
await expect(page.getByText('Invitation envoyée')).toBeVisible();
// 5 · La recrue active son compte via le lien et entre directement
await page.locator('.compte').click();
await page.getByRole('menuitem', { name: 'Se déconnecter' }).click();
await page.goto(lien!);
await page.getByLabel('Mot de passe (8 caractères minimum)').fill('MotDePasse!123');
await page.getByLabel('Confirmez le mot de passe').fill('MotDePasse!123');
await page.getByRole('button', { name: 'Activer mon compte' }).click();
await expect(page.getByRole('heading', { name: 'Tableau de bord' })).toBeVisible();
await expect(page.locator('.topbar')).toContainText('Recrue E2E');
await expect(page.locator('.topbar')).toContainText('Technicien');
});
test('la matrice pilote lUI : un technicien voit le parc sans boutons de création', async ({
page,
}) => {
await page.goto('/connexion');
await page.getByRole('button', { name: /Ahmed Benali/ }).click();
await expect(page.getByRole('heading', { name: 'Tableau de bord' })).toBeVisible();
// Catégories (SETTINGS) absent de sa sidebar ; Ascenseurs accessible
await expect(page.locator('.nav')).not.toContainText('Catégories');
await page.getByRole('link', { name: 'Ascenseurs' }).click();
await expect(page.getByRole('cell', { name: 'A1', exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '+ Nouvel ascenseur' })).toHaveCount(0);
// Fiche : statuts visibles, pas de retrait d'organe
await page.getByRole('cell', { name: 'A1', exact: true }).click();
await expect(page.getByText('Treuil / machinerie')).toBeVisible();
await expect(page.getByRole('button', { name: /Retirer/ })).toHaveCount(0);
});