Files
novalon-manage-system/novalon-manage-web/e2e/helpers/TestStabilityHelper.ts
T
张翔 bd21e2d1f7 test: E2E 测试用例更新与新增
- 更新 Page Object 模型适配新字段名
- 新增 UAT 测试套件与 journey 测试用例
- 优化测试辅助工具与数据工厂
- 更新 playwright 认证状态
2026-05-06 14:17:51 +08:00

192 lines
5.9 KiB
TypeScript

import { Page, expect } from '@playwright/test';
export class TestStabilityHelper {
private readonly page: Page;
private readonly maxRetries: number = 3;
private readonly retryDelay: number = 1000;
constructor(page: Page) {
this.page = page;
}
async waitForNetworkIdle(timeout: number = 30000): Promise<void> {
try {
await this.page.waitForLoadState('networkidle', { timeout });
} catch (error) {
console.log('Network idle timeout, continuing anyway');
}
}
async waitForElementVisible(selector: string, timeout: number = 10000): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await expect(element).toBeVisible({ timeout });
});
}
async safeClick(selector: string): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await element.waitFor({ state: 'visible', timeout: 10000 });
await element.click({ timeout: 5000 });
});
}
async safeFill(selector: string, value: string): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await element.waitFor({ state: 'visible', timeout: 10000 });
await element.clear();
await element.fill(value);
});
}
async safeSelect(selector: string, value: string): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await element.waitFor({ state: 'visible', timeout: 10000 });
await element.selectOption(value);
});
}
async waitForURL(urlPattern: RegExp | string, timeout: number = 30000): Promise<void> {
await this.retry(async () => {
await this.page.waitForURL(urlPattern, { timeout });
});
}
async handleModal(): Promise<void> {
try {
const modal = this.page.locator('.ant-modal, .ant-message-box');
const isVisible = await modal.isVisible({ timeout: 2000 });
if (isVisible) {
const confirmButton = modal.locator('.ant-btn--primary').first();
const cancelButton = modal.locator('.ant-btn--default').first();
if (await confirmButton.isVisible({ timeout: 1000 })) {
await confirmButton.click();
} else if (await cancelButton.isVisible({ timeout: 1000 })) {
await cancelButton.click();
}
}
} catch (error) {
console.log('No modal found or modal handling failed');
}
}
async waitForLoadingComplete(): Promise<void> {
try {
const loading = this.page.locator('.ant-spin-container, .loading');
await loading.waitFor({ state: 'hidden', timeout: 10000 });
} catch (error) {
console.log('Loading element not found or timeout');
}
}
async safeNavigate(url: string): Promise<void> {
await this.retry(async () => {
await this.page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
});
}
async waitForTableData(tableSelector: string, minRows: number = 1): Promise<void> {
await this.retry(async () => {
const table = this.page.locator(tableSelector);
await expect(table).toBeVisible({ timeout: 10000 });
const rows = table.locator('.ant-table__row');
const rowCount = await rows.count();
expect(rowCount).toBeGreaterThanOrEqual(minRows);
});
}
async safeScrollIntoView(selector: string): Promise<void> {
const element = this.page.locator(selector);
await element.scrollIntoViewIfNeeded();
await this.page.waitForTimeout(500);
}
async clearLocalStorage(): Promise<void> {
await this.page.evaluate(() => {
localStorage.clear();
});
}
async clearSessionStorage(): Promise<void> {
await this.page.evaluate(() => {
sessionStorage.clear();
});
}
async takeScreenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `test-results/screenshots/${name}.png`, fullPage: true });
}
async getErrorMessage(): Promise<string | null> {
try {
const errorElement = this.page.locator('.ant-message-error, .error-message');
const isVisible = await errorElement.isVisible({ timeout: 2000 });
if (isVisible) {
return await errorElement.textContent();
}
return null;
} catch (error) {
return null;
}
}
async hasErrorMessage(): Promise<boolean> {
const errorMessage = await this.getErrorMessage();
return errorMessage !== null;
}
private async retry<T>(fn: () => Promise<T>): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
console.log(`Attempt ${attempt} failed, retrying...`, error);
if (attempt < this.maxRetries) {
await this.page.waitForTimeout(this.retryDelay);
}
}
}
throw lastError || new Error('All retry attempts failed');
}
async waitForElementNotVisible(selector: string, timeout: number = 10000): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await expect(element).not.toBeVisible({ timeout });
});
}
async safeHover(selector: string): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await element.waitFor({ state: 'visible', timeout: 10000 });
await element.hover({ timeout: 5000 });
});
}
async waitForText(selector: string, text: string, timeout: number = 10000): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await expect(element).toContainText(text, { timeout });
});
}
async waitForTextNotPresent(selector: string, text: string, timeout: number = 10000): Promise<void> {
await this.retry(async () => {
const element = this.page.locator(selector);
await expect(element).not.toContainText(text, { timeout });
});
}
}