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>
88 lines
2.6 KiB
TypeScript
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';
|