Files
Classeo/frontend/e2e/dashboard-responsive-nav.spec.ts
Mathias STRASSER b7dc27f2a5
Some checks failed
CI / Backend Tests (push) Has been cancelled
CI / Frontend Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Naming Conventions (push) Has been cancelled
CI / Build Check (push) Has been cancelled
feat: Calculer automatiquement les moyennes après chaque saisie de notes
Les enseignants ont besoin de moyennes à jour immédiatement après la
publication ou modification des notes, sans attendre un batch nocturne.

Le système recalcule via Domain Events synchrones : statistiques
d'évaluation (min/max/moyenne/médiane), moyennes matières pondérées
(normalisation /20), et moyenne générale par élève. Les résultats sont
stockés dans des tables dénormalisées avec cache Redis (TTL 5 min).

Trois endpoints API exposent les données avec contrôle d'accès par rôle.
Une commande console permet le backfill des données historiques au
déploiement.
2026-04-04 02:25:00 +02:00

142 lines
5.1 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const baseUrl = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:4173';
const urlMatch = baseUrl.match(/:(\d+)$/);
const PORT = urlMatch ? urlMatch[1] : '4173';
const ALPHA_URL = `http://ecole-alpha.classeo.local:${PORT}`;
const STUDENT_EMAIL = 'e2e-dash-nav-student@example.com';
const STUDENT_PASSWORD = 'DashNavStudent123';
const projectRoot = join(__dirname, '../..');
const composeFile = join(projectRoot, 'compose.yaml');
async function loginAsStudent(page: import('@playwright/test').Page) {
await page.goto(`${ALPHA_URL}/login`);
await page.locator('#email').fill(STUDENT_EMAIL);
await page.locator('#password').fill(STUDENT_PASSWORD);
await Promise.all([
page.waitForURL(/\/dashboard/, { timeout: 60000 }),
page.getByRole('button', { name: /se connecter/i }).click()
]);
}
test.describe('Dashboard Responsive Navigation', () => {
test.beforeAll(async () => {
execSync(
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${STUDENT_EMAIL} --password=${STUDENT_PASSWORD} --role=ROLE_ELEVE 2>&1`,
{ encoding: 'utf-8' }
);
});
// =========================================================================
// MOBILE (375x667)
// =========================================================================
test.describe('Mobile (375x667)', () => {
test.use({ viewport: { width: 375, height: 667 } });
test('shows hamburger button and hides desktop nav', async ({ page }) => {
await loginAsStudent(page);
const hamburger = page.getByRole('button', { name: /ouvrir le menu/i });
await expect(hamburger).toBeVisible({ timeout: 10000 });
const desktopNav = page.locator('.desktop-nav');
await expect(desktopNav).not.toBeVisible();
});
test('opens drawer via hamburger and shows nav links', async ({ page }) => {
await loginAsStudent(page);
await page.getByRole('button', { name: /ouvrir le menu/i }).click();
const drawer = page.locator('[role="dialog"][aria-modal="true"]');
await expect(drawer).toBeVisible();
// Should show navigation links
await expect(drawer.getByText('Tableau de bord')).toBeVisible();
await expect(drawer.getByText('Mon emploi du temps')).toBeVisible();
await expect(drawer.getByText('Paramètres')).toBeVisible();
});
test('closes drawer via close button', async ({ page }) => {
await loginAsStudent(page);
await page.getByRole('button', { name: /ouvrir le menu/i }).click();
const drawer = page.locator('[role="dialog"][aria-modal="true"]');
await expect(drawer).toBeVisible();
await page.getByRole('button', { name: /fermer le menu/i }).click();
await expect(drawer).not.toBeVisible();
});
test('closes drawer on overlay click', async ({ page }) => {
await loginAsStudent(page);
await page.getByRole('button', { name: /ouvrir le menu/i }).click();
const drawer = page.locator('[role="dialog"][aria-modal="true"]');
await expect(drawer).toBeVisible();
const overlay = page.locator('.mobile-overlay');
await overlay.click({ position: { x: 350, y: 300 } });
await expect(drawer).not.toBeVisible();
});
test('navigates via mobile drawer and closes it', async ({ page }) => {
await loginAsStudent(page);
await page.getByRole('button', { name: /ouvrir le menu/i }).click();
const drawer = page.locator('[role="dialog"][aria-modal="true"]');
await expect(drawer).toBeVisible();
await drawer.getByText('Mon emploi du temps').click();
await expect(drawer).not.toBeVisible();
await expect(page).toHaveURL(/\/dashboard\/schedule/);
});
test('shows logout button in drawer footer', async ({ page }) => {
await loginAsStudent(page);
await page.getByRole('button', { name: /ouvrir le menu/i }).click();
const drawer = page.locator('[role="dialog"][aria-modal="true"]');
await expect(drawer).toBeVisible();
const logoutButton = drawer.locator('.mobile-logout');
await expect(logoutButton).toBeVisible();
await expect(logoutButton).toHaveText(/déconnexion/i);
});
});
// =========================================================================
// DESKTOP (1280x800)
// =========================================================================
test.describe('Desktop (1280x800)', () => {
test.use({ viewport: { width: 1280, height: 800 } });
test('hides hamburger and shows desktop nav', async ({ page }) => {
await loginAsStudent(page);
const hamburger = page.getByRole('button', { name: /ouvrir le menu/i });
await expect(hamburger).not.toBeVisible();
const desktopNav = page.locator('.desktop-nav');
await expect(desktopNav).toBeVisible({ timeout: 10000 });
});
test('desktop nav shows schedule link for student', async ({ page }) => {
await loginAsStudent(page);
const desktopNav = page.locator('.desktop-nav');
await expect(desktopNav.getByText('Mon EDT')).toBeVisible({ timeout: 10000 });
await expect(desktopNav.getByText('Tableau de bord')).toBeVisible();
});
});
});