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>
109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
/**
|
|
* Authentication fixture for E2E tests.
|
|
*
|
|
* Provides helpers to authenticate via the API and manage JWT tokens.
|
|
* The auth.setup.ts project uses this to create persistent auth state
|
|
* that other tests reuse (avoiding login in every test).
|
|
*/
|
|
|
|
import { type APIRequestContext, type BrowserContext } from '@playwright/test';
|
|
import { TEST_USERS } from './test-data';
|
|
|
|
const API_BASE = process.env.API_BASE_URL || 'http://localhost/api';
|
|
|
|
export interface AuthTokens {
|
|
accessToken: string;
|
|
user: Record<string, unknown>;
|
|
organizations: Array<Record<string, unknown>>;
|
|
}
|
|
|
|
/**
|
|
* Login via the API and return tokens.
|
|
* Uses Playwright's request context (no browser needed).
|
|
*/
|
|
export async function apiLogin(
|
|
request: APIRequestContext,
|
|
credentials: { email: string; password: string } = TEST_USERS.treasurer,
|
|
): Promise<AuthTokens> {
|
|
const response = await request.post(`${API_BASE}/auth/login`, {
|
|
data: {
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
},
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const body = await response.text();
|
|
throw new Error(`Login failed (${response.status()}): ${body}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Switch to a specific organization after login.
|
|
*/
|
|
export async function apiSwitchOrg(
|
|
request: APIRequestContext,
|
|
accessToken: string,
|
|
organizationId: string,
|
|
): Promise<AuthTokens> {
|
|
const response = await request.post(`${API_BASE}/auth/switch-org`, {
|
|
data: { organizationId },
|
|
headers: { Authorization: `Bearer ${accessToken}` },
|
|
});
|
|
|
|
if (!response.ok()) {
|
|
const body = await response.text();
|
|
throw new Error(`Switch org failed (${response.status()}): ${body}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Create an authenticated API request context with a Bearer token.
|
|
* Useful for API-level tests that don't need a browser.
|
|
*/
|
|
export function authHeaders(accessToken: string) {
|
|
return {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
'Content-Type': 'application/json',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Inject auth state into a browser context's localStorage.
|
|
* Matches the frontend's Zustand authStore shape.
|
|
*/
|
|
export async function injectAuthState(
|
|
context: BrowserContext,
|
|
tokens: AuthTokens,
|
|
orgId?: string,
|
|
): Promise<void> {
|
|
const baseURL = process.env.BASE_URL || 'http://localhost';
|
|
|
|
// The frontend uses Zustand with persist middleware in localStorage
|
|
// under key 'auth-storage'. We inject it so the frontend thinks
|
|
// the user is already logged in.
|
|
const selectedOrg = orgId
|
|
? tokens.organizations.find((o: any) => o.id === orgId)
|
|
: tokens.organizations[0];
|
|
|
|
const authState = {
|
|
state: {
|
|
token: tokens.accessToken,
|
|
user: tokens.user,
|
|
organizations: tokens.organizations,
|
|
currentOrg: selectedOrg
|
|
? { id: (selectedOrg as any).id, name: (selectedOrg as any).name, role: (selectedOrg as any).role }
|
|
: null,
|
|
},
|
|
version: 0,
|
|
};
|
|
|
|
await context.addInitScript((authData) => {
|
|
window.localStorage.setItem('auth-storage', JSON.stringify(authData));
|
|
}, authState);
|
|
}
|