tracker/e2e/live-check.mjs
// End-to-end check of real-time updates in a real browser.
//
// Usage (tracker must be running on :8099):
// node e2e/live-check.mjs <path-to-chrome> data/milestones.json
//
// Opens the overview in headless Chrome, appends a temporary milestone with one
// task to the data file, checks the open page's Task count rises without a
// reload, then restores the file. Needs Node 22+ (global fetch and WebSocket).
import { spawn } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
const [chrome, dataFile] = process.argv.slice(2);
if (!chrome || !dataFile) {
console.error("usage: node e2e/live-check.mjs <path-to-chrome> <milestones.json>");
process.exit(2);
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const original = readFileSync(dataFile, "utf8");
const proc = spawn(chrome, ["--headless=new", "--no-sandbox", "--disable-gpu", "--remote-debugging-port=9333", "about:blank"], { stdio: "ignore" });
let ws;
let id = 0;
const pending = new Map();
const send = (method, params = {}) =>
new Promise((res) => {
const i = ++id;
pending.set(i, res);
ws.send(JSON.stringify({ id: i, method, params }));
});
const evaluate = async (expression) =>
(await send("Runtime.evaluate", { expression, returnByValue: true })).result.value;
let ok = false;
try {
let target;
for (let i = 0; i < 50 && !target; i++) {
try {
target = (await (await fetch("http://127.0.0.1:9333/json")).json()).find((t) => t.type === "page");
} catch {}
await sleep(200);
}
if (!target) throw new Error("Chrome did not start");
ws = new WebSocket(target.webSocketDebuggerUrl);
await new Promise((r) => ws.addEventListener("open", r));
ws.addEventListener("message", (e) => {
const m = JSON.parse(e.data);
if (pending.has(m.id)) {
pending.get(m.id)(m.result);
pending.delete(m.id);
}
});
await send("Page.navigate", { url: "http://localhost:8099/" });
await sleep(2500);
const before = Number(await evaluate("window.__marker = 1; document.querySelector('.stat-num').textContent"));
const state = await evaluate("document.getElementById('live').dataset.state");
const data = JSON.parse(original);
data.push({ id: "e2e-temp", no: 99, title: "e2e temporary", epics: [{ id: "e2e-temp-epic", title: "temporary", tasks: [{ id: "E2E-1", title: "temporary", status: "todo" }] }] });
writeFileSync(dataFile, JSON.stringify(data, null, 2) + "\n");
await sleep(3000);
const after = Number(await evaluate("document.querySelector('.stat-num').textContent"));
const sameDocument = await evaluate("window.__marker === 1");
ok = state === "open" && after === before + 1 && sameDocument;
console.log(`live state: ${state}; task count ${before} -> ${after}; same document (no reload): ${sameDocument}`);
} finally {
writeFileSync(dataFile, original);
ws?.close();
proc.kill();
}
console.log(ok ? "PASS" : "FAIL");
process.exit(ok ? 0 : 1);