// Porchlight — Login page e2e test // // Tests branding, page structure, accessibility, theme, and responsive layout. // const { TARGET_URL, run } = require('./helpers'); run(async (page, assert) => { // ---- Branding ---- console.log('\n--- Branding ---'); await page.goto(`${TARGET_URL}/login`); const title = await page.title(); assert(title.includes('Porchlight'), `Page title contains "Porchlight" (got: "${title}")`); assert(!title.includes('FastAPI'), `Page title does not contain "FastAPI" (got: "${title}")`); // ---- Favicon ---- console.log('\n--- Favicon ---'); const favicon = await page.locator('link[rel="icon"]').getAttribute('href'); assert(favicon === '/static/favicon.png', `Favicon link points to /static/favicon.png (got: "${favicon}")`); const faviconResp = await page.request.get(`${TARGET_URL}/static/favicon.png`); assert(faviconResp.ok(), `Favicon file is served (status: ${faviconResp.status()})`); // ---- Site header & logo ---- console.log('\n--- Site header & logo ---'); const header = page.locator('.site-header'); assert(await header.isVisible(), 'Site header is visible'); const logo = page.locator('.site-logo'); assert(await logo.isVisible(), 'Logo image is visible'); const logoSrc = await logo.getAttribute('src'); assert(logoSrc === '/static/logo.svg', `Logo src is /static/logo.svg (got: "${logoSrc}")`); const logoResp = await page.request.get(`${TARGET_URL}/static/logo.svg`); assert(logoResp.ok(), `Logo SVG file is served (status: ${logoResp.status()})`); const siteTitle = page.locator('.site-title'); assert(await siteTitle.isVisible(), 'Site title text is visible'); const siteTitleText = await siteTitle.textContent(); assert(siteTitleText.trim() === 'Porchlight', `Site title text is "Porchlight" (got: "${siteTitleText.trim()}")`); // ---- Accessibility ---- console.log('\n--- Accessibility ---'); const skipLink = page.locator('.skip-link'); assert(await skipLink.count() === 1, 'Skip link is present'); const main = page.locator('main#main'); assert(await main.count() === 1, 'Main landmark with id="main" exists'); const liveRegion = page.locator('#live[aria-live="polite"]'); assert(await liveRegion.count() === 1, 'Polite live region exists'); // ---- Login form structure ---- console.log('\n--- Login form structure ---'); const h1 = page.locator('h1'); assert(await h1.textContent() === 'Sign in', `H1 says "Sign in" (got: "${await h1.textContent()}")`); const passwordForm = page.locator('form[hx-post="/login/password"]'); assert(await passwordForm.count() === 1, 'Password login form exists'); const usernameInput = page.locator('#username'); assert(await usernameInput.isVisible(), 'Username input is visible'); const passwordInput = page.locator('#password'); assert(await passwordInput.isVisible(), 'Password input is visible'); const submitBtn = passwordForm.locator('button[type="submit"]'); assert(await submitBtn.isVisible(), 'Submit button is visible'); const webauthnForm = page.locator('#webauthn-login-form'); assert(await webauthnForm.count() === 1, 'WebAuthn login form exists'); // ---- Theme / styling ---- console.log('\n--- Theme / styling ---'); const bgColor = await page.evaluate(() => { return getComputedStyle(document.body).backgroundColor; }); assert( bgColor === 'rgb(250, 250, 249)' || bgColor === 'rgb(28, 25, 23)', `Body background is themed (got: "${bgColor}")` ); const btnBg = await page.evaluate(() => { const btn = document.querySelector('button[type="submit"]'); return getComputedStyle(btn).backgroundColor; }); assert( btnBg === 'rgb(217, 119, 6)' || btnBg === 'rgb(245, 158, 11)', `Button uses amber accent color (got: "${btnBg}")` ); // ---- Section card styling ---- console.log('\n--- Section cards ---'); const sectionBg = await page.evaluate(() => { const section = document.querySelector('section'); return getComputedStyle(section).backgroundColor; }); assert( sectionBg === 'rgb(245, 245, 244)' || sectionBg === 'rgb(41, 37, 36)', `Sections have surface background (got: "${sectionBg}")` ); const sectionBorder = await page.evaluate(() => { const section = document.querySelector('section'); return getComputedStyle(section).borderStyle; }); assert(sectionBorder === 'solid', `Sections have solid border (got: "${sectionBorder}")`); // ---- Static assets ---- console.log('\n--- Static assets ---'); const cssResp = await page.request.get(`${TARGET_URL}/static/style.css`); assert(cssResp.ok(), `style.css is served (status: ${cssResp.status()})`); const cssBody = await cssResp.text(); assert(cssBody.includes('--accent'), 'CSS contains --accent custom property'); assert(cssBody.includes('#d97706'), 'CSS contains amber accent color #d97706'); assert(cssBody.includes('prefers-color-scheme: dark'), 'CSS contains dark mode media query'); assert(cssBody.includes('prefers-reduced-motion'), 'CSS contains reduced motion media query'); });