Files
gym-manage/gym-manage-web/e2e/utils/TestHelpers.ts
T
张翔 d2cef85187 docs: add test report and database reset scripts
- Add comprehensive test report (TEST_REPORT.md)
- Add database reset scripts for testing
- Update .gitignore to exclude temporary files
- Add frontend e2e test utilities and configuration
2026-04-23 16:36:12 +08:00

283 lines
8.0 KiB
TypeScript

import { Page, Locator } from '@playwright/test';
export class TestHelpers {
static async waitForElementVisible(locator: Locator, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'visible', timeout });
return true;
} catch {
return false;
}
}
static async waitForElementHidden(locator: Locator, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'hidden', timeout });
return true;
} catch {
return false;
}
}
static async safeClick(locator: Locator, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'visible', timeout });
await locator.click();
return true;
} catch (error) {
console.warn('Safe click failed:', error);
return false;
}
}
static async safeFill(locator: Locator, value: string, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'visible', timeout });
await locator.clear();
await locator.fill(value);
return true;
} catch (error) {
console.warn('Safe fill failed:', error);
return false;
}
}
static async safeSelect(locator: Locator, value: string, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'visible', timeout });
await locator.selectOption(value);
return true;
} catch (error) {
console.warn('Safe select failed:', error);
return false;
}
}
static async retryOperation<T>(
operation: () => Promise<T>,
maxRetries: number = 3,
delayMs: number = 1000
): Promise<T | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (attempt === maxRetries) {
console.error(`Operation failed after ${maxRetries} attempts:`, error);
return null;
}
console.log(`Attempt ${attempt} failed, retrying in ${delayMs}ms...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
return null;
}
static async waitForNetworkIdle(page: Page, timeout: number = 10000): Promise<void> {
try {
await page.waitForLoadState('networkidle', { timeout });
} catch (error) {
console.warn('Network idle timeout, continuing...');
}
}
static async waitForNavigation(page: Page, urlPattern: RegExp, timeout: number = 10000): Promise<boolean> {
try {
await page.waitForURL(urlPattern, { timeout });
return true;
} catch {
return false;
}
}
static async handleDialog(page: Page, action: 'accept' | 'dismiss' = 'accept'): Promise<void> {
page.on('dialog', async dialog => {
if (action === 'accept') {
await dialog.accept();
} else {
await dialog.dismiss();
}
});
}
static async getTableData(table: Locator): Promise<string[][]> {
const rows = await table.locator('tbody tr').all();
const data: string[][] = [];
for (const row of rows) {
const cells = await row.locator('td').allTextContents();
data.push(cells);
}
return data;
}
static async findTableRowByContent(table: Locator, content: string): Promise<Locator | null> {
const rows = await table.locator('tbody tr').all();
for (const row of rows) {
const textContent = await row.textContent();
if (textContent && textContent.includes(content)) {
return row;
}
}
return null;
}
static async scrollToElement(page: Page, locator: Locator): Promise<void> {
await locator.scrollIntoViewIfNeeded();
await page.waitForTimeout(300);
}
static async waitForAnimation(locator: Locator): Promise<void> {
await locator.waitFor({ state: 'attached' });
await locator.evaluate(el => {
return new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve, 300);
});
});
});
}
static async takeScreenshot(page: Page, name: string): Promise<void> {
await page.screenshot({ path: `test-results/screenshots/${name}.png`, fullPage: true });
}
static async waitForPageLoad(page: Page, timeout: number = 10000): Promise<void> {
try {
await page.waitForLoadState('load', { timeout });
} catch (error) {
console.warn('Page load timeout, continuing...');
}
}
static async waitForDOMContent(page: Page, timeout: number = 10000): Promise<void> {
try {
await page.waitForLoadState('domcontentloaded', { timeout });
} catch (error) {
console.warn('DOM content load timeout, continuing...');
}
}
static async isElementVisible(locator: Locator): Promise<boolean> {
try {
return await locator.isVisible({ timeout: 1000 });
} catch {
return false;
}
}
static async isElementEnabled(locator: Locator): Promise<boolean> {
try {
return await locator.isEnabled({ timeout: 1000 });
} catch {
return false;
}
}
static async getElementText(locator: Locator): Promise<string | null> {
try {
return await locator.textContent({ timeout: 5000 });
} catch {
return null;
}
}
static async getElementCount(locator: Locator): Promise<number> {
try {
return await locator.count();
} catch {
return 0;
}
}
static async waitForTextContent(locator: Locator, expectedText: string, timeout: number = 5000): Promise<boolean> {
try {
await locator.waitFor({ state: 'visible', timeout });
const text = await locator.textContent();
return text !== null && text.includes(expectedText);
} catch {
return false;
}
}
static async clearInput(locator: Locator): Promise<void> {
await locator.click();
await locator.fill('');
await locator.press('Control+A');
await locator.press('Backspace');
}
static async waitForSuccessMessage(page: Page, timeout: number = 5000): Promise<boolean> {
const successMessage = page.locator('.el-message--success, .success-message, [class*="success"]');
try {
await successMessage.waitFor({ state: 'visible', timeout });
return true;
} catch {
return false;
}
}
static async waitForErrorMessage(page: Page, timeout: number = 5000): Promise<boolean> {
const errorMessage = page.locator('.el-message--error, .error-message, [class*="error"]');
try {
await errorMessage.waitFor({ state: 'visible', timeout });
return true;
} catch {
return false;
}
}
static async waitForLoadingComplete(page: Page, timeout: number = 10000): Promise<void> {
const loadingSpinner = page.locator('.el-loading-mask, .loading, [class*="loading"]');
try {
await loadingSpinner.waitFor({ state: 'visible', timeout: 2000 });
await loadingSpinner.waitFor({ state: 'hidden', timeout });
} catch {
console.log('No loading spinner found or already hidden');
}
}
static async waitForModal(page: Page, timeout: number = 5000): Promise<boolean> {
const modal = page.locator('.el-dialog, .modal, [role="dialog"]');
try {
await modal.waitFor({ state: 'visible', timeout });
return true;
} catch {
return false;
}
}
static async closeModal(page: Page): Promise<boolean> {
const closeButton = page.locator('.el-dialog__close, .modal-close, button[aria-label="Close"]');
try {
await closeButton.click();
return true;
} catch {
return false;
}
}
static async waitForSelectDropdown(page: Page, timeout: number = 5000): Promise<boolean> {
const dropdown = page.locator('.el-select-dropdown, .select-dropdown');
try {
await dropdown.waitFor({ state: 'visible', timeout });
return true;
} catch {
return false;
}
}
static async selectFromDropdown(page: Page, value: string): Promise<boolean> {
const option = page.locator('.el-select-dropdown__item, .select-option').filter({ hasText: value });
try {
await option.click();
return true;
} catch {
return false;
}
}
}