perf: replace 169 blind sleeps with Playwright waits in coverage script

Remove all 169 waitForTimeout() calls (totaling 104.1s of blind sleeping)
from scripts/collect-frontend-coverage.js:

- Helper functions (safeClick, safeFill, safeSelect, clickAll, cycleSelect):
  removed 300-400ms waits after every interaction — Playwright's built-in
  actionability checks handle waiting for elements automatically
- Post-navigation waits: removed redundant sleeps after page.goto() calls
  that already use waitUntil: 'networkidle'
- Hash-change navigations: replaced waitForTimeout with
  waitForLoadState('networkidle') for proper SPA route settling
- Toggle/button waits: removed — event handlers execute synchronously
  before click() resolves
- Post-evaluate waits: removed — evaluate() is synchronous

Local benchmark (Windows, sparse test data):
  Before: 744.8s
  After:  484.8s (35% faster, 260s saved)

On CI runner (ARM Linux with real mesh data), savings will be
proportionally better since most elements exist and the 104s
of blind sleeping was the dominant bottleneck.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kpa-clawbot
2026-03-26 23:02:03 -07:00
parent b76891a871
commit 72161ba8fe

View File

@@ -22,7 +22,6 @@ async function collectCoverage() {
async function safeClick(selector, timeout) { async function safeClick(selector, timeout) {
try { try {
await page.click(selector, { timeout: timeout || 3000 }); await page.click(selector, { timeout: timeout || 3000 });
await page.waitForTimeout(300);
} catch {} } catch {}
} }
@@ -30,7 +29,6 @@ async function collectCoverage() {
async function safeFill(selector, text) { async function safeFill(selector, text) {
try { try {
await page.fill(selector, text); await page.fill(selector, text);
await page.waitForTimeout(300);
} catch {} } catch {}
} }
@@ -38,7 +36,6 @@ async function collectCoverage() {
async function safeSelect(selector, value) { async function safeSelect(selector, value) {
try { try {
await page.selectOption(selector, value); await page.selectOption(selector, value);
await page.waitForTimeout(300);
} catch {} } catch {}
} }
@@ -47,7 +44,7 @@ async function collectCoverage() {
try { try {
const els = await page.$$(selector); const els = await page.$$(selector);
for (let i = 0; i < Math.min(els.length, max); i++) { for (let i = 0; i < Math.min(els.length, max); i++) {
try { await els[i].click(); await page.waitForTimeout(300); } catch {} try { await els[i].click(); } catch {}
} }
} catch {} } catch {}
} }
@@ -57,7 +54,7 @@ async function collectCoverage() {
try { try {
const options = await page.$$eval(`${selector} option`, opts => opts.map(o => o.value)); const options = await page.$$eval(`${selector} option`, opts => opts.map(o => o.value));
for (const val of options) { for (const val of options) {
try { await page.selectOption(selector, val); await page.waitForTimeout(400); } catch {} try { await page.selectOption(selector, val); } catch {}
} }
} catch {} } catch {}
} }
@@ -70,34 +67,26 @@ async function collectCoverage() {
await page.goto(BASE, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(BASE, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.evaluate(() => localStorage.clear()).catch(() => {}); await page.evaluate(() => localStorage.clear()).catch(() => {});
await page.goto(`${BASE}/#/home`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/home`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
// Click "I'm new" // Click "I'm new"
await safeClick('#chooseNew'); await safeClick('#chooseNew');
await page.waitForTimeout(1000);
// Now on home page as "new" user — interact with search // Now on home page as "new" user — interact with search
await safeFill('#homeSearch', 'test'); await safeFill('#homeSearch', 'test');
await page.waitForTimeout(600);
// Click suggest items if any // Click suggest items if any
await clickAll('.suggest-item', 3); await clickAll('.suggest-item', 3);
// Click suggest claim buttons // Click suggest claim buttons
await clickAll('.suggest-claim', 2); await clickAll('.suggest-claim', 2);
await safeFill('#homeSearch', ''); await safeFill('#homeSearch', '');
await page.waitForTimeout(300);
// Click my-node-card elements // Click my-node-card elements
await clickAll('.my-node-card', 3); await clickAll('.my-node-card', 3);
await page.waitForTimeout(300);
// Click health/packets buttons on cards // Click health/packets buttons on cards
await clickAll('[data-action="health"]', 2); await clickAll('[data-action="health"]', 2);
await page.waitForTimeout(500);
await clickAll('[data-action="packets"]', 2); await clickAll('[data-action="packets"]', 2);
await page.waitForTimeout(500);
// Click toggle level // Click toggle level
await safeClick('#toggleLevel'); await safeClick('#toggleLevel');
await page.waitForTimeout(500);
// Click FAQ items // Click FAQ items
await clickAll('.faq-q, .question, [class*="accordion"]', 5); await clickAll('.faq-q, .question, [class*="accordion"]', 5);
@@ -117,46 +106,40 @@ async function collectCoverage() {
// Switch to experienced mode // Switch to experienced mode
await page.evaluate(() => localStorage.clear()).catch(() => {}); await page.evaluate(() => localStorage.clear()).catch(() => {});
await page.goto(`${BASE}/#/home`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/home`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1000);
await safeClick('#chooseExp'); await safeClick('#chooseExp');
await page.waitForTimeout(1000);
// Interact with experienced home page // Interact with experienced home page
await safeFill('#homeSearch', 'a'); await safeFill('#homeSearch', 'a');
await page.waitForTimeout(600);
await clickAll('.suggest-item', 2); await clickAll('.suggest-item', 2);
await safeFill('#homeSearch', ''); await safeFill('#homeSearch', '');
await page.waitForTimeout(300);
// Click outside to dismiss suggest // Click outside to dismiss suggest
await page.evaluate(() => document.body.click()).catch(() => {}); await page.evaluate(() => document.body.click()).catch(() => {});
await page.waitForTimeout(300);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
// NODES PAGE // NODES PAGE
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Nodes page...'); console.log(' [coverage] Nodes page...');
await page.goto(`${BASE}/#/nodes`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/nodes`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
// Sort by EVERY column // Sort by EVERY column
for (const col of ['name', 'public_key', 'role', 'last_seen', 'advert_count']) { for (const col of ['name', 'public_key', 'role', 'last_seen', 'advert_count']) {
try { await page.click(`th[data-sort="${col}"]`); await page.waitForTimeout(300); } catch {} try { await page.click(`th[data-sort="${col}"]`); } catch {}
// Click again for reverse sort // Click again for reverse sort
try { await page.click(`th[data-sort="${col}"]`); await page.waitForTimeout(300); } catch {} try { await page.click(`th[data-sort="${col}"]`); } catch {}
} }
// Click EVERY role tab // Click EVERY role tab
const roleTabs = await page.$$('.node-tab[data-tab]'); const roleTabs = await page.$$('.node-tab[data-tab]');
for (const tab of roleTabs) { for (const tab of roleTabs) {
try { await tab.click(); await page.waitForTimeout(500); } catch {} try { await tab.click(); } catch {}
} }
// Go back to "all" // Go back to "all"
try { await page.click('.node-tab[data-tab="all"]'); await page.waitForTimeout(400); } catch {} try { await page.click('.node-tab[data-tab="all"]'); } catch {}
// Click EVERY status filter // Click EVERY status filter
for (const status of ['active', 'stale', 'all']) { for (const status of ['active', 'stale', 'all']) {
try { await page.click(`#nodeStatusFilter .btn[data-status="${status}"]`); await page.waitForTimeout(400); } catch {} try { await page.click(`#nodeStatusFilter .btn[data-status="${status}"]`); } catch {}
} }
// Cycle EVERY Last Heard option // Cycle EVERY Last Heard option
@@ -164,33 +147,28 @@ async function collectCoverage() {
// Search // Search
await safeFill('#nodeSearch', 'test'); await safeFill('#nodeSearch', 'test');
await page.waitForTimeout(500);
await safeFill('#nodeSearch', ''); await safeFill('#nodeSearch', '');
await page.waitForTimeout(300);
// Click node rows to open side pane — try multiple // Click node rows to open side pane — try multiple
const nodeRows = await page.$$('#nodesBody tr'); const nodeRows = await page.$$('#nodesBody tr');
for (let i = 0; i < Math.min(nodeRows.length, 4); i++) { for (let i = 0; i < Math.min(nodeRows.length, 4); i++) {
try { await nodeRows[i].click(); await page.waitForTimeout(600); } catch {} try { await nodeRows[i].click(); } catch {}
} }
// In side pane — click detail/analytics links // In side pane — click detail/analytics links
await safeClick('a[href*="/nodes/"]', 2000); await safeClick('a[href*="/nodes/"]', 2000);
await page.waitForTimeout(1500);
// Click fav star // Click fav star
await clickAll('.fav-star', 2); await clickAll('.fav-star', 2);
// On node detail page — interact // On node detail page — interact
// Click back button // Click back button
await safeClick('#nodeBackBtn'); await safeClick('#nodeBackBtn');
await page.waitForTimeout(500);
// Navigate to a node detail page via hash // Navigate to a node detail page via hash
try { try {
const firstNodeKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim()); const firstNodeKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim());
if (firstNodeKey) { if (firstNodeKey) {
await page.goto(`${BASE}/#/nodes/${firstNodeKey}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/nodes/${firstNodeKey}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
// Click tabs on detail page // Click tabs on detail page
await clickAll('.tab-btn, [data-tab]', 10); await clickAll('.tab-btn, [data-tab]', 10);
@@ -204,7 +182,7 @@ async function collectCoverage() {
// Click node analytics day buttons // Click node analytics day buttons
for (const days of ['1', '7', '30', '365']) { for (const days of ['1', '7', '30', '365']) {
try { await page.click(`[data-days="${days}"]`); await page.waitForTimeout(800); } catch {} try { await page.click(`[data-days="${days}"]`); } catch {}
} }
} }
} catch {} } catch {}
@@ -214,7 +192,6 @@ async function collectCoverage() {
const firstKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim()).catch(() => null); const firstKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim()).catch(() => null);
if (firstKey) { if (firstKey) {
await page.goto(`${BASE}/#/nodes/${firstKey}?scroll=paths`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/nodes/${firstKey}?scroll=paths`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
} }
} catch {} } catch {}
@@ -223,11 +200,9 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Packets page...'); console.log(' [coverage] Packets page...');
await page.goto(`${BASE}/#/packets`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/packets`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
// Open filter bar // Open filter bar
await safeClick('#filterToggleBtn'); await safeClick('#filterToggleBtn');
await page.waitForTimeout(500);
// Type various filter expressions // Type various filter expressions
const filterExprs = [ const filterExprs = [
@@ -237,7 +212,6 @@ async function collectCoverage() {
]; ];
for (const expr of filterExprs) { for (const expr of filterExprs) {
await safeFill('#packetFilterInput', expr); await safeFill('#packetFilterInput', expr);
await page.waitForTimeout(500);
} }
// Cycle ALL time window options // Cycle ALL time window options
@@ -245,70 +219,52 @@ async function collectCoverage() {
// Toggle group by hash // Toggle group by hash
await safeClick('#fGroup'); await safeClick('#fGroup');
await page.waitForTimeout(600);
await safeClick('#fGroup'); await safeClick('#fGroup');
await page.waitForTimeout(600);
// Toggle My Nodes filter // Toggle My Nodes filter
await safeClick('#fMyNodes'); await safeClick('#fMyNodes');
await page.waitForTimeout(500);
await safeClick('#fMyNodes'); await safeClick('#fMyNodes');
await page.waitForTimeout(500);
// Click observer menu trigger // Click observer menu trigger
await safeClick('#observerTrigger'); await safeClick('#observerTrigger');
await page.waitForTimeout(400);
// Click items in observer menu // Click items in observer menu
await clickAll('#observerMenu input[type="checkbox"]', 5); await clickAll('#observerMenu input[type="checkbox"]', 5);
await safeClick('#observerTrigger'); await safeClick('#observerTrigger');
await page.waitForTimeout(300);
// Click type filter trigger // Click type filter trigger
await safeClick('#typeTrigger'); await safeClick('#typeTrigger');
await page.waitForTimeout(400);
await clickAll('#typeMenu input[type="checkbox"]', 5); await clickAll('#typeMenu input[type="checkbox"]', 5);
await safeClick('#typeTrigger'); await safeClick('#typeTrigger');
await page.waitForTimeout(300);
// Hash input // Hash input
await safeFill('#fHash', 'abc123'); await safeFill('#fHash', 'abc123');
await page.waitForTimeout(500);
await safeFill('#fHash', ''); await safeFill('#fHash', '');
await page.waitForTimeout(300);
// Node filter // Node filter
await safeFill('#fNode', 'test'); await safeFill('#fNode', 'test');
await page.waitForTimeout(500);
await clickAll('.node-filter-option', 3); await clickAll('.node-filter-option', 3);
await safeFill('#fNode', ''); await safeFill('#fNode', '');
await page.waitForTimeout(300);
// Observer sort // Observer sort
await cycleSelect('#fObsSort'); await cycleSelect('#fObsSort');
// Column toggle menu // Column toggle menu
await safeClick('#colToggleBtn'); await safeClick('#colToggleBtn');
await page.waitForTimeout(400);
await clickAll('#colToggleMenu input[type="checkbox"]', 8); await clickAll('#colToggleMenu input[type="checkbox"]', 8);
await safeClick('#colToggleBtn'); await safeClick('#colToggleBtn');
await page.waitForTimeout(300);
// Hex hash toggle // Hex hash toggle
await safeClick('#hexHashToggle'); await safeClick('#hexHashToggle');
await page.waitForTimeout(400);
await safeClick('#hexHashToggle'); await safeClick('#hexHashToggle');
await page.waitForTimeout(300);
// Pause button // Pause button
await safeClick('#pktPauseBtn'); await safeClick('#pktPauseBtn');
await page.waitForTimeout(400);
await safeClick('#pktPauseBtn'); await safeClick('#pktPauseBtn');
await page.waitForTimeout(400);
// Click packet rows to open detail pane // Click packet rows to open detail pane
const pktRows = await page.$$('#pktBody tr'); const pktRows = await page.$$('#pktBody tr');
for (let i = 0; i < Math.min(pktRows.length, 5); i++) { for (let i = 0; i < Math.min(pktRows.length, 5); i++) {
try { await pktRows[i].click(); await page.waitForTimeout(500); } catch {} try { await pktRows[i].click(); } catch {}
} }
// Resize handle drag simulation // Resize handle drag simulation
@@ -321,63 +277,50 @@ async function collectCoverage() {
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })); document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
} }
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
// Click outside filter menus to close them // Click outside filter menus to close them
try { try {
await page.evaluate(() => document.body.click()); await page.evaluate(() => document.body.click());
await page.waitForTimeout(300);
} catch {} } catch {}
// Navigate to specific packet by hash // Navigate to specific packet by hash
await page.goto(`${BASE}/#/packets/deadbeef`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/packets/deadbeef`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
// MAP PAGE // MAP PAGE
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Map page...'); console.log(' [coverage] Map page...');
await page.goto(`${BASE}/#/map`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/map`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(3000);
// Toggle controls panel // Toggle controls panel
await safeClick('#mapControlsToggle'); await safeClick('#mapControlsToggle');
await page.waitForTimeout(500);
// Toggle each role checkbox on/off // Toggle each role checkbox on/off
try { try {
const roleChecks = await page.$$('#mcRoleChecks input[type="checkbox"]'); const roleChecks = await page.$$('#mcRoleChecks input[type="checkbox"]');
for (const cb of roleChecks) { for (const cb of roleChecks) {
try { await cb.click(); await page.waitForTimeout(300); } catch {} try { await cb.click(); } catch {}
try { await cb.click(); await page.waitForTimeout(300); } catch {} try { await cb.click(); } catch {}
} }
} catch {} } catch {}
// Toggle clusters, heatmap, neighbors, hash labels // Toggle clusters, heatmap, neighbors, hash labels
await safeClick('#mcClusters'); await safeClick('#mcClusters');
await page.waitForTimeout(300);
await safeClick('#mcClusters'); await safeClick('#mcClusters');
await page.waitForTimeout(300);
await safeClick('#mcHeatmap'); await safeClick('#mcHeatmap');
await page.waitForTimeout(300);
await safeClick('#mcHeatmap'); await safeClick('#mcHeatmap');
await page.waitForTimeout(300);
await safeClick('#mcNeighbors'); await safeClick('#mcNeighbors');
await page.waitForTimeout(300);
await safeClick('#mcNeighbors'); await safeClick('#mcNeighbors');
await page.waitForTimeout(300);
await safeClick('#mcHashLabels'); await safeClick('#mcHashLabels');
await page.waitForTimeout(300);
await safeClick('#mcHashLabels'); await safeClick('#mcHashLabels');
await page.waitForTimeout(300);
// Last heard dropdown on map // Last heard dropdown on map
await cycleSelect('#mcLastHeard'); await cycleSelect('#mcLastHeard');
// Status filter buttons on map // Status filter buttons on map
for (const st of ['active', 'stale', 'all']) { for (const st of ['active', 'stale', 'all']) {
try { await page.click(`#mcStatusFilter .btn[data-status="${st}"]`); await page.waitForTimeout(400); } catch {} try { await page.click(`#mcStatusFilter .btn[data-status="${st}"]`); } catch {}
} }
// Click jump buttons (region jumps) // Click jump buttons (region jumps)
@@ -392,78 +335,61 @@ async function collectCoverage() {
// Zoom controls // Zoom controls
await safeClick('.leaflet-control-zoom-in'); await safeClick('.leaflet-control-zoom-in');
await page.waitForTimeout(300);
await safeClick('.leaflet-control-zoom-out'); await safeClick('.leaflet-control-zoom-out');
await page.waitForTimeout(300);
// Toggle dark mode while on map (triggers tile layer swap) // Toggle dark mode while on map (triggers tile layer swap)
await safeClick('#darkModeToggle'); await safeClick('#darkModeToggle');
await page.waitForTimeout(800);
await safeClick('#darkModeToggle'); await safeClick('#darkModeToggle');
await page.waitForTimeout(500);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
// ANALYTICS PAGE // ANALYTICS PAGE
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Analytics page...'); console.log(' [coverage] Analytics page...');
await page.goto(`${BASE}/#/analytics`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/analytics`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(3000);
// Click EVERY analytics tab // Click EVERY analytics tab
const analyticsTabs = ['overview', 'rf', 'topology', 'channels', 'hashsizes', 'collisions', 'subpaths', 'nodes', 'distance']; const analyticsTabs = ['overview', 'rf', 'topology', 'channels', 'hashsizes', 'collisions', 'subpaths', 'nodes', 'distance'];
for (const tabName of analyticsTabs) { for (const tabName of analyticsTabs) {
try { try {
await page.click(`#analyticsTabs [data-tab="${tabName}"]`, { timeout: 2000 }); await page.click(`#analyticsTabs [data-tab="${tabName}"]`, { timeout: 2000 });
await page.waitForTimeout(1500);
} catch {} } catch {}
} }
// On topology tab — click observer selector buttons // On topology tab — click observer selector buttons
try { try {
await page.click('#analyticsTabs [data-tab="topology"]', { timeout: 2000 }); await page.click('#analyticsTabs [data-tab="topology"]', { timeout: 2000 });
await page.waitForTimeout(1500);
await clickAll('#obsSelector .tab-btn', 5); await clickAll('#obsSelector .tab-btn', 5);
// Click the "All Observers" button // Click the "All Observers" button
await safeClick('[data-obs="__all"]'); await safeClick('[data-obs="__all"]');
await page.waitForTimeout(500);
} catch {} } catch {}
// On collisions tab — click navigate rows // On collisions tab — click navigate rows
try { try {
await page.click('#analyticsTabs [data-tab="collisions"]', { timeout: 2000 }); await page.click('#analyticsTabs [data-tab="collisions"]', { timeout: 2000 });
await page.waitForTimeout(1500);
await clickAll('tr[data-action="navigate"]', 3); await clickAll('tr[data-action="navigate"]', 3);
await page.waitForTimeout(500);
} catch {} } catch {}
// On subpaths tab — click rows // On subpaths tab — click rows
try { try {
await page.click('#analyticsTabs [data-tab="subpaths"]', { timeout: 2000 }); await page.click('#analyticsTabs [data-tab="subpaths"]', { timeout: 2000 });
await page.waitForTimeout(1500);
await clickAll('tr[data-action="navigate"]', 3); await clickAll('tr[data-action="navigate"]', 3);
await page.waitForTimeout(500);
} catch {} } catch {}
// On nodes tab — click sortable headers // On nodes tab — click sortable headers
try { try {
await page.click('#analyticsTabs [data-tab="nodes"]', { timeout: 2000 }); await page.click('#analyticsTabs [data-tab="nodes"]', { timeout: 2000 });
await page.waitForTimeout(1500);
await clickAll('.analytics-table th', 8); await clickAll('.analytics-table th', 8);
await page.waitForTimeout(300);
} catch {} } catch {}
// Deep-link to each analytics tab via URL // Deep-link to each analytics tab via URL
for (const tab of analyticsTabs) { for (const tab of analyticsTabs) {
await page.goto(`${BASE}/#/analytics?tab=${tab}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/analytics?tab=${tab}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
} }
// Region filter on analytics // Region filter on analytics
try { try {
await page.click('#analyticsRegionFilter'); await page.click('#analyticsRegionFilter');
await page.waitForTimeout(300);
await clickAll('#analyticsRegionFilter input[type="checkbox"]', 3); await clickAll('#analyticsRegionFilter input[type="checkbox"]', 3);
await page.waitForTimeout(300);
} catch {} } catch {}
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
@@ -471,19 +397,16 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Customizer...'); console.log(' [coverage] Customizer...');
await page.goto(BASE, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(BASE, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(500);
await safeClick('#customizeToggle'); await safeClick('#customizeToggle');
await page.waitForTimeout(1000);
// Click EVERY customizer tab // Click EVERY customizer tab
for (const tab of ['branding', 'theme', 'nodes', 'home', 'export']) { for (const tab of ['branding', 'theme', 'nodes', 'home', 'export']) {
try { await page.click(`.cust-tab[data-tab="${tab}"]`); await page.waitForTimeout(500); } catch {} try { await page.click(`.cust-tab[data-tab="${tab}"]`); } catch {}
} }
// On branding tab — change text inputs // On branding tab — change text inputs
try { try {
await page.click('.cust-tab[data-tab="branding"]'); await page.click('.cust-tab[data-tab="branding"]');
await page.waitForTimeout(300);
await safeFill('input[data-key="branding.siteName"]', 'Test Site'); await safeFill('input[data-key="branding.siteName"]', 'Test Site');
await safeFill('input[data-key="branding.tagline"]', 'Test Tagline'); await safeFill('input[data-key="branding.tagline"]', 'Test Tagline');
await safeFill('input[data-key="branding.logoUrl"]', 'https://example.com/logo.png'); await safeFill('input[data-key="branding.logoUrl"]', 'https://example.com/logo.png');
@@ -493,10 +416,9 @@ async function collectCoverage() {
// On theme tab — click EVERY preset // On theme tab — click EVERY preset
try { try {
await page.click('.cust-tab[data-tab="theme"]'); await page.click('.cust-tab[data-tab="theme"]');
await page.waitForTimeout(300);
const presets = await page.$$('.cust-preset-btn[data-preset]'); const presets = await page.$$('.cust-preset-btn[data-preset]');
for (const preset of presets) { for (const preset of presets) {
try { await preset.click(); await page.waitForTimeout(400); } catch {} try { await preset.click(); } catch {}
} }
} catch {} } catch {}
@@ -509,7 +431,6 @@ async function collectCoverage() {
el.value = '#ff5500'; el.value = '#ff5500';
el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('input', { bubbles: true }));
}); });
await page.waitForTimeout(200);
} catch {} } catch {}
} }
} catch {} } catch {}
@@ -522,7 +443,6 @@ async function collectCoverage() {
// On nodes tab — change node color inputs // On nodes tab — change node color inputs
try { try {
await page.click('.cust-tab[data-tab="nodes"]'); await page.click('.cust-tab[data-tab="nodes"]');
await page.waitForTimeout(300);
const nodeColors = await page.$$('input[type="color"][data-node]'); const nodeColors = await page.$$('input[type="color"][data-node]');
for (let i = 0; i < Math.min(nodeColors.length, 3); i++) { for (let i = 0; i < Math.min(nodeColors.length, 3); i++) {
try { try {
@@ -530,7 +450,6 @@ async function collectCoverage() {
el.value = '#00ff00'; el.value = '#00ff00';
el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('input', { bubbles: true }));
}); });
await page.waitForTimeout(200);
} catch {} } catch {}
} }
// Type color inputs // Type color inputs
@@ -541,7 +460,6 @@ async function collectCoverage() {
el.value = '#0000ff'; el.value = '#0000ff';
el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('input', { bubbles: true }));
}); });
await page.waitForTimeout(200);
} catch {} } catch {}
} }
} catch {} } catch {}
@@ -549,7 +467,6 @@ async function collectCoverage() {
// On home tab — edit home customization fields // On home tab — edit home customization fields
try { try {
await page.click('.cust-tab[data-tab="home"]'); await page.click('.cust-tab[data-tab="home"]');
await page.waitForTimeout(300);
await safeFill('input[data-key="home.heroTitle"]', 'Test Hero'); await safeFill('input[data-key="home.heroTitle"]', 'Test Hero');
await safeFill('input[data-key="home.heroSubtitle"]', 'Test Subtitle'); await safeFill('input[data-key="home.heroSubtitle"]', 'Test Subtitle');
// Edit journey steps // Edit journey steps
@@ -564,7 +481,6 @@ async function collectCoverage() {
for (let i = 0; i < Math.min(stepTitles.length, 2); i++) { for (let i = 0; i < Math.min(stepTitles.length, 2); i++) {
try { try {
await stepTitles[i].fill('Test Step ' + i); await stepTitles[i].fill('Test Step ' + i);
await page.waitForTimeout(200);
} catch {} } catch {}
} }
} catch {} } catch {}
@@ -572,27 +488,22 @@ async function collectCoverage() {
// On export tab // On export tab
try { try {
await page.click('.cust-tab[data-tab="export"]'); await page.click('.cust-tab[data-tab="export"]');
await page.waitForTimeout(500);
// Click export/import buttons if present // Click export/import buttons if present
await clickAll('.cust-panel[data-panel="export"] button', 3); await clickAll('.cust-panel[data-panel="export"] button', 3);
} catch {} } catch {}
// Reset preview and user theme // Reset preview and user theme
await safeClick('#custResetPreview'); await safeClick('#custResetPreview');
await page.waitForTimeout(400);
await safeClick('#custResetUser'); await safeClick('#custResetUser');
await page.waitForTimeout(400);
// Close customizer // Close customizer
await safeClick('.cust-close'); await safeClick('.cust-close');
await page.waitForTimeout(300);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
// CHANNELS PAGE // CHANNELS PAGE
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Channels page...'); console.log(' [coverage] Channels page...');
await page.goto(`${BASE}/#/channels`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/channels`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
// Click channel rows/items // Click channel rows/items
await clickAll('.channel-item, .channel-row, .channel-card', 3); await clickAll('.channel-item, .channel-row, .channel-card', 3);
await clickAll('table tbody tr', 3); await clickAll('table tbody tr', 3);
@@ -602,7 +513,6 @@ async function collectCoverage() {
const channelHash = await page.$eval('table tbody tr td:first-child', el => el.textContent.trim()).catch(() => null); const channelHash = await page.$eval('table tbody tr td:first-child', el => el.textContent.trim()).catch(() => null);
if (channelHash) { if (channelHash) {
await page.goto(`${BASE}/#/channels/${channelHash}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/channels/${channelHash}`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
} }
} catch {} } catch {}
@@ -611,78 +521,53 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Live page...'); console.log(' [coverage] Live page...');
await page.goto(`${BASE}/#/live`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/live`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(3000);
// VCR controls // VCR controls
await safeClick('#vcrPauseBtn'); await safeClick('#vcrPauseBtn');
await page.waitForTimeout(400);
await safeClick('#vcrPauseBtn'); await safeClick('#vcrPauseBtn');
await page.waitForTimeout(400);
// VCR speed cycle // VCR speed cycle
await safeClick('#vcrSpeedBtn'); await safeClick('#vcrSpeedBtn');
await page.waitForTimeout(300);
await safeClick('#vcrSpeedBtn'); await safeClick('#vcrSpeedBtn');
await page.waitForTimeout(300);
await safeClick('#vcrSpeedBtn'); await safeClick('#vcrSpeedBtn');
await page.waitForTimeout(300);
// VCR mode / missed // VCR mode / missed
await safeClick('#vcrMissed'); await safeClick('#vcrMissed');
await page.waitForTimeout(300);
// VCR prompt buttons // VCR prompt buttons
await safeClick('#vcrPromptReplay'); await safeClick('#vcrPromptReplay');
await page.waitForTimeout(300);
await safeClick('#vcrPromptSkip'); await safeClick('#vcrPromptSkip');
await page.waitForTimeout(300);
// Toggle visualization options // Toggle visualization options
await safeClick('#liveHeatToggle'); await safeClick('#liveHeatToggle');
await page.waitForTimeout(400);
await safeClick('#liveHeatToggle'); await safeClick('#liveHeatToggle');
await page.waitForTimeout(300);
await safeClick('#liveGhostToggle'); await safeClick('#liveGhostToggle');
await page.waitForTimeout(300);
await safeClick('#liveGhostToggle'); await safeClick('#liveGhostToggle');
await page.waitForTimeout(300);
await safeClick('#liveRealisticToggle'); await safeClick('#liveRealisticToggle');
await page.waitForTimeout(300);
await safeClick('#liveRealisticToggle'); await safeClick('#liveRealisticToggle');
await page.waitForTimeout(300);
await safeClick('#liveFavoritesToggle'); await safeClick('#liveFavoritesToggle');
await page.waitForTimeout(300);
await safeClick('#liveFavoritesToggle'); await safeClick('#liveFavoritesToggle');
await page.waitForTimeout(300);
await safeClick('#liveMatrixToggle'); await safeClick('#liveMatrixToggle');
await page.waitForTimeout(300);
await safeClick('#liveMatrixToggle'); await safeClick('#liveMatrixToggle');
await page.waitForTimeout(300);
await safeClick('#liveMatrixRainToggle'); await safeClick('#liveMatrixRainToggle');
await page.waitForTimeout(300);
await safeClick('#liveMatrixRainToggle'); await safeClick('#liveMatrixRainToggle');
await page.waitForTimeout(300);
// Audio toggle and controls // Audio toggle and controls
await safeClick('#liveAudioToggle'); await safeClick('#liveAudioToggle');
await page.waitForTimeout(400);
try { try {
await page.fill('#audioBpmSlider', '120'); await page.fill('#audioBpmSlider', '120');
await page.waitForTimeout(300);
// Dispatch input event on slider // Dispatch input event on slider
await page.evaluate(() => { await page.evaluate(() => {
const s = document.getElementById('audioBpmSlider'); const s = document.getElementById('audioBpmSlider');
if (s) { s.value = '140'; s.dispatchEvent(new Event('input', { bubbles: true })); } if (s) { s.value = '140'; s.dispatchEvent(new Event('input', { bubbles: true })); }
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
await safeClick('#liveAudioToggle'); await safeClick('#liveAudioToggle');
await page.waitForTimeout(300);
// VCR timeline click // VCR timeline click
try { try {
@@ -697,7 +582,6 @@ async function collectCoverage() {
})); }));
} }
}); });
await page.waitForTimeout(500);
} catch {} } catch {}
// VCR LCD canvas // VCR LCD canvas
@@ -706,7 +590,6 @@ async function collectCoverage() {
const canvas = document.getElementById('vcrLcdCanvas'); const canvas = document.getElementById('vcrLcdCanvas');
if (canvas) canvas.getContext('2d'); if (canvas) canvas.getContext('2d');
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
// Resize the live page panel // Resize the live page panel
@@ -714,7 +597,6 @@ async function collectCoverage() {
await page.evaluate(() => { await page.evaluate(() => {
window.dispatchEvent(new Event('resize')); window.dispatchEvent(new Event('resize'));
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
@@ -722,7 +604,6 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Traces page...'); console.log(' [coverage] Traces page...');
await page.goto(`${BASE}/#/traces`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/traces`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
await clickAll('table tbody tr', 3); await clickAll('table tbody tr', 3);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
@@ -730,11 +611,10 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Observers page...'); console.log(' [coverage] Observers page...');
await page.goto(`${BASE}/#/observers`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/observers`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
// Click observer rows // Click observer rows
const obsRows = await page.$$('table tbody tr, .observer-card, .observer-row'); const obsRows = await page.$$('table tbody tr, .observer-card, .observer-row');
for (let i = 0; i < Math.min(obsRows.length, 3); i++) { for (let i = 0; i < Math.min(obsRows.length, 3); i++) {
try { await obsRows[i].click(); await page.waitForTimeout(500); } catch {} try { await obsRows[i].click(); } catch {}
} }
// Navigate to observer detail page // Navigate to observer detail page
@@ -742,7 +622,6 @@ async function collectCoverage() {
const obsLink = await page.$('a[href*="/observers/"]'); const obsLink = await page.$('a[href*="/observers/"]');
if (obsLink) { if (obsLink) {
await obsLink.click(); await obsLink.click();
await page.waitForTimeout(2000);
// Change days select // Change days select
await cycleSelect('#obsDaysSelect'); await cycleSelect('#obsDaysSelect');
} }
@@ -753,11 +632,8 @@ async function collectCoverage() {
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
console.log(' [coverage] Perf page...'); console.log(' [coverage] Perf page...');
await page.goto(`${BASE}/#/perf`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/perf`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(2000);
await safeClick('#perfRefresh'); await safeClick('#perfRefresh');
await page.waitForTimeout(1000);
await safeClick('#perfReset'); await safeClick('#perfReset');
await page.waitForTimeout(500);
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
// APP.JS — Router, theme, global features // APP.JS — Router, theme, global features
@@ -766,14 +642,13 @@ async function collectCoverage() {
// Navigate to bad route to trigger error/404 // Navigate to bad route to trigger error/404
await page.goto(`${BASE}/#/nonexistent-route`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/nonexistent-route`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1000);
// Navigate to every route via hash // Navigate to every route via hash
const allRoutes = ['home', 'nodes', 'packets', 'map', 'live', 'channels', 'traces', 'observers', 'analytics', 'perf']; const allRoutes = ['home', 'nodes', 'packets', 'map', 'live', 'channels', 'traces', 'observers', 'analytics', 'perf'];
for (const route of allRoutes) { for (const route of allRoutes) {
try { try {
await page.evaluate((r) => { location.hash = '#/' + r; }, route); await page.evaluate((r) => { location.hash = '#/' + r; }, route);
await page.waitForTimeout(800); await page.waitForLoadState('networkidle').catch(() => {});
} catch {} } catch {}
} }
@@ -782,13 +657,11 @@ async function collectCoverage() {
await page.evaluate(() => { await page.evaluate(() => {
window.dispatchEvent(new HashChangeEvent('hashchange')); window.dispatchEvent(new HashChangeEvent('hashchange'));
}); });
await page.waitForTimeout(500);
} catch {} } catch {}
// Theme toggle multiple times // Theme toggle multiple times
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
await safeClick('#darkModeToggle'); await safeClick('#darkModeToggle');
await page.waitForTimeout(300);
} }
// Dispatch theme-changed event // Dispatch theme-changed event
@@ -796,65 +669,49 @@ async function collectCoverage() {
await page.evaluate(() => { await page.evaluate(() => {
window.dispatchEvent(new Event('theme-changed')); window.dispatchEvent(new Event('theme-changed'));
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
// Hamburger menu // Hamburger menu
await safeClick('#hamburger'); await safeClick('#hamburger');
await page.waitForTimeout(400);
// Click nav links in mobile menu // Click nav links in mobile menu
await clickAll('.nav-links .nav-link', 5); await clickAll('.nav-links .nav-link', 5);
await page.waitForTimeout(300);
// Favorites // Favorites
await safeClick('#favToggle'); await safeClick('#favToggle');
await page.waitForTimeout(500);
await clickAll('.fav-dd-item', 3); await clickAll('.fav-dd-item', 3);
// Click outside to close // Click outside to close
try { await page.evaluate(() => document.body.click()); await page.waitForTimeout(300); } catch {} try { await page.evaluate(() => document.body.click()); } catch {}
await safeClick('#favToggle'); await safeClick('#favToggle');
await page.waitForTimeout(300);
// Global search // Global search
await safeClick('#searchToggle'); await safeClick('#searchToggle');
await page.waitForTimeout(500);
await safeFill('#searchInput', 'test'); await safeFill('#searchInput', 'test');
await page.waitForTimeout(1000);
// Click search result items // Click search result items
await clickAll('.search-result-item', 3); await clickAll('.search-result-item', 3);
await page.waitForTimeout(500);
// Close search // Close search
try { await page.keyboard.press('Escape'); } catch {} try { await page.keyboard.press('Escape'); } catch {}
await page.waitForTimeout(300);
// Ctrl+K shortcut // Ctrl+K shortcut
try { try {
await page.keyboard.press('Control+k'); await page.keyboard.press('Control+k');
await page.waitForTimeout(500);
await safeFill('#searchInput', 'node'); await safeFill('#searchInput', 'node');
await page.waitForTimeout(800);
await page.keyboard.press('Escape'); await page.keyboard.press('Escape');
await page.waitForTimeout(300);
} catch {} } catch {}
// Click search overlay background to close // Click search overlay background to close
try { try {
await safeClick('#searchToggle'); await safeClick('#searchToggle');
await page.waitForTimeout(300);
await page.click('#searchOverlay', { position: { x: 5, y: 5 } }); await page.click('#searchOverlay', { position: { x: 5, y: 5 } });
await page.waitForTimeout(300);
} catch {} } catch {}
// Navigate via nav links with data-route // Navigate via nav links with data-route
for (const route of allRoutes) { for (const route of allRoutes) {
await safeClick(`a[data-route="${route}"]`); await safeClick(`a[data-route="${route}"]`);
await page.waitForTimeout(600);
} }
// Exercise apiPerf console function // Exercise apiPerf console function
try { try {
await page.evaluate(() => { if (window.apiPerf) window.apiPerf(); }); await page.evaluate(() => { if (window.apiPerf) window.apiPerf(); });
await page.waitForTimeout(300);
} catch {} } catch {}
// Exercise utility functions // Exercise utility functions
@@ -890,7 +747,6 @@ async function collectCoverage() {
invalidateApiCache('/test'); invalidateApiCache('/test');
} }
}); });
await page.waitForTimeout(300);
} catch {} } catch {}
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
@@ -933,21 +789,15 @@ async function collectCoverage() {
try { try {
// Open region filter on nodes page // Open region filter on nodes page
await page.goto(`${BASE}/#/nodes`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/nodes`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
await safeClick('#nodesRegionFilter'); await safeClick('#nodesRegionFilter');
await page.waitForTimeout(300);
await clickAll('#nodesRegionFilter input[type="checkbox"]', 3); await clickAll('#nodesRegionFilter input[type="checkbox"]', 3);
await page.waitForTimeout(300);
} catch {} } catch {}
// Region filter on packets // Region filter on packets
try { try {
await page.goto(`${BASE}/#/packets`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {}); await page.goto(`${BASE}/#/packets`, { waitUntil: 'networkidle', timeout: 15000 }).catch(() => {});
await page.waitForTimeout(1500);
await safeClick('#packetsRegionFilter'); await safeClick('#packetsRegionFilter');
await page.waitForTimeout(300);
await clickAll('#packetsRegionFilter input[type="checkbox"]', 3); await clickAll('#packetsRegionFilter input[type="checkbox"]', 3);
await page.waitForTimeout(300);
} catch {} } catch {}
// ══════════════════════════════════════════════ // ══════════════════════════════════════════════
@@ -957,7 +807,7 @@ async function collectCoverage() {
for (const route of allRoutes) { for (const route of allRoutes) {
try { try {
await page.evaluate((r) => { location.hash = '#/' + r; }, route); await page.evaluate((r) => { location.hash = '#/' + r; }, route);
await page.waitForTimeout(500); await page.waitForLoadState('networkidle').catch(() => {});
} catch {} } catch {}
} }