mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-25 21:43:42 +00:00
Closes #2037. Step 1 (#2045) wired in the nine suites that already passed. This is steps 2 and 3: the four that ran and failed, and the five that could not run at all. After this, the count of suites in `tests/e2e` invoked by nothing goes from 18 to 0. ## Step 2 — the four that ran and failed Triaged against a fixture server in CI with full output kept, not the four-line tail the first probe saved. | suite | verdict | |---|---| | `test-packets-scope-column.js` | passes on master today. It failed on 2026-09-18, so something between the two fixed it. Wired in unchanged rather than investigated. | | `test-node-reach-e2e.js` | test wrong. It waited for the reach map whenever any *link* had GPS; `public/node-reach.js` builds the map only when the *node* has coordinates. Guaranteed 10s timeout on a node with positioned neighbours and no position of its own. | | `test-channel-modal-e2e.js` | both failures test-side. The Add button's visible label was shortened to "+ Add" with the accessible name moved to `aria-label` (`public/channels.js:746`); and `.ch-section-mychannels` is conditional on the visitor having added a channel, which has not happened at that point in the suite. | | `test-touch-targets.js` | three test-side, two a product finding. | The three test-side touch-target failures were the harness measuring controls that are not shown: `.compare-btn` (the CTA was removed in #1646, and `style.css` says so), `.ch-back-btn` (`display:none` outside the mobile channels layout), and `.filter-toggle-btn` (`display:none` on mobile since #1461; the control shown is the navbar mirror, which `mobile-page-actions.js:70` builds as a `.nav-btn`, so it was already measured). All three are dropped from the table with the reason recorded in the file. The remaining two are **not** a test problem: `.nav-btn` and `.ch-icon-btn` are each declared twice in `public/style.css`, 48px in the touch-target block and 44px in their own component rule, and the later one wins. Rather than lower the blanket or hide the failures, the suite now has `DEFAULT_MIN = 48` plus a `MIN_OVERRIDES` table holding those two at their effective 44, so a third selector dropping to 44 still fails the build. The contradiction is **#2052**, with both ways out costed; the override entries should go when it is settled. ## Step 3 — the five that could not run None is deleted. I checked each selector and seam against the product before deciding, and every one still targets a surface that exists and that nothing else covers. Four were written against `@playwright/test`, a runner the project neither installs nor uses anywhere else. Adopting a second runner for twelve tests costs more than porting them, and the precedent is already set: `test-path-inspector-coverage-e2e.js` exists, as its own header says, because `test-path-inspector-e2e.js` could not run. So they are ported to the plain-node Chromium pattern the other 109 suites use. - **`test-issue-1522-trace-url-sync-e2e.js`** — the trace hash in the URL, both directions. `test-e2e-playwright.js` covers that the page loads and searches; it never looks at the URL, which is the whole of #1522. - **`test-marker-outline-weight.js`** — the canvas pulse ring never thins below 2px. There is no CSS rule to read and axe cannot see inside a canvas, so sampling the seam is the only way. Added a guard that the ring was actually visible, so the weight check cannot pass vacuously on a pulse that never rendered. - **`test-pr-1490-live-map-gpu-animations-e2e.js`** — the queue drains, the engine sleeps again, the fading trails stay under the cap of 5, and the canvas sits on `animationsPane` rather than under the markers. - **`test-path-inspector-e2e.js`** — reduced to what nothing else covers: the map side pane, the `/#/traces/<hash>` redirect, the tools landing. Its standalone-page test duplicated the wired coverage suite and is dropped. Its "switching candidate clears prior polyline" case ended after the click with a comment and no assertion, which is the same green-but-empty problem this issue is about; it now compares path counts, and skips loudly when the fixture yields too few candidates. The fifth, **`test-table-sort.js`**, needed `jsdom`, which was declared nowhere. It is a unit test of `public/table-sort.js` filed under `tests/e2e`, so: `jsdom` is a devDependency (lockfile updated, `npm ci` stays consistent), the file moved to `tests/unit/`, and the `domIntegration` group in `scripts/non-unit-tests.json` is gone with its only member. It runs 22 tests. 20 passed immediately; 2 had rotted, because #1648 M2 replaced the up/down glyphs with Phosphor sprites and the direction moved out of `textContent` into the `<use href>`. Those two now read the sprite ref and the `aria-sort` value, so they also guard the accessible announcement. ## Verification `tests/unit/test-table-sort.js` 22/22 and `test-test-inventory.js` pass locally; the E2E suites need a fixture server, which I cannot build here (no cgo toolchain since #1992), so CI is their first run as committed. The triage above was measured in CI, not assumed. ## Not done The per-assertion skips named in the second comment on #2037 are untouched: the two flaky packet-detail cases, the fixture-data ones, and the two `clientRxCoverage` suites that skip wholesale while reporting success. Those need a fixture deployment with coverage enabled, which is its own change. I have not opened it. Option 2 from the issue, making `test-test-inventory.js` require a `deploy.yml` line for every `tests/e2e` file, is also not here. It is the right guard and it is now enforceable, since the list is finally at zero, but it belongs in its own change where a red build means what it says. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
85 lines
3.9 KiB
JavaScript
85 lines
3.9 KiB
JavaScript
// E2E for the per-node Reach page (#/nodes/<pubkey>/reach).
|
|
// Defaults to localhost:3000 — NEVER point at prod (AGENTS.md). CI sets BASE_URL.
|
|
const { chromium } = require('playwright');
|
|
const BASE = process.env.BASE_URL || 'http://localhost:3000';
|
|
|
|
async function getJson(page, url) {
|
|
const resp = await page.request.get(url);
|
|
if (!resp.ok()) throw new Error('GET ' + url + ' → HTTP ' + resp.status());
|
|
return resp.json();
|
|
}
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch();
|
|
const page = await browser.newPage();
|
|
|
|
// A repeater is most likely to have reach data (it relays).
|
|
const nodes = await getJson(page, BASE + '/api/nodes?role=repeater&limit=1');
|
|
if (!nodes.nodes || !nodes.nodes.length) {
|
|
console.log('node-reach E2E SKIP (no repeater in dataset)');
|
|
await browser.close();
|
|
return;
|
|
}
|
|
const pk = nodes.nodes[0].public_key;
|
|
|
|
// 1. The endpoint returns the documented shape.
|
|
const reach = await getJson(page, BASE + '/api/nodes/' + pk + '/reach?days=7');
|
|
for (const k of ['node', 'window', 'reliable_tokens', 'importance', 'links', 'direct_observers']) {
|
|
if (!(k in reach)) throw new Error('reach response missing key: ' + k);
|
|
}
|
|
if (!Array.isArray(reach.links)) throw new Error('reach.links must be an array');
|
|
|
|
// 2. The page renders.
|
|
await page.goto(BASE + '/#/nodes/' + pk + '/reach');
|
|
await page.waitForSelector('.nq-head', { timeout: 20000 });
|
|
if (!(await page.locator('h2', { hasText: 'Reach' }).count())) {
|
|
throw new Error('Reach header missing');
|
|
}
|
|
|
|
// 3. If this node is identifiable, exercise the table, toggles and links.
|
|
if (reach.reliable_tokens.length && (await page.locator('#nqRows').count())) {
|
|
await page.waitForSelector('#nqIncoming');
|
|
await page.waitForSelector('#nqOutgoing');
|
|
|
|
// Derive the EXACT expected row counts from the API so the toggles are
|
|
// verified, not just "didn't shrink" (tautology). Base shows two-way only;
|
|
// incoming adds we-only links; +outgoing adds the rest (= all links).
|
|
const twoWayExp = reach.links.filter(l => l.bidir).length;
|
|
const weOnlyExp = reach.links.filter(l => !l.bidir && l.we_hear > 0 && l.they_hear === 0).length;
|
|
const allExp = reach.links.length;
|
|
|
|
const base = await page.locator('#nqRows tr').count();
|
|
if (base !== twoWayExp) throw new Error(`base rows ${base} != two-way ${twoWayExp}`);
|
|
await page.check('#nqIncoming');
|
|
const withIncoming = await page.locator('#nqRows tr').count();
|
|
if (withIncoming !== twoWayExp + weOnlyExp) {
|
|
throw new Error(`incoming rows ${withIncoming} != two-way+we-only ${twoWayExp + weOnlyExp}`);
|
|
}
|
|
await page.check('#nqOutgoing');
|
|
const withBoth = await page.locator('#nqRows tr').count();
|
|
if (withBoth !== allExp) throw new Error(`both-toggles rows ${withBoth} != all links ${allExp}`);
|
|
|
|
// Neighbour rows link to a node detail page.
|
|
if (await page.locator('#nqRows a.nq-link').count()) {
|
|
const href = await page.locator('#nqRows a.nq-link').first().getAttribute('href');
|
|
if (!href || !href.startsWith('#/nodes/')) throw new Error('neighbour link malformed: ' + href);
|
|
}
|
|
|
|
// Leaflet puts .leaflet-container on the element you hand L.map(), so the
|
|
// class lands on #nqMap itself. The old selector had a space in it and so
|
|
// waited for a DESCENDANT carrying the class, which never exists: that is
|
|
// why this suite timed out here every time it was run.
|
|
//
|
|
// The condition is the node's own coordinates, not a link's:
|
|
// public/node-reach.js calls NodeReachMap.render only when n.lat != null,
|
|
// so a node with positioned neighbours but no position of its own has no
|
|
// map to wait for.
|
|
if (reach.node && reach.node.lat != null && reach.node.lon != null) {
|
|
await page.waitForSelector('#nqMap.leaflet-container', { timeout: 10000 });
|
|
}
|
|
}
|
|
|
|
console.log('node-reach E2E OK');
|
|
await browser.close();
|
|
})().catch((e) => { console.error(e); process.exit(1); });
|