Preserve radio playback and imagery comparisons when switching planets

This commit is contained in:
German Khokhlov committed 2026-11-03 04:16:00 +04:00
1 parent b98cbeebf2
commit b01f1ee7dc
9 files changed
+246 -18

No files matched your search

+70
View File
@@ -36,3 +36,73 @@ export const stations = Array.from({ length: 750 }, (_, index) => {
bitrate: 128,
};
});
export async function installRadioFixture(page) {
await page.evaluateOnNewDocument((stations) => {
localStorage.clear();
localStorage.setItem('gev:first-run-mission:v1', 'suppressed');
window.__qaRadio = {
stations,
generation: 1,
plays: [],
delay: 0,
response: 'normal',
pending: null,
};
HTMLMediaElement.prototype.play = function () {
window.qaPlayer = this;
window.__qaRadio.plays.push(this.src);
if (window.__qaRadio.delayPlay) {
window.__qaRadio.delayPlay = false;
return new Promise((resolve) => {
window.__qaRadio.releasePlay = () => {
this.dispatchEvent(new Event('playing'));
resolve();
};
});
}
queueMicrotask(() => this.dispatchEvent(new Event('playing')));
return Promise.resolve();
};
const fetch = window.fetch.bind(window);
window.fetch = async (input, init) => {
const address = String(
typeof input === 'string' || input instanceof URL ? input : input.url,
);
if (address.includes('/api/radio/stations')) {
const qa = window.__qaRadio;
qa.reads = (qa.reads || 0) + 1;
const response = {
stations: qa.response === 'empty' ? [] : qa.stations,
updatedAt: new Date().toISOString(),
stale: false,
degraded: false,
acceptedGeneration: qa.generation,
catalogInstance: 'w2-radio-fixture',
};
const mode = qa.response;
const delay = qa.delay;
qa.delay = 0;
if (delay)
await new Promise((resolve) => {
qa.pending = resolve;
});
init?.signal?.throwIfAborted();
return new Response(
mode === 'failure' ? '{}' : JSON.stringify(response),
{
status: mode === 'failure' ? 503 : 200,
headers: { 'Content-Type': 'application/json' },
},
);
}
if (address.includes('/api/radio/click/'))
return new Response(null, { status: 204 });
if (address.includes('/api/flights')) return new Response('{"states":[]}');
if (address.includes('/api/military')) return new Response('{"ac":[]}');
if (address.includes('/api/vessels'))
return new Response('{"connected":false,"vessels":[]}');
return fetch(input, init);
};
}, stations);
}
+95
View File
@@ -0,0 +1,95 @@
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]);
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');
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);
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;
});
for (const kind of ['cesium', 'navara']) {
await switchGlobe(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);
}
assert.deepEqual(errors, []);
console.log(
'PASS one live radio player, one station directory and the same selection',
);
} finally {
await browser.close();
}
+22
View File
@@ -1,3 +1,4 @@
import {switchGlobe} from './qa-switch-globe.mjs';
import fs from 'node:fs/promises';
import puppeteer from 'puppeteer';
import sharp from 'sharp';
@@ -220,6 +221,27 @@ try {
await sleep(2500);
image = await capture('ab');
check('A/B dates on opposite sides', red(image.left) && green(image.right));
if (args.includes('--switch')) {
const first = await page.evaluate(() => {
window.qaImageryOwner = window.__gevRecentImagery.layer;
return window.qaImageryOwner.diagnostics().pins;
});
const reads = () => report.requests.filter(r => r.host === 'cmr.earthdata.nasa.gov').length;
const expectedReads = reads();
const initial = await page.evaluate(() => window.__godsEyeView.engine.kind);
for (const kind of [initial === 'navara' ? 'cesium' : 'navara', initial]) {
await switchGlobe(page, kind);
const state = await page.evaluate(() => ({
same: window.__gevRecentImagery.layer === window.qaImageryOwner,
...window.qaImageryOwner.diagnostics(),
}));
check(kind+' holds both original pins',state.same && state.mode === 'ab' && state.pins.a === first.a && state.pins.b === first.b);
await sleep(1800);
const pixels=await capture('switch-'+kind);
check(kind+' renders both pinned dates',red(pixels.left) && green(pixels.right));
check(kind+' reuses the STAC search', reads() === expectedReads);
}
}
await page.evaluate(() => window.__gevRecentImagery.layer.swapSides());
image = await capture('swap');
check('swap reverses dates', green(image.left) && red(image.right));