Les tests E2E échouaient pour trois raisons principales : 1. Initialisation asynchrone TipTap — L'éditeur rich-text s'initialise via des imports dynamiques dans onMount(). Les tests interagissaient avec .rich-text-content avant que l'élément n'existe dans le DOM. Ajout d'attentes explicites avant chaque interaction avec l'éditeur. 2. Pollution inter-tests — Les fonctions de nettoyage (classes, subjects) ne supprimaient pas les tables dépendantes (homework, evaluations, schedule_slots), provoquant des erreurs FK silencieuses dans les try/catch. De plus, homework_submissions n'a pas de ON DELETE CASCADE sur homework_id, nécessitant une suppression explicite. 3. État partagé du tenant — Les règles de devoirs (homework_rules) et le calendrier scolaire (school_calendar_entries avec Vacances de Printemps) persistaient entre les fichiers de test, bloquant la création de devoirs dans des tests non liés aux règles.
214 lines
7.4 KiB
TypeScript
214 lines
7.4 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');
|
|
|
|
// 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 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();
|
|
});
|
|
});
|