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 projectRoot = join(__dirname, '../..'); const composeFile = join(projectRoot, 'compose.yaml'); 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 SA_PASSWORD = 'SuperAdmin123'; const UNIQUE_SUFFIX = Date.now(); function getSuperAdminEmail(browserName: string): string { return `e2e-prov-sa-${browserName}@test.com`; } // eslint-disable-next-line no-empty-pattern test.beforeAll(async ({}, testInfo) => { const browserName = testInfo.project.name; const saEmail = getSuperAdminEmail(browserName); try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-super-admin --email=${saEmail} --password=${SA_PASSWORD} 2>&1`, { encoding: 'utf-8' } ); } catch (error) { console.error(`[${browserName}] Failed to create super admin:`, error); } }); async function loginAsSuperAdmin( page: import('@playwright/test').Page, email: string ) { await page.goto(`${ALPHA_URL}/login`); await expect(page.getByRole('heading', { name: /connexion/i })).toBeVisible(); await page.locator('#email').fill(email); await page.locator('#password').fill(SA_PASSWORD); const submitButton = page.getByRole('button', { name: /se connecter/i }); await Promise.all([ page.waitForURL('**/super-admin/dashboard', { timeout: 30000 }), submitButton.click() ]); } async function navigateToEstablishments(page: import('@playwright/test').Page) { const link = page.getByRole('link', { name: /établissements/i }); await Promise.all([ page.waitForURL('**/super-admin/establishments', { timeout: 10000 }), link.click() ]); await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible({ timeout: 10000 }); } async function navigateToCreateForm(page: import('@playwright/test').Page) { const newLink = page.getByRole('link', { name: /nouvel établissement/i }); await expect(newLink).toBeVisible({ timeout: 10000 }); await Promise.all([ page.waitForURL('**/super-admin/establishments/new', { timeout: 10000 }), newLink.click() ]); await expect(page.locator('#name')).toBeVisible({ timeout: 10000 }); } test.describe('Establishment Provisioning (Story 2-17) [P1]', () => { test.describe.configure({ mode: 'serial' }); test.describe('Subdomain Auto-generation', () => { test('[P1] typing name auto-generates subdomain', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); await page.locator('#name').fill('École Saint-Exupéry'); // Subdomain should be auto-generated: accents removed, spaces→hyphens, lowercase await expect(page.locator('#subdomain')).toHaveValue('ecole-saint-exupery'); }); test('[P2] subdomain suffix .classeo.fr is displayed', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); await expect(page.locator('.subdomain-suffix')).toHaveText('.classeo.fr'); }); }); test.describe('Create Establishment Flow', () => { const establishmentName = `E2E Test ${UNIQUE_SUFFIX}`; const adminEmailForEstab = `admin-prov-${UNIQUE_SUFFIX}@test.com`; test('[P1] submitting form creates establishment and redirects to list', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); // Fill in the form await page.locator('#name').fill(establishmentName); await page.locator('#adminEmail').fill(adminEmailForEstab); // Subdomain should be auto-generated const subdomain = await page.locator('#subdomain').inputValue(); expect(subdomain.length).toBeGreaterThan(0); // Submit const submitButton = page.getByRole('button', { name: /créer l'établissement/i }); await expect(submitButton).toBeEnabled(); const apiResponsePromise = page.waitForResponse( (resp) => resp.url().includes('/super-admin/establishments') && resp.request().method() === 'POST' ); await submitButton.click(); // Verify API returns establishment in provisioning status const apiResponse = await apiResponsePromise; expect(apiResponse.status()).toBeLessThan(400); const body = await apiResponse.json(); expect(body.status).toBe('provisioning'); // Should redirect back to establishments list await page.waitForURL('**/super-admin/establishments', { timeout: 15000 }); await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible({ timeout: 10000 }); }); test('[P1] created establishment appears in the list', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); // The establishment created in previous test should be visible await expect(page.locator('table')).toBeVisible({ timeout: 10000 }); await expect(page.locator('td', { hasText: establishmentName })).toBeVisible({ timeout: 10000 }); }); test('[P1] created establishment has a visible status badge', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); // Find the row for our establishment const row = page.locator('tr', { has: page.locator(`text=${establishmentName}`) }); await expect(row).toBeVisible({ timeout: 10000 }); // Status badge should be visible (provisioning status already verified via API response in creation test) const badge = row.locator('.badge'); await expect(badge).toBeVisible(); await expect(badge).not.toHaveText(''); }); }); test.describe('Form Validation', () => { test('[P2] submit button disabled with empty fields', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); const submitButton = page.getByRole('button', { name: /créer l'établissement/i }); await expect(submitButton).toBeDisabled(); }); test('[P2] submit button enabled when all fields filled', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); await page.locator('#name').fill('Test School'); await page.locator('#adminEmail').fill('admin@test.com'); const submitButton = page.getByRole('button', { name: /créer l'établissement/i }); await expect(submitButton).toBeEnabled(); }); test('[P2] cancel button returns to establishments list', async ({ page }, testInfo) => { const email = getSuperAdminEmail(testInfo.project.name); await loginAsSuperAdmin(page, email); await navigateToEstablishments(page); await navigateToCreateForm(page); const cancelLink = page.getByRole('link', { name: /annuler/i }); await Promise.all([ page.waitForURL('**/super-admin/establishments', { timeout: 10000 }), cancelLink.click() ]); await expect(page.getByRole('heading', { name: /établissements/i })).toBeVisible(); }); }); });