Files
bakery-ia/frontend/tests/helpers/utils.ts

156 lines
3.8 KiB
TypeScript
Raw Normal View History

2025-11-14 07:46:29 +01:00
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<T>(
action: () => Promise<T>,
maxAttempts: number = 3,
delayMs: number = 1000
): Promise<T> {
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<boolean> {
try {
await page.locator(selector).waitFor({ state: 'visible', timeout: 2000 });
return true;
} catch {
return false;
}
}