Files
HOA_Financial_Platform/tests/e2e/auth.spec.ts
JoeBot dfd1bccb89 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>
2026-04-05 12:40:05 -04:00

98 lines
3.1 KiB
TypeScript

/**
* E2E tests for the authentication flow.
*
* Tests login via UI (not reusing stored auth state).
* These tests use a fresh browser context per test.
*/
import { test, expect } from '@playwright/test';
import { LoginPage } from '../page-objects';
import { TEST_USERS } from '../fixtures/test-data';
// Don't use stored auth state — we're testing the login flow itself
test.use({ storageState: { cookies: [], origins: [] } });
test.describe('Login Page', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto();
});
test('should display login form', async () => {
await expect(loginPage.emailInput).toBeVisible();
await expect(loginPage.passwordInput).toBeVisible();
await expect(loginPage.signInButton).toBeVisible();
});
test('should show error for invalid credentials', async () => {
await loginPage.login('invalid@example.com', 'WrongPassword!');
// Wait for the error alert to appear
await loginPage.assertError(/invalid|unauthorized|failed/i);
await loginPage.assertStillOnLogin();
});
test('should show validation error for empty fields', async ({ page }) => {
// Try to submit with empty password
await loginPage.emailInput.fill('test@example.com');
await loginPage.signInButton.click();
// Should remain on login page (Mantine form validation)
await loginPage.assertStillOnLogin();
});
test('should login successfully with valid credentials', async () => {
await loginPage.loginAndWaitForRedirect(
TEST_USERS.treasurer.email,
TEST_USERS.treasurer.password,
);
// Should be redirected away from login
await expect(loginPage.page).not.toHaveURL(/\/login/);
});
test('should show register link', async () => {
await expect(loginPage.registerLink).toBeVisible();
});
test('should redirect unauthenticated users to login', async ({ page }) => {
// Try accessing a protected route directly
await page.goto('/dashboard');
// Should be redirected to login
await expect(page).toHaveURL(/\/login/);
});
});
test.describe('Logout', () => {
test('should clear auth state on logout', async ({ page }) => {
// First login
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.loginAndWaitForRedirect(
TEST_USERS.treasurer.email,
TEST_USERS.treasurer.password,
);
// Handle org selection if needed
if (page.url().includes('/select-org')) {
await page.getByRole('button').first().click();
await page.waitForURL(/\/dashboard/, { timeout: 10_000 });
}
// Look for logout in user menu/settings
const userMenu = page.locator('[class*="avatar"], [class*="Avatar"]').first();
if (await userMenu.isVisible({ timeout: 3_000 }).catch(() => false)) {
await userMenu.click();
}
const logoutButton = page.getByRole('button', { name: /logout|sign out/i }).first();
if (await logoutButton.isVisible({ timeout: 3_000 }).catch(() => false)) {
await logoutButton.click();
await expect(page).toHaveURL(/\/login/);
}
});
});