Skip to content

tests: Playwright spec for explorer URL state round-trip (closes #209)#214

Open
rdhyee wants to merge 1 commit into
isamplesorg:mainfrom
rdhyee:tests/209-url-roundtrip-specs
Open

tests: Playwright spec for explorer URL state round-trip (closes #209)#214
rdhyee wants to merge 1 commit into
isamplesorg:mainfrom
rdhyee:tests/209-url-roundtrip-specs

Conversation

@rdhyee
Copy link
Copy Markdown
Contributor

@rdhyee rdhyee commented May 12, 2026

Closes #209. CI-safe Playwright specs covering the URL state contract slices fixed by PRs #203, #205, #210, #212.

What this adds

New file: tests/playwright/url-roundtrip.spec.js. Five tests:

# Test Regression for
1 Point-mode deep-link with mode=point enters point mode Bug B fix in #203 (omitted heading)
2 Low-alt deep-link WITHOUT mode=point enters point mode #207 item 4 (altitude-authoritative)
3 Sub-threshold pan updates URL hash via moveEnd #205 (percentageChanged suppressed events)
4 URL round-trips across browser contexts #203 + #205 combined
5 h3 hashchange with invalid cell clears _globeState.selectedH3 #207 item 6 (boot/hashchange asymmetry)

What's intentionally NOT covered

  • Search-result row click selection (explorer: URL state contract micro-fixes (post-#203 follow-ups) #207 item 8). Requires working FTS data; depends on substrate the spec shouldn't have to set up. Deferred to a manual smoke or a separate spec once a fixture dataset is available.
  • Cold-cache live-site investigation. tests/playwright/url_roundtrip_investigation.js remains as a manual diagnostic — too slow and network-sensitive for CI. The new spec covers the same surface against the Quarto preview.
  • Filter URL state (?sources=, ?material=, etc.). Separate UX path; could be a follow-up spec.

Notes on the implementation

Verification

All 5 tests pass against localhost (Quarto preview of current main) in ~1m 24s on this machine. Output:

✓  1 deep-link with mode=point enters point mode (Bug B from #203) (12.9s)
✓  2 deep-link with low altitude AND no mode=point still enters point mode (#207 item 4) (12.1s)
✓  3 sub-threshold pan updates URL hash via moveEnd (#205) (15.9s)
✓  4 URL round-trips across browser contexts (#203 + #205 combined) (38.6s)
✓  5 h3 hashchange with invalid cell clears selectedH3 (#207 item 6) (7.0s)

Risks

  • CI runtime: ~1.5 min added to the test job. Slower than other specs but mostly bounded by Cesium / DuckDB-WASM cold-start cost.
  • Flakiness from cold-cache timing: the 360s test timeout is generous but assumes the test machine can render Cesium reasonably quickly. CI machines vary.
  • Tests run against localhost:5860 by default (via TEST_URL env). Same convention as the existing specs; no CI-config changes needed.

🤖 Generated with Claude Code

…plesorg#209)

CI-safe spec set extracted from `tests/playwright/url_roundtrip_investigation.js` (which remains as a manual diagnostic). Five tests covering the URL state contract slices fixed in PRs isamplesorg#203, isamplesorg#205, isamplesorg#210, isamplesorg#212:

1. **Point-mode deep-link with `mode=point` enters point mode** —
   regression for Bug B fix in isamplesorg#203 (when heading is omitted, the
   URL must still hydrate into point mode).
2. **Low-alt deep-link WITHOUT `mode=point` enters point mode** —
   regression for isamplesorg#207 item 4 (boot should enter point mode based
   on altitude alone, not just the URL's mode flag).
3. **Sub-threshold pan updates URL hash via `moveEnd`** — regression
   for isamplesorg#205 (Cesium's `camera.changed` suppresses small moves below
   `percentageChanged` threshold; `moveEnd` is the backstop).
4. **URL round-trips across browser contexts** — combined regression
   for isamplesorg#203 + isamplesorg#205. Context A pans + zooms after settle, captures
   URL; Context B opens the captured URL and arrives at the same
   camera + mode within tight tolerance.
5. **`h3` hashchange with invalid cell clears `_globeState.selectedH3`** —
   regression for isamplesorg#207 item 6 (asymmetry between boot and hashchange
   null-result branches).

Search-result row click selection (isamplesorg#207 item 8) deferred — needs
working FTS data and depends on substrate the spec shouldn't have
to set up.

Tests use `waitForPointModeSettled` (waits for the phase message's
"Click one for details." text) instead of `waitForMode` for the
no-`mode=point` cases — `waitForMode` can match a transient mode
flip during the boot transition (the dual-mode-state anomaly being
addressed structurally in isamplesorg#208).

`test.setTimeout(360000)` per describe block — cold-cache point-mode
boot can take 60–90s per isamplesorg#190, and the round-trip test opens
multiple fresh contexts each paying the cost.

Verified: all 5 tests pass against localhost in ~1m 24s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rdhyee rdhyee force-pushed the tests/209-url-roundtrip-specs branch from 2c97398 to 3b3555b Compare May 12, 2026 05:18
@rdhyee
Copy link
Copy Markdown
Contributor Author

rdhyee commented May 12, 2026

Codex review — round 1

Self-contained Codex review (no shared context with our session). Pasting verbatim per the iterative-review pattern.


Summary

The specs are useful, but I would not approve this as-is if the PR title/body says closes #209. The biggest test-design issue is Test 5: it can pass without proving that stale selectedH3 state was cleared.

Per-ask response

1. h3 hashchange spec assertion strength

tests/playwright/url-roundtrip.spec.js:190-213 is too weak. It starts with an invalid h3, so selectedH3 can be null from boot and remain null; that does not prove the hashchange null-result branch clears prior state.

Stronger version: start with a known valid h3, wait until selectedH3 is non-null and preferably canonicalized, then mutate to invalid h3 and assert it clears:

await page.goto(`${EXPLORER_PATH}#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}&h3=${VALID_H3}`);
await waitForMode(page, 'cluster');
await page.waitForFunction(async () => {
  const v = await window._ojs.ojsConnector.mainModule.value('viewer');
  return !!v._globeState.selectedH3;
});

await page.evaluate((badH3) => {
  const params = new URLSearchParams(location.hash.slice(1));
  params.set('h3', badH3);
  params.set('heading', '5.0');
  location.hash = '#' + params.toString();
}, '0baadbeeffffffff');

await page.waitForFunction(async () => {
  const v = await window._ojs.ojsConnector.mainModule.value('viewer');
  return v._globeState.selectedH3 === null;
});

That would actually catch a regression in the explorer.qmd:2278-2288 null-result branch.

2. requestRenderMode = false

This is probably OK because Playwright gives each test its own page/context, and Test 4 creates separate contexts explicitly. It should not leak across tests under the current config.

I would still extract a local helper like flyCameraToAndWaitForHash(...), mostly to centralize the pattern and avoid future drift. Cleanup is not necessary unless the same page is reused inside a test.

3. waitForPointModeSettled text match

Yes, tests/playwright/url-roundtrip.spec.js:53-61 makes user-facing copy load-bearing. A copy edit to explorer.qmd:1610-1612 would break these tests without changing behavior.

Better signals:

  • expose a test-only/state flag such as viewer._globeState.pointModeSettled
  • wait on data/state rather than prose, e.g. viewer._globeState.mode === 'point' plus viewport sample data present
  • if staying DOM-based, match the more semantic count phrase already used elsewhere: \d[\d,]*\s+individual\s+samples

4. Timeout

test.setTimeout(360000) at url-roundtrip.spec.js:88 is too broad. The multi-context test may need a high cap, but the h3 invalid-cell test should not be allowed to hang for 6 minutes.

Recommendation:

  • keep describe default closer to 120s or 180s
  • use test.setTimeout(360000) only inside the round-trip/context-heavy test
  • pass tighter helper timeouts in the tests that should settle quickly

5. Helper duplication

Reasonable for this PR, with one caveat. The existing helper file is itself a spec, not a clean support module, so importing from it would be awkward and could accidentally register tests.

I would endorse local duplication for now, but if a third Explorer spec needs waitForMode, phase-message history, or camera flight helpers, move them into tests/playwright/helpers/explorer.js.

6. "closes #209" correctness

I do not think closes #209 is correct.

From the eight listed #209 checklist items, this PR fully covers only:

  • sub-threshold pan settles via moveEnd

It partially touches:

  • cold-cache / low-alt point-mode deep-link, though the file comment says it intentionally does not try cold-cache deep-links
  • h3 behavior, but not the requested valid h3 deep-link round-trip with cluster card hydration and URL persistence

Uncovered:

  • pid deep-link + active filters
  • valid h3 deep-link round-trip
  • search + search_scope
  • facet filters
  • view=table
  • search-result flight

So I would change this to addresses #209 or refs #209, and open/fill a follow-up issue for the remaining matrix.

7. Test 4 race

Yes, url-roundtrip.spec.js:169 should poll the URL hash rather than sleep. The implementation has immediate moveEnd hash writing at explorer.qmd:2117-2120, but timing still depends on Cesium flight completion and render scheduling.

Use waitForFunction:

await pageA.waitForFunction(
  ({ lat, lng }) => {
    const params = new URLSearchParams(location.hash.slice(1));
    return Math.abs(parseFloat(params.get('lat')) - lat) < 0.001 &&
           Math.abs(parseFloat(params.get('lng')) - lng) < 0.001;
  },
  { lat: newLat, lng: newLng },
  { timeout: 10000 }
);

Same improvement applies to Test 3 at url-roundtrip.spec.js:138.

8. selectedPid / selectedH3 field names

They are canonical today; explorer.qmd initializes and writes them directly in many places. But the test is coupled to internal state, not public UI.

Worth a short comment in snapshot() saying these fields are intentionally asserted as the URL-selection backing state. That makes future refactors update the tests consciously instead of treating breakage as mysterious.

9. Other findings

url-roundtrip.spec.js:120 stores before but never uses it. Drop it or assert that the URL changed from before.hash.

url-roundtrip.spec.js:151-187 should close contexts in finally; right now a mid-test assertion failure leaks ctxA/ctxB until Playwright cleanup.

The file header is internally inconsistent: it says the tests “don’t try to drive cold-cache deep-links” at url-roundtrip.spec.js:7-9, while the timeout comment at url-roundtrip.spec.js:86-87 cites cold-cache point-mode boot as the reason for 6 minutes. Tighten that wording.

Verdict

BLOCK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

explorer: expand Playwright URL round-trip coverage beyond camera+mode

1 participant