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.
119 lines
5 KiB
JavaScript
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');
|
|
});
|