Files
HOA_Financial_Platform/tests/fixtures/base.fixture.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

88 lines
2.6 KiB
TypeScript

/**
* Base Playwright fixture that combines auth + DB helpers.
*
* Extends the default `test` object so every test file can
* import from here and get typed access to fixtures.
*
* Usage:
* import { test, expect } from '../fixtures/base.fixture';
* test('my test', async ({ authedPage, apiContext }) => { ... });
*/
import { test as base, type Page, type APIRequestContext } from '@playwright/test';
import { apiLogin, apiSwitchOrg, authHeaders, type AuthTokens } from './auth.fixture';
import { testDb } from './db.fixture';
import { TEST_USERS } from './test-data';
const API_BASE = process.env.API_BASE_URL || 'http://localhost/api';
type TestFixtures = {
/** Pre-authenticated API request context with Bearer token */
authedRequest: APIRequestContext & { _tokens: AuthTokens };
/** Access token string for manual header construction */
accessToken: string;
};
type WorkerFixtures = {
/** Shared database connection (one per worker) */
db: typeof testDb;
};
export const test = base.extend<TestFixtures, WorkerFixtures>({
/**
* Worker-scoped database connection.
* Connects once per worker, disconnects when the worker exits.
*/
db: [
async ({}, use) => {
await testDb.connect();
await use(testDb);
await testDb.disconnect();
},
{ scope: 'worker' },
],
/**
* Per-test authenticated API request context.
* Logs in as the default test user and attaches the Bearer token.
*/
authedRequest: async ({ playwright }, use) => {
const requestContext = await playwright.request.newContext({
baseURL: API_BASE,
});
const tokens = await apiLogin(requestContext, TEST_USERS.treasurer);
// If user belongs to orgs, switch to the first one
let finalTokens = tokens;
if (tokens.organizations?.length > 0) {
const orgId = (tokens.organizations[0] as any).id;
try {
finalTokens = await apiSwitchOrg(requestContext, tokens.accessToken, orgId);
} catch {
// switch-org may not be needed if token already scoped
finalTokens = tokens;
}
}
// Create a new context with the auth header baked in
const authedContext = await playwright.request.newContext({
baseURL: API_BASE,
extraHTTPHeaders: authHeaders(finalTokens.accessToken),
});
// Attach tokens for tests that need them
(authedContext as any)._tokens = finalTokens;
await use(authedContext as any);
await authedContext.dispose();
await requestContext.dispose();
},
accessToken: async ({ authedRequest }, use) => {
await use((authedRequest as any)._tokens.accessToken);
},
});
export { expect } from '@playwright/test';