diff options
Diffstat (limited to 'src/api/dashboard/check.js')
| -rw-r--r-- | src/api/dashboard/check.js | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/src/api/dashboard/check.js b/src/api/dashboard/check.js new file mode 100644 index 0000000..95f805a --- /dev/null +++ b/src/api/dashboard/check.js @@ -0,0 +1,87 @@ +// Browser checks of the dashboard page (`cargo test --features dashboard +// dashboard_in_a_browser -- --ignored`, which serves this the page as the node does): the +// page loads without errors; the peers table has a row per peer, and an update changes +// cells without making the rows anew (`morphChildren`); the map draws a node per member; +// and with 300 members, laid out by round trip, the first drawing doesn't hold the page up +// for long (`LAYOUT_FOR_MS`). +// +// Needs Node.js, Playwright's library (`playwright-core`) and its Chromium; `check.nix` +// gives all three from nixpkgs. +// +// Usage: node check.js <page.html> + +const { chromium } = require("playwright-core"); + +const page = process.argv[2]; +const failures = []; +const check = (ok, what) => { + if (!ok) failures.push(what); + console.log((ok ? "ok " : "FAIL ") + what); +}; + +(async () => { + const browser = await chromium.launch(); + const tab = await browser.newPage({ viewport: { width: 1400, height: 900 } }); + const errors = []; + tab.on("pageerror", error => errors.push(String(error))); + await tab.goto("file://" + page); + await tab.waitForTimeout(300); + + // The peers table, and an update to it. + const peers = await tab.evaluate(() => { + const peer = (alias, rtt) => ({ alias, status: "active", lastSeenSecsAgo: 1, + destination: { address: "sim:" + alias, paths: [{ rttMs: rtt }] }, clock: null, + neighbour: true, sameEpoch: true, epoch: { generation: 1, id: "ab" }, + reaches: ["a"], hopDelay: null }); + const members = { a: { self: true, alias: "a" }, b: { alias: "b" }, c: { alias: "c" }, + d: { alias: "d" } }; + state = { node: { id: "a", alias: "a" }, stats: {}, + peers: { localReaches: ["b", "c", "d"], + peers: { b: peer("b", 10), c: peer("c", 20), d: peer("d", 30) } }, + graph: { members, intended: {}, measured: {} } }; + renderPeers(); + const before = [...document.querySelectorAll("#peers tr")]; + state.peers.peers.c.destination.paths[0].rttMs = 25; + renderPeers(); + const after = [...document.querySelectorAll("#peers tr")]; + return { rows: after.length, same: before.every((row, i) => row === after[i]), + rtt: after[2].textContent.includes("25.0 ms") }; + }); + check(peers.rows === 4, "the peers table has a header and a row per peer"); + check(peers.same, "an update keeps the table's rows"); + check(peers.rtt, "an update changes what changed"); + + // The map, small and large. + for (const [n, budget] of [[13, 500], [300, 1500]]) { + const map = await tab.evaluate(n => { + const ids = [...Array(n).keys()].map(i => "n" + String(i).padStart(3, "0")); + const members = {}; + for (const id of ids) members[id] = { alias: id, self: id === "n000" }; + const intended = {}, measured = {}; + for (let i = 0; i < n; i++) { + const neighbours = [1, 2, 7, 13].map(k => ids[(i + k) % n]).filter(id => id !== ids[i]); + intended[ids[i]] = neighbours; + measured[ids[i]] = { secsAgo: 1, links: neighbours.map(peer => ({ peer, + rttMs: 20 + (i * 37) % 200, dormant: false, down: false, others: [] })) }; + } + state = { node: { id: "n000", alias: "n000" }, stats: {}, + peers: { localReaches: [], peers: {} }, graph: { members, intended, measured } }; + byRtt = true; relayout = true; lastShape = null; + const started = performance.now(); + renderGraph(); + const ms = performance.now() - started; + const nodes = [...drawn.keys()].filter(key => key.startsWith("node ")).length; + return { ms, nodes }; + }, n); + check(map.nodes === n, `the map draws a node per member (${map.nodes} of ${n})`); + check(map.ms < budget, `${n} members drawn in ${map.ms.toFixed(0)} ms (under ${budget})`); + } + + await tab.waitForTimeout(500); + check(errors.length === 0, "no errors on the page" + (errors.length ? ": " + errors : "")); + await browser.close(); + process.exit(failures.length ? 1 : 0); +})().catch(error => { + console.error(error); + process.exit(2); +}); |
