dev1/tests/e2e/12-reset-password.spec.mjs

86 lines
3.6 KiB
JavaScript

// @ts-check
import { test, expect } from '@playwright/test';
test.describe('@p0 Reset Password — exact UI flow', () => {
test.setTimeout(15000);
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();
});
});