// @ts-check import { test, expect } from '@playwright/test'; test.describe('@p0 Reset Password — exact UI flow', () => { test.setTimeout(20000); test('mismatch passwords → inline error, no request', async ({ page }) => { // Route with a token param (the component checks presence only) await page.goto('/reset-password/abcd1234', { waitUntil: 'networkidle' }); const newPw = page.getByLabel(/^New password$/i).or(page.locator('input[type="password"]').first()); const confirm = page.getByLabel(/^Confirm password$/i).or(page.locator('input[type="password"]').nth(1)); const submit = page.getByRole('button', { name: /^Update password$/i }); await expect(newPw).toBeVisible(); await newPw.fill('Str0ng!Passw0rd'); await confirm.fill('Different!Pass'); // Ensure no network call is made when mismatch let sawConfirmCall = false; page.on('request', (req) => { if (req.method() === 'POST' && /\/api\/auth\/password-reset\/confirm$/.test(req.url())) { sawConfirmCall = true; } }); await submit.click(); await expect(page.getByText(/^Passwords do not match\.$/i)).toBeVisible(); expect(sawConfirmCall).toBeFalsy(); }); test('success path → “Password updated” → Go to Sign In navigates', async ({ page }) => { await page.goto('/reset-password/successToken123', { waitUntil: 'networkidle' }); // Intercept confirm endpoint with 200 OK await page.route('**/api/auth/password-reset/confirm', async (route) => { const req = route.request(); expect(req.method()).toBe('POST'); const body = JSON.parse(req.postData() || '{}'); // basic shape check expect(body).toHaveProperty('token'); expect(body).toHaveProperty('password'); await route.fulfill({ status: 200, contentType: 'application/json', body: '{}' }); }); const newPw = page.getByLabel(/^New password$/i).or(page.locator('input[type="password"]').first()); const confirm = page.getByLabel(/^Confirm password$/i).or(page.locator('input[type="password"]').nth(1)); const submit = page.getByRole('button', { name: /^Update password$/i }); await newPw.fill('Str0ng!Passw0rd$'); await confirm.fill('Str0ng!Passw0rd$'); await submit.click(); await expect(page.getByRole('heading', { name: /^Password updated$/i })).toBeVisible({ timeout: 10000 }); const goSignin = page.getByRole('button', { name: /^Go to Sign In$/i }); await expect(goSignin).toBeVisible(); await goSignin.click(); await expect(page).toHaveURL(/\/signin(\?|$)/, { timeout: 10000 }); }); test('rate limited (429) → shows friendly error and stays on form', async ({ page }) => { await page.goto('/reset-password/ratelimitToken', { waitUntil: 'networkidle' }); // Intercept with 429 (Too Many Requests) await page.route('**/api/auth/password-reset/confirm', async (route) => { await route.fulfill({ status: 429, contentType: 'application/json', body: '{}' }); }); const newPw = page.getByLabel(/^New password$/i).or(page.locator('input[type="password"]').first()); const confirm = page.getByLabel(/^Confirm password$/i).or(page.locator('input[type="password"]').nth(1)); const submit = page.getByRole('button', { name: /^Update password$/i }); await newPw.fill('Str0ng!Passw0rd$'); await confirm.fill('Str0ng!Passw0rd$'); await submit.click(); await expect( page.getByText(/Too many attempts\. Please wait ~30 seconds and try again\./i) ).toBeVisible({ timeout: 5000 }); // Still on form (not the success screen) await expect(page.getByRole('heading', { name: /^Set a new password$/i })).toBeVisible(); }); });