mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Each script launches and closes Chrome through scripts/qa/browser.mjs, waits on the application through scripts/qa/ready.mjs instead of the loading screen and fixed pauses, and reads the contract instead of Cesium where the check is not about Cesium itself.
313 lines
10 KiB
JavaScript
313 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Location UI acceptance; controlled search results isolate UI races from geocoder availability. */
|
|
import { whenSettled, whenTilesLoaded } from './qa/ready.mjs';
|
|
import { launchBrowser, closeBrowser } from './qa/browser.mjs';
|
|
import fs from 'node:fs';
|
|
const browser = await launchBrowser({
|
|
headless: true,
|
|
args: [
|
|
'--no-sandbox',
|
|
...(process.platform === 'darwin' ? ['--enable-gpu'] : []),
|
|
],
|
|
});
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
page.on('pageerror', (e) => errors.push(e.message));
|
|
let failures = 0;
|
|
const check = (name, ok) => {
|
|
console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}`);
|
|
if (!ok) failures++;
|
|
};
|
|
try {
|
|
await page.setViewport({ width: 1440, height: 900 });
|
|
await page.goto(
|
|
`${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
|
|
{ waitUntil: 'domcontentloaded' },
|
|
);
|
|
await page.waitForFunction(
|
|
() => window.__godsEyeView?.styleManager?._locationControls,
|
|
{ timeout: 60000 },
|
|
);
|
|
await whenSettled(page);
|
|
await page.click('#location-bar-toggle');
|
|
await page.click('[data-pin-target="location-bar"]');
|
|
const cityId = await page.$eval(
|
|
'.location-pill',
|
|
(node) => node.dataset.locationId,
|
|
);
|
|
await page.click('.location-pill');
|
|
await page.waitForFunction(() =>
|
|
document.getElementById('poi-row').classList.contains('expanded'),
|
|
);
|
|
check(
|
|
'native city selection highlights and expands its POIs',
|
|
await page.evaluate(
|
|
(id) =>
|
|
window.__godsEyeView.styleManager._activeLocationId === id &&
|
|
document.querySelector('.location-pill.active')?.dataset.locationId ===
|
|
id,
|
|
cityId,
|
|
),
|
|
);
|
|
await page.keyboard.press('w');
|
|
check(
|
|
'POI keyboard action selects the second landmark',
|
|
await page.evaluate(
|
|
() =>
|
|
window.__godsEyeView.styleManager._activePoiIndex === 1 &&
|
|
document.querySelector('.poi-pill.active')?.dataset.poiIndex === '1',
|
|
),
|
|
);
|
|
check(
|
|
'orbit controls and indicator agree',
|
|
await page.evaluate(async () => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
// Vue renders on a microtask; a task later the indicator shows it.
|
|
const rendered = () => new Promise((resolve) => setTimeout(resolve));
|
|
ui.setOrbit(true);
|
|
await rendered();
|
|
const active =
|
|
ui.orbitController.active &&
|
|
document.getElementById('orbit-indicator').classList.contains('active');
|
|
ui.setOrbit(false);
|
|
await rendered();
|
|
return (
|
|
active &&
|
|
!ui.orbitController.active &&
|
|
!document.getElementById('orbit-indicator').classList.contains('active')
|
|
);
|
|
}),
|
|
);
|
|
await page.click('#search-toggle');
|
|
await page.type('#location-search', 'qwerty');
|
|
check(
|
|
'typing in search leaves POI selection unchanged',
|
|
await page.evaluate(
|
|
() => window.__godsEyeView.styleManager._activePoiIndex === 1,
|
|
),
|
|
);
|
|
const geocoderRequests = [];
|
|
const recordGeocoderRequest = (request) => {
|
|
const url = new URL(request.url());
|
|
if (
|
|
url.pathname === '/api/geocode' ||
|
|
url.pathname === '/api/google/geocode' ||
|
|
(url.hostname === 'photon.komoot.io' && url.pathname === '/api/')
|
|
)
|
|
geocoderRequests.push(request.url());
|
|
};
|
|
page.on('request', recordGeocoderRequest);
|
|
await page.$eval('#location-search', (input) => {
|
|
input.value = 'geo:39.7392,-104.9903';
|
|
input.focus();
|
|
});
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForFunction(
|
|
() => {
|
|
const state =
|
|
window.__godsEyeView.styleManager._locationLookup.getState();
|
|
return (
|
|
state.query === 'geo:39.7392,-104.9903' && state.status === 'found'
|
|
);
|
|
},
|
|
{ timeout: 30000 },
|
|
);
|
|
await whenSettled(page);
|
|
check(
|
|
'native geo URI search shows the decimal coordinate readout',
|
|
await page.evaluate(() => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
return (
|
|
ui._searchedLocationLabel === '39.7392° N, 104.9903° W' &&
|
|
document
|
|
.getElementById('location-mini-city')
|
|
.textContent.includes('39.7392° N') &&
|
|
document.getElementById('location-mini-poi').textContent ===
|
|
'104.9903° W'
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'geo URI navigation settles near the requested coordinate',
|
|
await page.evaluate(() => {
|
|
const position = window.__godsEyeView.engine.camera.pose().cartographic;
|
|
const degrees = 180 / Math.PI;
|
|
return (
|
|
Math.abs(position.latitude * degrees - 39.7392) < 0.05 &&
|
|
Math.abs(position.longitude * degrees + 104.9903) < 0.05
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'geo URI search makes no geocoder request',
|
|
geocoderRequests.length === 0,
|
|
);
|
|
page.off('request', recordGeocoderRequest);
|
|
await page.evaluate(() => {
|
|
const lookup = window.__godsEyeView.styleManager._locationLookup;
|
|
window.__qaSearchRequests = [];
|
|
window.__qaLocationState = [];
|
|
window.__godsEyeView.styleManager.subscribeLocationSearch((notification) =>
|
|
window.__qaLocationState.push(notification),
|
|
);
|
|
lookup.search = (query, options) =>
|
|
new Promise((resolve) =>
|
|
window.__qaSearchRequests.push({ query, options, resolve }),
|
|
);
|
|
document.getElementById('location-search').value = 'First';
|
|
document.getElementById('location-search').focus();
|
|
});
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForFunction(() => window.__qaSearchRequests.length === 1);
|
|
await page.evaluate(() => {
|
|
document.getElementById('location-search').value = 'Second';
|
|
document.getElementById('location-search').focus();
|
|
});
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForFunction(() => window.__qaSearchRequests.length === 2);
|
|
check(
|
|
'second native submit aborts the first request and keeps current busy state',
|
|
await page.evaluate(
|
|
() =>
|
|
window.__qaSearchRequests[0].options.signal.aborted &&
|
|
document
|
|
.getElementById('location-search')
|
|
.classList.contains('searching'),
|
|
),
|
|
);
|
|
await page.evaluate(() =>
|
|
window.__qaSearchRequests[1].resolve({
|
|
label: 'Second landmark, Test city',
|
|
}),
|
|
);
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView.styleManager._searchedLocationLabel ===
|
|
'Second landmark, Test city',
|
|
);
|
|
await page.evaluate(() =>
|
|
window.__qaSearchRequests[0].resolve({
|
|
label: 'First landmark, Test city',
|
|
}),
|
|
);
|
|
check(
|
|
'late result cannot overwrite the current location readout or style',
|
|
await page.evaluate(() => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
return (
|
|
ui._searchedLocationLabel === 'Second landmark, Test city' &&
|
|
document
|
|
.getElementById('location-mini-city')
|
|
.textContent.includes('Second landmark') &&
|
|
document.getElementById('location-mini-poi').textContent ===
|
|
'Test city' &&
|
|
ui.activeStyle === 'normal' &&
|
|
!ui._locationSearch.classList.contains('searching')
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'Location subscriptions accept only the current result and retain request identities',
|
|
await page.evaluate(() => {
|
|
const seen = window.__qaLocationState;
|
|
const started = seen.filter(({ change }) => change?.type === 'started');
|
|
const found = seen.filter(({ change }) => change?.type === 'found');
|
|
return (
|
|
seen[0].initial &&
|
|
started.length === 2 &&
|
|
found.length === 1 &&
|
|
found[0].change.requestId === started[1].change.requestId &&
|
|
found[0].state.destination.label === 'Second landmark, Test city' &&
|
|
Object.isFrozen(found[0].state.destination)
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'cancelled POI expansion stays closed after a frame',
|
|
await page.evaluate(async (id) => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
ui._expandPOIRow(id);
|
|
ui._collapsePOIRow();
|
|
await new Promise((resolve) =>
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
|
);
|
|
return (
|
|
!document.getElementById('poi-row').classList.contains('expanded') &&
|
|
!document
|
|
.getElementById('location-bar-divider')
|
|
.classList.contains('visible')
|
|
);
|
|
}, cityId),
|
|
);
|
|
await page.evaluate(
|
|
(id) => window.__godsEyeView.styleManager._onCityPillClick(id),
|
|
cityId,
|
|
);
|
|
await page.waitForFunction(() =>
|
|
document.getElementById('poi-row').classList.contains('expanded'),
|
|
);
|
|
// Finish the camera's existing city flight before inspecting both layouts.
|
|
await whenSettled(page);
|
|
await page.evaluate(
|
|
() =>
|
|
new Promise((resolve) =>
|
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
|
),
|
|
);
|
|
const settled = await whenTilesLoaded(page, { timeoutMs: 60000 }).then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
check('visible city tiles settle before screenshots', settled);
|
|
fs.mkdirSync('qa-shots/location-controls', { recursive: true });
|
|
await page.screenshot({ path: 'qa-shots/location-controls/desktop.png' });
|
|
await page.setViewport({ width: 620, height: 900 });
|
|
await page.waitForFunction(
|
|
() =>
|
|
document.getElementById('left-panel-stack')?.dataset.layoutMode ===
|
|
'mobile',
|
|
);
|
|
await page.screenshot({ path: 'qa-shots/location-controls/narrow.png' });
|
|
check(
|
|
'native reset returns through the existing camera handoff',
|
|
await page.evaluate(async () => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
document.getElementById('reset-globe-view').click();
|
|
const pending = ui._globeResetPromise;
|
|
if (!pending) return false;
|
|
await pending;
|
|
return (
|
|
!ui.orbitController.active &&
|
|
!window.__godsEyeView.engine.camera.following()
|
|
);
|
|
}),
|
|
);
|
|
check(
|
|
'destroyed controls cannot navigate or submit another lookup',
|
|
await page.evaluate(() => {
|
|
const ui = window.__godsEyeView.styleManager;
|
|
const count = window.__qaSearchRequests.length;
|
|
ui._locationControls.destroy();
|
|
ui._locationLookup.destroy();
|
|
const before = ui._navigationGeneration;
|
|
document
|
|
.getElementById('location-pills')
|
|
.querySelector('button')
|
|
?.click();
|
|
document.getElementById('reset-globe-view').click();
|
|
ui._locationSearch.dispatchEvent(
|
|
new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }),
|
|
);
|
|
return (
|
|
before === ui._navigationGeneration &&
|
|
count === window.__qaSearchRequests.length
|
|
);
|
|
}),
|
|
);
|
|
check('no uncaught browser errors', errors.length === 0);
|
|
} finally {
|
|
await closeBrowser(browser);
|
|
}
|
|
console.log(`RESULT: ${failures} failures`);
|
|
process.exitCode = failures ? 1 : 0;
|