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}`; const USER_EMAIL = 'e2e-settings-user@example.com'; const USER_PASSWORD = 'SettingsTest123'; function getTenantUrl(path: string): string { return `${ALPHA_URL}${path}`; } test.describe('Settings Page [P1]', () => { // eslint-disable-next-line no-empty-pattern test.beforeAll(async ({}, testInfo) => { const browserName = testInfo.project.name; const projectRoot = join(__dirname, '../..'); const composeFile = join(projectRoot, 'compose.yaml'); // Create a test user (any role works for settings) const email = `e2e-settings-${browserName}@example.com`; try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${email} --password=${USER_PASSWORD} 2>&1`, { encoding: 'utf-8' } ); } catch (error) { console.error(`[${browserName}] Failed to create settings test user:`, error); } // Also create the shared user try { execSync( `docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${USER_EMAIL} --password=${USER_PASSWORD} 2>&1`, { encoding: 'utf-8' } ); } catch (error) { console.error('Failed to create shared settings test user:', error); } }); function getTestEmail(browserName: string): string { return `e2e-settings-${browserName}@example.com`; } async function login(page: import('@playwright/test').Page, email: string) { await page.goto(getTenantUrl('/login')); await page.locator('#email').fill(email); await page.locator('#password').fill(USER_PASSWORD); await Promise.all([ page.waitForURL(getTenantUrl('/dashboard'), { timeout: 30000 }), page.getByRole('button', { name: /se connecter/i }).click() ]); } // ============================================================================ // Settings page access // ============================================================================ test('[P1] authenticated user can access /settings page', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); await expect(page).toHaveURL(/\/settings/); await expect( page.getByRole('heading', { name: /paramètres/i }) ).toBeVisible({ timeout: 10000 }); }); // ============================================================================ // Settings page content // ============================================================================ test('[P1] settings page shows description text', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); await expect( page.getByText(/gérez votre compte et vos préférences/i) ).toBeVisible(); }); test('[P1] settings page shows Sessions navigation card', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); // The Sessions card should be visible await expect(page.getByRole('heading', { name: /mes sessions/i })).toBeVisible(); await expect( page.getByText(/gérez vos sessions actives/i) ).toBeVisible(); }); // ============================================================================ // Navigation from settings to sessions // ============================================================================ test('[P1] clicking Sessions card navigates to /settings/sessions', async ({ page, browserName }, testInfo) => { // Skip on webkit due to navigation timing issues with SvelteKit test.skip(browserName === 'webkit', 'Webkit has navigation timing issues with SvelteKit'); const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); // Wait for the settings page to be fully interactive before clicking const sessionsCard = page.getByText(/mes sessions/i); await expect(sessionsCard).toBeVisible({ timeout: 15000 }); // Click and retry once if navigation doesn't happen (Svelte hydration race) await sessionsCard.click(); try { await page.waitForURL(/\/settings\/sessions/, { timeout: 10000 }); } catch { // Retry click in case hydration wasn't complete await sessionsCard.click(); await page.waitForURL(/\/settings\/sessions/, { timeout: 30000 }); } await expect( page.getByRole('heading', { name: /mes sessions/i }) ).toBeVisible(); }); // ============================================================================ // Settings layout - header with logo // ============================================================================ test('[P1] settings layout shows header with Classeo logo', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); // Header should have the Classeo logo text await expect(page.locator('.logo-text')).toBeVisible(); await expect(page.locator('.logo-text')).toHaveText('Classeo'); }); test('[P1] settings layout shows Tableau de bord navigation link', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); await expect( page.getByRole('link', { name: /tableau de bord/i }) ).toBeVisible(); }); test('[P1] settings layout shows Parametres navigation link as active', async ({ page }, testInfo) => { const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); const parametresLink = page.getByRole('link', { name: /parametres/i }); await expect(parametresLink).toBeVisible(); await expect(parametresLink).toHaveClass(/active/); }); // ============================================================================ // Logout from settings layout // ============================================================================ test('[P1] logout button on settings layout logs user out', async ({ page, browserName }, testInfo) => { // Skip on webkit due to navigation timing issues with SvelteKit test.skip(browserName === 'webkit', 'Webkit has navigation timing issues with SvelteKit'); const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); // Click logout button const logoutButton = page.getByRole('button', { name: /d[eé]connexion/i }); await expect(logoutButton).toBeVisible(); await logoutButton.click(); // Should redirect to login await expect(page).toHaveURL(/\/login/, { timeout: 10000 }); }); // ============================================================================ // Logo click navigates to dashboard // ============================================================================ test('[P1] clicking Classeo logo navigates to dashboard', async ({ page, browserName }, testInfo) => { // Skip on webkit due to navigation timing issues with SvelteKit test.skip(browserName === 'webkit', 'Webkit has navigation timing issues with SvelteKit'); const email = getTestEmail(testInfo.project.name); await login(page, email); await page.goto(getTenantUrl('/settings')); // Click the logo button await page.locator('.logo-button').click(); await expect(page).toHaveURL(/\/dashboard/, { timeout: 30000 }); }); });