L'élève avait accès à ses compétences mais pas à ses notes numériques. Cette fonctionnalité lui donne une vue complète de sa progression scolaire avec moyennes par matière, détail par évaluation, statistiques de classe, et un mode "découverte" pour révéler ses notes à son rythme (FR14, FR15). Les notes ne sont visibles qu'après publication par l'enseignant, ce qui garantit que l'élève les découvre avant ses parents (délai 24h story 6.7).
340 lines
12 KiB
TypeScript
340 lines
12 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-search-admin@example.com';
|
|
const ADMIN_PASSWORD = 'SearchTest123';
|
|
|
|
const projectRoot = join(__dirname, '../..');
|
|
const composeFile = join(projectRoot, 'compose.yaml');
|
|
|
|
test.describe('Admin Search & Pagination (Story 2.8b)', () => {
|
|
test.beforeAll(async () => {
|
|
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' }
|
|
);
|
|
});
|
|
|
|
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: 60000 }),
|
|
page.getByRole('button', { name: /se connecter/i }).click()
|
|
]);
|
|
}
|
|
|
|
// ============================================================================
|
|
// USERS PAGE - Search & Pagination
|
|
// ============================================================================
|
|
test.describe('Users Page', () => {
|
|
test('displays search input on users page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await expect(searchInput).toBeVisible({ timeout: 10000 });
|
|
await expect(searchInput).toHaveAttribute('placeholder', /rechercher/i);
|
|
});
|
|
|
|
test('search filters users and shows results', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Wait for initial load
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('e2e-search-admin');
|
|
|
|
// Wait for debounce + API response
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Should find our test admin user
|
|
await expect(page.locator('.users-table')).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('search with no results shows empty state', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('zzz-nonexistent-user-xyz');
|
|
|
|
// Wait for debounce + API response
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Should show "Aucun résultat" empty state
|
|
await expect(page.locator('.empty-state')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('.empty-state')).toContainText(/aucun résultat/i);
|
|
});
|
|
|
|
test('search term is synced to URL', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('test-search');
|
|
|
|
// Wait for debounce
|
|
await page.waitForTimeout(500);
|
|
|
|
// URL should contain search param
|
|
await expect(page).toHaveURL(/[?&]search=test-search/, { timeout: 15000 });
|
|
});
|
|
|
|
test('search term from URL is restored on page load', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users?search=admin`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await expect(searchInput).toHaveValue('admin');
|
|
});
|
|
|
|
test('clear search button resets results', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('test');
|
|
|
|
// Wait for debounce
|
|
await page.waitForTimeout(500);
|
|
|
|
// Clear button should appear
|
|
const clearButton = page.locator('.search-clear');
|
|
await expect(clearButton).toBeVisible();
|
|
await clearButton.click();
|
|
|
|
// Search input should be empty
|
|
await expect(searchInput).toHaveValue('');
|
|
});
|
|
|
|
test('Escape key clears search', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('test');
|
|
await page.waitForTimeout(500);
|
|
|
|
await searchInput.press('Escape');
|
|
await expect(searchInput).toHaveValue('');
|
|
});
|
|
|
|
test('filters work together with search', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/users`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.users-table, .empty-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
// Apply a role filter
|
|
await page.locator('#filter-role').selectOption('ROLE_ADMIN');
|
|
await page.getByRole('button', { name: /filtrer/i }).click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Then search
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('e2e-search');
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// URL should have both params
|
|
await expect(page).toHaveURL(/search=e2e-search/);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// CLASSES PAGE - Search & Pagination
|
|
// ============================================================================
|
|
test.describe('Classes Page', () => {
|
|
test('displays search input on classes page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/classes`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await expect(searchInput).toBeVisible({ timeout: 10000 });
|
|
await expect(searchInput).toHaveAttribute('placeholder', /rechercher/i);
|
|
});
|
|
|
|
test('search with no results shows empty state', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/classes`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Wait for initial load
|
|
await expect(
|
|
page.locator('.classes-grid, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('zzz-nonexistent-class-xyz');
|
|
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(page.locator('.empty-state')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('.empty-state')).toContainText(/aucun résultat/i);
|
|
});
|
|
|
|
test('search term is synced to URL', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/classes`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.classes-grid, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('6eme');
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page).toHaveURL(/[?&]search=6eme/);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// SUBJECTS PAGE - Search & Pagination
|
|
// ============================================================================
|
|
test.describe('Subjects Page', () => {
|
|
test('displays search input on subjects page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/subjects`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await expect(searchInput).toBeVisible({ timeout: 10000 });
|
|
await expect(searchInput).toHaveAttribute('placeholder', /rechercher/i);
|
|
});
|
|
|
|
test('search with no results shows empty state', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/subjects`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.subjects-grid, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('zzz-nonexistent-subject-xyz');
|
|
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(page.locator('.empty-state')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('.empty-state')).toContainText(/aucun résultat/i);
|
|
});
|
|
|
|
test('search term is synced to URL', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/subjects`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.subjects-grid, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 10000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('MATH');
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page).toHaveURL(/[?&]search=MATH/);
|
|
});
|
|
});
|
|
|
|
// ============================================================================
|
|
// ASSIGNMENTS PAGE - Search & Pagination
|
|
// ============================================================================
|
|
test.describe('Assignments Page', () => {
|
|
test('displays search input on assignments page', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/assignments`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await expect(searchInput).toBeVisible({ timeout: 15000 });
|
|
await expect(searchInput).toHaveAttribute('placeholder', /rechercher/i);
|
|
});
|
|
|
|
test('search with no results shows empty state', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/assignments`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Wait for initial load
|
|
await expect(
|
|
page.locator('.table-container, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 15000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('zzz-nonexistent-teacher-xyz');
|
|
|
|
await page.waitForTimeout(500);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(page.locator('.empty-state')).toBeVisible({ timeout: 10000 });
|
|
await expect(page.locator('.empty-state')).toContainText(/aucun résultat/i);
|
|
});
|
|
|
|
test('search term is synced to URL', async ({ page }) => {
|
|
await loginAsAdmin(page);
|
|
await page.goto(`${ALPHA_URL}/admin/assignments`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
await expect(
|
|
page.locator('.table-container, .empty-state, .loading-state')
|
|
).toBeVisible({ timeout: 15000 });
|
|
|
|
const searchInput = page.locator('input[type="search"]');
|
|
await searchInput.fill('Dupont');
|
|
await page.waitForTimeout(500);
|
|
|
|
await expect(page).toHaveURL(/[?&]search=Dupont/);
|
|
});
|
|
});
|
|
});
|