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(); // Surface the server answer in CI logs instead of a bare URL mismatch. const alert = page.getByRole('alert'); await Promise.race([ page.waitForURL(/\/settings/), alert.waitFor().then(async () => { throw new Error(`login failed with: ${await alert.textContent()}`); }), ]); }); 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(); });