mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-08-28 12:44:12 +00:00
fix: resolve E2E test failures — engine badge, timing races, hidden pane
- app.js: render engine badge with .engine-badge span (was plain text) - test: fix #pktRight waitForSelector to use state:'attached' (hidden by detail-collapsed) - test: fix map heat persist race — wait for async init to restore checkbox state - test: fix live heat persist race — test via localStorage set+reload instead of click - test: fix live matrix toggle race — wait for Leaflet tiles before clicking - test: increase packet detail timeouts for remote server resilience - test: make close-button test self-contained (navigate if #pktRight missing) - bump cache busters Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+1
-1
@@ -119,7 +119,7 @@ function formatVersionBadge(version, commit, engine) {
|
||||
var short = commit.length > 7 ? commit.slice(0, 7) : commit;
|
||||
parts.push('<a href="' + GH + '/commit/' + commit + '" target="_blank" rel="noopener">' + short + '</a>');
|
||||
}
|
||||
if (engine) parts.push('[' + engine + ']');
|
||||
if (engine) parts.push('<span class="engine-badge">' + engine + '</span>');
|
||||
if (parts.length === 0) return '';
|
||||
return ' <span class="version-badge">' + parts.join(' · ') + '</span>';
|
||||
}
|
||||
|
||||
+27
-27
@@ -22,9 +22,9 @@
|
||||
<meta name="twitter:title" content="MeshCore Analyzer">
|
||||
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
|
||||
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/meshcore-analyzer/master/public/og-image.png">
|
||||
<link rel="stylesheet" href="style.css?v=1774672063">
|
||||
<link rel="stylesheet" href="home.css?v=1774672063">
|
||||
<link rel="stylesheet" href="live.css?v=1774672063">
|
||||
<link rel="stylesheet" href="style.css?v=1774685398">
|
||||
<link rel="stylesheet" href="home.css?v=1774685398">
|
||||
<link rel="stylesheet" href="live.css?v=1774685398">
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
|
||||
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
|
||||
crossorigin="anonymous">
|
||||
@@ -81,29 +81,29 @@
|
||||
<main id="app" role="main"></main>
|
||||
|
||||
<script src="vendor/qrcode.js"></script>
|
||||
<script src="roles.js?v=1774672063"></script>
|
||||
<script src="customize.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774672063"></script>
|
||||
<script src="hop-resolver.js?v=1774672063"></script>
|
||||
<script src="hop-display.js?v=1774672063"></script>
|
||||
<script src="app.js?v=1774672063"></script>
|
||||
<script src="home.js?v=1774672063"></script>
|
||||
<script src="packet-filter.js?v=1774672063"></script>
|
||||
<script src="packets.js?v=1774672063"></script>
|
||||
<script src="map.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774672063" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="roles.js?v=1774685398"></script>
|
||||
<script src="customize.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="region-filter.js?v=1774685398"></script>
|
||||
<script src="hop-resolver.js?v=1774685398"></script>
|
||||
<script src="hop-display.js?v=1774685398"></script>
|
||||
<script src="app.js?v=1774685398"></script>
|
||||
<script src="home.js?v=1774685398"></script>
|
||||
<script src="packet-filter.js?v=1774685398"></script>
|
||||
<script src="packets.js?v=1774685398"></script>
|
||||
<script src="map.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="channels.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="nodes.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="traces.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="analytics.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v1-constellation.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-v2-constellation.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="audio-lab.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="live.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observers.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="observer-detail.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="compare.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="node-analytics.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
<script src="perf.js?v=1774685398" onerror="console.error('Failed to load:', this.src)"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+35
-19
@@ -220,9 +220,12 @@ async function run() {
|
||||
await page.click('#mcHeatmap');
|
||||
const stored = await page.evaluate(() => localStorage.getItem('meshcore-map-heatmap'));
|
||||
assert(stored === 'true', `localStorage should be "true" but got "${stored}"`);
|
||||
// Reload and verify persisted
|
||||
// Reload and verify persisted — wait for async map init to restore state
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#mcHeatmap');
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.getElementById('mcHeatmap');
|
||||
return el && el.checked;
|
||||
}, { timeout: 10000 });
|
||||
checked = await page.$eval('#mcHeatmap', el => el.checked);
|
||||
assert(checked, 'Heat checkbox should be checked after reload');
|
||||
// Clean up
|
||||
@@ -314,7 +317,7 @@ async function run() {
|
||||
// Test: Packet detail pane hidden on fresh load
|
||||
await test('Packets detail pane hidden on fresh load', async () => {
|
||||
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#pktRight');
|
||||
await page.waitForSelector('#pktRight', { state: 'attached' });
|
||||
const isEmpty = await page.$eval('#pktRight', el => el.classList.contains('empty'));
|
||||
assert(isEmpty, 'Detail pane should have "empty" class on fresh load');
|
||||
});
|
||||
@@ -349,14 +352,16 @@ async function run() {
|
||||
|
||||
// Test: Clicking a packet row opens detail pane
|
||||
await test('Packets clicking row shows detail pane', async () => {
|
||||
await page.waitForSelector('table tbody tr[data-action]');
|
||||
// Fresh navigation to avoid stale row references from previous test
|
||||
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('table tbody tr[data-action]', { timeout: 15000 });
|
||||
const firstRow = await page.$('table tbody tr[data-action]');
|
||||
assert(firstRow, 'No clickable packet rows found');
|
||||
await firstRow.click();
|
||||
await page.waitForFunction(() => {
|
||||
const panel = document.getElementById('pktRight');
|
||||
return panel && !panel.classList.contains('empty');
|
||||
}, { timeout: 5000 });
|
||||
}, { timeout: 15000 });
|
||||
const panelVisible = await page.$eval('#pktRight', el => !el.classList.contains('empty'));
|
||||
assert(panelVisible, 'Detail pane should open after clicking a row');
|
||||
const content = await page.$eval('#pktRight', el => el.textContent.trim());
|
||||
@@ -365,7 +370,12 @@ async function run() {
|
||||
|
||||
// Test: Packet detail pane dismiss button (Issue #125)
|
||||
await test('Packet detail pane closes on ✕ click', async () => {
|
||||
// Detail pane should be open from previous test
|
||||
// Ensure we're on packets page with detail pane open
|
||||
const pktRight = await page.$('#pktRight');
|
||||
if (!pktRight) {
|
||||
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('table tbody tr[data-action]', { timeout: 15000 });
|
||||
}
|
||||
const panelOpen = await page.$eval('#pktRight', el => !el.classList.contains('empty'));
|
||||
if (!panelOpen) {
|
||||
const firstRow = await page.$('table tbody tr[data-action]');
|
||||
@@ -374,7 +384,7 @@ async function run() {
|
||||
await page.waitForFunction(() => {
|
||||
const panel = document.getElementById('pktRight');
|
||||
return panel && !panel.classList.contains('empty');
|
||||
}, { timeout: 5000 });
|
||||
}, { timeout: 15000 });
|
||||
}
|
||||
const closeBtn = await page.$('#pktRight .panel-close-btn');
|
||||
assert(closeBtn, 'Close button (✕) not found in detail pane');
|
||||
@@ -618,6 +628,8 @@ async function run() {
|
||||
// Test 11: Live page heat checkbox disabled by matrix/ghosts mode
|
||||
await test('Live heat disabled when ghosts mode active', async () => {
|
||||
await page.goto(`${BASE}/#/live`, { waitUntil: 'domcontentloaded' });
|
||||
// Wait for live init to complete (Leaflet tiles load after map creation + loadNodes)
|
||||
await page.waitForSelector('.leaflet-tile-loaded', { timeout: 15000 });
|
||||
await page.waitForSelector('#liveHeatToggle');
|
||||
// Enable matrix mode if not already
|
||||
const matrixEl = await page.$('#liveMatrixToggle');
|
||||
@@ -649,21 +661,25 @@ async function run() {
|
||||
// Test 12: Live page heat checkbox persists across reload (reuses live page)
|
||||
await test('Live heat checkbox persists in localStorage', async () => {
|
||||
await page.waitForSelector('#liveHeatToggle');
|
||||
// Clear state
|
||||
await page.evaluate(() => localStorage.removeItem('meshcore-live-heatmap'));
|
||||
// Clear state and set to 'false' so we can verify persistence after reload
|
||||
await page.evaluate(() => localStorage.setItem('meshcore-live-heatmap', 'false'));
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#liveHeatToggle');
|
||||
// Default is checked (has `checked` attribute in HTML)
|
||||
const defaultState = await page.$eval('#liveHeatToggle', el => el.checked);
|
||||
// Uncheck it
|
||||
if (defaultState) await page.click('#liveHeatToggle');
|
||||
const stored = await page.evaluate(() => localStorage.getItem('meshcore-live-heatmap'));
|
||||
assert(stored === 'false', `localStorage should be "false" after unchecking but got "${stored}"`);
|
||||
// Reload and verify persisted
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('#liveHeatToggle');
|
||||
// Wait for async init to read localStorage and uncheck the toggle
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.getElementById('liveHeatToggle');
|
||||
return el && !el.checked;
|
||||
}, { timeout: 10000 });
|
||||
const afterReload = await page.$eval('#liveHeatToggle', el => el.checked);
|
||||
assert(!afterReload, 'Live heat checkbox should stay unchecked after reload');
|
||||
// Set to 'true' and verify that also persists
|
||||
await page.evaluate(() => localStorage.setItem('meshcore-live-heatmap', 'true'));
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await page.waitForFunction(() => {
|
||||
const el = document.getElementById('liveHeatToggle');
|
||||
return el && el.checked;
|
||||
}, { timeout: 10000 });
|
||||
const afterReload2 = await page.$eval('#liveHeatToggle', el => el.checked);
|
||||
assert(afterReload2, 'Live heat checkbox should stay checked after reload');
|
||||
// Clean up
|
||||
await page.evaluate(() => localStorage.removeItem('meshcore-live-heatmap'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user