Files
Classeo/frontend/e2e/homework-rules.spec.ts
Mathias STRASSER 5f3c5c2d71
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: Permettre aux administrateurs de configurer les règles de devoirs
Les établissements ont besoin de protéger les élèves et familles des
devoirs de dernière minute. Cette configuration au niveau tenant permet
de définir des règles de timing (délai minimum, pas de devoir pour
lundi après une heure limite) et un mode d'application (avertissement,
blocage ou désactivé).

Le service de validation est prêt pour être branché dans le flux de
création de devoirs (Stories 5.4/5.5). L'historique des changements
assure la traçabilité des modifications de configuration.
2026-03-17 21:28:10 +01:00

191 lines
6.8 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 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');
// 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
}
});
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 Promise.all([
page.waitForURL(/\/dashboard/, { timeout: 30000 }),
page.getByRole('button', { name: /se connecter/i }).click()
]);
}
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();
});
});