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
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>
108 lines
4.7 KiB
TypeScript
108 lines
4.7 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
import { contextForUser, latestMailFor, tokenFromMail } from './helpers';
|
|
|
|
/**
|
|
* M1 auth regression pack. Runs against a full local stack with Mailpit
|
|
* (CI job `auth-e2e`, or `deploy/compose` dev stack locally); skipped
|
|
* where no mail catcher is available (e.g. the CD smoke run against a
|
|
* deployed stage — that pipeline runs e2e/smoke.spec.ts only anyway).
|
|
*/
|
|
const MAILPIT_URL = process.env.E2E_MAILPIT_URL;
|
|
test.skip(!MAILPIT_URL, 'requires a Mailpit instance (E2E_MAILPIT_URL)');
|
|
|
|
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173';
|
|
|
|
test('full signup journey: register, mail, verify, first login', async ({ page }) => {
|
|
const stamp = Date.now().toString(36);
|
|
const username = `e2e-${stamp}`;
|
|
const email = `${username}@dorfteich.test`;
|
|
const password = 'ein sehr langes e2e passwort';
|
|
|
|
await page.goto('/signup');
|
|
await page.getByLabel(/username|benutzername/i).fill(username);
|
|
await page.getByLabel(/e-mail/i).fill(email);
|
|
await page.getByLabel(/display name|anzeigename/i).fill(username);
|
|
await page.getByLabel(/^password|^passwort/i).fill(password);
|
|
await page.getByRole('button', { name: /register|registrieren/i }).click();
|
|
|
|
await expect(page.getByRole('heading', { name: /inbox|postfach/i })).toBeVisible();
|
|
|
|
const mail = await latestMailFor(MAILPIT_URL!, email);
|
|
const token = tokenFromMail(mail.text);
|
|
|
|
await page.goto(`/verify-email?token=${token}`);
|
|
await expect(page.getByRole('heading', { name: /confirmed|bestätigt/i })).toBeVisible();
|
|
|
|
await page.getByRole('link', { name: /sign-in|anmeldung/i }).click();
|
|
await page.getByLabel(/username or e-mail|benutzername oder e-mail/i).fill(username);
|
|
await page.getByLabel(/^password|^passwort/i).fill(password);
|
|
await page.getByRole('button', { name: /sign in|anmelden/i }).click();
|
|
|
|
// Signed in: the user menu shows the display name.
|
|
await expect(page.getByRole('button', { name: username })).toBeVisible();
|
|
});
|
|
|
|
test('login rejects a wrong password with a visible error', async ({ page }) => {
|
|
await page.goto('/login');
|
|
await page.getByLabel(/username or e-mail|benutzername oder e-mail/i).fill('fixture-user');
|
|
await page.getByLabel(/^password|^passwort/i).fill('definitiv falsch');
|
|
await page.getByRole('button', { name: /sign in|anmelden/i }).click();
|
|
await expect(page.getByRole('alert')).toBeVisible();
|
|
});
|
|
|
|
test('anonymous visitors are redirected to login and return after', async ({ page }) => {
|
|
await page.goto('/settings');
|
|
await expect(page).toHaveURL(/\/login\?next=%2Fsettings/);
|
|
await page.getByLabel(/username or e-mail|benutzername oder e-mail/i).fill('fixture-user');
|
|
await page.getByLabel(/^password|^passwort/i).fill('fixture passwort 123');
|
|
await page.getByRole('button', { name: /sign in|anmelden/i }).click();
|
|
await expect(page).toHaveURL(/\/settings/);
|
|
});
|
|
|
|
test('fixture user signs out via the menu', async ({ browser }) => {
|
|
const context = await contextForUser(browser, BASE_URL, 'fixture-user');
|
|
const page = await context.newPage();
|
|
await page.goto('/');
|
|
await page.getByRole('button', { name: 'Fixture User' }).click();
|
|
await page.getByRole('menuitem', { name: /sign out|abmelden/i }).click();
|
|
await expect(page).toHaveURL(/\/login/);
|
|
await context.close();
|
|
});
|
|
|
|
test('admin menu and page are reserved for site admins', async ({ browser }) => {
|
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
|
const adminPage = await admin.newPage();
|
|
await adminPage.goto('/admin');
|
|
await expect(adminPage.getByRole('heading', { name: /administration/i })).toBeVisible();
|
|
await admin.close();
|
|
|
|
const member = await contextForUser(browser, BASE_URL, 'fixture-user');
|
|
const memberPage = await member.newPage();
|
|
await memberPage.goto('/admin');
|
|
// Non-admins are bounced to the start page.
|
|
await expect(memberPage).toHaveURL(/\/$/);
|
|
await member.close();
|
|
});
|
|
|
|
test('profile display-name change shows up in the top bar', async ({ browser }) => {
|
|
const context = await contextForUser(browser, BASE_URL, 'fixture-user');
|
|
const page = await context.newPage();
|
|
await page.goto('/settings');
|
|
const nameField = page.getByLabel(/display name|anzeigename/i);
|
|
await nameField.fill('Fixture Umbenannt');
|
|
await page
|
|
.getByRole('button', { name: /^save$|^speichern$/i })
|
|
.first()
|
|
.click();
|
|
await expect(page.getByRole('button', { name: 'Fixture Umbenannt' })).toBeVisible();
|
|
// Restore for the next run (idempotent pack).
|
|
await nameField.fill('Fixture User');
|
|
await page
|
|
.getByRole('button', { name: /^save$|^speichern$/i })
|
|
.first()
|
|
.click();
|
|
await expect(page.getByRole('button', { name: 'Fixture User' })).toBeVisible();
|
|
await context.close();
|
|
});
|