// tests/e2e/test_credentials.js // Tests credential management page: structure, password set/change, validation errors. const { TARGET_URL, run } = require('./helpers'); run(async (page, assert) => { const fixtures = JSON.parse(process.env.E2E_FIXTURES || '{}'); // ---- Setup: Log in with dedicated credentials user ---- console.log('\n--- Setup: login ---'); await page.goto(`${TARGET_URL}/login`); await page.fill('#username', fixtures.cred_username); await page.fill('#password', fixtures.cred_password); await page.click('form[hx-post="/login/password"] button[type="submit"]'); await page.waitForURL('**/manage/credentials', { timeout: 5000 }); // ---- Page structure ---- console.log('\n--- Credentials page structure ---'); const title = await page.title(); assert(title.includes('Credentials'), `Title contains "Credentials" (got: "${title}")`); assert(title.includes('Porchlight'), `Title contains "Porchlight" (got: "${title}")`); const h1 = await page.locator('h1').textContent(); assert(h1 === 'Credentials', `H1 says "Credentials" (got: "${h1}")`); // Security keys section const securityKeysH2 = page.locator('h2:has-text("Security keys")'); assert(await securityKeysH2.isVisible(), 'Security keys heading visible'); const registerBtn = page.locator('#webauthn-register-btn'); assert(await registerBtn.isVisible(), 'Add security key button visible'); // Password section const passwordH2 = page.locator('h2:has-text("Password")'); assert(await passwordH2.isVisible(), 'Password heading visible'); const passwordSection = page.locator('#password-section'); assert(await passwordSection.isVisible(), 'Password section visible'); // ---- Password validation: mismatch ---- console.log('\n--- Password validation: mismatch ---'); await page.fill('#password', 'newpassword1'); await page.fill('#confirm', 'newpassword2'); await page.click('#password-section button[type="submit"]'); await page.waitForSelector('#password-section [role="alert"]', { timeout: 5000 }); const mismatchErr = await page.locator('#password-section [role="alert"]').textContent(); assert( mismatchErr.includes('do not match'), `Shows mismatch error (got: "${mismatchErr}")` ); // ---- Password validation: minlength enforced client-side ---- console.log('\n--- Password validation: minlength attribute ---'); // Reload page to clear HTMX state (the form was replaced by the error div) await page.goto(`${TARGET_URL}/manage/credentials`); const pwMinlength = await page.locator('#password').getAttribute('minlength'); assert(pwMinlength === '8', `Password input has minlength="8" (got: "${pwMinlength}")`); const confirmMinlength = await page.locator('#confirm').getAttribute('minlength'); assert(confirmMinlength === '8', `Confirm input has minlength="8" (got: "${confirmMinlength}")`); // ---- Password change: success ---- console.log('\n--- Password change: success ---'); await page.goto(`${TARGET_URL}/manage/credentials`); await page.fill('#password', 'newpassword123'); await page.fill('#confirm', 'newpassword123'); await page.click('#password-section button[type="submit"]'); await page.waitForSelector('#password-section [role="status"]', { timeout: 5000 }); const successMsg = await page.locator('#password-section [role="status"]').textContent(); assert( successMsg.includes('Password updated'), `Shows success message (got: "${successMsg}")` ); });