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
4 changes: 2 additions & 2 deletions tests/playwright/cesium-queries.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
const { test, expect } = require('@playwright/test');

// Configuration
const BASE_URL = process.env.TEST_URL || 'http://localhost:5860';
const { siteUrl } = require('./helpers/url');
const PAGE_PATH = '/tutorials/parquet_cesium.html';

// Test data - PKAP location with known samples
Expand All @@ -27,7 +27,7 @@ test.describe('Cesium Query Results UI', () => {

test.beforeEach(async ({ page }) => {
// Navigate to page
await page.goto(`${BASE_URL}${PAGE_PATH}`, {
await page.goto(siteUrl(PAGE_PATH), {
waitUntil: 'domcontentloaded',
timeout: 60000
});
Expand Down
12 changes: 5 additions & 7 deletions tests/playwright/explorer-helper.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@
*/

const { test, expect } = require('@playwright/test');

const BASE_URL = process.env.TEST_URL || 'http://localhost:5860';
const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

// Phrases written by `updatePhaseMsg` we check for in tests.
const FETCHING_SAMPLE_INDEX = 'Fetching sample index'; // the helper's loadingMsg
Expand Down Expand Up @@ -218,7 +216,7 @@ async function flyCameraTo(page, lat, lng, alt) {
test.describe('explorer: tryEnterPointModeIfNeeded short-circuit invariants', () => {

test('boot to cluster mode reaches a done message with cluster counts', async ({ page }) => {
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand All @@ -235,7 +233,7 @@ test.describe('explorer: tryEnterPointModeIfNeeded short-circuit invariants', ()
// helper after its own loadRes settles; at world altitude the chase
// should bail at the altitude check WITHOUT painting "Fetching sample
// index…".
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -277,7 +275,7 @@ test.describe('explorer: tryEnterPointModeIfNeeded short-circuit invariants', ()
// and paints "Fetching sample index…"; warm-cache: currentRes is
// already 8 so the helper short-circuits its loadRes and goes
// straight to enterPointMode → "Loading individual samples…".)
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -316,7 +314,7 @@ test.describe('explorer: tryEnterPointModeIfNeeded short-circuit invariants', ()
// wires a chase in, the short-circuit must keep the helper's
// loadingMsg ("Fetching sample index…") suppressed because we're
// already in point mode.
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down
10 changes: 4 additions & 6 deletions tests/playwright/explorer-layout-stability.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const { test, expect } = require('@playwright/test');

const BASE_URL = process.env.TEST_URL || 'http://localhost:5860';
const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

const ALT_WORLD = 10000000;
const ALT_POINT_CYPRUS = 62054;
Expand Down Expand Up @@ -95,7 +93,7 @@ async function resolveMapHeightPx(page) {
test.describe('explorer layout stability', () => {
test('desktop globe rect is stable across boot, status, and point-mode flight; table is permanent below', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -142,7 +140,7 @@ test.describe('explorer layout stability', () => {
test('mobile globe height override is stable across boot and wrapped status', async ({ page }) => {
const viewport = { width: 390, height: 844 };
await page.setViewportSize(viewport);
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -172,7 +170,7 @@ test.describe('explorer layout stability', () => {
// 50vh = 284px — below the 360px floor — so map height = 360px.
// Covers the clamp-floor branch which the 390×844 case never exercises.
await page.setViewportSize({ width: 320, height: 568 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=${ALT_WORLD}`, {
await page.goto(explorerUrl(`#v=1&lat=20&lng=0&alt=${ALT_WORLD}`), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down
22 changes: 10 additions & 12 deletions tests/playwright/explorer-map-overlay.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const { test, expect } = require('@playwright/test');

const BASE_URL = process.env.TEST_URL || 'http://localhost:5860';
const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

// Cesium + OJS boot can be slow on CI; the in-map-overlay specs all wait
// for #cesiumContainer + toolbar render before measuring.
Expand Down Expand Up @@ -31,7 +29,7 @@ async function waitForBootReady(page) {
test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)', () => {
test('desktop: overlay does not cover Cesium toolbar buttons', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand All @@ -56,7 +54,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('mobile (390px): overlay does not cover Cesium toolbar', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand All @@ -71,7 +69,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('iPhone SE (320px): overlay clears toolbar and search buttons do not overflow', async ({ page }) => {
await page.setViewportSize({ width: 320, height: 568 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -102,7 +100,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('base-layer picker dropdown is clickable (not occluded) above the overlay', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -154,7 +152,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',
await page.setViewportSize({ width: 1280, height: 900 });

// Load at world zoom and read total.
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -203,7 +201,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('table v2: pagination is server-side, pager shows Page X of Y, Next loads new rows', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -236,7 +234,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('table v2: filter change clears pager text and re-fetches count', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand All @@ -260,7 +258,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('clicking a table row selects the sample, updates #pid hash, and marks the row selected', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 900 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}#v=1&lat=20&lng=0&alt=10000000`, {
await page.goto(explorerUrl('#v=1&lat=20&lng=0&alt=10000000'), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down Expand Up @@ -295,7 +293,7 @@ test.describe('Map search overlay — Cesium toolbar coexistence (#200 / M-1A)',

test('sidebar search input mirrors in-map search input', async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto(`${BASE_URL}${EXPLORER_PATH}`, {
await page.goto(explorerUrl(), {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
Expand Down
11 changes: 5 additions & 6 deletions tests/playwright/facet-viewport.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@
* the implementation plan logs query latency for visibility.
*/
const { test, expect } = require('@playwright/test');

const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

// Global view position. alt=15000000 (15,000 km) is above the
// `GLOBAL_VIEW_ALT_M = 1e7` shortcut in `isGlobalView()`, so the
Expand Down Expand Up @@ -133,7 +132,7 @@ test.describe('B1 viewport-aware facet counts (#234 step 3)', () => {
test.setTimeout(180000);

test('zoom in → counts shrink to viewport; zoom out → counts restore', async ({ page }) => {
await page.goto(`${EXPLORER_PATH}${GLOBAL_HASH}`);
await page.goto(explorerUrl(GLOBAL_HASH));
await waitForFacetUI(page);
await waitForFacetCountsStable(page);

Expand Down Expand Up @@ -181,7 +180,7 @@ test.describe('B1 viewport-aware facet counts (#234 step 3)', () => {
// DuckDB since `lite_url` also carries a `source` column) and
// against the JOIN inadvertently double-counting via duplicate pids.
// Codex round-1 review of PR #237 called out the coverage gap.
await page.goto(`${EXPLORER_PATH}${GLOBAL_HASH}`);
await page.goto(explorerUrl(GLOBAL_HASH));
await waitForFacetUI(page);
await waitForFacetCountsStable(page);

Expand Down Expand Up @@ -243,7 +242,7 @@ test.describe('B1 viewport-aware facet counts (#234 step 3)', () => {
});

test('moveStart marks .recomputing before the debounce can run', async ({ page }) => {
await page.goto(`${EXPLORER_PATH}${GLOBAL_HASH}`);
await page.goto(explorerUrl(GLOBAL_HASH));
await waitForFacetUI(page);
await waitForFacetCountsStable(page);

Expand Down Expand Up @@ -289,7 +288,7 @@ test.describe('B1 viewport-aware facet counts (#234 step 3)', () => {
// must NOT leave any `.recomputing` class behind. Guards against a
// future refactor that moves `markFacetCountsRecomputing()` above
// the early-return.
await page.goto(`${EXPLORER_PATH}${GLOBAL_HASH}`);
await page.goto(explorerUrl(GLOBAL_HASH));
await waitForFacetUI(page);
await waitForFacetCountsStable(page);
const stuck = await page.evaluate(
Expand Down
9 changes: 4 additions & 5 deletions tests/playwright/facetnote-url-load.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
*/

const { test, expect } = require('@playwright/test');

const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

// Cluster-altitude default (well above ENTER_POINT_ALT = 120000).
const ALT_CLUSTER = 5000000;
Expand Down Expand Up @@ -60,7 +59,7 @@ test.describe('#facetNote URL deep-link visibility (issue #234 step 1)', () => {
// Boot the page once to discover a real material URI from the rendered
// checkboxes. Hardcoding a URI would couple the test to a specific
// vocabulary version; reading the live data keeps it self-healing.
await page.goto(`${EXPLORER_PATH}#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`);
await page.goto(explorerUrl(`#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`));
await waitForMode(page, 'cluster');
await waitForFacetCheckboxes(page);

Expand All @@ -75,7 +74,7 @@ test.describe('#facetNote URL deep-link visibility (issue #234 step 1)', () => {
// syncFacetNote() must run to flip #facetNote visible.
const encoded = encodeURIComponent(materialUri);
await page.goto(
`${EXPLORER_PATH}?material=${encoded}#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`
explorerUrl(`?material=${encoded}#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`)
);
await waitForMode(page, 'cluster');
await waitForFacetCheckboxes(page);
Expand Down Expand Up @@ -112,7 +111,7 @@ test.describe('#facetNote URL deep-link visibility (issue #234 step 1)', () => {
// Negative control: arriving with no facet params must keep the note
// hidden. Guards against an over-eager `syncFacetNote()` that flips
// visibility independent of `hasFacetFilters()`.
await page.goto(`${EXPLORER_PATH}#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`);
await page.goto(explorerUrl(`#v=1&lat=${LAT}&lng=${LNG}&alt=${ALT_CLUSTER}`));
await waitForMode(page, 'cluster');
await waitForFacetCheckboxes(page);

Expand Down
57 changes: 57 additions & 0 deletions tests/playwright/helpers/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// URL helpers for the Playwright suite.
//
// Why this exists: all specs in tests/playwright/ navigate to rendered
// pages on the site (explorer.html, tutorials/*.html, etc.) and accept
// a TEST_URL env var so they can run against:
//
// - the dev / CI smoke gate's static server (`http://localhost:5860`)
// - production (`https://isamples.org`)
// - the fork-staging GitHub Pages URL
// (`https://rdhyee.github.io/isamplesorg.github.io/`, with sub-path)
//
// The historical hand-rolled patterns had two latent gotchas:
//
// 1. Some specs called `page.goto('/explorer.html')` and relied on
// Playwright's `baseURL` config. Playwright resolves an absolute
// path against the ORIGIN of baseURL, so a sub-path TEST_URL like
// `https://rdhyee.github.io/isamplesorg.github.io/` silently
// resolves to `https://rdhyee.github.io/explorer.html` (404).
//
// 2. Specs that string-concat `${BASE_URL}${EXPLORER_PATH}` work on
// sub-path TEST_URLs only when TEST_URL has no trailing slash.
// A trailing slash produces `//explorer.html` — tolerated by some
// servers, but not the intended URL shape.
//
// `siteUrl()` / `explorerUrl()` below collapse both gotchas: strip the
// trailing slash from BASE_URL once, then string-concat a leading-slash
// path. Same result whether TEST_URL is given with or without a trailing
// slash, and the sub-path is preserved on fork-staging.
//
// History: extracted 2026-05-27 from PR #238 (Codex round-1 review of
// the facet-viewport.spec.js URL fix recommended a shared helper rather
// than duplicating the fix into every spec).

const BASE_URL = (process.env.TEST_URL || 'http://localhost:5860').replace(/\/$/, '');

/** Build a URL on the rendered site.
*
* @param {string} path Path on the site, should start with `/`
* (e.g. `/explorer.html`, `/tutorials/parquet_cesium.html`).
* @param {string} [suffix] Optional hash or query suffix appended as-is
* (e.g. `#v=1&lat=0&lng=0&alt=15000000`).
* @returns {string} Full URL ready for `page.goto()`.
*/
function siteUrl(path, suffix = '') {
return `${BASE_URL}${path}${suffix}`;
}

/** Convenience for the most common case: a URL on the explorer page.
*
* @param {string} [suffix] Optional hash or query suffix.
* @returns {string} Full URL ready for `page.goto()`.
*/
function explorerUrl(suffix = '') {
return siteUrl('/explorer.html', suffix);
}

module.exports = { BASE_URL, siteUrl, explorerUrl };
9 changes: 4 additions & 5 deletions tests/playwright/search-real-count.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@
* unchanged from pre-#232 ("N results for term" with no "of M").
*/
const { test, expect } = require('@playwright/test');

const EXPLORER_PATH = '/explorer.html';
const { explorerUrl } = require('./helpers/url');

/** Wait until the explorer has rendered the search input. Boot sequence:
* phase1 (viewer + cluster cache) → facetFilters → search input wiring.
Expand Down Expand Up @@ -77,7 +76,7 @@ test.describe('Search real-count display (#232 / #234 step 2)', () => {

test('cap-hit search shows "N of M results" + structured log carries total_count', async ({ page }) => {
const logs = attachSearchLogCollector(page);
await page.goto(EXPLORER_PATH);
await page.goto(explorerUrl());
await waitForSearchReady(page);

await runSearch(page, 'pottery');
Expand Down Expand Up @@ -148,7 +147,7 @@ test.describe('Search real-count display (#232 / #234 step 2)', () => {
// `finally`, this test fails. Both round-1 (predicate snapshot)
// and round-2 (telemetry snapshot) fixes are exercised.
const logs = attachSearchLogCollector(page);
await page.goto(EXPLORER_PATH);
await page.goto(explorerUrl());
await waitForSearchReady(page);

// Sanity: no facet active at search-fire time.
Expand Down Expand Up @@ -222,7 +221,7 @@ test.describe('Search real-count display (#232 / #234 step 2)', () => {

test('no-results search short-circuits without COUNT (total_count remains null)', async ({ page }) => {
const logs = attachSearchLogCollector(page);
await page.goto(EXPLORER_PATH);
await page.goto(explorerUrl());
await waitForSearchReady(page);

await runSearch(page, 'xyzzyqqqplugh');
Expand Down
Loading
Loading