learning-website-nextjs1-5 Exercise 3: Does the Page Look the Same as on the Django Site? Measure It ===================================================================================================== "It looks the same" is an opinion until you measure. Both sites were started (Django on 8123, the Next.js languages app on 3001), and a real headless Chrome, driven through the DevTools protocol, opened the SAME Hungarian lesson on both and read the computed style of 27 properties. Save as parity.mjs: // Do the same lesson page and the same CSS give the same computed styles in the Django site and in this one? // node parity.mjs (both servers running) import { spawn } from "node:child_process"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; const [djangoUrl, nextUrl] = process.argv.slice(2); const chrome = spawn("C:/Program Files/Google/Chrome/Application/chrome.exe", [ "--headless=new", "--disable-gpu", "--remote-debugging-port=9334", `--user-data-dir=${mkdtempSync(join(tmpdir(), "lw-chrome-"))}`, "--window-size=1100,900", "--host-resolver-rules=MAP *.localhost 127.0.0.1", "about:blank", ], { stdio: "ignore" }); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); let targets; for (let i = 0; i < 40; i++) { try { targets = await (await fetch("http://127.0.0.1:9334/json")).json(); if (targets.length) break; } catch { /* not up yet */ } await sleep(250); } const ws = new WebSocket(targets.find((t) => t.type === "page").webSocketDebuggerUrl); await new Promise((r) => ws.addEventListener("open", r)); let id = 0; const pending = new Map(); ws.addEventListener("message", (e) => { const m = JSON.parse(e.data); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); } }); const send = (method, params = {}) => new Promise((r) => { const n = ++id; pending.set(n, r); ws.send(JSON.stringify({ id: n, method, params })); }); const evaluate = async (expression) => (await send("Runtime.evaluate", { expression, returnByValue: true })).result?.result?.value; const CHECKS = [ ["html", "--accent", true], ["body", "background-color"], ["body", "color"], ["body", "font-family"], ["body", "line-height"], ["body", "margin-top"], ["main", "max-width"], ["main", "padding-left"], ["main", "padding-top"], [".page-title", "color"], [".page-title", "font-size"], [".page-title", "margin-top"], [".page-title", "line-height"], [".page-body a", "color"], [".tip-box", "background-color"], [".tip-box", "border-left-color"], [".tip-box", "padding-left"], [".tip-box", "border-radius"], [".dialog-card", "background-color"], [".dialog-card", "border-top-color"], [".vocab-table th", "color"], [".vocab-table th", "background-color"], [".vocab-table th", "text-transform"], [".vocab-table th", "font-size"], [".vocab-table td", "padding-left"], [".vocab-table td", "border-bottom-color"], [".speaker", "color"], ]; async function measure(url) { await send("Page.enable"); await send("Page.navigate", { url }); await sleep(2500); const out = {}; for (const [selector, property, isVar] of CHECKS) { const sel = JSON.stringify(selector); const prop = JSON.stringify(property); out[`${selector} ${property}`] = await evaluate(`(() => { const e = document.querySelector(${sel}); if (!e) return "(absent)"; const s = getComputedStyle(e); return ${isVar ? `s.getPropertyValue(${prop}).trim()` : `s.getPropertyValue(${prop})`}; })()`); } return out; } const a = await measure(djangoUrl); const b = await measure(nextUrl); let same = 0, differ = 0, absent = 0; for (const key of Object.keys(a)) { if (a[key] === "(absent)" && b[key] === "(absent)") { absent++; console.log("absent in both: ", key); continue; } if (a[key] === b[key]) { same++; continue; } differ++; console.log("DIFFERENT:", key, "| Django:", a[key], "| Next:", b[key]); } console.log(`\n${same} identical, ${differ} different, ${absent} absent in both (of ${Object.keys(a).length} checks)`); ws.close(); chrome.kill(); process.exit(0); node parity.mjs "http://languages.localhost:8123/hungary/hungarian-basic-3/hungarian_basic_conversation_3_1/" \ "http://languages.localhost:3001/hungary/hungarian-basic-3/hungarian_basic_conversation_3_1" absent in both: .page-body a color 26 identical, 0 different, 1 absent in both (of 27 checks) The checks cover the accent variable, the body (background, colour, font, line height), the main column (width and padding), the page title, a tip box, a dialog card, a vocabulary table's headings and cells, and a speaker label. (One selector, a link inside the page body, matches nothing on this page, on either site.) CAN THIS CHECK FAIL? Two deliberate changes to tokens.css, each rebuilt, measured and undone: --radius 8px to 10px and the language surface off by one: 26 identical, 0 different <-- no difference found --bg and --text each changed by one step: 24 identical, 2 different body background-color Django rgb(15, 17, 23) Next rgb(15, 17, 24) body color Django rgb(226, 228, 236) Next rgb(226, 228, 237) The first result is a finding, not a failure: this lesson carries its OWN scoped stylesheet, which sets its own radius and surfaces, so changing those tokens changes nothing the lesson shows. The shared tokens only reach the page frame and anything a fragment leaves unstyled. So this check proves the frame and the page-level colours match, and says nothing about a token that every fragment overrides. It shows a one-step change in a colour the page does use. Then a look at the style guide, a page of every shared building block, in two sites (screenshots checked by eye): the Languages site is purple on the warmer neutral surface, the Web Development site is sky blue on the cooler one; the warning and finding boxes keep their own colours on both. What was NOT done: the comparison covers one lesson page; the other two languages and the other sites' pages were not compared; and only Chrome was used. WHY THIS WORKS AS AN ANSWER --------------------------- It measures the thing the claim is about (the colours and sizes a browser really uses), shows that the measurement can fail, and says which part of the design it cannot see.