Files
canvas-frontend/qa/audit_idle_poll.mjs
T
shad 70ed7e9aa4
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
fix(topbar): idle-quiet badge poll (count threads, not per-thread reads)
Idle-poll audit caught a regression I introduced in the prior commit:
the topbar badge poll iterated chatApi.listMessages per thread on each
60s tick, firing N requests per refresh. Landing scene showed 9 reqs
in a 30s idle window (failed the <=5 budget).

Now the topbar badge just shows the THREAD COUNT (not per-thread
unread), which is a single chatApi.listThreads call. Per-thread unread
math stays in the Chat scene where it belongs (already wired:
chat-thread-row.unread + .chat-unread-summary).
2026-06-14 18:16:24 +04:00

81 lines
3.1 KiB
JavaScript

// Idle-poll audit. User explicitly complained "way too many GETs / non-stop".
// For each scene: visit, settle, then watch network for 30s with no user input.
// Pass if total requests in that 30s window is reasonable (<= 5).
// The topbar identity-hydration + chat/queue badge polls at 60s, so a 30s
// window should see at most 1 such fire.
import { chromium } from "playwright";
const URL = "https://canvas.flow-master.ai/";
const args = ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"];
const SCENES = [
{ chip: null, label: "landing" },
{ chip: /Approvals queue/, label: "approvals" },
{ chip: /^Documents$/, label: "documents" },
{ chip: /Procurement Hub/, label: "procurement_hub" },
{ chip: /Attendance Map/, label: "geo" },
{ chip: /Command Assistant/, label: "assistant" },
{ chip: /Team Chat/, label: "chat" },
{ chip: /What is FlowMaster/, label: "explainer" },
];
const IDLE_WINDOW_MS = 30_000;
const MAX_REQ_PER_WINDOW = 5;
const results = [];
function record(name, ok, detail = "") {
results.push({ name, ok, detail });
console.log(`${ok ? "PASS" : "FAIL"} ${name}${detail ? " — " + detail : ""}`);
}
const b = await chromium.launch({ headless: true, args });
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
await p.goto(URL, { waitUntil: "networkidle" });
await p.waitForTimeout(800);
await p.locator(".persona-chip", { hasText: /Mariana/ }).click();
await p.locator(".dev-login-btn").click();
await p.waitForSelector(".hero-actions", { timeout: 15000 });
await p.waitForFunction(() => document.querySelectorAll(".hub-chip").length >= 8, undefined, { timeout: 15000 }).catch(() => {});
for (const s of SCENES) {
if (s.chip) {
await p.goto(URL, { waitUntil: "networkidle" }).catch(() => {});
await p.waitForSelector(".hub-chip", { timeout: 15000 }).catch(() => {});
await p.locator(".hub-chip", { hasText: s.chip }).click().catch(() => {});
await p.waitForTimeout(2500);
}
// Settle period — the scene's initial fetch burst should complete here.
await p.waitForLoadState("networkidle").catch(() => {});
await p.waitForTimeout(1500);
// Now record every request fired for the next IDLE_WINDOW_MS.
const requests = [];
const onReq = (req) => {
const u = req.url();
if (/\/api\/|\/internal\//.test(u)) {
requests.push({ method: req.method(), url: u.replace("https://canvas.flow-master.ai", "") });
}
};
p.on("request", onReq);
await p.waitForTimeout(IDLE_WINDOW_MS);
p.off("request", onReq);
const ok = requests.length <= MAX_REQ_PER_WINDOW;
const summary = requests.length
? `${requests.length} reqs · ${requests.slice(0, 3).map((r) => `${r.method} ${r.url.slice(0, 50)}`).join(", ")}${requests.length > 3 ? "…" : ""}`
: "0 reqs · quiet";
record(`idle_${s.label}_quiet_at_30s`, ok, summary);
}
await b.close();
const fails = results.filter((r) => !r.ok);
console.log(`\n=== idle audit: ${results.length - fails.length}/${results.length} passed ===`);
if (fails.length) {
console.log("FAILED:");
fails.forEach((f) => console.log(` - ${f.name}: ${f.detail}`));
process.exit(1);
}