porchlight/tests/e2e/test_login.js
Johan Lundberg c381896de4
test: add comprehensive e2e test suite with shared helpers and DB seeding
Extract shared test runner (helpers.js), add file-based SQLite with
setup_db.py for fixture seeding, and add tests for auth guard, credentials
management, full registration flow, health endpoint, password auth, and
magic link registration errors. 66 checks across 7 test files.
2026-02-16 14:41:14 +01:00

119 lines
5 KiB
JavaScript

// 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');
});