import { Page, expect } from '@playwright/test'; /** * General utility functions for Playwright tests */ /** * Waits for the loading spinner to disappear */ export async function waitForLoadingToFinish(page: Page) { // Adjust selectors based on your loading indicators await page.waitForSelector('[data-testid="loading"], .loading, .spinner', { state: 'hidden', timeout: 10000, }).catch(() => { // If no loading indicator found, that's fine }); } /** * Waits for an API call to complete */ export async function waitForApiCall(page: Page, urlPattern: string | RegExp) { return page.waitForResponse( (response) => { const url = response.url(); if (typeof urlPattern === 'string') { return url.includes(urlPattern); } return urlPattern.test(url); }, { timeout: 15000 } ); } /** * Mocks an API endpoint with a custom response */ export async function mockApiResponse( page: Page, urlPattern: string | RegExp, response: any, statusCode: number = 200 ) { await page.route(urlPattern, async (route) => { await route.fulfill({ status: statusCode, contentType: 'application/json', body: JSON.stringify(response), }); }); } /** * Checks if a toast/notification is visible */ export async function expectToastMessage(page: Page, message: string | RegExp) { // Adjust selector based on your toast implementation const toast = page.locator('[role="alert"], .toast, .notification'); await expect(toast).toContainText(message, { timeout: 5000 }); } /** * Fills a form field by label */ export async function fillFormField(page: Page, label: string | RegExp, value: string) { await page.getByLabel(label).fill(value); } /** * Selects an option from a dropdown by label */ export async function selectDropdownOption(page: Page, label: string | RegExp, value: string) { await page.getByLabel(label).selectOption(value); } /** * Uploads a file to a file input */ export async function uploadFile(page: Page, inputSelector: string, filePath: string) { const fileInput = page.locator(inputSelector); await fileInput.setInputFiles(filePath); } /** * Scrolls an element into view */ export async function scrollIntoView(page: Page, selector: string) { await page.locator(selector).scrollIntoViewIfNeeded(); } /** * Takes a screenshot with a custom name */ export async function takeScreenshot(page: Page, name: string) { await page.screenshot({ path: `test-results/screenshots/${name}.png`, fullPage: true }); } /** * Waits for navigation to complete */ export async function waitForNavigation(page: Page, urlPattern?: string | RegExp) { if (urlPattern) { await page.waitForURL(urlPattern); } else { await page.waitForLoadState('networkidle'); } } /** * Generates a unique test identifier */ export function generateTestId(prefix: string = 'test'): string { return `${prefix}-${Date.now()}-${Math.random().toString(36).substring(7)}`; } /** * Waits for a specific amount of time (use sparingly, prefer waitFor* methods) */ export async function wait(ms: number) { await new Promise((resolve) => setTimeout(resolve, ms)); } /** * Retries an action until it succeeds or max attempts reached */ export async function retryAction( action: () => Promise, maxAttempts: number = 3, delayMs: number = 1000 ): Promise { for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await action(); } catch (error) { if (attempt === maxAttempts) { throw error; } await wait(delayMs); } } throw new Error('Retry action failed'); } /** * Checks if an element is visible on the page */ export async function isVisible(page: Page, selector: string): Promise { try { await page.locator(selector).waitFor({ state: 'visible', timeout: 2000 }); return true; } catch { return false; } }