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 ADMIN_EMAIL = 'e2e-homework-rules-admin@example.com'; const ADMIN_PASSWORD = 'HomeworkRulesAdmin123'; const TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'; test.describe('Homework Rules Configuration', () => { // Serial: les tests d'historique dépendent des entrées créées par les tests précédents test.describe.configure({ mode: 'serial' }); test.beforeAll(async () => { const projectRoot = join(__dirname, '../..'); const composeFile = join(projectRoot, 'compose.yaml'); // Clear rate limiter to prevent login throttling across serial tests try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`, { encoding: 'utf-8' } ); } catch { // Cache pool may not exist } // Create admin user 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' } ); // Clean up homework rules from previous test runs execSync( `docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "DELETE FROM homework_rules_history WHERE tenant_id = '${TENANT_ID}'" 2>&1`, { encoding: 'utf-8' } ); execSync( `docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "DELETE FROM homework_rules WHERE tenant_id = '${TENANT_ID}'" 2>&1`, { encoding: 'utf-8' } ); try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear paginated_queries.cache 2>&1`, { encoding: 'utf-8' } ); } catch { // Cache pool may not exist } }); test.beforeEach(async () => { const projectRoot = join(__dirname, '../..'); const composeFile = join(projectRoot, 'compose.yaml'); try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console cache:pool:clear cache.rate_limiter --env=dev 2>&1`, { encoding: 'utf-8' } ); } catch { // Cache pool may not exist } }); 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 }); } async function waitForPageLoaded(page: import('@playwright/test').Page) { await expect( page.getByRole('heading', { name: /règles de devoirs/i }) ).toBeVisible({ timeout: 15000 }); await expect( page.locator('.card').first() ).toBeVisible({ timeout: 15000 }); } // AC1: Page configuration pédagogique → section "Règles de devoirs" accessible test('page affiche la configuration des règles de devoirs', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); await expect( page.getByRole('heading', { name: /règles de devoirs/i }) ).toBeVisible(); // Mode selector visible await expect(page.getByRole('radio', { name: /avertissement/i })).toBeVisible(); await expect(page.getByRole('radio', { name: /blocage/i })).toBeVisible(); await expect(page.getByRole('radio', { name: /désactivé/i })).toBeVisible(); }); // AC5: Mode par défaut = "Soft" test('mode par défaut est Avertissement (soft)', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); const softRadio = page.getByRole('radio', { name: /avertissement/i }); await expect(softRadio).toHaveAttribute('aria-checked', 'true'); }); // AC2: Règle définissable : délai minimum test('configurer la règle de délai minimum', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); // Enable minimum delay rule const delayCheckbox = page.getByRole('checkbox', { name: /délai minimum/i }); await delayCheckbox.check(); // Set 3 days const daysInput = page.locator('input[type="number"]'); await daysInput.fill('3'); // Save await page.getByRole('button', { name: /enregistrer/i }).click(); // Success message await expect(page.getByText(/mises à jour avec succès/i)).toBeVisible({ timeout: 10000 }); }); // AC2: Règle définissable : jours concernés (pas de devoir pour lundi) test('configurer la règle pas de devoir pour lundi', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); // Enable no-monday rule const mondayCheckbox = page.getByRole('checkbox', { name: /pas de devoir pour lundi/i }); await mondayCheckbox.check(); // Save await page.getByRole('button', { name: /enregistrer/i }).click(); await expect(page.getByText(/mises à jour avec succès/i)).toBeVisible({ timeout: 10000 }); }); // AC5: Choix mode Hard (blocage) test('changer le mode en Blocage', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); // Select hard mode await page.getByRole('radio', { name: /blocage/i }).click(); // Save await page.getByRole('button', { name: /enregistrer/i }).click(); await expect(page.getByText(/mises à jour avec succès/i)).toBeVisible({ timeout: 10000 }); }); // AC7: Règles désactivables complètement test('désactiver les règles complètement', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); // Select disabled mode await page.getByRole('radio', { name: /désactivé/i }).click(); // Rules section should disappear await expect(page.getByText(/les enseignants peuvent créer des devoirs librement/i)).toBeVisible(); // Save await page.getByRole('button', { name: /enregistrer/i }).click(); await expect(page.getByText(/mises à jour avec succès/i)).toBeVisible({ timeout: 10000 }); }); // AC6: Historique des changements conservé test('voir l\'historique des changements avec contenu', async ({ page }) => { await loginAsAdmin(page); await page.goto(`${ALPHA_URL}/admin/homework-rules`); await waitForPageLoaded(page); // Click history button await page.getByRole('button', { name: /voir l'historique/i }).click(); // Modal should appear const dialog = page.getByRole('dialog', { name: /historique des changements/i }); await expect(dialog).toBeVisible({ timeout: 10000 }); // Verify history is not empty (previous tests created entries) await expect(dialog.getByText(/aucun changement/i)).not.toBeVisible(); // Verify at least one history entry with mode and date const firstEntry = dialog.locator('.history-entry').first(); await expect(firstEntry).toBeVisible(); await expect(firstEntry.locator('.history-mode')).toBeVisible(); await expect(firstEntry.locator('.history-date')).toBeVisible(); }); });