Lorsqu'un super-admin crée un établissement via l'interface, le système doit automatiquement créer la base tenant, exécuter les migrations, créer le premier utilisateur admin et envoyer l'invitation — le tout de manière asynchrone pour ne pas bloquer la réponse HTTP. Ce mécanisme rend chaque établissement opérationnel dès sa création sans intervention manuelle sur l'infrastructure.
215 lines
8.3 KiB
TypeScript
215 lines
8.3 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);
|
|
|
|
// 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 page.getByRole('button', { name: /se connecter/i }).click();
|
|
await page.waitForURL(/\/dashboard/, { timeout: 30000 });
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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 page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.getByRole('link', { name: /tableau de bord/i })
|
|
).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
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');
|
|
test.setTimeout(90000);
|
|
|
|
const email = getTestEmail(testInfo.project.name);
|
|
await login(page, email);
|
|
|
|
await page.goto(getTenantUrl('/settings'));
|
|
await expect(page.getByRole('heading', { name: /paramètres/i })).toBeVisible({ timeout: 10000 });
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Click logout button and wait for the API call + redirect
|
|
const logoutButton = page.getByRole('button', { name: /d[eé]connexion/i });
|
|
await expect(logoutButton).toBeVisible();
|
|
await logoutButton.click();
|
|
|
|
// Should redirect to login — logout involves an API call before navigation
|
|
await expect(page).toHaveURL(/\/login/, { timeout: 60000 });
|
|
});
|
|
|
|
// ============================================================================
|
|
// 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'));
|
|
await expect(page.getByRole('heading', { name: /paramètres/i })).toBeVisible({ timeout: 10000 });
|
|
|
|
// Click the logo button and wait for navigation
|
|
await page.locator('.logo-button').click();
|
|
|
|
await expect(page).toHaveURL(/\/dashboard/, { timeout: 30000 });
|
|
});
|
|
});
|