Files
tfm_ainventory/frontend/e2e/utils/helpers.ts

344 lines
8.0 KiB
TypeScript

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<void> {
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<void> {
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<void> {
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<void> {
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<string> {
const locator = page.locator(selector);
return locator.textContent() || '';
}
/**
* Get input value
*/
export async function getInputValue(page: Page, selector: string): Promise<string> {
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<boolean> {
const locator = page.locator(selector);
return locator.isVisible();
}
/**
* Check if element is enabled
*/
export async function isElementEnabled(page: Page, selector: string): Promise<boolean> {
const locator = page.locator(selector);
return locator.isEnabled();
}
/**
* Select dropdown option by text
*/
export async function selectOption(
page: Page,
selector: string,
optionText: string
): Promise<void> {
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<void> {
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<Record<string, string>> {
const cells = page.locator(`${rowSelector} [data-testid*="cell"]`);
const count = await cells.count();
const data: Record<string, string> = {};
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<void> {
const locator = page.locator(selector);
await locator.hover();
}
/**
* Double-click element
*/
export async function doubleClickElement(page: Page, selector: string): Promise<void> {
const locator = page.locator(selector);
await locator.dblclick();
}
/**
* Right-click element
*/
export async function rightClickElement(page: Page, selector: string): Promise<void> {
const locator = page.locator(selector);
await locator.click({ button: 'right' });
}
/**
* Scroll element into view
*/
export async function scrollIntoView(page: Page, selector: string): Promise<void> {
const locator = page.locator(selector);
await locator.scrollIntoViewIfNeeded();
}
/**
* Get number of elements matching selector
*/
export async function getElementCount(page: Page, selector: string): Promise<number> {
const locator = page.locator(selector);
return locator.count();
}
/**
* Wait for specific network condition
*/
export async function waitForNetworkIdle(
page: Page,
timeout: number = 5000
): Promise<void> {
await page.waitForLoadState('networkidle', { timeout });
}
/**
* Extract all text from selector and split by newlines
*/
export async function getAllText(page: Page, selector: string): Promise<string[]> {
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<boolean> {
return isElementVisible(page, '[data-testid="toast-error"]');
}
/**
* Check for success message
*/
export async function hasSuccessMessage(page: Page): Promise<boolean> {
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<void> {
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<void> {
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<void> {
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<void> {
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<string | null> {
return page.evaluate((k) => localStorage.getItem(k), key);
}
/**
* Set localStorage data
*/
export async function setLocalStorage(page: Page, key: string, value: string): Promise<void> {
await page.evaluate((k, v) => localStorage.setItem(k, v), key, value);
}
/**
* Clear localStorage
*/
export async function clearLocalStorage(page: Page): Promise<void> {
await page.evaluate(() => localStorage.clear());
}
/**
* Get URL pathname
*/
export async function getCurrentPath(page: Page): Promise<string> {
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<void> {
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<void> {
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),
});
}
});
}