/** * E2E Test Utilities * * Shared utilities and helpers for E2E tests */ import { Page, expect, APIRequestContext } from '@playwright/test'; // Base URL for API calls const API_BASE_URL = process.env.VITE_API_URL || 'http://localhost:8000/api/v1'; /** * Navigate to a page and wait for it to be ready */ export async function navigateTo(page: Page, path: string) { await page.goto(path); await page.waitForLoadState('networkidle'); } /** * Wait for loading states to complete */ export async function waitForLoading(page: Page) { // Wait for any loading text to disappear const loadingElement = page.getByText('Loading...'); await expect(loadingElement).toHaveCount(0, { timeout: 30000 }); } /** * Wait for table to be populated */ export async function waitForTableData(page: Page, tableTestId?: string) { const tableSelector = tableTestId ? `[data-testid="${tableTestId}"] tbody tr` : 'table tbody tr'; // Wait for at least one row to be present await page.waitForSelector(tableSelector, { timeout: 10000 }); } /** * Create a scenario via API */ export async function createScenarioViaAPI( request: APIRequestContext, scenario: { name: string; description?: string; tags?: string[]; region: string; }, accessToken?: string ) { const headers: Record = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const response = await request.post(`${API_BASE_URL}/scenarios`, { data: scenario, headers: Object.keys(headers).length > 0 ? headers : undefined, }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Delete a scenario via API */ export async function deleteScenarioViaAPI( request: APIRequestContext, scenarioId: string, accessToken?: string ) { const headers: Record = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const response = await request.delete(`${API_BASE_URL}/scenarios/${scenarioId}`, { headers: Object.keys(headers).length > 0 ? headers : undefined, }); // Accept 204 (No Content) or 200 (OK) or 404 (already deleted) expect([200, 204, 404]).toContain(response.status()); } /** * Start a scenario via API */ export async function startScenarioViaAPI( request: APIRequestContext, scenarioId: string, accessToken?: string ) { const headers: Record = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/start`, { headers: Object.keys(headers).length > 0 ? headers : undefined, }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Stop a scenario via API */ export async function stopScenarioViaAPI( request: APIRequestContext, scenarioId: string, accessToken?: string ) { const headers: Record = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const response = await request.post(`${API_BASE_URL}/scenarios/${scenarioId}/stop`, { headers: Object.keys(headers).length > 0 ? headers : undefined, }); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Send test logs to a scenario */ export async function sendTestLogs( request: APIRequestContext, scenarioId: string, logs: unknown[], accessToken?: string ) { const headers: Record = {}; if (accessToken) { headers['Authorization'] = `Bearer ${accessToken}`; } const response = await request.post( `${API_BASE_URL}/scenarios/${scenarioId}/ingest`, { data: { logs }, headers: Object.keys(headers).length > 0 ? headers : undefined, } ); expect(response.ok()).toBeTruthy(); return await response.json(); } /** * Generate a unique test scenario name */ export function generateTestScenarioName(prefix = 'E2E Test'): string { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return `${prefix} ${timestamp}`; } /** * Wait for toast notification */ export async function waitForToast(page: Page, message?: string) { const toastSelector = '[data-testid="toast"]' + (message ? `:has-text("${message}")` : ''); await page.waitForSelector(toastSelector, { timeout: 10000 }); } /** * Click on a navigation link by label */ export async function clickNavigation(page: Page, label: string) { const navLink = page.locator('nav').getByRole('link', { name: label }); await navLink.click(); await page.waitForLoadState('networkidle'); } /** * Get scenario by name from the scenarios table */ export async function getScenarioRow(page: Page, scenarioName: string) { return page.locator('table tbody tr').filter({ hasText: scenarioName }); } /** * Open scenario actions dropdown */ export async function openScenarioActions(page: Page, scenarioName: string) { const row = await getScenarioRow(page, scenarioName); const actionsButton = row.locator('button').first(); await actionsButton.click(); return row.locator('[role="menu"]'); } /** * Take a screenshot with a descriptive name */ export async function takeScreenshot(page: Page, name: string) { await page.screenshot({ path: `e2e/screenshots/${name}.png`, fullPage: true, }); } /** * Check if element is visible with retry */ export async function isElementVisible( page: Page, selector: string, timeout = 5000 ): Promise { try { await page.waitForSelector(selector, { timeout, state: 'visible' }); return true; } catch { return false; } } /** * Mobile viewport helper */ export async function setMobileViewport(page: Page) { await page.setViewportSize({ width: 375, height: 667 }); } /** * Tablet viewport helper */ export async function setTabletViewport(page: Page) { await page.setViewportSize({ width: 768, height: 1024 }); } /** * Desktop viewport helper */ export async function setDesktopViewport(page: Page) { await page.setViewportSize({ width: 1280, height: 720 }); }