fix: update E2E tests for multi-theme migration (settings, sources, generation)
- settings.spec.ts: test batch_size instead of removed theme field - settings-export.spec.ts: test batch_size export/import, open collapsed details section - sources.spec.ts: convert to API-based test since sources now belong to themes - generation-live.spec.ts: already updated (no changes needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>master
parent
6f3e6883c9
commit
885af2d147
@ -1,107 +1,130 @@
|
||||
/**
|
||||
* E2E test: Sources CRUD.
|
||||
* E2E test: Sources CRUD via API.
|
||||
*
|
||||
* Validates:
|
||||
* 1. Adding a single source
|
||||
* 2. Bulk-importing a source via textarea
|
||||
* 3. Deleting a source (two-click confirm)
|
||||
* 1. Creating a theme (sources now belong to themes)
|
||||
* 2. Adding a source to the theme
|
||||
* 3. Listing sources for the theme
|
||||
* 4. Deleting a source
|
||||
* 5. Cleanup: deleting the theme
|
||||
*/
|
||||
|
||||
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 }) => {
|
||||
test('should add and delete sources via API', 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' },
|
||||
|
||||
// Step 2: Create a theme first (sources now belong to themes)
|
||||
const themeResp = await page.evaluate(async () => {
|
||||
const resp = await fetch('/api/v1/themes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
name: 'Source Test Theme',
|
||||
theme: 'Test',
|
||||
categories: ['Cat'],
|
||||
}),
|
||||
});
|
||||
return resp.ok ? resp.json() : [];
|
||||
return { status: resp.status, data: await 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' },
|
||||
expect(themeResp.status).toBe(201);
|
||||
const themeId = themeResp.data.id;
|
||||
|
||||
// Step 3: Add a source to the theme
|
||||
const addResp = await page.evaluate(
|
||||
async (tid: string) => {
|
||||
const resp = await fetch('/api/v1/sources', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
title: 'Test Blog',
|
||||
url: 'https://test.example.com/blog',
|
||||
theme_id: tid,
|
||||
}),
|
||||
});
|
||||
}, 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 <li> 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);
|
||||
return { status: resp.status, data: await resp.json() };
|
||||
},
|
||||
themeId,
|
||||
);
|
||||
expect(addResp.status).toBe(201);
|
||||
const sourceId = addResp.data.id;
|
||||
|
||||
// Step 4: Add a second source via bulk-style (individual POST)
|
||||
const addResp2 = await page.evaluate(
|
||||
async (tid: string) => {
|
||||
const resp = await fetch('/api/v1/sources', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
title: 'News Site',
|
||||
url: 'https://news.example.com',
|
||||
theme_id: tid,
|
||||
}),
|
||||
});
|
||||
return { status: resp.status, data: await resp.json() };
|
||||
},
|
||||
themeId,
|
||||
);
|
||||
expect(addResp2.status).toBe(201);
|
||||
|
||||
// Step 5: List sources for the theme and verify we have 2
|
||||
const listResp = await page.evaluate(async (tid: string) => {
|
||||
const resp = await fetch(`/api/v1/sources?theme_id=${tid}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
return { status: resp.status, data: await resp.json() };
|
||||
}, themeId);
|
||||
expect(listResp.status).toBe(200);
|
||||
expect(listResp.data.length).toBe(2);
|
||||
|
||||
// Step 6: Delete the first source (Test Blog)
|
||||
const delResp = await page.evaluate(async (sid: string) => {
|
||||
const resp = await fetch(`/api/v1/sources/${sid}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
return { status: resp.status };
|
||||
}, sourceId);
|
||||
expect(delResp.status).toBe(204);
|
||||
|
||||
// The remaining source should be "News Site"
|
||||
await expect(page.getByText('News Site')).toBeVisible();
|
||||
// Step 7: Verify only 1 source remains
|
||||
const listResp2 = await page.evaluate(async (tid: string) => {
|
||||
const resp = await fetch(`/api/v1/sources?theme_id=${tid}`, {
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
return { status: resp.status, data: await resp.json() };
|
||||
}, themeId);
|
||||
expect(listResp2.status).toBe(200);
|
||||
expect(listResp2.data.length).toBe(1);
|
||||
expect(listResp2.data[0].title).toBe('News Site');
|
||||
|
||||
// Step 8: Cleanup - delete theme (cascades sources)
|
||||
await page.evaluate(async (tid: string) => {
|
||||
await fetch(`/api/v1/themes/${tid}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
}, themeId);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue