import { Page, expect } from '@playwright/test'; /** * Helper utilities for Playwright E2E tests */ /** * Wait for LiveComponent to be ready */ export async function waitForLiveComponent(page: Page, componentId: string) { await page.waitForSelector(`[data-live-component="${componentId}"]`, { state: 'visible', timeout: 5000 }); } /** * Wait for HTMX request to complete */ export async function waitForHtmxRequest(page: Page) { await page.waitForEvent('htmx:afterRequest', { timeout: 5000 }); } /** * Fill form and submit with LiveComponent validation */ export async function fillAndSubmitForm( page: Page, formSelector: string, data: Record ) { const form = page.locator(formSelector); for (const [name, value] of Object.entries(data)) { await form.locator(`[name="${name}"]`).fill(value); } await form.locator('button[type="submit"]').click(); } /** * Assert validation error is shown */ export async function assertValidationError( page: Page, fieldName: string, expectedError?: string ) { const errorSelector = `[data-field="${fieldName}"] .error-message, .error-${fieldName}`; await expect(page.locator(errorSelector)).toBeVisible(); if (expectedError) { await expect(page.locator(errorSelector)).toContainText(expectedError); } } /** * Assert no validation errors */ export async function assertNoValidationErrors(page: Page) { const errors = page.locator('.error-message, [class*="error-"]'); await expect(errors).toHaveCount(0); } /** * Wait for success message */ export async function waitForSuccessMessage(page: Page, message?: string) { const successSelector = '.flash-success, .alert-success, [role="alert"][data-type="success"]'; await expect(page.locator(successSelector)).toBeVisible(); if (message) { await expect(page.locator(successSelector)).toContainText(message); } } /** * Login helper for authenticated tests */ export async function login(page: Page, email: string, password: string) { await page.goto('/login'); await page.fill('[name="email"]', email); await page.fill('[name="password"]', password); await page.click('button[type="submit"]'); // Wait for redirect after login await page.waitForURL('**/dashboard', { timeout: 5000 }); } /** * Logout helper */ export async function logout(page: Page) { await page.click('[data-action="logout"]'); await page.waitForURL('**/login', { timeout: 5000 }); } /** * Check if element is visible in viewport */ export async function isInViewport(page: Page, selector: string): Promise { return await page.locator(selector).evaluate((element) => { const rect = element.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); }); } /** * Scroll element into view */ export async function scrollIntoView(page: Page, selector: string) { await page.locator(selector).scrollIntoViewIfNeeded(); } /** * Upload file helper */ export async function uploadFile( page: Page, inputSelector: string, filePath: string ) { const fileInput = page.locator(inputSelector); await fileInput.setInputFiles(filePath); } /** * Wait for network idle */ export async function waitForNetworkIdle(page: Page) { await page.waitForLoadState('networkidle', { timeout: 10000 }); } /** * Clear browser storage */ export async function clearStorage(page: Page) { await page.context().clearCookies(); await page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); }); }