feat: Permettre aux enseignants de créer et gérer les devoirs
Les enseignants avaient besoin d'un outil pour créer des devoirs assignés à leurs classes, avec filtrage automatique par matière selon la classe sélectionnée. Le système valide que la date d'échéance tombe un jour ouvrable (lundi-vendredi) et empêche les dates dans le passé. Le domaine modélise le devoir comme un agrégat avec pièces jointes, statut brouillon/publié, et événements métier (création, modification, suppression). Les handlers de notification écoutent ces événements pour les futurs envois aux parents et élèves.
This commit is contained in:
@@ -174,14 +174,15 @@ test.describe('Dashboard', () => {
|
||||
await goToDashboard(page);
|
||||
await switchToDemoRole(page, 'Enseignant');
|
||||
|
||||
// Action cards should be disabled since hasRealData=false
|
||||
// First two action cards should be disabled since hasRealData=false
|
||||
// "Créer un devoir" navigates to homework page and is always enabled
|
||||
const actionCards = page.locator('.action-card');
|
||||
const count = await actionCards.count();
|
||||
expect(count).toBeGreaterThanOrEqual(3);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
await expect(actionCards.nth(i)).toBeDisabled();
|
||||
}
|
||||
await expect(actionCards.nth(0)).toBeDisabled();
|
||||
await expect(actionCards.nth(1)).toBeDisabled();
|
||||
await expect(actionCards.nth(2)).toBeEnabled();
|
||||
});
|
||||
|
||||
test('shows placeholder sections for teacher data', async ({ page }) => {
|
||||
|
||||
450
frontend/e2e/homework.spec.ts
Normal file
450
frontend/e2e/homework.spec.ts
Normal file
@@ -0,0 +1,450 @@
|
||||
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 TEACHER_EMAIL = 'e2e-homework-teacher@example.com';
|
||||
const TEACHER_PASSWORD = 'HomeworkTest123';
|
||||
const TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
|
||||
|
||||
const projectRoot = join(__dirname, '../..');
|
||||
const composeFile = join(projectRoot, 'compose.yaml');
|
||||
|
||||
function runSql(sql: string) {
|
||||
execSync(
|
||||
`docker compose -f "${composeFile}" exec -T php php bin/console dbal:run-sql "${sql}" 2>&1`,
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
}
|
||||
|
||||
function clearCache() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDeterministicIds(): { schoolId: string; academicYearId: string } {
|
||||
const output = execSync(
|
||||
`docker compose -f "${composeFile}" exec -T php php -r '` +
|
||||
`require "/app/vendor/autoload.php"; ` +
|
||||
`$t="${TENANT_ID}"; $ns="6ba7b814-9dad-11d1-80b4-00c04fd430c8"; ` +
|
||||
`echo Ramsey\\Uuid\\Uuid::uuid5($ns,"school-$t")->toString()."\\n"; ` +
|
||||
`$m=(int)date("n"); $s=$m>=9?(int)date("Y"):(int)date("Y")-1; $e=$s+1; ` +
|
||||
`echo Ramsey\\Uuid\\Uuid::uuid5($ns,"$t:$s-$e")->toString();` +
|
||||
`' 2>&1`,
|
||||
{ encoding: 'utf-8' }
|
||||
).trim();
|
||||
const [schoolId, academicYearId] = output.split('\n');
|
||||
return { schoolId: schoolId!, academicYearId: academicYearId! };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future weekday date string (YYYY-MM-DD).
|
||||
* Skips weekends to satisfy DueDateValidator.
|
||||
*/
|
||||
function getNextWeekday(daysFromNow: number): string {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() + daysFromNow);
|
||||
// Skip to Monday if weekend
|
||||
const day = date.getDay();
|
||||
if (day === 0) date.setDate(date.getDate() + 1); // Sunday → Monday
|
||||
if (day === 6) date.setDate(date.getDate() + 2); // Saturday → Monday
|
||||
// Use local date components to avoid UTC timezone shift from toISOString()
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
async function loginAsTeacher(page: import('@playwright/test').Page) {
|
||||
await page.goto(`${ALPHA_URL}/login`);
|
||||
await page.locator('#email').fill(TEACHER_EMAIL);
|
||||
await page.locator('#password').fill(TEACHER_PASSWORD);
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/dashboard/, { timeout: 30000 }),
|
||||
page.getByRole('button', { name: /se connecter/i }).click()
|
||||
]);
|
||||
}
|
||||
|
||||
async function navigateToHomework(page: import('@playwright/test').Page) {
|
||||
await page.goto(`${ALPHA_URL}/dashboard/teacher/homework`);
|
||||
await expect(page.getByRole('heading', { name: /mes devoirs/i })).toBeVisible({ timeout: 15000 });
|
||||
}
|
||||
|
||||
function seedTeacherAssignments() {
|
||||
const { academicYearId } = resolveDeterministicIds();
|
||||
try {
|
||||
runSql(
|
||||
`INSERT INTO teacher_assignments (id, tenant_id, teacher_id, school_class_id, subject_id, academic_year_id, status, start_date, created_at, updated_at) ` +
|
||||
`SELECT gen_random_uuid(), '${TENANT_ID}', u.id, c.id, s.id, '${academicYearId}', 'active', NOW(), NOW(), NOW() ` +
|
||||
`FROM users u CROSS JOIN school_classes c CROSS JOIN subjects s ` +
|
||||
`WHERE u.email = '${TEACHER_EMAIL}' AND u.tenant_id = '${TENANT_ID}' ` +
|
||||
`AND c.tenant_id = '${TENANT_ID}' ` +
|
||||
`AND s.tenant_id = '${TENANT_ID}' ` +
|
||||
`ON CONFLICT DO NOTHING`
|
||||
);
|
||||
} catch {
|
||||
// Table may not exist
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('Homework Management (Story 5.1)', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Create teacher user
|
||||
execSync(
|
||||
`docker compose -f "${composeFile}" exec -T php php bin/console app:dev:create-test-user --tenant=ecole-alpha --email=${TEACHER_EMAIL} --password=${TEACHER_PASSWORD} --role=ROLE_PROF 2>&1`,
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
|
||||
// Ensure class and subject exist
|
||||
const { schoolId, academicYearId } = resolveDeterministicIds();
|
||||
try {
|
||||
runSql(
|
||||
`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
|
||||
`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6A', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
|
||||
);
|
||||
runSql(
|
||||
`INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at) ` +
|
||||
`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-HW-Maths', 'E2EMAT', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
|
||||
);
|
||||
} catch {
|
||||
// May already exist
|
||||
}
|
||||
|
||||
seedTeacherAssignments();
|
||||
clearCache();
|
||||
});
|
||||
|
||||
test.beforeEach(async () => {
|
||||
// Clean up homework data
|
||||
try {
|
||||
runSql(`DELETE FROM homework WHERE tenant_id = '${TENANT_ID}' AND teacher_id IN (SELECT id FROM users WHERE email = '${TEACHER_EMAIL}' AND tenant_id = '${TENANT_ID}')`);
|
||||
} catch {
|
||||
// Table may not exist
|
||||
}
|
||||
|
||||
// Re-ensure data exists
|
||||
const { schoolId, academicYearId } = resolveDeterministicIds();
|
||||
try {
|
||||
runSql(
|
||||
`INSERT INTO school_classes (id, tenant_id, school_id, academic_year_id, name, level, status, created_at, updated_at) ` +
|
||||
`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', '${academicYearId}', 'E2E-HW-6A', '6ème', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
|
||||
);
|
||||
runSql(
|
||||
`INSERT INTO subjects (id, tenant_id, school_id, name, code, status, created_at, updated_at) ` +
|
||||
`VALUES (gen_random_uuid(), '${TENANT_ID}', '${schoolId}', 'E2E-HW-Maths', 'E2EMAT', 'active', NOW(), NOW()) ON CONFLICT DO NOTHING`
|
||||
);
|
||||
} catch {
|
||||
// May already exist
|
||||
}
|
||||
|
||||
seedTeacherAssignments();
|
||||
clearCache();
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Navigation
|
||||
// ============================================================================
|
||||
test.describe('Navigation', () => {
|
||||
test('homework link appears in teacher navigation', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
const nav = page.locator('.desktop-nav');
|
||||
await expect(nav.getByRole('link', { name: /devoirs/i })).toBeVisible({ timeout: 15000 });
|
||||
});
|
||||
|
||||
test('can navigate to homework page', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
await expect(page.getByRole('heading', { name: /mes devoirs/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Empty State
|
||||
// ============================================================================
|
||||
test.describe('Empty State', () => {
|
||||
test('shows empty state when no homework exists', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
await expect(page.getByText(/aucun devoir/i)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// AC1: Create Homework
|
||||
// ============================================================================
|
||||
test.describe('AC1: Create Homework', () => {
|
||||
test('can create a new homework', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
// Open create modal
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Select class
|
||||
const classSelect = page.locator('#hw-class');
|
||||
await expect(classSelect).toBeVisible();
|
||||
await classSelect.selectOption({ index: 1 });
|
||||
|
||||
// Select subject (AC2: filtered by class)
|
||||
const subjectSelect = page.locator('#hw-subject');
|
||||
await expect(subjectSelect).toBeEnabled();
|
||||
await subjectSelect.selectOption({ index: 1 });
|
||||
|
||||
// Fill title
|
||||
await page.locator('#hw-title').fill('Exercices chapitre 5');
|
||||
|
||||
// Fill description
|
||||
await page.locator('#hw-description').fill('Pages 42-45, exercices 1 à 10');
|
||||
|
||||
// Set due date (next weekday, at least 2 days from now)
|
||||
await page.locator('#hw-due-date').fill(getNextWeekday(3));
|
||||
|
||||
// Submit
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
|
||||
// Modal closes and homework appears in list
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Exercices chapitre 5')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('cancel closes the modal without creating', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.getByRole('button', { name: /annuler/i }).click();
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// AC2: Subject Filtering
|
||||
// ============================================================================
|
||||
test.describe('AC2: Subject Filtering', () => {
|
||||
test('subject dropdown is disabled until class is selected', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Subject should be disabled initially
|
||||
await expect(page.locator('#hw-subject')).toBeDisabled();
|
||||
|
||||
// Select class
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
|
||||
// Subject should now be enabled
|
||||
await expect(page.locator('#hw-subject')).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// AC5: Published Homework Appears in List
|
||||
// ============================================================================
|
||||
test.describe('AC5: Published Homework', () => {
|
||||
test('created homework appears in list with correct info', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
// Create a homework
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
await page.locator('#hw-subject').selectOption({ index: 1 });
|
||||
await page.locator('#hw-title').fill('Devoir de mathématiques');
|
||||
await page.locator('#hw-due-date').fill(getNextWeekday(5));
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
|
||||
await expect(dialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify homework card shows
|
||||
await expect(page.getByText('Devoir de mathématiques')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText(/publié/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// AC6: Edit Homework
|
||||
// ============================================================================
|
||||
test.describe('AC6: Edit Homework', () => {
|
||||
test('can modify an existing homework', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
// First create a homework
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
await page.locator('#hw-subject').selectOption({ index: 1 });
|
||||
await page.locator('#hw-title').fill('Devoir à modifier');
|
||||
await page.locator('#hw-due-date').fill(getNextWeekday(5));
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Devoir à modifier')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click edit
|
||||
await page.getByRole('button', { name: /modifier/i }).first().click();
|
||||
const editDialog = page.getByRole('dialog');
|
||||
await expect(editDialog).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Change title
|
||||
await page.locator('#edit-title').clear();
|
||||
await page.locator('#edit-title').fill('Devoir modifié');
|
||||
|
||||
// Save
|
||||
await page.getByRole('button', { name: /enregistrer/i }).click();
|
||||
await expect(editDialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify updated title
|
||||
await expect(page.getByText('Devoir modifié')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('can delete an existing homework', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
// First create a homework
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
await page.locator('#hw-subject').selectOption({ index: 1 });
|
||||
await page.locator('#hw-title').fill('Devoir à supprimer');
|
||||
await page.locator('#hw-due-date').fill(getNextWeekday(5));
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Devoir à supprimer')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Click delete
|
||||
await page.getByRole('button', { name: /supprimer/i }).first().click();
|
||||
|
||||
// Confirm in alertdialog
|
||||
const confirmDialog = page.getByRole('alertdialog');
|
||||
await expect(confirmDialog).toBeVisible({ timeout: 5000 });
|
||||
await expect(page.getByText(/êtes-vous sûr/i)).toBeVisible();
|
||||
|
||||
await confirmDialog.getByRole('button', { name: /supprimer/i }).click();
|
||||
await expect(confirmDialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Homework should be gone from the list (or show as deleted)
|
||||
await expect(page.getByText('Devoir à supprimer')).not.toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Date Validation
|
||||
// ============================================================================
|
||||
test.describe('Date Validation', () => {
|
||||
test('due date input has min attribute set to tomorrow', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
const dueDateInput = page.locator('#hw-due-date');
|
||||
const minValue = await dueDateInput.getAttribute('min');
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const ey = tomorrow.getFullYear();
|
||||
const em = String(tomorrow.getMonth() + 1).padStart(2, '0');
|
||||
const ed = String(tomorrow.getDate()).padStart(2, '0');
|
||||
expect(minValue).toBe(`${ey}-${em}-${ed}`);
|
||||
});
|
||||
|
||||
test('backend rejects a past due date', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
await page.locator('#hw-subject').selectOption({ index: 1 });
|
||||
await page.locator('#hw-title').fill('Devoir date passée');
|
||||
await page.locator('#hw-description').fill('Test validation');
|
||||
|
||||
// Set a past date — fill() works with Svelte 5 bind:value
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const y = yesterday.getFullYear();
|
||||
const m = String(yesterday.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(yesterday.getDate()).padStart(2, '0');
|
||||
const pastDate = `${y}-${m}-${d}`;
|
||||
await page.locator('#hw-due-date').fill(pastDate);
|
||||
|
||||
// Bypass HTML native validation (min attribute) to test backend validation
|
||||
await page.locator('form.modal-body').evaluate((el) => el.setAttribute('novalidate', ''));
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
|
||||
// Backend should reject — an error alert appears
|
||||
await expect(page.locator('.alert-error')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Partial Update
|
||||
// ============================================================================
|
||||
test.describe('Partial Update', () => {
|
||||
test('can update only the title without changing other fields', async ({ page }) => {
|
||||
await loginAsTeacher(page);
|
||||
await navigateToHomework(page);
|
||||
|
||||
const dueDate = getNextWeekday(5);
|
||||
|
||||
// Create a homework with description
|
||||
await page.getByRole('button', { name: /nouveau devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).toBeVisible({ timeout: 10000 });
|
||||
await page.locator('#hw-class').selectOption({ index: 1 });
|
||||
await page.locator('#hw-subject').selectOption({ index: 1 });
|
||||
await page.locator('#hw-title').fill('Titre original');
|
||||
await page.locator('#hw-description').fill('Description inchangée');
|
||||
await page.locator('#hw-due-date').fill(dueDate);
|
||||
await page.getByRole('button', { name: /créer le devoir/i }).click();
|
||||
await expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Titre original')).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Open edit modal
|
||||
await page.getByRole('button', { name: /modifier/i }).first().click();
|
||||
const editDialog = page.getByRole('dialog');
|
||||
await expect(editDialog).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify pre-filled values
|
||||
await expect(page.locator('#edit-description')).toHaveValue('Description inchangée');
|
||||
await expect(page.locator('#edit-due-date')).toHaveValue(dueDate);
|
||||
|
||||
// Change only the title
|
||||
await page.locator('#edit-title').clear();
|
||||
await page.locator('#edit-title').fill('Titre mis à jour');
|
||||
|
||||
await page.getByRole('button', { name: /enregistrer/i }).click();
|
||||
await expect(editDialog).not.toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Verify title changed
|
||||
await expect(page.getByText('Titre mis à jour')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.getByText('Titre original')).not.toBeVisible();
|
||||
|
||||
// Verify description still visible
|
||||
await expect(page.getByText('Description inchangée')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -48,8 +48,10 @@ 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(TEST_PASSWORD);
|
||||
await page.getByRole('button', { name: /se connecter/i }).click();
|
||||
await page.waitForURL(getTenantUrl('/dashboard'), { timeout: 30000 });
|
||||
await Promise.all([
|
||||
page.waitForURL(/\/dashboard/, { timeout: 30000 }),
|
||||
page.getByRole('button', { name: /se connecter/i }).click()
|
||||
]);
|
||||
}
|
||||
|
||||
test.describe('Sessions Management', () => {
|
||||
|
||||
Reference in New Issue
Block a user