/** * E2E test: Sources CRUD. * * Validates: * 1. Adding a single source * 2. Bulk-importing a source via textarea * 3. Deleting a source (two-click confirm) */ import { test, expect } from '@playwright/test'; import { loginAsUser } from '../helpers/auth'; test.describe('Sources management', () => { test('should add, bulk-import, and delete sources', async ({ page }) => { // Step 1: Login as regular user via cookie injection await loginAsUser(page); // Clean up any leftover sources from previous test runs via API await page.goto('/', { waitUntil: 'domcontentloaded' }); await page.waitForLoadState('domcontentloaded'); const existingSources: { id: string }[] = await page.evaluate(async () => { const resp = await fetch('/api/v1/sources', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', }); return resp.ok ? resp.json() : []; }); for (const source of existingSources) { await page.evaluate(async (id: string) => { await fetch(`/api/v1/sources/${id}`, { method: 'DELETE', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', }); }, source.id); } // Step 2: Navigate to sources await page.goto('/sources', { waitUntil: 'domcontentloaded' }); // Wait for the sources page to load await expect( page.locator('h1', { hasText: 'Sources Personnalisees' }), ).toBeVisible({ timeout: 10_000 }); // Step 3: Add a single source await page.locator('#source-title').fill('Test Blog'); await page.locator('#source-url').fill('https://test.example.com/blog'); // Click the "Ajouter" submit button await page.locator('button[type="submit"]', { hasText: 'Ajouter' }).click(); // Wait for the source to appear in the list await expect(page.getByText('Test Blog')).toBeVisible({ timeout: 5_000 }); await expect( page.getByText('https://test.example.com/blog'), ).toBeVisible(); // Step 4: Bulk import another source via textarea const bulkTextarea = page.locator('#bulk-import'); await bulkTextarea.fill('News Site;https://news.example.com'); // Click the bulk import button await page .locator('button[type="submit"]', { hasText: 'Importer les sources' }) .click(); // Wait for the second source to appear await expect(page.getByText('News Site')).toBeVisible({ timeout: 5_000 }); // Verify we have 2 sources in the list // Each source is an
  • in the sources list const sourceItems = page.locator('ul > li').filter({ has: page.locator('.text-indigo-600'), }); await expect(sourceItems).toHaveCount(2); // Step 5: Delete the first source (Test Blog) // Find the source item containing "Test Blog" and click its delete button const testBlogItem = page.locator('li').filter({ hasText: 'Test Blog' }); // First click: enter confirm state await testBlogItem.locator('button').last().click(); // The button text should change to "Confirmer ?" await expect( testBlogItem.getByText('Confirmer ?'), ).toBeVisible({ timeout: 3_000 }); // Second click: confirm delete await testBlogItem.getByText('Confirmer ?').click(); // Wait for the source to be removed from the list await expect(page.getByText('Test Blog')).not.toBeVisible({ timeout: 5_000, }); // Step 6: Verify only 1 source remains const remainingItems = page.locator('ul > li').filter({ has: page.locator('.text-indigo-600'), }); await expect(remainingItems).toHaveCount(1); // The remaining source should be "News Site" await expect(page.getByText('News Site')).toBeVisible(); }); });