diff --git a/public/app.js b/public/app.js index 17a98c00..58c2f25a 100644 --- a/public/app.js +++ b/public/app.js @@ -965,10 +965,11 @@ window.addEventListener('DOMContentLoaded', () => { }).catch(() => { window.SITE_CONFIG = { timestamps: { defaultMode: 'ago', timezone: 'local', formatPreset: 'iso', customFormat: '', allowCustomFormat: false } }; if (window._customizerV2) window._customizerV2.init(window.SITE_CONFIG); - }).finally(() => { - if (!location.hash || location.hash === '#/') location.hash = '#/home'; - else navigate(); }); + + // Navigate immediately — don't gate data-fetching pages on cosmetic theme fetch + if (!location.hash || location.hash === '#/') location.hash = '#/home'; + else navigate(); }); /** diff --git a/public/map.js b/public/map.js index 742b273b..6ad94b0d 100644 --- a/public/map.js +++ b/public/map.js @@ -549,6 +549,10 @@ renderMarkers(); + // Signal that map data is loaded and markers rendered (used by E2E tests) + var mapContainer = document.getElementById('leaflet-map'); + if (mapContainer) mapContainer.setAttribute('data-loaded', 'true'); + // Restore heatmap if previously enabled if (localStorage.getItem('meshcore-map-heatmap') === 'true') { toggleHeatmap(true); diff --git a/public/nodes.js b/public/nodes.js index 5d3355e8..aa6ed645 100644 --- a/public/nodes.js +++ b/public/nodes.js @@ -951,6 +951,9 @@ } else { renderLeft(); } + // Signal that node data is loaded and rendered (used by E2E tests) + var nodesContainer = document.getElementById('nodesLeft') || document.getElementById('nodesBody'); + if (nodesContainer) nodesContainer.setAttribute('data-loaded', 'true'); } catch (e) { console.error('Failed to load nodes:', e); const tbody = document.getElementById('nodesBody'); diff --git a/public/packets.js b/public/packets.js index 5135d6d0..83054ff4 100644 --- a/public/packets.js +++ b/public/packets.js @@ -744,6 +744,9 @@ sortPacketsArray(); renderLeft(); + // Signal that packet data is loaded and rendered (used by E2E tests) + var pktContainer = document.getElementById('pktLeft') || document.getElementById('pktBody'); + if (pktContainer) pktContainer.setAttribute('data-loaded', 'true'); } catch (e) { console.error('Failed to load packets:', e); const tbody = document.getElementById('pktBody'); diff --git a/test-e2e-playwright.js b/test-e2e-playwright.js index aba475ef..7d514efd 100644 --- a/test-e2e-playwright.js +++ b/test-e2e-playwright.js @@ -211,6 +211,7 @@ async function run() { // Test 2: Nodes page loads with data await test('Nodes page loads with data', async () => { await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 }); await page.waitForSelector('table tbody tr'); const headers = await page.$$eval('th', els => els.map(e => e.textContent.trim())); for (const col of ['Name', 'Public Key', 'Role']) { @@ -236,6 +237,7 @@ async function run() { // Test: Node side panel Details link navigates to full detail page (#778) await test('Node side panel Details link navigates', async () => { await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 }); await page.waitForSelector('table tbody tr'); await page.click('table tbody tr'); await page.waitForSelector('.node-detail'); @@ -257,6 +259,7 @@ async function run() { // Test: Nodes page has WebSocket auto-update listener (#131) await test('Nodes page has WebSocket auto-update', async () => { await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 }); await page.waitForSelector('table tbody tr'); // The live dot in navbar indicates WS connection status const liveDot = await page.$('#liveDot'); @@ -282,11 +285,12 @@ async function run() { // Test 3: Map page loads with markers await test('Map page loads with markers', async () => { await page.goto(`${BASE}/#/map`, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 }); await page.waitForSelector('.leaflet-container'); await page.waitForSelector('.leaflet-tile-loaded'); // Wait for markers/overlays to render (may not exist with empty DB) try { - await page.waitForSelector('.leaflet-marker-icon, .leaflet-interactive, circle, .marker-cluster, .leaflet-marker-pane > *, .leaflet-overlay-pane svg path, .leaflet-overlay-pane svg circle', { timeout: 3000 }); + await page.waitForSelector('.leaflet-marker-icon, .leaflet-interactive, circle, .marker-cluster, .leaflet-marker-pane > *, .leaflet-overlay-pane svg path, .leaflet-overlay-pane svg circle', { timeout: 8000 }); } catch (_) { // No markers with empty DB \u2014 assertion below handles it } @@ -362,7 +366,7 @@ async function run() { await page.waitForSelector('.leaflet-container'); // Wait for markers (may not exist with empty DB) try { - await page.waitForSelector('.leaflet-marker-icon, .leaflet-interactive', { timeout: 3000 }); + await page.waitForSelector('.leaflet-marker-icon, .leaflet-interactive', { timeout: 8000 }); } catch (_) { // No markers with empty DB } @@ -394,6 +398,7 @@ async function run() { await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' }); await page.evaluate(() => localStorage.setItem('meshcore-time-window', '525600')); await page.reload({ waitUntil: 'load' }); + await page.waitForSelector('[data-loaded="true"]', { timeout: 15000 }); await page.waitForSelector('table tbody tr', { timeout: 15000 }); const rowsBefore = await page.$$('table tbody tr'); assert(rowsBefore.length > 0, 'No packets visible');