mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
qa-switch-equals-cold-start switches engines in both directions with each layer on deterministic fixtures, then opens the share link of the same state in a new page on the target engine, and compares the draw ledgers and the probe's view of the layer. It runs as 19 acceptance groups covering all 38 layers. State a layer keeps through a switch but the share link does not carry is declared as sessionState (the directions route, Radio's selected station and audio state, the wind reading, local ADS-B's and OSM Places' local toggles and results); a test checks each declaration against what the layer really retains, so a declaration cannot hide a regression.
364 lines
11 KiB
JavaScript
364 lines
11 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import {
|
|
createStrict,
|
|
localPreview,
|
|
openPage,
|
|
waitForEngine,
|
|
} from './qa-strict-harness.mjs';
|
|
import { whenSettled } from './qa/ready.mjs';
|
|
import { drawLedger } from './qa/ledger.mjs';
|
|
import {
|
|
COLD_START_GROUPS,
|
|
coldStartUrl,
|
|
compareColdStart,
|
|
probeSnapshot,
|
|
sessionExclusion,
|
|
} from './qa/coldStart.mjs';
|
|
import { coldStartFixtures } from './qa/coldStartFixtures.mjs';
|
|
import { stations } from './fixtures/layers/radio.mjs';
|
|
|
|
// Each registered group compares both directions, with a fresh storage context
|
|
// for every cold page. The URL writer and restore path supply the reference.
|
|
// node scripts/qa-switch-equals-cold-start.mjs http://127.0.0.1:4309
|
|
// --group=ground --evidence=tmp_switch_cold_ground.json
|
|
const base = localPreview(process.argv[2]);
|
|
const group = process.argv.find((arg) => arg.startsWith('--group='))?.slice(8);
|
|
assert.ok(
|
|
COLD_START_GROUPS[group],
|
|
`Supply --group=${Object.keys(COLD_START_GROUPS).join('|')}`,
|
|
);
|
|
const { layers, view } = COLD_START_GROUPS[group];
|
|
const fixture = await coldStartFixtures(base.origin, group);
|
|
const allow = JSON.parse(
|
|
readFileSync(new URL('./qa-ledger-allow.json', import.meta.url), 'utf8'),
|
|
);
|
|
const strict = createStrict({
|
|
origin: base.origin,
|
|
layers,
|
|
routes: fixture.routes,
|
|
fixtures: fixture.fixtures,
|
|
name: `switch-equals-cold-start--${group}`,
|
|
time: new Date(fixture.epoch).toISOString(),
|
|
});
|
|
const evidence = (strict.extra.coldStart = {
|
|
group,
|
|
layers,
|
|
samples: [],
|
|
differences: [],
|
|
skipped: [],
|
|
});
|
|
|
|
async function settle(page, cold = false) {
|
|
await whenSettled(page, { timeoutMs: 30000 });
|
|
await page.waitForFunction(
|
|
(layers, cold) => {
|
|
const { dataManager, engine } = window.__godsEyeView;
|
|
engine.render.request('qa-cold-start-readiness');
|
|
return layers.every((id) => {
|
|
const entry = dataManager.layers.get(id),
|
|
module = entry?.module;
|
|
if (!entry) return false;
|
|
if (cold && module.sessionState?.includes('enabled'))
|
|
return !entry.enabled;
|
|
if (
|
|
!entry.enabled ||
|
|
!entry.initialized ||
|
|
entry.sceneDetached ||
|
|
entry.refreshing
|
|
)
|
|
return false;
|
|
const stats = module.getStats?.() ?? {};
|
|
if (stats.loading || stats.refreshing) return false;
|
|
// These layers can be enabled with no search, route or selected subject.
|
|
if (
|
|
[
|
|
'directions',
|
|
'military-awareness',
|
|
'osm-places',
|
|
'sea-marks',
|
|
'ski-pistes',
|
|
].includes(id)
|
|
)
|
|
return true;
|
|
return stats.count > 0;
|
|
});
|
|
},
|
|
{ timeout: 30000, polling: 100 },
|
|
layers,
|
|
cold,
|
|
);
|
|
await whenSettled(page, { frames: 5, timeoutMs: 30000 });
|
|
}
|
|
|
|
async function prepare(page, owners) {
|
|
for (const id of layers) {
|
|
const before = await drawLedger(page);
|
|
assert.equal(
|
|
await page.evaluate(async (id) => {
|
|
const manager = window.__godsEyeView.dataManager;
|
|
return manager.setEnabled(id, true, { origin: 'user' });
|
|
}, id),
|
|
true,
|
|
`${id} enabled`,
|
|
);
|
|
for (const row of await drawLedger(page)) {
|
|
if (!before.some((old) => old.family === row.family && old.id === row.id))
|
|
owners[`${row.family}:${row.id}`] = id;
|
|
}
|
|
}
|
|
await page.evaluate(
|
|
async (group, stationId) => {
|
|
const manager = window.__godsEyeView.dataManager;
|
|
const module = (id) => manager.layers.get(id).module;
|
|
if (group === 'open')
|
|
module('osm-places').setResults(
|
|
[
|
|
{
|
|
id: 'node/1',
|
|
name: 'Fixture Clinic',
|
|
lat: 38.9,
|
|
lon: -77.03,
|
|
tags: {},
|
|
},
|
|
{
|
|
id: 'node/2',
|
|
name: 'Fixture Hospital',
|
|
lat: 38.905,
|
|
lon: -77.02,
|
|
tags: {},
|
|
},
|
|
],
|
|
{ kind: 'clinic', label: 'Clinics', area: 'Washington' },
|
|
);
|
|
if (group === 'street')
|
|
manager.setLayerParams(
|
|
'street-level',
|
|
{ kartaview: true, mapillary: false },
|
|
{ origin: 'user' },
|
|
);
|
|
if (group === 'wind')
|
|
await manager.setLayerParams(
|
|
'wind',
|
|
{ paused: true, overlay: 'temperature', units: 'm/s' },
|
|
{ origin: 'user' },
|
|
);
|
|
if (group === 'radio') {
|
|
module('radio').selectStation(stationId, {
|
|
autoplay: true,
|
|
focus: false,
|
|
origin: 'user',
|
|
});
|
|
await manager.setLayerParams(
|
|
'radio',
|
|
{ volume: 0.36 },
|
|
{ origin: 'user' },
|
|
);
|
|
}
|
|
if (group === 'imagery')
|
|
module('recent-imagery').setBox({
|
|
west: -97.77,
|
|
east: -97.71,
|
|
south: 30.25,
|
|
north: 30.29,
|
|
});
|
|
if (group === 'cables') {
|
|
module('directions').placeEndpoint('a', { lat: 55.32, lon: 10.78 });
|
|
module('directions').placeEndpoint('b', { lat: 55.33, lon: 10.82 });
|
|
}
|
|
},
|
|
group,
|
|
stations[10].id,
|
|
);
|
|
await settle(page);
|
|
if (group === 'cables')
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('directions')
|
|
.module.getStats().count > 0,
|
|
{ timeout: 30000 },
|
|
);
|
|
if (group === 'wind')
|
|
await page.evaluate(() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('wind')
|
|
.module.setParams({ inspect: true }),
|
|
);
|
|
if (group === 'imagery') {
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__godsEyeView.dataManager.layers
|
|
.get('recent-imagery')
|
|
.module.getSnapshot()
|
|
.candidates.some(
|
|
(row) =>
|
|
row.product === 'S30' && row.thumbnail?.status === 'present',
|
|
),
|
|
{ timeout: 30000 },
|
|
);
|
|
await page.evaluate(() => {
|
|
const module =
|
|
window.__godsEyeView.dataManager.layers.get('recent-imagery').module;
|
|
const row = module
|
|
.getSnapshot()
|
|
.candidates.find(
|
|
(row) => row.product === 'S30' && row.thumbnail?.status === 'present',
|
|
);
|
|
module.setAssignment('a', row.key);
|
|
module.setMode('image');
|
|
});
|
|
}
|
|
await settle(page);
|
|
// Asynchronous feed completion can create registries after enable resolves.
|
|
for (const row of await drawLedger(page)) {
|
|
const owner = layers.find((id) => row.id.startsWith(id));
|
|
if (owner) owners[`${row.family}:${row.id}`] ??= owner;
|
|
}
|
|
}
|
|
|
|
await strict.run({}, async (browser) => {
|
|
for (const [from, target] of [
|
|
['cesium', 'navara'],
|
|
['navara', 'cesium'],
|
|
]) {
|
|
const direction = `${from} -> ${target}`;
|
|
const sample = { direction, owners: {} };
|
|
evidence.samples.push(sample);
|
|
let context, page;
|
|
const close = async () => {
|
|
const unexpected = await page
|
|
?.evaluate(() => window.__coldStartUnexpectedApi ?? [])
|
|
.catch(() => []);
|
|
for (const path of unexpected ?? [])
|
|
evidence.differences.push({
|
|
layer: '[page]',
|
|
direction,
|
|
family: 'fixture',
|
|
id: path,
|
|
field: 'unstubbed API',
|
|
switchValue: 'blocked',
|
|
coldStartValue: 'fixture required',
|
|
});
|
|
await page?.evaluate(() => window.__qaFinishMonitors?.()).catch(() => {});
|
|
await context?.close();
|
|
context = page = null;
|
|
};
|
|
const open = async (url) => {
|
|
context = await browser.createBrowserContext();
|
|
const opened = await openPage(context, { shaderErrors: true });
|
|
page = opened.page;
|
|
sample.errors ??= [];
|
|
sample.errors.push(opened.errors);
|
|
await page.evaluateOnNewDocument(() => {
|
|
if (localStorage.length || sessionStorage.length)
|
|
throw new Error('Cold-start storage is not empty');
|
|
});
|
|
await fixture.install(page);
|
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
return opened;
|
|
};
|
|
try {
|
|
const url = new URL(base.origin);
|
|
url.search = `?engine=${from}&welcome=0&look=cinematic`;
|
|
const [lat, lon, alt] = view;
|
|
url.hash = `v=2&lat=${lat}&lon=${group === 'street' ? fixture.streetLongitude : lon}&alt=${alt}&heading=0&pitch=-90&roll=0&map=osm&l=`;
|
|
await open(url.href);
|
|
await waitForEngine(page, from, { timeout: 60000 });
|
|
await prepare(page, sample.owners);
|
|
sample.before = await probeSnapshot(page, layers);
|
|
await strict.switchTo(page, target);
|
|
await settle(page);
|
|
sample.switched = await probeSnapshot(page, layers);
|
|
sample.url = await page.evaluate(() => {
|
|
window.__godsEyeView.styleManager.shareLinkManager.flush();
|
|
return location.href;
|
|
});
|
|
const retention = compareColdStart(sample.before, sample.switched, {
|
|
direction,
|
|
owners: sample.owners,
|
|
sessionState: false,
|
|
});
|
|
sample.retention = retention.differences.filter((row) =>
|
|
sessionExclusion(row, sample.before),
|
|
);
|
|
evidence.differences.push(
|
|
...sample.retention.map((row) => ({
|
|
...row,
|
|
family: `handoff:${row.family}`,
|
|
})),
|
|
);
|
|
await close();
|
|
sample.coldUrl = coldStartUrl(sample.url, target);
|
|
await open(sample.coldUrl);
|
|
await waitForEngine(page, target, { timeout: 60000 });
|
|
await settle(page, true);
|
|
sample.cold = await probeSnapshot(page, layers);
|
|
sample.comparison = compareColdStart(sample.switched, sample.cold, {
|
|
direction,
|
|
owners: sample.owners,
|
|
allow,
|
|
});
|
|
evidence.differences.push(...sample.comparison.differences);
|
|
evidence.skipped.push(...sample.comparison.skipped);
|
|
for (const id of layers) {
|
|
if (
|
|
![...sample.retention, ...sample.comparison.differences].some(
|
|
(row) => row.layer === id || row.layer === '[page]',
|
|
)
|
|
)
|
|
strict.record(
|
|
id,
|
|
target,
|
|
'engine switch equals a fresh share-link restore; declared session fields survive the switch',
|
|
);
|
|
}
|
|
for (const error of sample.errors.flat())
|
|
evidence.differences.push({
|
|
layer: '[page]',
|
|
direction,
|
|
family: 'error',
|
|
id: '',
|
|
field: 'pageerror',
|
|
switchValue: error,
|
|
coldStartValue: null,
|
|
});
|
|
} catch (error) {
|
|
sample.error = String(error.stack ?? error);
|
|
sample.failureSnapshot = await probeSnapshot(page, layers).catch(
|
|
() => null,
|
|
);
|
|
evidence.differences.push({
|
|
layer: layers.join(', '),
|
|
direction,
|
|
family: 'readiness',
|
|
id: '',
|
|
field: 'error',
|
|
switchValue: error.message,
|
|
coldStartValue: '<not captured>',
|
|
});
|
|
} finally {
|
|
await close();
|
|
}
|
|
}
|
|
console.log(
|
|
`switch-equals-cold-start ${group}: ${evidence.differences.length} differences, ${evidence.skipped.length} declared session differences`,
|
|
);
|
|
if (evidence.differences.length)
|
|
console.table(
|
|
evidence.differences.map((row) => ({
|
|
...row,
|
|
switchValue: JSON.stringify(row.switchValue),
|
|
coldStartValue: JSON.stringify(row.coldStartValue),
|
|
})),
|
|
);
|
|
assert.equal(
|
|
evidence.differences.length,
|
|
0,
|
|
'See coldStart.differences in the evidence file',
|
|
);
|
|
console.log(
|
|
`PASS switch-equals-cold-start ${group}: 2 directions, ${layers.length} layers, 0 differences`,
|
|
);
|
|
});
|