mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-25 08:51:55 +00:00
a4af0285fd
## Summary Fixes #1692 — `public/packets.js::init()` serialized `loadObservers()` and `loadPackets()`, blocking `/api/packets` behind `/api/observers`. On loaded CI runners the cumulative wait pushed first-row render to 25–40s, which is the root cause of the persistent #1662 slideover flake and a real operator-felt latency on slow links. ## Fix (Option B — `Promise.all`) ```js // before await loadObservers(); loadPackets(); // after await Promise.all([loadObservers(), loadPackets()]); ``` Option B chosen over fire-and-forget (Option A) because `renderLeft()` synchronously iterates `observers` to build the observer-filter dropdown (`for (const o of observers)` at packets.js:1636). With Option A the menu would render empty on first paint and not refresh until the next user-triggered render. Promise.all preserves the existing render contract while halving worst-case latency — the two fetches now run in parallel and the slower one gates `renderLeft()`. ## TDD - **RED `c7184188`** — `test-issue-1692-packets-init-parallel-e2e.js` stubs `/api/observers` with a 4s delay via `page.route()`, asserts first `tr[data-hash]` < 3000ms. Fails on serial init (blocked at 4s). - **GREEN `903020c5`** — init refactor + wire test into `.github/workflows/deploy.yml` deploy job. ## Out of scope (separate PR per #1692 acceptance #2/#3) The 30s row-wait timeout and 3-iter flake-gate in `test-slideover-1056-e2e.js` + `deploy.yml` were stop-gaps for the underlying serialization. They stay in this PR — they should be reverted in a follow-up after operators confirm the latency fix holds in production. ## Preflight `bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master` → all gates pass (PII, branch scope, red commit, CSS vars, LIKE-on-JSON, sync/async migration, XSS). ## Browser verification Local headless chromium on this sandbox crashes on the heavy `/packets` page (small `/dev/shm`, ARM constraints documented in AGENTS.md). Test is gated on CI runner where the harness runs. --------- Co-authored-by: CoreScope Bot <bot@corescope.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com> Co-authored-by: Kpa-clawbot <bot@kpa-clawbot>
88 lines
4.0 KiB
JavaScript
88 lines
4.0 KiB
JavaScript
/**
|
|
* E2E (#1692): /packets init() must NOT serialize loadObservers → loadPackets.
|
|
*
|
|
* Before #1692 fix: init() does `await loadObservers(); loadPackets();` so
|
|
* /api/packets cannot start until /api/observers resolves. Under a slow
|
|
* observers fetch (CI load, large fixture DB, slow link) the first table row
|
|
* does not appear until well after the observers call returns.
|
|
*
|
|
* Repro strategy: stub /api/observers with a deterministic 4s delay via
|
|
* page.route(), then measure wallclock time from navigation start to the
|
|
* first `tr[data-hash]` appearance in #pktBody. Serial behavior pushes
|
|
* first-row past 4s. Parallel (fixed) behavior should land first-row well
|
|
* under 3s — the /api/packets call runs in parallel.
|
|
*
|
|
* Acceptance budget: first-row < 3000ms. Headroom of ~1s below the 4s
|
|
* observers-stub delay so a serial implementation reliably trips the gate
|
|
* even with timing jitter, and a parallel implementation comfortably passes.
|
|
*
|
|
* Usage: BASE_URL=http://localhost:13581 node test-issue-1692-packets-init-parallel-e2e.js
|
|
*/
|
|
'use strict';
|
|
const { chromium } = require('playwright');
|
|
|
|
const BASE = process.env.BASE_URL || 'http://localhost:13581';
|
|
const OBSERVERS_DELAY_MS = 4000;
|
|
const FIRST_ROW_BUDGET_MS = 3000;
|
|
|
|
let passed = 0, failed = 0;
|
|
async function step(name, fn) {
|
|
try { await fn(); passed++; console.log(' \u2713 ' + name); }
|
|
catch (e) { failed++; console.error(' \u2717 ' + name + ': ' + e.message); }
|
|
}
|
|
function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({
|
|
headless: true,
|
|
executablePath: process.env.CHROMIUM_PATH || undefined,
|
|
args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'],
|
|
});
|
|
const ctx = await browser.newContext({ viewport: { width: 1400, height: 900 } });
|
|
const page = await ctx.newPage();
|
|
page.setDefaultTimeout(15000);
|
|
page.on('pageerror', (e) => console.error('[pageerror]', e.message));
|
|
|
|
console.log(`\n=== #1692 packets init() parallel-fetch E2E against ${BASE} ===`);
|
|
|
|
// Stub /api/observers with a 4s artificial delay. The real response body
|
|
// is fetched and forwarded so the page still gets a valid observer list —
|
|
// only the latency is amplified. This isolates the init() sequencing
|
|
// behavior from network/IO variance on the CI runner.
|
|
await page.route('**/api/observers**', async (route) => {
|
|
const req = route.request();
|
|
let body = '{"observers":[]}';
|
|
let contentType = 'application/json';
|
|
try {
|
|
const upstream = await page.context().request.fetch(req);
|
|
body = await upstream.text();
|
|
contentType = upstream.headers()['content-type'] || contentType;
|
|
} catch (_) { /* fall through with empty observers */ }
|
|
await new Promise(r => setTimeout(r, OBSERVERS_DELAY_MS));
|
|
await route.fulfill({ status: 200, contentType, body });
|
|
});
|
|
|
|
await step(`first table row appears < ${FIRST_ROW_BUDGET_MS}ms despite ${OBSERVERS_DELAY_MS}ms /api/observers stub`, async () => {
|
|
// Clean SPA state — mirrors gotoPackets() pattern from test-e2e-playwright.js.
|
|
await page.goto(BASE, { waitUntil: 'domcontentloaded' });
|
|
await page.evaluate(() => {
|
|
localStorage.removeItem('meshcore-groupbyhash');
|
|
localStorage.setItem('meshcore-time-window', '525600');
|
|
});
|
|
|
|
const t0 = Date.now();
|
|
await page.goto(BASE + '/#/packets', { waitUntil: 'domcontentloaded' });
|
|
await page.waitForSelector('#pktBody tr[data-hash]', { timeout: 10000 });
|
|
const elapsed = Date.now() - t0;
|
|
console.log(` first-row elapsed: ${elapsed}ms (budget ${FIRST_ROW_BUDGET_MS}ms, observers stub ${OBSERVERS_DELAY_MS}ms)`);
|
|
assert(elapsed < FIRST_ROW_BUDGET_MS,
|
|
`first tr[data-hash] took ${elapsed}ms, expected < ${FIRST_ROW_BUDGET_MS}ms — packets.js::init() is serializing loadObservers → loadPackets (#1692)`);
|
|
});
|
|
|
|
await page.unroute('**/api/observers**');
|
|
await browser.close();
|
|
|
|
console.log(`\nResults: ${passed} passed, ${failed} failed`);
|
|
process.exit(failed ? 1 : 0);
|
|
})();
|