Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
587 lines
24 KiB
TypeScript
587 lines
24 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);
|
|
|
|
// Extract port from PLAYWRIGHT_BASE_URL or use default (4173 matches playwright.config.ts)
|
|
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}`;
|
|
|
|
// Test credentials for authenticated tests
|
|
const ADMIN_EMAIL = 'e2e-dashboard-admin@example.com';
|
|
const ADMIN_PASSWORD = 'DashboardTest123';
|
|
|
|
test.describe('Dashboard', () => {
|
|
/**
|
|
* Navigate to the dashboard and wait for SvelteKit hydration.
|
|
* SSR renders the HTML immediately, but event handlers are only
|
|
* attached after client-side hydration completes.
|
|
*/
|
|
async function goToDashboard(page: import('@playwright/test').Page) {
|
|
await page.goto('/dashboard', { waitUntil: 'networkidle' });
|
|
await expect(page.locator('.demo-controls')).toBeVisible({ timeout: 5000 });
|
|
}
|
|
|
|
/**
|
|
* Switch to a demo role with retry logic to handle hydration timing.
|
|
* Retries the click until the button's active class confirms the switch.
|
|
*/
|
|
async function switchToDemoRole(
|
|
page: import('@playwright/test').Page,
|
|
roleName: string | RegExp
|
|
) {
|
|
const button = page.locator('.demo-controls button', { hasText: roleName });
|
|
await expect(async () => {
|
|
await button.click();
|
|
await expect(button).toHaveClass(/active/, { timeout: 1000 });
|
|
}).toPass({ timeout: 10000 });
|
|
}
|
|
|
|
// ============================================================================
|
|
// Demo Mode (unauthenticated) - Role Switcher
|
|
// ============================================================================
|
|
test.describe('Demo Mode', () => {
|
|
test('shows demo role switcher when not authenticated', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
await expect(page).toHaveURL(/\/dashboard/);
|
|
await expect(page.getByText(/Démo - Changer de rôle/i)).toBeVisible();
|
|
});
|
|
|
|
test('page title is set correctly', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
await expect(page).toHaveTitle(/tableau de bord/i);
|
|
});
|
|
|
|
test('demo role switcher has all 4 role buttons', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
const demoControls = page.locator('.demo-controls');
|
|
await expect(demoControls).toBeVisible();
|
|
|
|
await expect(demoControls.getByRole('button', { name: 'Parent' })).toBeVisible();
|
|
await expect(demoControls.getByRole('button', { name: 'Enseignant' })).toBeVisible();
|
|
await expect(demoControls.getByRole('button', { name: /Élève/i })).toBeVisible();
|
|
await expect(demoControls.getByRole('button', { name: 'Admin' })).toBeVisible();
|
|
});
|
|
|
|
test('Parent role is selected by default', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
const parentButton = page.locator('.demo-controls button', { hasText: 'Parent' });
|
|
await expect(parentButton).toHaveClass(/active/);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Parent Dashboard View
|
|
// ============================================================================
|
|
test.describe('Parent Dashboard', () => {
|
|
test('shows Score Serenite card', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Parent is the default demo role
|
|
await expect(page.getByText(/score sérénité/i).first()).toBeVisible();
|
|
});
|
|
|
|
test('shows serenity score with numeric value', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// The score card should display a number value
|
|
const scoreCard = page.locator('.serenity-card');
|
|
await expect(scoreCard).toBeVisible();
|
|
|
|
// Should have a numeric value followed by /100
|
|
await expect(scoreCard.locator('.value')).toBeVisible();
|
|
await expect(scoreCard.getByText('/100')).toBeVisible();
|
|
});
|
|
|
|
test('serenity score shows demo badge', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
await expect(page.getByText(/données de démonstration/i)).toBeVisible();
|
|
});
|
|
|
|
test('shows placeholder sections for schedule, notes, and homework', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// These sections show as placeholders since hasRealData is false
|
|
await expect(page.getByRole('heading', { name: /emploi du temps/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /notes récentes/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /devoirs à venir/i })).toBeVisible();
|
|
});
|
|
|
|
test('placeholder sections show informative messages', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
await expect(page.getByText(/l'emploi du temps sera disponible/i)).toBeVisible();
|
|
await expect(page.getByText(/les notes apparaîtront ici/i)).toBeVisible();
|
|
await expect(page.getByText(/les devoirs seront affichés ici/i)).toBeVisible();
|
|
});
|
|
|
|
test('onboarding banner is visible on first login', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// The onboarding banner should be visible (isFirstLogin=true initially)
|
|
await expect(page.getByText(/bienvenue sur classeo/i)).toBeVisible();
|
|
await expect(page.getByText(/score sérénité/i).first()).toBeVisible();
|
|
});
|
|
|
|
test('clicking serenity score opens explainer', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Click the serenity score card
|
|
const scoreCard = page.locator('.serenity-card');
|
|
await expect(scoreCard).toBeVisible();
|
|
await scoreCard.click();
|
|
|
|
// The explainer modal/overlay should appear
|
|
// SerenityScoreExplainer should be visible after click
|
|
await expect(page.getByText(/cliquez pour en savoir plus/i)).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Teacher Dashboard View
|
|
// ============================================================================
|
|
test.describe('Teacher Dashboard', () => {
|
|
test('shows teacher dashboard header', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Switch to teacher
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
await expect(page.getByRole('heading', { name: /tableau de bord enseignant/i })).toBeVisible();
|
|
await expect(page.getByText(/bienvenue.*voici vos outils du jour/i)).toBeVisible();
|
|
});
|
|
|
|
test('shows quick action cards', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
await expect(page.getByText(/faire l'appel/i)).toBeVisible();
|
|
await expect(page.getByText(/saisir des notes/i)).toBeVisible();
|
|
await expect(page.getByText(/créer un devoir/i)).toBeVisible();
|
|
});
|
|
|
|
test('quick action cards are disabled in demo mode', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
// First two action cards should be disabled since hasRealData=false
|
|
// "Créer un devoir" navigates to homework page and is always enabled
|
|
const actionCards = page.locator('.action-card');
|
|
const count = await actionCards.count();
|
|
expect(count).toBeGreaterThanOrEqual(3);
|
|
|
|
await expect(actionCards.nth(0)).toBeDisabled();
|
|
await expect(actionCards.nth(1)).toBeDisabled();
|
|
await expect(actionCards.nth(2)).toBeEnabled();
|
|
});
|
|
|
|
test('shows placeholder sections for teacher data', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
await expect(page.getByRole('heading', { name: /mes classes aujourd'hui/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /notes à saisir/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /appels du jour/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /statistiques rapides/i })).toBeVisible();
|
|
});
|
|
|
|
test('placeholder sections have informative messages', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
await expect(page.getByText(/vos classes apparaîtront ici/i)).toBeVisible();
|
|
await expect(page.getByText(/évaluations en attente de notation/i)).toBeVisible();
|
|
await expect(page.getByText(/les appels à effectuer/i)).toBeVisible();
|
|
await expect(page.getByText(/les statistiques apparaîtront après la publication de notes/i)).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Student Dashboard View
|
|
// ============================================================================
|
|
test.describe('Student Dashboard', () => {
|
|
test('shows student dashboard header', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Switch to student
|
|
await switchToDemoRole(page, /Élève/i);
|
|
|
|
await expect(page.getByRole('heading', { name: /mon espace/i })).toBeVisible();
|
|
// Student is minor by default, so "ton" instead of "votre"
|
|
await expect(page.getByText(/bienvenue.*voici ton tableau de bord/i)).toBeVisible();
|
|
});
|
|
|
|
test('shows info banner for student in demo mode', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, /Élève/i);
|
|
|
|
await expect(page.getByText(/ton emploi du temps, tes notes et tes devoirs/i)).toBeVisible();
|
|
});
|
|
|
|
test('shows placeholder sections for student data', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, /Élève/i);
|
|
|
|
await expect(page.getByRole('heading', { name: /mon emploi du temps/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /mes notes/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /mes devoirs/i })).toBeVisible();
|
|
});
|
|
|
|
test('placeholder sections show minor-appropriate messages', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, /Élève/i);
|
|
|
|
// Uses "ton/tes" for minors
|
|
await expect(page.getByText(/ton emploi du temps sera bientôt disponible/i)).toBeVisible();
|
|
await expect(page.getByText(/tes notes apparaîtront ici/i)).toBeVisible();
|
|
await expect(page.getByText(/tes devoirs s'afficheront ici/i)).toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Admin Dashboard View
|
|
// ============================================================================
|
|
test.describe('Admin Dashboard', () => {
|
|
test('shows admin dashboard header', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Switch to admin
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible();
|
|
});
|
|
|
|
test('shows establishment name', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
// Demo data uses "École Alpha" as establishment name
|
|
await expect(page.getByText(/école alpha/i)).toBeVisible();
|
|
});
|
|
|
|
test('shows quick action links for admin', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
await expect(page.getByText(/gérer les utilisateurs/i)).toBeVisible();
|
|
await expect(page.getByText(/configurer les classes/i)).toBeVisible();
|
|
await expect(page.getByText(/gérer les matières/i)).toBeVisible();
|
|
await expect(page.getByText(/périodes scolaires/i)).toBeVisible();
|
|
await expect(page.getByText(/pédagogie/i)).toBeVisible();
|
|
});
|
|
|
|
test('admin quick action links have correct hrefs', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
// Verify action cards link to correct pages
|
|
const usersLink = page.locator('.action-card[href="/admin/users"]');
|
|
await expect(usersLink).toBeVisible();
|
|
|
|
const classesLink = page.locator('.action-card[href="/admin/classes"]');
|
|
await expect(classesLink).toBeVisible();
|
|
|
|
const subjectsLink = page.locator('.action-card[href="/admin/subjects"]');
|
|
await expect(subjectsLink).toBeVisible();
|
|
|
|
const periodsLink = page.locator('.action-card[href="/admin/academic-year/periods"]');
|
|
await expect(periodsLink).toBeVisible();
|
|
|
|
const pedagogyLink = page.locator('.action-card[href="/admin/pedagogy"]');
|
|
await expect(pedagogyLink).toBeVisible();
|
|
});
|
|
|
|
test('shows import action cards for students and teachers', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
const studentImport = page.getByRole('link', { name: /importer des élèves/i });
|
|
await expect(studentImport).toBeVisible();
|
|
await expect(studentImport).toHaveAttribute('href', '/admin/import/students');
|
|
|
|
const teacherImport = page.getByRole('link', { name: /importer des enseignants/i });
|
|
await expect(teacherImport).toBeVisible();
|
|
await expect(teacherImport).toHaveAttribute('href', '/admin/import/teachers');
|
|
});
|
|
|
|
test('shows placeholder sections for admin stats', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
await expect(page.getByRole('heading', { name: /utilisateurs/i })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Configuration', exact: true })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: /activité récente/i })).toBeVisible();
|
|
});
|
|
|
|
test('shows section titles grouping action cards', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
await expect(page.getByRole('heading', { name: 'Personnes', exact: true })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Organisation', exact: true })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Année scolaire', exact: true })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Paramètres', exact: true })).toBeVisible();
|
|
await expect(page.getByRole('heading', { name: 'Imports', exact: true })).toBeVisible();
|
|
});
|
|
|
|
test('groups correct cards within each section', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
// Personnes: Utilisateurs, Invitations parents, Élèves
|
|
const personnes = page.locator('section[aria-labelledby="section-personnes"]');
|
|
await expect(personnes.locator('.action-card')).toHaveCount(3);
|
|
await expect(personnes.locator('.action-card[href="/admin/users"]')).toBeVisible();
|
|
await expect(personnes.locator('.action-card[href="/admin/parent-invitations"]')).toBeVisible();
|
|
await expect(personnes.locator('.action-card[href="/admin/students"]')).toBeVisible();
|
|
|
|
// Organisation: Classes, Matières, Affectations, Remplacements, Emploi du temps
|
|
const organisation = page.locator('section[aria-labelledby="section-organisation"]');
|
|
await expect(organisation.locator('.action-card')).toHaveCount(5);
|
|
await expect(organisation.locator('.action-card[href="/admin/classes"]')).toBeVisible();
|
|
await expect(organisation.locator('.action-card[href="/admin/subjects"]')).toBeVisible();
|
|
await expect(organisation.locator('.action-card[href="/admin/assignments"]')).toBeVisible();
|
|
await expect(organisation.locator('.action-card[href="/admin/replacements"]')).toBeVisible();
|
|
await expect(organisation.locator('.action-card[href="/admin/schedule"]')).toBeVisible();
|
|
|
|
// Année scolaire: Périodes scolaires, Calendrier
|
|
const anneeScolaire = page.locator('section[aria-labelledby="section-annee-scolaire"]');
|
|
await expect(anneeScolaire.locator('.action-card')).toHaveCount(2);
|
|
await expect(anneeScolaire.locator('.action-card[href="/admin/academic-year/periods"]')).toBeVisible();
|
|
await expect(anneeScolaire.locator('.action-card[href="/admin/calendar"]')).toBeVisible();
|
|
|
|
// Paramètres: Droit à l'image, Pédagogie, Identité visuelle, Règles de devoirs
|
|
const parametres = page.locator('section[aria-labelledby="section-parametres"]');
|
|
await expect(parametres.locator('.action-card')).toHaveCount(4);
|
|
await expect(parametres.locator('.action-card[href="/admin/image-rights"]')).toBeVisible();
|
|
await expect(parametres.locator('.action-card[href="/admin/pedagogy"]')).toBeVisible();
|
|
await expect(parametres.locator('.action-card[href="/admin/branding"]')).toBeVisible();
|
|
await expect(parametres.locator('.action-card[href="/admin/homework-rules"]')).toBeVisible();
|
|
|
|
// Imports: Importer des élèves, Importer des enseignants
|
|
const imports = page.locator('section[aria-labelledby="section-imports"]');
|
|
await expect(imports.locator('.action-card')).toHaveCount(2);
|
|
await expect(imports.locator('.action-card[href="/admin/import/students"]')).toBeVisible();
|
|
await expect(imports.locator('.action-card[href="/admin/import/teachers"]')).toBeVisible();
|
|
});
|
|
|
|
test('sections are displayed in the expected order', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
const sectionTitles = page.locator('.dashboard-section .section-title');
|
|
await expect(sectionTitles).toHaveCount(8);
|
|
await expect(sectionTitles.nth(0)).toHaveText('Personnes');
|
|
await expect(sectionTitles.nth(1)).toHaveText('Organisation');
|
|
await expect(sectionTitles.nth(2)).toHaveText('Année scolaire');
|
|
await expect(sectionTitles.nth(3)).toHaveText('Paramètres');
|
|
await expect(sectionTitles.nth(4)).toHaveText('Imports');
|
|
await expect(sectionTitles.nth(5)).toHaveText('Utilisateurs');
|
|
await expect(sectionTitles.nth(6)).toHaveText('Configuration');
|
|
await expect(sectionTitles.nth(7)).toHaveText('Activité récente');
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Role Switching
|
|
// ============================================================================
|
|
test.describe('Role Switching', () => {
|
|
test('switching from parent to teacher changes dashboard content', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Verify parent view
|
|
await expect(page.getByText(/score sérénité/i).first()).toBeVisible();
|
|
|
|
// Switch to teacher
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
// Parent content should be gone
|
|
await expect(page.locator('.serenity-card')).not.toBeVisible();
|
|
|
|
// Teacher content should appear
|
|
await expect(page.getByRole('heading', { name: /tableau de bord enseignant/i })).toBeVisible();
|
|
});
|
|
|
|
test('switching from teacher to student changes dashboard content', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Switch to teacher first
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
await expect(page.getByRole('heading', { name: /tableau de bord enseignant/i })).toBeVisible();
|
|
|
|
// Switch to student
|
|
await switchToDemoRole(page, /Élève/i);
|
|
|
|
// Teacher content should be gone
|
|
await expect(page.getByRole('heading', { name: /tableau de bord enseignant/i })).not.toBeVisible();
|
|
|
|
// Student content should appear
|
|
await expect(page.getByRole('heading', { name: /mon espace/i })).toBeVisible();
|
|
});
|
|
|
|
test('switching from student to admin changes dashboard content', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Switch to student first
|
|
await switchToDemoRole(page, /Élève/i);
|
|
await expect(page.getByRole('heading', { name: /mon espace/i })).toBeVisible();
|
|
|
|
// Switch to admin
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
// Student content should be gone
|
|
await expect(page.getByRole('heading', { name: /mon espace/i })).not.toBeVisible();
|
|
|
|
// Admin content should appear
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible();
|
|
});
|
|
|
|
test('active role button changes visual state', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Parent should be active initially
|
|
const parentBtn = page.locator('.demo-controls button', { hasText: 'Parent' });
|
|
await expect(parentBtn).toHaveClass(/active/);
|
|
|
|
// Switch to teacher
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
// Teacher should now be active, parent should not
|
|
const teacherBtn = page.locator('.demo-controls button', { hasText: 'Enseignant' });
|
|
await expect(teacherBtn).toHaveClass(/active/);
|
|
await expect(parentBtn).not.toHaveClass(/active/);
|
|
});
|
|
|
|
test('onboarding banner disappears after switching roles', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
// Onboarding banner is visible initially (isFirstLogin=true)
|
|
await expect(page.getByText(/bienvenue sur classeo/i)).toBeVisible();
|
|
|
|
// Switch role - this calls switchDemoRole which sets isFirstLogin=false
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
// Switch back to parent
|
|
await switchToDemoRole(page, 'Parent');
|
|
|
|
// Onboarding banner should no longer be visible
|
|
await expect(page.getByText(/bienvenue sur classeo/i)).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Admin Dashboard - Navigation from Quick Actions
|
|
// ============================================================================
|
|
test.describe('Admin Quick Action Navigation', () => {
|
|
test.beforeAll(async () => {
|
|
const projectRoot = join(__dirname, '../..');
|
|
const composeFile = join(projectRoot, 'compose.yaml');
|
|
|
|
execSync(
|
|
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${ADMIN_EMAIL} --password=${ADMIN_PASSWORD} --role=ROLE_ADMIN 2>&1`,
|
|
{ encoding: 'utf-8' }
|
|
);
|
|
});
|
|
|
|
async function loginAsAdmin(page: import('@playwright/test').Page) {
|
|
await page.goto(`${ALPHA_URL}/login`);
|
|
await page.locator('#email').fill(ADMIN_EMAIL);
|
|
await page.locator('#password').fill(ADMIN_PASSWORD);
|
|
await page.getByRole('button', { name: /se connecter/i }).click();
|
|
await page.waitForURL(/\/dashboard/, { timeout: 60000 });
|
|
}
|
|
|
|
test('clicking "Gerer les utilisateurs" navigates to users page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
|
|
// Admin dashboard should show after login (ROLE_ADMIN maps to admin view)
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click users link
|
|
await page.locator('.action-card[href="/admin/users"]').click();
|
|
await expect(page).toHaveURL(/\/admin\/users/);
|
|
});
|
|
|
|
test('clicking "Configurer les classes" navigates to classes page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.locator('.action-card[href="/admin/classes"]').click();
|
|
await expect(page).toHaveURL(/\/admin\/classes/);
|
|
});
|
|
|
|
test('clicking "Gerer les matieres" navigates to subjects page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.locator('.action-card[href="/admin/subjects"]').click();
|
|
await expect(page).toHaveURL(/\/admin\/subjects/);
|
|
});
|
|
|
|
test('clicking "Periodes scolaires" navigates to periods page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.locator('.action-card[href="/admin/academic-year/periods"]').click();
|
|
await expect(page).toHaveURL(/\/admin\/academic-year\/periods/);
|
|
});
|
|
|
|
test('clicking "Pedagogie" navigates to pedagogy page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await expect(page.getByRole('heading', { name: /administration/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
await page.locator('.action-card[href="/admin/pedagogy"]').click();
|
|
await expect(page).toHaveURL(/\/admin\/pedagogy/);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// Accessibility
|
|
// ============================================================================
|
|
test.describe('Accessibility', () => {
|
|
test('serenity score card has accessible label', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
|
|
const scoreCard = page.locator('[aria-label*="Score Sérénité"]');
|
|
await expect(scoreCard).toBeVisible();
|
|
});
|
|
|
|
test('teacher quick actions have a visually hidden heading', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Enseignant');
|
|
|
|
// The "Actions rapides" heading exists but is sr-only
|
|
const actionsHeading = page.getByRole('heading', { name: /actions rapides/i });
|
|
await expect(actionsHeading).toBeAttached();
|
|
});
|
|
|
|
test('admin configuration actions have a visually hidden heading', async ({ page }) => {
|
|
await goToDashboard(page);
|
|
await switchToDemoRole(page, 'Admin');
|
|
|
|
const configHeading = page.getByRole('heading', { name: /actions de configuration/i });
|
|
await expect(configHeading).toBeAttached();
|
|
});
|
|
});
|
|
});
|
|
|
|
test.describe('Dashboard Components', () => {
|
|
test('demo data JSON is valid and accessible', async ({ page }) => {
|
|
// This tests that the demo data file is bundled correctly
|
|
await page.goto('/');
|
|
|
|
// The app should load without errors
|
|
await expect(page.locator('body')).toBeVisible();
|
|
});
|
|
});
|