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.
142 lines
5.1 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|