test(e2e): assert the configured-scope row after the theme-refresh re-render (#2180)

## What

`test-issue-1648-m2-icons-e2e.js` now asserts the configured-scope row
only after the page's `theme-refresh` re-render has fired and the detail
has rendered again.

## Why

On 2026-10-10 this assertion failed CI on four PRs (#2169, #2171, #2154,
#2164), each time at the first live-map case: `confirmed scope row must
be visible, including an empty scope`. A re-run of the failed jobs
passed every time.

The code path:
- `customize-v2.js:814` dispatches `theme-changed` once
`/api/config/theme` resolves, on every page load.
- `app.js:1309-1315` turns that into `theme-refresh` 300 ms later.
- `live.js:4713` re-runs `showNodeDetail(activeNodeDetailKey)` on
`theme-refresh`, and `showNodeDetail` first sets `#nodeDetailContent` to
"Loading…" (`live.js:2598`). This is the only caller besides the marker
click (`live.js:3090`).
- The test waited for `#nodeDetailContent table` and then called
`row.isVisible()`, which does not wait. A refresh landing between the
two fails the check.

The same race could let the null-scope case pass on the "Loading…"
panel, because `row.count() === 0` holds there too.

The fix uses the same `addInitScript` flag pattern as
`test-e2e-playwright.js:83`.

## Testing

- Locally against a server on the E2E fixture: 5 of 5 runs pass, 27 of
27 checks each.
- **Not reproduced locally.** The unchanged test also passed every local
run: 5 plain, 3 with `/api/config/theme` delayed by 1 s, 5 with 8x CPU
throttling. So the reasoning above comes from the code path and the four
CI failures, not from a local red-then-green.
- Test-only change, no product code touched.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
efiten
2026-10-11 10:58:20 +02:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 4e91c5cf0b
commit 55dd28164d
+10 -1
View File
@@ -86,6 +86,12 @@ async function checkConfiguredScope(page) {
await page.route(nodeListUrl, route => route.fulfill({
json: { nodes: [{ ...node, lat: 1, lon: 1 }], total: 1 },
}));
// Every page load fires one 'theme-refresh' about 300ms after the theme
// loads, and live.js re-runs showNodeDetail on it, which briefly swaps the
// detail panel to "Loading…". Assert only after that re-render.
await page.addInitScript(() => {
window.addEventListener('theme-refresh', () => { window.__scopeThemeReady = true; }, { once: true });
});
try {
for (const live of [false, true]) {
for (const scope of ['#fixture', '', null]) {
@@ -95,7 +101,10 @@ async function checkConfiguredScope(page) {
const route = live ? '/live?lat=1&lon=1&zoom=10' : '/nodes/' + key;
await page.goto(`${BASE}/#${route}`, { waitUntil: 'domcontentloaded' });
if (live) await page.locator('#liveMap .live-node-marker').click();
await page.locator(live ? '#nodeDetailContent table' : '#node-stats').waitFor({ state: 'visible', timeout: 8000 });
const detail = page.locator(live ? '#nodeDetailContent table' : '#node-stats');
await detail.waitFor({ state: 'visible', timeout: 8000 });
await page.waitForFunction(() => window.__scopeThemeReady === true, null, { timeout: 8000 });
await detail.waitFor({ state: 'visible', timeout: 8000 });
const row = live
? page.locator('#nodeDetailContent tr').filter({ hasText: 'Configured scope' })
: page.locator('#row-configured-scope');