mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Run the layer acceptance under a default-deny network policy with per-layer assertion manifests and strict switch contracts
Every scenario runs through scripts/qa-strict-harness.mjs: each page, frame and worker gets an ordered block list before it runs (paid hosts and routes first, then only the preview, declared fixtures, keyless probes and cached free tile hosts), switches with real input on the picker, and records its assertions per layer and engine. The runner reads the catalog from the live page, fails any layer without an assertion on either engine, can build and serve a keyless checkout itself, and adds rapid, hidden-tab, context-loss, failure-injection, cold-chunk and soak scenarios. The main chunk gets a 1% size budget.
This commit is contained in:
1 parent
3144169057
commit
1773494116
36 files changed
+6003
-1774
No files matched your search
@@ -1,168 +1,91 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import assert from 'node:assert/strict';
|
||||
import { stations, installRadioFixture } from './fixtures/w2/radio.mjs';
|
||||
import { blockPaidQaMaps } from './qa-map-policy.mjs';
|
||||
import { switchGlobe } from './qa-switch-globe.mjs';
|
||||
const url = new URL(process.argv[2]);
|
||||
const soak = process.argv.includes('--soak');
|
||||
const requested = process.argv.find((arg) => arg.startsWith('--swaps='));
|
||||
const swaps = requested ? Number(requested.slice(8)) : soak ? 50 : 2;
|
||||
if (requested && (!soak || !Number.isInteger(swaps) || swaps % 2 || swaps < 4 || swaps > 1000))
|
||||
throw Error('Use --soak --swaps=N with an even count from 4 to 1000');
|
||||
|
||||
if (
|
||||
!['localhost', '127.0.0.1'].includes(url.hostname) ||
|
||||
['', '4173', '4185', '4186', '4187', '4188', '4189', '4194'].includes(
|
||||
url.port,
|
||||
)
|
||||
)
|
||||
throw Error('Local keyless preview required');
|
||||
import { createStrict, localPreview } from './qa-strict-harness.mjs';
|
||||
const url = localPreview(process.argv[2]);
|
||||
// The long soak and switch timings live in qa-strict-soak.mjs.
|
||||
url.search = '?engine=navara&welcome=0';
|
||||
url.hash = 'v=2&lat=30.27&lon=-97.74&alt=12000&pitch=-90&map=sentinel2&l=';
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--use-angle=metal', '--enable-gpu'],
|
||||
});
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
await blockPaidQaMaps(page);
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => errors.push(e.message));
|
||||
await installRadioFixture(page);
|
||||
if (soak) await page.evaluateOnNewDocument(() => {
|
||||
const fetch = window.fetch.bind(window);
|
||||
window.qaQuakeReads = 0;
|
||||
window.fetch = (input, init) => {
|
||||
const url = new URL(input?.url ?? input, location.href);
|
||||
if (url.hostname !== 'earthquake.usgs.gov') return fetch(input, init);
|
||||
window.qaQuakeReads++;
|
||||
return Promise.resolve(Response.json({
|
||||
type: 'FeatureCollection',
|
||||
features: [{ type: 'Feature', id: 'qa-austin', properties: { mag: 4.1, time: Date.now(), place: 'Austin' },
|
||||
geometry: { type: 'Point', coordinates: [-97.74, 30.27, 8] } }],
|
||||
}));
|
||||
};
|
||||
});
|
||||
await page.goto(url.href);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__godsEyeView?.engine?.kind === 'navara' &&
|
||||
document.querySelector('#loading-screen.hidden'),
|
||||
);
|
||||
await page.evaluate(async (id) => {
|
||||
const { dataManager, styleManager, tileset } = window.__godsEyeView;
|
||||
await styleManager.initialRestorePromise;
|
||||
if (window.__GOOGLE_MAPS_API_KEY__ || tileset)
|
||||
throw Error('Expected keyless preview');
|
||||
window.qaLayer = dataManager.layers.get('radio').module;
|
||||
await dataManager.setEnabled('radio', true, { origin: 'user' });
|
||||
}, stations[0].id);
|
||||
await page.waitForFunction(() => window.qaLayer?.getStats().count === 750);
|
||||
await page.evaluate((id) => {
|
||||
window.qaLayer.selectStation(id, {
|
||||
autoplay: true,
|
||||
focus: false,
|
||||
origin: 'user',
|
||||
});
|
||||
window.qaLayer.setRadioVolume(0.36);
|
||||
}, stations[10].id);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__qaRadio.plays.length === 1 &&
|
||||
window.qaLayer.getUIState().audioState === 'playing',
|
||||
);
|
||||
const first = await page.evaluate(() => {
|
||||
window.qaAudio = window.qaPlayer;
|
||||
window.qaSnapshot = window.qaLayer.getAcceptedCatalogSnapshot();
|
||||
return window.__qaRadio.reads;
|
||||
});
|
||||
const client = soak ? await page.createCDPSession() : null;
|
||||
if (soak) await page.evaluate(async () => {
|
||||
const { dataManager, engine } = window.__godsEyeView;
|
||||
await dataManager.setEnabled('earthquakes', true, { origin: 'user' });
|
||||
window.qaModels = new Map([...dataManager.layers].map(([id, entry]) => [id, entry.module]));
|
||||
window.qaEngines = new Map([[engine.kind, engine]]);
|
||||
});
|
||||
const measurements = [], memory = [];
|
||||
let contextListeners;
|
||||
for (let i = 1; i <= swaps; i++) {
|
||||
const kind = i % 2 ? 'cesium' : 'navara';
|
||||
const start = await page.evaluate(() => performance.now());
|
||||
await switchGlobe(page, kind);
|
||||
const elapsed = await page.evaluate((start) => performance.now() - start, start);
|
||||
measurements.push({ kind, ms: Math.round(elapsed) });
|
||||
if (soak) {
|
||||
assert.ok(await page.evaluate(() => {
|
||||
const { dataManager, engine } = window.__godsEyeView;
|
||||
const previous = window.qaEngines.get(engine.kind);
|
||||
window.qaEngines.set(engine.kind, engine);
|
||||
return (!previous || previous === engine) && window.qaEngines.size === 2 &&
|
||||
[...window.qaModels].every(([id, model]) => dataManager.layers.get(id)?.module === model) &&
|
||||
window.qaModels.size === 33 && window.qaQuakeReads >= 1 &&
|
||||
dataManager.getEnabledLayerIds().has('earthquakes');
|
||||
}));
|
||||
if ([2, 10, 50, 100, 250, swaps].includes(i)) {
|
||||
const { result } = await client.send('Runtime.evaluate', {
|
||||
includeCommandLineAPI: true,
|
||||
returnByValue: true,
|
||||
expression: `(() => {
|
||||
const events = getEventListeners(window.__godsEyeView.engine.host.canvas);
|
||||
return [events.webglcontextlost.length, events.webglcontextrestored.length];
|
||||
})()`,
|
||||
});
|
||||
assert.ok(
|
||||
Array.isArray(result.value) && result.value.length === 2 &&
|
||||
result.value.every((count) => Number.isInteger(count) && count > 0),
|
||||
'the active canvas must expose both WebGL event subscriptions',
|
||||
);
|
||||
contextListeners ??= result.value;
|
||||
assert.deepEqual(result.value, contextListeners, 'warm Navara must release old context listeners');
|
||||
await client.send('HeapProfiler.collectGarbage');
|
||||
const heap = await client.send('Runtime.getHeapUsage');
|
||||
const dom = await client.send('Memory.getDOMCounters');
|
||||
memory.push({ swap: i, contextListeners, jsMiB: Math.round(heap.usedSize / 1048576), ...dom });
|
||||
}
|
||||
}
|
||||
const state = await page.evaluate(() => ({
|
||||
same:
|
||||
window.qaLayer ===
|
||||
window.__godsEyeView.dataManager.layers.get('radio').module,
|
||||
player: window.qaAudio === window.qaPlayer,
|
||||
snapshot:
|
||||
window.qaSnapshot === window.qaLayer.getAcceptedCatalogSnapshot(),
|
||||
plays: window.__qaRadio.plays.length,
|
||||
reads: window.__qaRadio.reads,
|
||||
state: window.qaLayer.getUIState(),
|
||||
count: window.qaLayer.getStats().count,
|
||||
}));
|
||||
console.log(
|
||||
kind,
|
||||
state.count,
|
||||
state.state.selected?.id,
|
||||
state.state.audioState,
|
||||
state.plays,
|
||||
state.reads,
|
||||
const strict = createStrict({ origin: url.origin, layers: ['radio'] });
|
||||
await strict.run(
|
||||
{ args: ['--use-angle=metal', '--enable-gpu'] },
|
||||
async (browser) => {
|
||||
const page = await browser.newPage();
|
||||
const errors = [];
|
||||
page.on('pageerror', (e) => errors.push(e.message));
|
||||
await installRadioFixture(page);
|
||||
await page.goto(url.href);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__godsEyeView?.engine?.kind === 'navara' &&
|
||||
document.querySelector('#loading-screen.hidden'),
|
||||
);
|
||||
assert.ok(state.same && state.player && state.snapshot);
|
||||
assert.equal(state.plays, 1);
|
||||
assert.equal(state.reads, first);
|
||||
assert.equal(state.count, 750);
|
||||
assert.equal(state.state.audioState, 'playing');
|
||||
assert.equal(state.state.selected.id, stations[10].id);
|
||||
assert.equal(state.state.volume, 0.36);
|
||||
}
|
||||
if (soak) {
|
||||
const warm = {};
|
||||
for (const kind of ['navara', 'cesium']) {
|
||||
const ms = measurements.slice(2).filter(x => x.kind === kind).map(x => x.ms).sort((a, b) => a - b);
|
||||
warm[kind] = { p50: ms[Math.ceil(ms.length * 0.50) - 1], p95: ms[Math.ceil(ms.length * 0.95) - 1] };
|
||||
await page.evaluate(async (id) => {
|
||||
const { dataManager, styleManager, tileset } = window.__godsEyeView;
|
||||
await styleManager.initialRestorePromise;
|
||||
if (window.__GOOGLE_MAPS_API_KEY__ || tileset)
|
||||
throw Error('Expected keyless preview');
|
||||
window.qaLayer = dataManager.layers.get('radio').module;
|
||||
await dataManager.setEnabled('radio', true, { origin: 'user' });
|
||||
}, stations[0].id);
|
||||
await page.waitForFunction(() => window.qaLayer?.getStats().count === 750);
|
||||
await page.evaluate((id) => {
|
||||
window.qaLayer.selectStation(id, {
|
||||
autoplay: true,
|
||||
focus: false,
|
||||
origin: 'user',
|
||||
});
|
||||
window.qaLayer.setRadioVolume(0.36);
|
||||
}, stations[10].id);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
window.__qaRadio.plays.length === 1 &&
|
||||
window.qaLayer.getUIState().audioState === 'playing',
|
||||
);
|
||||
const first = await page.evaluate(() => {
|
||||
window.qaAudio = window.qaPlayer;
|
||||
window.qaSnapshot = window.qaLayer.getAcceptedCatalogSnapshot();
|
||||
return window.__qaRadio.reads;
|
||||
});
|
||||
for (const kind of ['cesium', 'navara']) {
|
||||
await strict.switchTo(page, kind);
|
||||
const state = await page.evaluate(() => ({
|
||||
same:
|
||||
window.qaLayer ===
|
||||
window.__godsEyeView.dataManager.layers.get('radio').module,
|
||||
player: window.qaAudio === window.qaPlayer,
|
||||
snapshot:
|
||||
window.qaSnapshot === window.qaLayer.getAcceptedCatalogSnapshot(),
|
||||
plays: window.__qaRadio.plays.length,
|
||||
reads: window.__qaRadio.reads,
|
||||
state: window.qaLayer.getUIState(),
|
||||
count: window.qaLayer.getStats().count,
|
||||
}));
|
||||
console.log(
|
||||
kind,
|
||||
state.count,
|
||||
state.state.selected?.id,
|
||||
state.state.audioState,
|
||||
state.plays,
|
||||
state.reads,
|
||||
);
|
||||
assert.ok(state.same && state.player && state.snapshot);
|
||||
assert.equal(state.plays, 1);
|
||||
assert.equal(state.reads, first);
|
||||
assert.equal(state.count, 750);
|
||||
assert.equal(state.state.audioState, 'playing');
|
||||
assert.equal(state.state.selected.id, stations[10].id);
|
||||
assert.equal(state.state.volume, 0.36);
|
||||
// The fixture's HTMLMediaElement.prototype.play counts play() calls: the
|
||||
// switch must not start the stream again.
|
||||
await strict.passed(
|
||||
page,
|
||||
'radio',
|
||||
'same model, player element and catalog snapshot; 750 stations, one catalog read, no second play(), same station playing at volume 0.36',
|
||||
);
|
||||
}
|
||||
console.log('SOAK', JSON.stringify({ swaps, first: measurements.slice(0, 2), warm, memory }));
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS one live radio player, one station directory and the same selection',
|
||||
);
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(
|
||||
'PASS one live radio player, one station directory and the same selection',
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user