Previous audit allowed up to 5 requests in a 30s window — generous, hid real regressions. Tighten: - 5s settle after each scene becomes interactive (covers the burst of mount-time fetches every scene legitimately fires) - 55s observation window after settle, sized to fit between two consecutive 60s topbar badge polls - MAX_REQ_PER_WINDOW = 0; any /api/* or /internal/* request during the window fails the assertion This is the operator-experience contract: when you stop typing, canvas stops fetching.
90 lines
3.6 KiB
JavaScript
90 lines
3.6 KiB
JavaScript
// Idle-poll audit. User explicitly complained "way too many GETs / non-stop".
|
|
// For each scene: visit, settle for a generous burst window, then watch network
|
|
// for 60s with no user input. Strict assertion: zero background /api/* or
|
|
// /internal/* requests during that window.
|
|
//
|
|
// What counts as "background":
|
|
// - Anything fired more than SETTLE_MS after the scene becomes interactive
|
|
// - Excludes the 60s topbar badge poll fired exactly at minute boundaries —
|
|
// the window is sized to fit BETWEEN two such fires
|
|
//
|
|
// The topbar badge poll runs every 60s. A 55s observation window after a
|
|
// 5s settle covers a full minute of operator inactivity without spanning
|
|
// the next poll boundary, so the strict assertion "zero" is correct.
|
|
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 SETTLE_MS = 5_000;
|
|
const IDLE_WINDOW_MS = 55_000;
|
|
const MAX_REQ_PER_WINDOW = 0;
|
|
|
|
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(SETTLE_MS);
|
|
|
|
// 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} background reqs in ${IDLE_WINDOW_MS / 1000}s · ${requests.slice(0, 3).map((r) => `${r.method} ${r.url.slice(0, 50)}`).join(", ")}${requests.length > 3 ? "…" : ""}`
|
|
: `0 background reqs in ${IDLE_WINDOW_MS / 1000}s`;
|
|
record(`idle_${s.label}_zero_background_traffic`, 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);
|
|
}
|