feat: add Playwright E2E and API regression test suite
Production-ready test infrastructure with Page Object Model pattern, reusable fixtures for auth/DB/test-data, and example tests covering login flow, dashboard, accounts CRUD API, and visual regression. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
73
tests/page-objects/BasePage.ts
Normal file
73
tests/page-objects/BasePage.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Base page object with shared helpers.
|
||||
*
|
||||
* All page objects extend this class to inherit common navigation,
|
||||
* waiting, and assertion patterns.
|
||||
*/
|
||||
|
||||
import { type Page, type Locator, expect } from '@playwright/test';
|
||||
|
||||
export abstract class BasePage {
|
||||
constructor(protected readonly page: Page) {}
|
||||
|
||||
/** The path segment for this page (e.g. '/dashboard', '/accounts') */
|
||||
abstract readonly path: string;
|
||||
|
||||
/** Navigate to this page */
|
||||
async goto(): Promise<void> {
|
||||
await this.page.goto(this.path);
|
||||
await this.waitForReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override in subclasses to wait for page-specific readiness signals.
|
||||
* Default: waits for network idle.
|
||||
*/
|
||||
async waitForReady(): Promise<void> {
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/** Assert the page URL contains the expected path */
|
||||
async assertOnPage(): Promise<void> {
|
||||
await expect(this.page).toHaveURL(new RegExp(this.path));
|
||||
}
|
||||
|
||||
/** Get the page title text (Mantine AppShell header or h1) */
|
||||
async getPageHeading(): Promise<string> {
|
||||
const heading = this.page.getByRole('heading', { level: 1 }).first();
|
||||
return heading.innerText();
|
||||
}
|
||||
|
||||
/** Wait for a Mantine notification to appear with the given text */
|
||||
async waitForNotification(text: string | RegExp): Promise<void> {
|
||||
await expect(
|
||||
this.page.locator('.mantine-Notification-root').filter({ hasText: text }),
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
}
|
||||
|
||||
/** Click a navigation link in the sidebar */
|
||||
async navigateTo(linkText: string): Promise<void> {
|
||||
await this.page.getByRole('link', { name: linkText }).click();
|
||||
await this.page.waitForLoadState('networkidle');
|
||||
}
|
||||
|
||||
/** Get a Mantine table body locator */
|
||||
get tableBody(): Locator {
|
||||
return this.page.locator('tbody');
|
||||
}
|
||||
|
||||
/** Get all table rows */
|
||||
get tableRows(): Locator {
|
||||
return this.tableBody.locator('tr');
|
||||
}
|
||||
|
||||
/** Wait for API response on a specific endpoint pattern */
|
||||
async waitForApi(urlPattern: string | RegExp): Promise<void> {
|
||||
await this.page.waitForResponse(
|
||||
(response) =>
|
||||
(typeof urlPattern === 'string'
|
||||
? response.url().includes(urlPattern)
|
||||
: urlPattern.test(response.url())) && response.status() < 400,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user