dorfteich/apps/web/e2e/helpers.ts
Claude Fable 5 1cea675983
Some checks failed
CD / Promote to Int (push) Blocked by required conditions
CD / Build and push images (push) Successful in 1m41s
CI / Lint, typecheck, test (push) Failing after 56s
CI / Auth e2e pack (push) Failing after 43s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Has been cancelled
Add auth e2e regression pack with fixtures and CI stack
The seed script now provisions the documented fixture matrix
(fixture-admin / fixture-user / fixture-pending, idempotent upserts,
rate-limit reset for disposable databases). A six-test Playwright pack
drives the real UI against a full local stack with Mailpit: complete
signup→mail→verify→first-login journey, wrong-password error, guarded
route redirect honoring ?next (race between the login page and the
anonymous guard fixed by teaching the guard about ?next), menu logout,
site-admin gating, and a profile rename reflected in the top bar. The
pack self-skips without E2E_MAILPIT_URL, so the CD smoke stage (now
pinned to smoke.spec.ts) stays untouched; a new CI job boots api +
web dev server against postgres/mailpit service containers and runs
the pack on every PR and push. Also fixed: the web api client choked
on empty 201 bodies. e2e/README.md documents targets and fixtures.

Closes #20

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 05:43:05 +02:00

58 lines
2.1 KiB
TypeScript

import { type Browser, type BrowserContext, request } from '@playwright/test';
export const FIXTURE_PASSWORD = 'fixture passwort 123';
/**
* Signs a fixture user in through the api and returns a browser context
* that already carries the session cookie — UI tests skip the login form
* unless the form itself is under test.
*/
export async function contextForUser(
browser: Browser,
baseURL: string,
username: string,
password: string = FIXTURE_PASSWORD,
): Promise<BrowserContext> {
const api = await request.newContext({ baseURL });
const response = await api.post('/api/v1/auth/login', {
data: { usernameOrEmail: username, password },
});
if (!response.ok()) {
throw new Error(`fixture login for ${username} failed: ${response.status()}`);
}
const storageState = await api.storageState();
await api.dispose();
return browser.newContext({ baseURL, storageState });
}
/** Latest mail for an address from the Mailpit REST api. */
export async function latestMailFor(
mailpitUrl: string,
address: string,
): Promise<{ subject: string; text: string }> {
const api = await request.newContext({ baseURL: mailpitUrl });
for (let attempt = 0; attempt < 30; attempt += 1) {
const list = await api.get('/api/v1/search', {
params: { query: `to:${address}`, limit: 1 },
});
const body = (await list.json()) as { messages?: { ID: string; Subject: string }[] };
const found = body.messages?.[0];
if (found) {
const message = await api.get(`/api/v1/message/${found.ID}`);
const details = (await message.json()) as { Text: string };
await api.dispose();
return { subject: found.Subject, text: details.Text };
}
// The outbox worker delivers every 15s — poll until it does.
await new Promise((resolve) => setTimeout(resolve, 2000));
}
await api.dispose();
throw new Error(`no mail arrived for ${address}`);
}
export function tokenFromMail(text: string): string {
const match = text.match(/token=([A-Za-z0-9_-]+)/);
if (!match) throw new Error('mail contains no token link');
return match[1]!;
}