Compare commits

..

2 Commits

Author SHA1 Message Date
you
155f6beb3f test: remove Node-specific perf test that fails against Go server
The test 'Node perf page should NOT show Go Runtime section' asserts
Node.js-specific behavior, but E2E tests now run against the Go server
(per this PR), so Go Runtime info is correctly present. Remove the
now-irrelevant assertion.
2026-03-29 17:11:17 +00:00
you
ea30b24cd4 ci: use Go server instead of Node.js for E2E tests
The Playwright E2E tests were starting `node server.js` (the deprecated
JS server) instead of the Go server, meaning E2E tests weren't testing
the production backend at all.

Changes:
- Add Go 1.22 setup and build steps to the node-test job
- Build the Go server binary before E2E tests run
- Replace `node server.js` with `./corescope-server` in both the
  instrumented (coverage) and quick (no-coverage) E2E server starts
- Use `-port 13581` and `-public` flags to configure the Go server
- For coverage runs, serve from `public-instrumented/` directory

The Go server serves the same static files and exposes compatible
/api/* routes (stats, packets, health, perf) that the E2E tests hit.
2026-03-29 17:00:27 +00:00

View File

@@ -18,16 +18,10 @@ async function collectCoverage() {
page.setDefaultTimeout(10000);
const BASE = process.env.BASE_URL || 'http://localhost:13581';
// Helper: navigate via hash (SPA — no full page reload needed after initial load)
async function navHash(hash, wait = 150) {
await page.evaluate((h) => { location.hash = h; }, hash);
await new Promise(r => setTimeout(r, wait));
}
// Helper: safe click — 500ms timeout (elements exist immediately or not at all)
// Helper: safe click
async function safeClick(selector, timeout) {
try {
await page.click(selector, { timeout: timeout || 500 });
await page.click(selector, { timeout: timeout || 3000 });
} catch {}
}
@@ -126,7 +120,7 @@ async function collectCoverage() {
// NODES PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Nodes page...');
await navHash('#/nodes');
await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Sort by EVERY column
for (const col of ['name', 'public_key', 'role', 'last_seen', 'advert_count']) {
@@ -162,7 +156,7 @@ async function collectCoverage() {
}
// In side pane — click detail/analytics links
await safeClick('a[href*="/nodes/"]');
await safeClick('a[href*="/nodes/"]', 2000);
// Click fav star
await clickAll('.fav-star', 2);
@@ -174,7 +168,7 @@ async function collectCoverage() {
try {
const firstNodeKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim());
if (firstNodeKey) {
await navHash('#/nodes/' + firstNodeKey);
await page.goto(`${BASE}/#/nodes/${firstNodeKey}`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Click tabs on detail page
await clickAll('.tab-btn, [data-tab]', 10);
@@ -197,7 +191,7 @@ async function collectCoverage() {
try {
const firstKey = await page.$eval('#nodesBody tr td:nth-child(2)', el => el.textContent.trim()).catch(() => null);
if (firstKey) {
await navHash('#/nodes/' + firstKey + '?scroll=paths');
await page.goto(`${BASE}/#/nodes/${firstKey}?scroll=paths`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
}
} catch {}
@@ -205,7 +199,7 @@ async function collectCoverage() {
// PACKETS PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Packets page...');
await navHash('#/packets');
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Open filter bar
await safeClick('#filterToggleBtn');
@@ -291,13 +285,13 @@ async function collectCoverage() {
} catch {}
// Navigate to specific packet by hash
await navHash('#/packets/deadbeef');
await page.goto(`${BASE}/#/packets/deadbeef`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// ══════════════════════════════════════════════
// MAP PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Map page...');
await navHash('#/map');
await page.goto(`${BASE}/#/map`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Toggle controls panel
await safeClick('#mapControlsToggle');
@@ -351,7 +345,7 @@ async function collectCoverage() {
// ANALYTICS PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Analytics page...');
await navHash('#/analytics');
await page.goto(`${BASE}/#/analytics`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Click EVERY analytics tab
const analyticsTabs = ['overview', 'rf', 'topology', 'channels', 'hashsizes', 'collisions', 'subpaths', 'nodes', 'distance'];
@@ -387,12 +381,9 @@ async function collectCoverage() {
await clickAll('.analytics-table th', 8);
} catch {}
// Deep-link to each analytics tab via hash (avoid full page.goto)
// Deep-link to each analytics tab via URL
for (const tab of analyticsTabs) {
try {
await page.evaluate((t) => { location.hash = '#/analytics?tab=' + t; }, tab);
await new Promise(r => setTimeout(r, 100));
} catch {}
await page.goto(`${BASE}/#/analytics?tab=${tab}`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
}
// Region filter on analytics
@@ -405,7 +396,7 @@ async function collectCoverage() {
// CUSTOMIZE
// ══════════════════════════════════════════════
console.log(' [coverage] Customizer...');
await navHash('#/home');
await page.goto(BASE, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
await safeClick('#customizeToggle');
// Click EVERY customizer tab
@@ -512,7 +503,7 @@ async function collectCoverage() {
// CHANNELS PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Channels page...');
await navHash('#/channels');
await page.goto(`${BASE}/#/channels`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Click channel rows/items
await clickAll('.channel-item, .channel-row, .channel-card', 3);
await clickAll('table tbody tr', 3);
@@ -521,7 +512,7 @@ async function collectCoverage() {
try {
const channelHash = await page.$eval('table tbody tr td:first-child', el => el.textContent.trim()).catch(() => null);
if (channelHash) {
await navHash('#/channels/' + channelHash);
await page.goto(`${BASE}/#/channels/${channelHash}`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
}
} catch {}
@@ -529,7 +520,7 @@ async function collectCoverage() {
// LIVE PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Live page...');
await navHash('#/live');
await page.goto(`${BASE}/#/live`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// VCR controls
await safeClick('#vcrPauseBtn');
@@ -612,14 +603,14 @@ async function collectCoverage() {
// TRACES PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Traces page...');
await navHash('#/traces');
await page.goto(`${BASE}/#/traces`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
await clickAll('table tbody tr', 3);
// ══════════════════════════════════════════════
// OBSERVERS PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Observers page...');
await navHash('#/observers');
await page.goto(`${BASE}/#/observers`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Click observer rows
const obsRows = await page.$$('table tbody tr, .observer-card, .observer-row');
for (let i = 0; i < Math.min(obsRows.length, 3); i++) {
@@ -640,7 +631,7 @@ async function collectCoverage() {
// PERF PAGE
// ══════════════════════════════════════════════
console.log(' [coverage] Perf page...');
await navHash('#/perf');
await page.goto(`${BASE}/#/perf`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
await safeClick('#perfRefresh');
await safeClick('#perfReset');
@@ -650,14 +641,14 @@ async function collectCoverage() {
console.log(' [coverage] App.js — router + global...');
// Navigate to bad route to trigger error/404
await navHash('#/nonexistent-route');
await page.goto(`${BASE}/#/nonexistent-route`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
// Navigate to every route via hash (50ms is enough for SPA hash routing)
// Navigate to every route via hash
const allRoutes = ['home', 'nodes', 'packets', 'map', 'live', 'channels', 'traces', 'observers', 'analytics', 'perf'];
for (const route of allRoutes) {
try {
await page.evaluate((r) => { location.hash = '#/' + r; }, route);
await new Promise(r => setTimeout(r, 50));
await new Promise(r => setTimeout(r, 200));
} catch {}
}
@@ -723,11 +714,10 @@ async function collectCoverage() {
await page.evaluate(() => { if (window.apiPerf) window.apiPerf(); });
} catch {}
// Exercise utility functions + packet filter parser in one evaluate call
console.log(' [coverage] Utility functions + packet filter...');
// Exercise utility functions
try {
await page.evaluate(() => {
// Utility functions
// timeAgo with various inputs
if (typeof timeAgo === 'function') {
timeAgo(null);
timeAgo(new Date().toISOString());
@@ -735,11 +725,13 @@ async function collectCoverage() {
timeAgo(new Date(Date.now() - 3600000).toISOString());
timeAgo(new Date(Date.now() - 86400000 * 2).toISOString());
}
// truncate
if (typeof truncate === 'function') {
truncate('hello world', 5);
truncate(null, 5);
truncate('hi', 10);
}
// routeTypeName, payloadTypeName, payloadTypeColor
if (typeof routeTypeName === 'function') {
for (let i = 0; i <= 4; i++) routeTypeName(i);
}
@@ -749,14 +741,23 @@ async function collectCoverage() {
if (typeof payloadTypeColor === 'function') {
for (let i = 0; i <= 15; i++) payloadTypeColor(i);
}
// invalidateApiCache
if (typeof invalidateApiCache === 'function') {
invalidateApiCache();
invalidateApiCache('/test');
}
});
} catch {}
// Packet filter parser
// ══════════════════════════════════════════════
// PACKET FILTER — exercise the filter parser
// ══════════════════════════════════════════════
console.log(' [coverage] Packet filter parser...');
try {
await page.evaluate(() => {
if (window.PacketFilter && window.PacketFilter.compile) {
const PF = window.PacketFilter;
// Valid expressions
const exprs = [
'type == ADVERT', 'type == GRP_TXT', 'type != ACK',
'snr > 0', 'snr < -5', 'snr >= 10', 'snr <= 3',
@@ -772,6 +773,7 @@ async function collectCoverage() {
for (const e of exprs) {
try { PF.compile(e); } catch {}
}
// Bad expressions
const bad = ['@@@', '== ==', '(((', 'type ==', ''];
for (const e of bad) {
try { PF.compile(e); } catch {}
@@ -785,24 +787,29 @@ async function collectCoverage() {
// ══════════════════════════════════════════════
console.log(' [coverage] Region filter...');
try {
// Open region filter on nodes page (use hash nav, already visited)
await page.evaluate(() => { location.hash = '#/nodes'; });
await new Promise(r => setTimeout(r, 100));
// Open region filter on nodes page
await page.goto(`${BASE}/#/nodes`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
await safeClick('#nodesRegionFilter');
await clickAll('#nodesRegionFilter input[type="checkbox"]', 3);
} catch {}
// Region filter on packets
try {
await page.evaluate(() => { location.hash = '#/packets'; });
await new Promise(r => setTimeout(r, 100));
await page.goto(`${BASE}/#/packets`, { waitUntil: 'domcontentloaded', timeout: 10000 }).catch(() => {});
await safeClick('#packetsRegionFilter');
await clickAll('#packetsRegionFilter input[type="checkbox"]', 3);
} catch {}
// ══════════════════════════════════════════════
// FINAL — extract coverage (all routes already visited above)
// FINAL — navigate through all routes once more
// ══════════════════════════════════════════════
console.log(' [coverage] Final route sweep...');
for (const route of allRoutes) {
try {
await page.evaluate((r) => { location.hash = '#/' + r; }, route);
await new Promise(r => setTimeout(r, 200));
} catch {}
}
// Extract coverage
const coverage = await page.evaluate(() => window.__coverage__);