From 8e6fc9602fdd7de14026ae3e14865dfe3fb508bc Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Tue, 31 Mar 2026 23:27:51 -0700 Subject: [PATCH] fix: stabilize Playwright packets test with explicit time window (#348) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the Playwright CI regression on master where the "Packets page loads with filter" test times out after 15 seconds waiting for able tbody tr to appear. ## Root Cause Three packets tests used an bout:blank round-trip pattern to force a full page reload: ` page.goto(BASE) → set localStorage → page.goto('about:blank') → page.goto(BASE/#/packets) ` This cross-origin round-trip through bout:blank causes the SPA's config fetch and router to not fire reliably in CI's headless Chromium, leaving the page uninitialized past the 15-second timeout. ## Fix Replace the bout:blank pattern with page.reload() in all three affected tests: ` page.goto(BASE/#/packets) → set localStorage → page.reload() ` This stays on the same origin throughout. Playwright handles same-origin reloads predictably — the page fully re-initializes, the IIFE re-reads localStorage, and loadPackets() uses the correct time window. ## Tests affected | Test | Change | |------|--------| | Packets page loads with filter | bout:blank → page.reload() | | Packets initial fetch honors persisted time window | bout:blank → page.reload() | | Packets groupByHash toggle works | bout:blank → page.reload() | ## Validation - All 318 unit tests pass (packet-filter: 62, aging: 29, frontend: 227) - No public/ files changed — no cache buster needed - Single file changed: est-e2e-playwright.js (9 insertions, 15 deletions) Co-authored-by: Kpa-clawbot <259247574+Kpa-clawbot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- test-e2e-playwright.js | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/test-e2e-playwright.js b/test-e2e-playwright.js index 76468868..a845af78 100644 --- a/test-e2e-playwright.js +++ b/test-e2e-playwright.js @@ -365,13 +365,12 @@ async function run() { await test('Packets page loads with filter', async () => { // Ensure desktop viewport and broad time window so fixture timestamps are included. await page.setViewportSize({ width: 1280, height: 720 }); - await page.goto(BASE, { waitUntil: 'domcontentloaded' }); - // Set time window BEFORE packets.js IIFE re-executes (525600 min ≈ 1 year) + // Set time window BEFORE packets.js IIFE re-executes (525600 min ≈ 1 year). + // Navigate to the packets URL then reload — avoids about:blank cross-origin issues + // that can prevent the SPA from fully initializing within the timeout. + await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' }); await page.evaluate(() => localStorage.setItem('meshcore-time-window', '525600')); - // Navigate away so next goto is a full page load (not a same-document hash change). - // This guarantees scripts re-execute and packets.js IIFE reads the new localStorage. - await page.goto('about:blank'); - await page.goto(`${BASE}/#/packets`, { waitUntil: 'load' }); + await page.reload({ waitUntil: 'load' }); await page.waitForSelector('table tbody tr', { timeout: 15000 }); const rowsBefore = await page.$$('table tbody tr'); assert(rowsBefore.length > 0, 'No packets visible'); @@ -387,11 +386,8 @@ async function run() { }); await test('Packets initial fetch honors persisted time window', async () => { - // Navigate to base first to get same-origin context for localStorage - await page.goto(BASE, { waitUntil: 'domcontentloaded' }); + // Set persisted time window to 60 min and reload so the IIFE reads it await page.evaluate(() => localStorage.setItem('meshcore-time-window', '60')); - // Navigate away so next goto is a full page load - await page.goto('about:blank'); const packetsRequestPromise = page.waitForRequest((req) => { try { @@ -402,8 +398,8 @@ async function run() { } }, { timeout: 10000 }); - // Full navigation from about:blank — scripts re-execute, IIFE reads localStorage - await page.goto(`${BASE}/#/packets`, { waitUntil: 'load' }); + // Full reload on the packets page — scripts re-execute, IIFE reads localStorage + await page.reload({ waitUntil: 'load' }); await page.waitForSelector('#fTimeWindow', { timeout: 10000 }); const timeWindowValue = await page.$eval('#fTimeWindow', (el) => el.value); assert(timeWindowValue === '60', `Expected time window dropdown to restore 60, got ${timeWindowValue}`); @@ -428,10 +424,8 @@ async function run() { // Test: Packets groupByHash toggle changes view await test('Packets groupByHash toggle works', async () => { // Restore wide time window — previous test set it to 60 min which excludes fixture data - await page.goto(BASE, { waitUntil: 'domcontentloaded' }); await page.evaluate(() => localStorage.setItem('meshcore-time-window', '525600')); - await page.goto('about:blank'); - await page.goto(`${BASE}/#/packets`, { waitUntil: 'load' }); + await page.reload({ waitUntil: 'load' }); await page.waitForSelector('table tbody tr', { timeout: 15000 }); const groupBtn = await page.$('#fGroup'); assert(groupBtn, 'Group by hash button (#fGroup) not found');