import { test, expect } from '@playwright/test'; /** * Password-reset E2E tests. * * These tests run WITHOUT a stored session because they test unauthenticated flows. * * They rely on the "e2e" Spring profile being active in CI (see playwright.config.ts / * docker-compose.e2e.yml). The profile exposes GET /api/auth/reset-token-for-test?email= * so we can retrieve the generated token without a real mail server. */ test.use({ storageState: { cookies: [], origins: [] } }); // The backend is accessible directly for E2E helper calls (no SvelteKit proxy needed). const BACKEND_URL = process.env.E2E_BACKEND_URL ?? 'http://localhost:8080'; async function getResetToken(email: string): Promise { const res = await fetch( `${BACKEND_URL}/api/auth/reset-token-for-test?email=${encodeURIComponent(email)}` ); if (!res.ok) throw new Error(`Could not retrieve reset token for ${email}: ${res.status}`); return res.text(); } test.describe('Password reset', () => { test('forgot-password page is accessible without login', async ({ page }) => { await page.goto('/forgot-password'); await expect(page).toHaveURL('/forgot-password'); await expect(page.getByRole('heading', { name: /Passwort vergessen/i })).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/password-reset-form.png' }); }); test('forgot-password shows success banner for any email (prevents user enumeration)', async ({ page }) => { await page.goto('/forgot-password'); await page.getByLabel(/E-Mail/i).fill('nonexistent@example.com'); await page.getByRole('button', { name: /Link anfordern/i }).click(); // Always shows success — never reveals whether the email exists await expect(page.locator('.bg-green-50')).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/password-reset-success-banner.png' }); }); test('full password reset flow', async ({ page }) => { // Uses a dedicated low-privilege test account so the admin account is never touched. const testEmail = 'reset@familyarchive.local'; const originalPassword = 'reset123'; const newPassword = 'NewP@ssw0rd_E2E!'; // 1. Request reset await page.goto('/forgot-password'); await page.getByLabel(/E-Mail/i).fill(testEmail); await page.getByRole('button', { name: /Link anfordern/i }).click(); await expect(page.locator('.bg-green-50')).toBeVisible(); // 2. Fetch the token via the test helper endpoint const token = await getResetToken(testEmail); expect(token.length).toBeGreaterThan(0); // 3. Open the reset-password page with the token await page.goto(`/reset-password?token=${token}`); await expect(page.getByRole('heading', { name: /Neues Passwort/i })).toBeVisible(); await page.getByLabel(/^Neues Passwort$/i).fill(newPassword); await page.getByLabel(/Passwort bestätigen/i).fill(newPassword); await page.getByRole('button', { name: /Passwort speichern/i }).click(); // 4. Success banner — then navigate to login await expect(page.locator('.bg-green-50')).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/password-reset-changed.png' }); await page.getByRole('link', { name: /Zurück zum Login/i }).click(); // 5. Log in with new password await expect(page).toHaveURL(/\/login/); await page.getByLabel('Benutzername').fill(testEmail); await page.getByLabel('Passwort').fill(newPassword); await page.getByRole('button', { name: 'Anmelden' }).click(); await expect(page).toHaveURL('/'); // 6. Restore original password via profile page await page.goto('/profile'); await page.locator('input[name="currentPassword"]').fill(newPassword); await page.locator('input[name="newPassword"]').fill(originalPassword); await page.locator('input[name="confirmPassword"]').fill(originalPassword); await page.getByTestId('submit-password').click(); // After changing password, auth_token is stale → redirect to login await expect(page).toHaveURL(/\/login/); // 7. Log back in with original password to confirm restore worked await page.getByLabel('Benutzername').fill(testEmail); await page.getByLabel('Passwort').fill(originalPassword); await page.getByRole('button', { name: 'Anmelden' }).click(); await expect(page).toHaveURL('/'); await page.screenshot({ path: 'test-results/e2e/password-reset-restored.png' }); }); test('reset-password page shows error for invalid token', async ({ page }) => { await page.goto('/reset-password?token=invalidtoken000'); await page.getByLabel(/^Neues Passwort$/i).fill('somepassword'); await page.getByLabel(/Passwort bestätigen/i).fill('somepassword'); await page.getByRole('button', { name: /Passwort speichern/i }).click(); await expect(page.locator('.text-red-600')).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/password-reset-invalid-token.png' }); }); test('reset-password page shows mismatch error when passwords differ', async ({ page }) => { await page.goto('/reset-password?token=anytoken'); await page.getByLabel(/^Neues Passwort$/i).fill('password1'); await page.getByLabel(/Passwort bestätigen/i).fill('password2'); await page.getByRole('button', { name: /Passwort speichern/i }).click(); await expect(page.locator('.text-red-600')).toBeVisible(); await page.screenshot({ path: 'test-results/e2e/password-reset-mismatch.png' }); }); });