From 7cefe32b0512865c8853c0048dd392e24b7ca4cd Mon Sep 17 00:00:00 2001 From: root Date: Wed, 9 Sep 2026 12:35:51 +0800 Subject: [PATCH] test: add network-path first-load diagnostics for public nginx route Request-level tracing + wire-size measurement via nginx access log; used to diagnose the ~1min incognito stuck-loading (13.5MB uncompressed transfer, antd.js 10.1MB with inline sourcemap). Post gzip+http2 fix verified ~1.1MB. --- scripts/timing-network.mjs | 60 +++++++++++++++++++++++++++++ scripts/verify-public-firstload.mjs | 26 +++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 scripts/timing-network.mjs create mode 100644 scripts/verify-public-firstload.mjs diff --git a/scripts/timing-network.mjs b/scripts/timing-network.mjs new file mode 100644 index 0000000..57f0efa --- /dev/null +++ b/scripts/timing-network.mjs @@ -0,0 +1,60 @@ +import { chromium } from "playwright"; + +const PUB = "https://aidemo.istation.cloud/t/stationbuilder/"; + +const runOnce = async (n) => { + const browser = await chromium.launch(); + const ctx = await browser.newContext(); + const page = await ctx.newPage(); + const started = new Map(); // url -> t + const done = new Map(); // url -> {status,size,enc,ms} + const failed = new Map(); + page.on("request", (r) => { started.set(r.url(), Date.now()); }); + page.on("response", (r) => { + const h = r.headers(); + done.set(r.url(), { status: r.status(), size: Number(h["content-length"]||0), enc: h["content-encoding"]||"-", ms: Date.now()-(started.get(r.url())||Date.now()) }); + }); + page.on("requestfailed", (r) => { failed.set(r.url(), r.failure()?.errorText || "?"); }); + + const t0 = Date.now(); + let resolved = null; + try { + await page.goto(PUB, { waitUntil: "commit", timeout: 60000 }); + await page.waitForFunction(() => { + const els = Array.from(document.querySelectorAll(".empty-state")); + return !els.some(e => /加载中/.test(e.textContent||"")); + }, undefined, { timeout: 60000 }); + resolved = Date.now() - t0; + } catch (e) { + resolved = "TIMEOUT/HANG"; + } + + const hung = [...started.keys()].filter(u => !done.has(u) && !failed.has(u)); + const apiDone = [...done.keys()].filter(u => u.includes("/api/")); + console.log(`\n===== run ${n}: resolved=${resolved}ms =====`); + console.log(`requests started=${started.size} done=${done.size} failed=${failed.size}`); + console.log(`API responses received: ${apiDone.length}`); + for (const u of apiDone) console.log(` API OK ${done.get(u).status} ${done.get(u).ms}ms ${u.replace(PUB,"").slice(0,70)}`); + if (failed.size) { console.log("failed:"); for (const [u,e] of failed) console.log(` ${e} ${u.replace(PUB,"").slice(0,90)}`); } + if (hung.length) { + console.log(`HUNG requests (${hung.length}) — sent but no response ever:`); + for (const u of hung) console.log(` HUNG [${((Date.now()-started.get(u))/1000).toFixed(0)}s] ${u.slice(0,110)}`); + // server-side check of same paths while browser is hung + console.log("server-side direct check of hung paths (from node, same moment):"); + for (const u of hung.slice(0, 6)) { + const t = Date.now(); + try { + const r = await fetch(u, { headers: { host: "aidemo.istation.cloud" }, signal: AbortSignal.timeout(8000) }); + await r.text(); + console.log(` direct ${r.status} in ${Date.now()-t}ms ${u.replace(PUB,"").slice(0,70)}`); + } catch (e) { console.log(` direct ERR ${Date.now()-t}ms ${e.message} ${u.replace(PUB,"").slice(0,70)}`); } + } + } + // slowest responses + const slow = [...done.entries()].filter(([,v])=>v.ms>3000).sort((a,b)=>b[1].ms-a[1].ms).slice(0,6); + if (slow.length) { console.log("responses slower than 3s:"); for (const [u,v] of slow) console.log(` ${v.ms}ms ${(v.size/1024).toFixed(0)}KB ${u.replace(PUB,"").slice(0,80)}`); } + await browser.close(); +}; + +for (let i = 1; i <= 3; i++) await runOnce(i); +process.exit(0); diff --git a/scripts/verify-public-firstload.mjs b/scripts/verify-public-firstload.mjs new file mode 100644 index 0000000..e2670e0 --- /dev/null +++ b/scripts/verify-public-firstload.mjs @@ -0,0 +1,26 @@ +import { chromium } from "playwright"; + +const PUB = "https://aidemo.istation.cloud/t/stationbuilder/"; + +const browser = await chromium.launch(); +const ctx = await browser.newContext(); +const page = await ctx.newPage(); +let wireBytes = 0, reqCount = 0; +page.on("response", async (r) => { + reqCount++; + wireBytes += Number(r.headers()["content-length"] || 0); +}); +const t0 = Date.now(); +await page.goto(PUB, { waitUntil: "commit", timeout: 60000 }); +// real completion: app shell mounted AND no 加载中 anywhere +await page.waitForSelector(".company-side-nav", { timeout: 60000 }); +await page.waitForFunction(() => { + const els = Array.from(document.querySelectorAll(".empty-state")); + return !els.some(e => /加载中/.test(e.textContent || "")); +}, undefined, { timeout: 60000 }); +const loadMs = Date.now() - t0; +await page.waitForTimeout(1500); +console.log(`first load complete: ${loadMs}ms, requests=${reqCount}, wireBytes(with content-length)=${(wireBytes/1024).toFixed(0)}KB`); +console.log(`protocol check: response headers via CDP skipped; side-nav visible + data loaded OK`); +await browser.close(); +process.exit(0); -- 2.43.0