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>
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
/**
|
|
* Page object for the Login page (/login).
|
|
*
|
|
* Maps to: frontend/src/pages/auth/LoginPage.tsx
|
|
* Auth: POST /api/auth/login (Passport local strategy)
|
|
*/
|
|
|
|
import { type Page, expect } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
|
|
export class LoginPage extends BasePage {
|
|
readonly path = '/login';
|
|
|
|
// ─── Locators (resilient, role/label based) ──────────────────
|
|
get emailInput() {
|
|
return this.page.getByLabel('Email');
|
|
}
|
|
|
|
get passwordInput() {
|
|
return this.page.getByLabel('Password');
|
|
}
|
|
|
|
get signInButton() {
|
|
return this.page.getByRole('button', { name: 'Sign in' });
|
|
}
|
|
|
|
get passkeyButton() {
|
|
return this.page.getByRole('button', { name: /passkey/i });
|
|
}
|
|
|
|
get errorAlert() {
|
|
return this.page.getByRole('alert');
|
|
}
|
|
|
|
get registerLink() {
|
|
return this.page.getByRole('link', { name: /register/i });
|
|
}
|
|
|
|
// ─── MFA locators ────────────────────────────────────────────
|
|
get mfaHeading() {
|
|
return this.page.getByText('Two-Factor Authentication');
|
|
}
|
|
|
|
get mfaPinInput() {
|
|
return this.page.locator('.mantine-PinInput-input').first();
|
|
}
|
|
|
|
get mfaVerifyButton() {
|
|
return this.page.getByRole('button', { name: 'Verify' });
|
|
}
|
|
|
|
// ─── Actions ─────────────────────────────────────────────────
|
|
|
|
/** Fill and submit login credentials */
|
|
async login(email: string, password: string): Promise<void> {
|
|
await this.emailInput.fill(email);
|
|
await this.passwordInput.fill(password);
|
|
await this.signInButton.click();
|
|
}
|
|
|
|
/** Login and wait for successful redirect */
|
|
async loginAndWaitForRedirect(
|
|
email: string,
|
|
password: string,
|
|
): Promise<void> {
|
|
await this.login(email, password);
|
|
await this.page.waitForURL(/\/(select-org|dashboard|admin|onboarding)/, {
|
|
timeout: 15_000,
|
|
});
|
|
}
|
|
|
|
/** Assert an error message is displayed */
|
|
async assertError(message: string | RegExp): Promise<void> {
|
|
await expect(this.errorAlert).toContainText(message);
|
|
}
|
|
|
|
/** Assert we're still on the login page */
|
|
async assertStillOnLogin(): Promise<void> {
|
|
await expect(this.page).toHaveURL(/\/login/);
|
|
}
|
|
|
|
override async waitForReady(): Promise<void> {
|
|
await expect(this.signInButton).toBeVisible();
|
|
}
|
|
}
|