d2cef85187
- 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
192 lines
5.9 KiB
TypeScript
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('.el-dialog, .el-message-box');
|
|
const isVisible = await modal.isVisible({ timeout: 2000 });
|
|
|
|
if (isVisible) {
|
|
const confirmButton = modal.locator('.el-button--primary').first();
|
|
const cancelButton = modal.locator('.el-button--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('.el-loading-mask, .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('.el-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('.el-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 });
|
|
});
|
|
}
|
|
} |