Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -391,29 +391,20 @@ jobs:
# → header=0x15 (route_type=1, payload_type=5)
# → ComputeContentHash(...) = fae0c9e6d357a814
#
# The first_seen / observation timestamps are pinned to a date
# within retentionHours but outside the default 15-min UI
# window so the row is hidden in the default view (keeping
# test-e2e-playwright's first-10-rows hex-pane test
# unaffected) and reachable via the explicit ?timeWindow=0
# deep-link the #1486 test uses.
# Fixed old timestamps keep the row behind the freshly shifted
# fixture observations and outside the default 15-min UI window.
# The #1486 and #2104 tests reach it via an explicit hash +
# ?timeWindow=0 deep-link.
run: |
sqlite3 test-fixtures/e2e-fixture.db <<'SQL'
-- Sort the seeded row LAST in BOTH default packets views:
-- • flat view sorts by transmissions.id DESC → id=0 puts it last
-- • grouped view (#default for the packets page) sorts by
-- MAX(observations.timestamp) DESC → we must keep our obs
-- timestamps OLDER than every other fixture observation.
-- Fixture (after freshen) has obs timestamps spanning
-- 2026-05-17 16:01:39Z .. 2026-05-28 00:00:00Z (max).
-- Note: freshen only shifts transmissions.first_seen forward
-- to ~now; observation.timestamp is left alone except for
-- the timestamp=0 case.
-- Use 2026-05-15 (~2 days older than the oldest fixture obs)
-- so our row sorts LAST in the grouped view too, keeping
-- test-e2e-playwright's first-10-rows hex-pane test
-- unaffected. The #1486 test still reaches the row via the
-- explicit hash + ?timeWindow=0 deep-link.
-- freshen-fixture.sh shifts fixture observations forward.
-- Keep this row at 2026-05-15 so it sorts LAST in the grouped
-- view too; the #1486 and #2104 tests use the explicit deep-link.
INSERT INTO transmissions(id,raw_hex,hash,first_seen,route_type,payload_type,payload_version,decoded_json,channel_hash,from_pubkey)
VALUES (0,'15000102030405060708090a0b0c0d0e0f','fae0c9e6d357a814','2026-05-15T00:00:00Z',1,5,0,'{"type":"CHAN","channel":"#test","text":"#1486 fixture"}',NULL,NULL);
INSERT INTO observations(transmission_id,observer_idx,direction,snr,rssi,score,path_json,timestamp,resolved_path) VALUES
Expand Down Expand Up @@ -452,6 +443,18 @@ jobs:
# required shape before the server boots.
run: ./corescope-migrate -db test-fixtures/e2e-fixture.db

- name: 'Seed distinct observation bytes for #2104'
# raw_hex exists only after migration. Keep the #1486 payload/hash
# while encoding each observation's existing one-hop path.
run: |
sqlite3 test-fixtures/e2e-fixture.db <<'SQL'
UPDATE observations SET raw_hex = CASE observer_idx
WHEN 1 THEN '1501aa0102030405060708090a0b0c0d0e0f'
WHEN 2 THEN '1501bb0102030405060708090a0b0c0d0e0f'
WHEN 3 THEN '1501cc0102030405060708090a0b0c0d0e0f'
END WHERE transmission_id = 0 AND observer_idx IN (1,2,3);
SQL

- name: Seed deterministic Path Inspector routes (#2060)
run: sqlite3 test-fixtures/e2e-fixture.db < test-fixtures/path-inspector.sql

Expand Down
72 changes: 37 additions & 35 deletions tests/e2e/test-e2e-playwright.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const REPO_ROOT = require('path').resolve(__dirname, '..', '..');
* Usage: node test-e2e-playwright.js
*/
const { chromium } = require('playwright');
const { doesNotReject } = require('node:assert/strict');

const BASE = process.env.BASE_URL || 'http://localhost:3000';
const GO_BASE = process.env.GO_BASE_URL || ''; // e.g. https://analyzer.00id.net:82
Expand Down Expand Up @@ -2596,41 +2597,42 @@ async function run() {

// Test: per-observation raw_hex — hex pane updates when switching observations (#881)
await test('Packet detail hex pane updates per observation', async () => {
await gotoPackets(page);
await page.waitForTimeout(500);

// Try clicking packet rows to find one with multiple observations
const rows = await page.$$('table tbody tr[data-action]');
let obsRows = [];
for (let i = 0; i < Math.min(rows.length, 10); i++) {
await rows[i].click({ timeout: 3000 }).catch(() => null);
await page.waitForTimeout(600);
obsRows = await page.$$('.detail-obs-row');
if (obsRows.length >= 2) break;
}

if (obsRows.length < 2) {
console.log(' ⏭ Skipped: no packet with ≥2 observations found in first 10 rows');
return;
}

// Click first observation, capture hex dump
await obsRows[0].click({ timeout: 5000 });
await page.waitForTimeout(500);
const hex1 = await page.$eval('.hex-dump', el => el.textContent).catch(() => '');

// Click second observation, capture hex dump
await obsRows[1].click({ timeout: 5000 });
await page.waitForTimeout(500);
const hex2 = await page.$eval('.hex-dump', el => el.textContent).catch(() => '');

// If both have content and differ, the feature works
if (hex1 && hex2 && hex1 !== hex2) {
console.log(' ✓ Hex pane content differs between observations');
} else if (hex1 && hex2 && hex1 === hex2) {
console.log(' ⏭ Hex same for both observations (likely historical NULL raw_hex — OK)');
} else {
console.log(' ⏭ Could not capture hex content from both observations');
// Reuse the real #1486 transmission, seeded with distinct raw bytes after
// migration in deploy.yml. Missing fixture data must fail, never skip.
const hash = 'fae0c9e6d357a814';
const expectedHex = [
'1501aa0102030405060708090a0b0c0d0e0f',
'1501bb0102030405060708090a0b0c0d0e0f'
];
const response = await context.request.get(BASE + '/api/packets/' + hash);
assert(response.ok(), '#2104 requires the #1486 grouped-packet fixture');
const detail = await response.json();
assert(detail.packet && detail.packet.hash === hash && Array.isArray(detail.observations),
'fixture must expose the expected packet and observations');
const observations = expectedHex.map(hex => detail.observations.find(o => o.raw_hex === hex));
assert(observations.every(Boolean), 'fixture must include both distinct observation raw byte strings');
assert(String(observations[0].id) !== String(observations[1].id), 'observation IDs must differ');

// Start on B so selecting A must complete a render before selecting B again.
await page.goto(BASE + '/#/packets/' + hash + '?obs=' + observations[1].id + '&timeWindow=0',
{ waitUntil: 'domcontentloaded' });
await page.reload({ waitUntil: 'load' });
await page.waitForSelector('#pktRight .detail-obs-row.observation-current[data-obs-id="' + observations[1].id + '"]');
const obsRows = observations.map(o =>
page.locator('#pktRight .detail-obs-row[data-obs-id="' + o.id + '"]'));

for (const index of [0, 1, 0]) {
const id = String(observations[index].id);
await doesNotReject(() => obsRows[index].click({ timeout: 5000 }),
'Observation ' + id + ' must remain selectable after the detail rerenders');
await page.waitForFunction(expectedId => {
const selected = document.querySelector('#pktRight .detail-obs-row.observation-current');
return selected && selected.dataset.obsId === expectedId;
}, id);
const selectedId = await page.locator('#pktRight .detail-obs-row.observation-current').getAttribute('data-obs-id');
assert(selectedId === id, 'expected selected observation ' + id + ', got ' + selectedId);
const hex = (await page.locator('#pktRight .hex-dump').textContent()).replace(/\s+/g, '').toLowerCase();
assert(hex === expectedHex[index], 'observation ' + id + ': expected hex ' + expectedHex[index] + ', got ' + hex);
}
});

Expand Down
Loading