diff --git a/test-issue-180-packets-url-modal-e2e.js b/test-issue-180-packets-url-modal-e2e.js index 5dadbec3f..efecc461a 100644 --- a/test-issue-180-packets-url-modal-e2e.js +++ b/test-issue-180-packets-url-modal-e2e.js @@ -49,11 +49,6 @@ async function fresh(page, url) { await page.reload({ waitUntil: 'load' }); } -// The "Latest Packets (N)" count, once the list has rendered. -async function listCount(page) { - await page.waitForSelector('#pktLeft .count', { timeout: 15000 }); - return page.evaluate(() => Number((document.querySelector('#pktLeft .count').textContent.match(/\d+/) || [])[0])); -} function clearShown(page) { return page.evaluate(() => { const b = document.getElementById('clearFiltersBtn'); @@ -102,27 +97,78 @@ function detailPaneOpen(page) { await waitFor(page, () => /\(1\)/.test(document.querySelector('#pktLeft .count').textContent), 'detail URL did not filter the list to 1 packet'); assert(await clearShown(page), 'Clear button hidden on the filtered detail URL'); - // The fixture's packets may have aged out of the default 15 min window - // that Clear restores, so compare what Clear showed with what the - // reload shows rather than expecting a number of rows. const listLoaded = () => page.waitForResponse((r) => /\/api\/packets\?/.test(r.url()), { timeout: 15000 }); await Promise.all([listLoaded(), page.click('#clearFiltersBtn')]); await page.waitForTimeout(500); let h = await page.evaluate(() => location.hash); + // What Clear itself must do, asserted right after the click: the bare + // list URL, the Clear button gone and the detail pane closed. These are + // what a "Clear keeps ?timeWindow=" or "Clear keeps the detail" mutant + // breaks. (The window reset to the default is not visible here beyond + // Clear being hidden; it is asserted across the reload below.) assert(h === '#/packets', 'URL after Clear: ' + h); - const shown = await listCount(page); assert(!(await clearShown(page)), 'Clear button still visible after Clear'); assert(!(await detailPaneOpen(page)), 'detail pane still open after Clear'); + // Reloading the URL Clear wrote must give Clear's own state back — + // that is the "a reload shows the same list" half of this step, and the + // one a mutant where Clear's default no longer matches the default a + // cold load restores breaks. Compare STATE (URL, Clear button, the + // saved window, the detail pane, the hash input), all of which are + // clock-independent; the list contents are compared separately below at + // a pinned window, because row counts at the default window are not + // (#271). + const windowValue = () => page.evaluate(() => document.getElementById('fTimeWindow').value); + const twAfterClear = await windowValue(); await Promise.all([listLoaded(), page.reload({ waitUntil: 'load' })]); await page.waitForTimeout(500); h = await page.evaluate(() => location.hash); - assert(h === '#/packets', 'URL after reload: ' + h); - const reloaded = await listCount(page); - assert(reloaded === shown, 'reload shows ' + reloaded + ' packets, Clear showed ' + shown); + assert(h === '#/packets', 'URL after reloading the Clear state: ' + h); assert(!(await clearShown(page)), 'Clear button back after reload'); - assert(!(await detailPaneOpen(page)), 'detail pane open after reload'); + const twAfterReload = await windowValue(); + assert(twAfterReload === twAfterClear, 'time window after reload ' + twAfterReload + ', after Clear ' + twAfterClear); + assert(!(await detailPaneOpen(page)), 'detail pane open after reloading the Clear state'); assert(await page.evaluate(() => document.getElementById('fHash').value === ''), 'hash filter input filled after reload'); + + // #271: the list-contents half of this step used to compare row COUNTS + // at the default 15-min window across two renders (Clear's in-SPA list, + // then a full reload). Both compute `since = now - 15min` from the + // browser's live clock, so a fixture packet can cross that boundary in + // the real-time gap between the two fetches, and the CI fixture ages + // while the job runs (same class of problem #252 fixed for the Details + // test). Pin a wide window (24h; the widest option, same as #252) and + // compare the "Latest Packets (N)" total — the whole filtered set — + // plus the hashes of the rows actually rendered, so the comparison + // cannot depend on how old the fixture is. + await Promise.all([listLoaded(), page.evaluate(() => { + const sel = document.getElementById('fTimeWindow'); + sel.value = '1440'; + sel.dispatchEvent(new Event('change', { bubbles: true })); + })]); + await page.waitForTimeout(500); + // The table is virtualised, so only a window of rows is in the DOM at + // any time: `total` is the full filtered count, `hashes` is what is + // rendered. The two #vscroll-* spacer rows carry no data-hash and are + // dropped, so an empty table cannot pass as "a set of hashes". + const listState = async () => { + await page.waitForSelector('#pktLeft .count', { timeout: 15000 }); + return page.evaluate(() => ({ + total: Number((document.querySelector('#pktLeft .count').textContent.match(/\d+/) || [])[0]), + hashes: Array.from(document.querySelectorAll('#pktBody > tr:not(.group-child)')).map((tr) => tr.dataset.hash).filter(Boolean).sort(), + })); + }; + const shown = await listState(); + assert(shown.total > 0, 'no packets in the pinned 24h window'); + assert(shown.hashes.length > 0, 'no rendered rows with a hash to compare at the pinned 24h window'); + + await Promise.all([listLoaded(), page.reload({ waitUntil: 'load' })]); + await page.waitForTimeout(500); + h = await page.evaluate(() => location.hash); + assert(h === '#/packets?timeWindow=1440', 'URL after reloading the pinned window: ' + h); + const reloaded = await listState(); + assert(reloaded.total === shown.total, 'reload shows ' + reloaded.total + ' packets at the pinned window, before the reload it showed ' + shown.total); + assert(JSON.stringify(reloaded.hashes) === JSON.stringify(shown.hashes), 'reload renders ' + JSON.stringify(reloaded.hashes) + ' at the pinned window, before the reload it rendered ' + JSON.stringify(shown.hashes)); + assert(!(await detailPaneOpen(page)), 'detail pane open after reloading the pinned window'); }); await context.close(); }