// @ts-check import { test, expect } from '@playwright/test'; import { loadTestUser } from '../utils/testUser.js'; test.describe('@p0 Profile — ChangePasswordForm submit', () => { test.setTimeout(20000); test('success path closes the form; 429 path shows error and stays open', async ({ page }) => { const u = loadTestUser(); // First POST => 200; second => 429 (no endpoint guessing needed) let calls = 0; await page.route('**/api/**password**', async (route) => { calls += 1; if (calls === 1) return route.fulfill({ status: 200, contentType: 'application/json', body: '{}' }); if (calls === 2) return route.fulfill({ status: 429, contentType: 'application/json', body: JSON.stringify({ error: 'Too many attempts' }) }); return route.fallback(); }); // Sign in (your existing flow) await page.context().clearCookies(); await page.goto('/signin', { waitUntil: 'networkidle' }); await page.getByPlaceholder('Username', { exact: true }).fill(u.username); await page.getByPlaceholder('Password', { exact: true }).fill(u.password); await page.getByRole('button', { name: /^Sign In$/ }).click(); await page.waitForURL('**/signin-landing**', { timeout: 15000 }); await page.goto('/profile', { waitUntil: 'networkidle' }); // If already open, close to make deterministic const cancelIfOpen = page.getByRole('button', { name: /^Cancel password change$/i }); if (await cancelIfOpen.isVisible().catch(() => false)) { await cancelIfOpen.click(); await expect(cancelIfOpen).toBeHidden({ timeout: 5000 }); } // Open (use exact text; fresh locator) await page.getByRole('button', { name: /^Change Password$/i }).click(); // 3 password inputs: current / new / confirm const pwInputs = page.locator('input[type="password"]'); await expect(pwInputs.first()).toBeVisible({ timeout: 5000 }); // 1) Success submit — form should close await pwInputs.nth(0).fill('OldPass!1'); await pwInputs.nth(1).fill('NewPass!1'); await pwInputs.nth(2).fill('NewPass!1'); const submit = page.getByRole('button', { name: /^Update Password$/i }); await submit.click(); // Form closes => Update button disappears await expect(submit).toBeHidden({ timeout: 7000 }); // Reopen deterministically (works whether trigger is a button or link, // or if the section needs a refresh) async function reopenChangePassword(page) { // If somehow still open, close first const update = page.getByRole('button', { name: /^Update Password$/i }).first(); if (await update.isVisible().catch(() => false)) { const cancel = page.getByRole('button', { name: /^Cancel password change$/i }).first(); if (await cancel.isVisible().catch(() => false)) { await cancel.click(); await expect(update).toBeHidden({ timeout: 7000 }); } } // Try common triggers const triggers = [ page.getByRole('button', { name: /^Change Password$/i }).first(), page.getByRole('link', { name: /^Change Password$/i }).first(), page.locator('text=Change Password').first(), ]; for (const t of triggers) { if (await t.isVisible().catch(() => false)) { await t.click(); return; } } // Fallback: reload the page section and try once more await page.goto('/profile', { waitUntil: 'networkidle' }); for (const t of triggers) { if (await t.isVisible().catch(() => false)) { await t.click(); return; } } throw new Error('Could not find Change Password trigger after success submit'); } await reopenChangePassword(page); await pwInputs.nth(0).fill('OldPass!1'); await pwInputs.nth(1).fill('NewPass!2'); await pwInputs.nth(2).fill('NewPass!2'); await page.getByRole('button', { name: /^Update Password$/i }).click(); // For 429: either see an error, OR the form remains open (Update/Cancel still visible) const errText = page.getByText(/too many attempts|please wait|try again/i); const sawError = await errText.isVisible({ timeout: 3000 }).catch(() => false); const stillOpen = (await page.getByRole('button', { name: /^Update Password$/i }).isVisible().catch(() => false)) || (await page.getByRole('button', { name: /^Cancel password change$/i }).isVisible().catch(() => false)); expect(sawError || stillOpen).toBeTruthy(); }); });