/** * E2E test: Themes CRUD operations. * * Validates that a user can: * 1. Navigate to /themes * 2. Create a new theme via the API * 3. Update the theme via the API * 4. Delete the theme via the API * 5. Verify the theme is removed from the list */ import { test, expect } from '@playwright/test'; import { loginAsUser } from '../helpers/auth'; test.describe('Theme management', () => { test('should create, edit, and delete a theme', async ({ page }) => { // Step 1: Login await loginAsUser(page); await page.goto('/themes', { waitUntil: 'domcontentloaded' }); // Step 2: Wait for page to load // The page title should be visible await expect( page.locator('h1').filter({ hasText: 'Personnaliser' }), ).toBeVisible({ timeout: 10_000 }); // Step 3: Create a new theme via the API (faster than UI interaction) // Use page.evaluate to call the themes API const createResp = 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: 'E2E Test Theme', theme: 'Testing Topic', categories: ['Category A', 'Category B'], }), }); return { status: resp.status, data: await resp.json() }; }); expect(createResp.status).toBe(201); const themeId = createResp.data.id; expect(themeId).toBeTruthy(); // Step 4: Reload the page and verify the theme appears await page.goto('/themes', { waitUntil: 'domcontentloaded' }); // The theme should be in the dropdown or visible // Wait for themes to load await page.waitForTimeout(1000); // Step 5: Update the theme via API const updateResp = await page.evaluate(async (id: string) => { const resp = await fetch(`/api/v1/themes/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, credentials: 'same-origin', body: JSON.stringify({ name: 'E2E Updated Theme' }), }); return { status: resp.status, data: await resp.json() }; }, themeId); expect(updateResp.status).toBe(200); expect(updateResp.data.name).toBe('E2E Updated Theme'); // Step 6: Create and verify schedule via API const schedResp = await page.evaluate(async (tid: string) => { const resp = await fetch(`/api/v1/themes/${tid}/schedule`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, credentials: 'same-origin', body: JSON.stringify({ enabled: true, days: ['mon', 'fri'], time_utc: '09:00', emails: ['test@example.com'], }), }); return { status: resp.status, data: await resp.json() }; }, themeId); expect(schedResp.status).toBe(200); expect(schedResp.data.days).toContain('mon'); expect(schedResp.data.days).toContain('fri'); expect(schedResp.data.emails.length).toBe(1); // Step 7: Delete schedule const delSchedResp = await page.evaluate(async (tid: string) => { const resp = await fetch(`/api/v1/themes/${tid}/schedule`, { method: 'DELETE', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', }); return { status: resp.status }; }, themeId); expect(delSchedResp.status).toBe(204); // Step 8: Delete the theme via API const deleteResp = await page.evaluate(async (id: string) => { const resp = await fetch(`/api/v1/themes/${id}`, { method: 'DELETE', headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', }); return { status: resp.status }; }, themeId); expect(deleteResp.status).toBe(204); // Step 9: Verify theme is gone const listResp = await page.evaluate(async () => { const resp = await fetch('/api/v1/themes', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, credentials: 'same-origin', }); return { status: resp.status, data: await resp.json() }; }); expect(listResp.status).toBe(200); const remaining = listResp.data.filter((t: any) => t.name === 'E2E Updated Theme'); expect(remaining.length).toBe(0); }); });