import { Page, expect } from '@playwright/test'; /** * Helper utilities for E2E tests * Navigation, wait conditions, and common actions */ /** * Navigate to page and wait for load */ export async function navigateTo(page: Page, url: string, waitUntil: 'load' | 'domcontentloaded' | 'networkidle' = 'networkidle'): Promise { await page.goto(url, { waitUntil }); } /** * Wait for element to be ready (visible and stable) */ export async function waitForElement( page: Page, selector: string, timeout: number = 5000 ): Promise { const locator = page.locator(selector); await expect(locator).toBeVisible({ timeout }); // Give DOM a moment to settle await page.waitForLoadState('networkidle'); } /** * Fill form field with clearing first */ export async function fillField( page: Page, selector: string, value: string, clear: boolean = true ): Promise { const field = page.locator(selector); if (clear) { await field.fill(''); // Clear } await field.fill(value); // Trigger change event if not triggered automatically await field.dispatchEvent('change'); } /** * Click element and wait for navigation */ export async function clickAndWait( page: Page, selector: string, waitFor: 'navigation' | 'loadstate' = 'loadstate' ): Promise { const locator = page.locator(selector); if (waitFor === 'navigation') { await Promise.all([ page.waitForNavigation(), locator.click(), ]); } else { await locator.click(); await page.waitForLoadState('networkidle'); } } /** * Get text content of element */ export async function getText(page: Page, selector: string): Promise { const locator = page.locator(selector); return locator.textContent() || ''; } /** * Get input value */ export async function getInputValue(page: Page, selector: string): Promise { const locator = page.locator(selector); const value = await locator.inputValue(); return value || ''; } /** * Check if element is visible */ export async function isElementVisible(page: Page, selector: string): Promise { const locator = page.locator(selector); return locator.isVisible(); } /** * Check if element is enabled */ export async function isElementEnabled(page: Page, selector: string): Promise { const locator = page.locator(selector); return locator.isEnabled(); } /** * Select dropdown option by text */ export async function selectOption( page: Page, selector: string, optionText: string ): Promise { const dropdown = page.locator(selector); await dropdown.selectOption({ label: optionText }); } /** * Wait for table row to contain text */ export async function waitForTableRow( page: Page, tableSelector: string, cellText: string, timeout: number = 5000 ): Promise { const row = page.locator(`${tableSelector} >> text=${cellText}`); await expect(row).toBeVisible({ timeout }); } /** * Get row data from table */ export async function getTableRowData( page: Page, rowSelector: string ): Promise> { const cells = page.locator(`${rowSelector} [data-testid*="cell"]`); const count = await cells.count(); const data: Record = {}; for (let i = 0; i < count; i++) { const cellText = await cells.nth(i).textContent(); data[`cell_${i}`] = cellText || ''; } return data; } /** * Hover over element */ export async function hoverElement(page: Page, selector: string): Promise { const locator = page.locator(selector); await locator.hover(); } /** * Double-click element */ export async function doubleClickElement(page: Page, selector: string): Promise { const locator = page.locator(selector); await locator.dblclick(); } /** * Right-click element */ export async function rightClickElement(page: Page, selector: string): Promise { const locator = page.locator(selector); await locator.click({ button: 'right' }); } /** * Scroll element into view */ export async function scrollIntoView(page: Page, selector: string): Promise { const locator = page.locator(selector); await locator.scrollIntoViewIfNeeded(); } /** * Get number of elements matching selector */ export async function getElementCount(page: Page, selector: string): Promise { const locator = page.locator(selector); return locator.count(); } /** * Wait for specific network condition */ export async function waitForNetworkIdle( page: Page, timeout: number = 5000 ): Promise { await page.waitForLoadState('networkidle', { timeout }); } /** * Extract all text from selector and split by newlines */ export async function getAllText(page: Page, selector: string): Promise { const locator = page.locator(selector); const count = await locator.count(); const texts: string[] = []; for (let i = 0; i < count; i++) { const text = await locator.nth(i).textContent(); if (text) { texts.push(text.trim()); } } return texts; } /** * Check for error/success messages */ export async function hasErrorMessage(page: Page): Promise { return isElementVisible(page, '[data-testid="toast-error"]'); } /** * Check for success message */ export async function hasSuccessMessage(page: Page): Promise { return isElementVisible(page, '[data-testid="toast-success"]'); } /** * Wait for success message to appear and disappear */ export async function waitForSuccessAndDismiss( page: Page, timeout: number = 5000 ): Promise { const successToast = page.locator('[data-testid="toast-success"]'); await expect(successToast).toBeVisible({ timeout }); await expect(successToast).not.toBeVisible({ timeout: 10000 }); } /** * Clear input field completely */ export async function clearField(page: Page, selector: string): Promise { const field = page.locator(selector); await field.focus(); await field.press('Control+A'); await field.press('Backspace'); } /** * Type text slowly (for OCR testing) */ export async function typeSlowly( page: Page, selector: string, text: string, delayMs: number = 50 ): Promise { const field = page.locator(selector); await field.focus(); for (const char of text) { await field.type(char); await page.waitForTimeout(delayMs); } } /** * Take screenshot with timestamp */ export async function takeScreenshot(page: Page, name: string): Promise { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); await page.screenshot({ path: `screenshots/${name}-${timestamp}.png` }); } /** * Get localStorage data */ export async function getLocalStorage(page: Page, key: string): Promise { return page.evaluate((k) => localStorage.getItem(k), key); } /** * Set localStorage data */ export async function setLocalStorage(page: Page, key: string, value: string): Promise { await page.evaluate((k, v) => localStorage.setItem(k, v), key, value); } /** * Clear localStorage */ export async function clearLocalStorage(page: Page): Promise { await page.evaluate(() => localStorage.clear()); } /** * Get URL pathname */ export async function getCurrentPath(page: Page): Promise { return page.evaluate(() => window.location.pathname); } /** * Wait for URL to match pattern */ export async function waitForUrl( page: Page, urlPattern: RegExp | string, timeout: number = 5000 ): Promise { if (typeof urlPattern === 'string') { await expect(page).toHaveURL(new RegExp(urlPattern), { timeout }); } else { await expect(page).toHaveURL(urlPattern, { timeout }); } } /** * Mock API response */ export async function mockApiResponse( page: Page, urlPattern: RegExp | string, responseData: any, status: number = 200 ): Promise { await page.route(urlPattern, (route) => { route.abort('blockedbyresponse'); route.continue(); }); await page.on('route', (route) => { if (route.request().url().match(urlPattern)) { route.respond({ status, contentType: 'application/json', body: JSON.stringify(responseData), }); } }); }