test: qualify Transit visibility and city feed ownership

This commit is contained in:
Said committed 2026-10-20 14:07:13 +01:00
1 parent eabff9005d
commit e4955f351f
5 files changed
+54 -7

No files matched your search

+2
View File
@@ -1 +1,3 @@
- Pause periodic network updates in hidden tabs (asturksever, #1282). - Pause periodic network updates in hidden tabs (asturksever, #1282).
- Report and guard browser visibility during Transit QA so hidden-page polling pauses cannot masquerade as playback regressions (asturksever, #1282).
- Require each live city to supply its own feed vehicles in Transit QA; retained vehicles cannot qualify another city (asturksever, #1282).
+5
View File
@@ -47,6 +47,7 @@ export async function reportTransitVisibility(page, label) {
const layer = app.dataManager.layers.get('transit').module; const layer = app.dataManager.layers.get('transit').module;
const state = layer._transitStateForTest(); const state = layer._transitStateForTest();
return { return {
pageVisibility: document.visibilityState,
total: state._vehicles.size, total: state._vehicles.size,
visible: state._visible.size, visible: state._visible.size,
moving: state._moving.size, moving: state._moving.size,
@@ -59,6 +60,10 @@ export async function reportTransitVisibility(page, label) {
}; };
}); });
console.log(` ${label}: ${JSON.stringify(result)}`); console.log(` ${label}: ${JSON.stringify(result)}`);
if (result.pageVisibility !== 'visible')
throw new Error(
`${label}: Transit QA requires a visible page; hidden pages deliberately pause polling`,
);
if (result.renderLoop === false || result.renderErrors.length) if (result.renderLoop === false || result.renderErrors.length)
throw new Error( throw new Error(
`${label}: Cesium render loop stopped or raised an error: ${JSON.stringify(result.renderErrors)}`, `${label}: Cesium render loop stopped or raised an error: ${JSON.stringify(result.renderErrors)}`,
+5
View File
@@ -1,6 +1,7 @@
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export const BOSTON = { export const BOSTON = {
id: 'boston', id: 'boston',
feedId: 'mbta',
label: 'Boston (MBTA)', label: 'Boston (MBTA)',
lat: 42.3554, lat: 42.3554,
lon: -71.0605, lon: -71.0605,
@@ -11,6 +12,7 @@ const LIVE_CITIES = [
BOSTON, BOSTON,
{ {
id: 'austin', id: 'austin',
feedId: 'capmetro-austin',
label: 'Austin (CapMetro)', label: 'Austin (CapMetro)',
lat: 30.2672, lat: 30.2672,
lon: -97.7431, lon: -97.7431,
@@ -19,6 +21,7 @@ const LIVE_CITIES = [
}, },
{ {
id: 'minneapolis', id: 'minneapolis',
feedId: 'metrotransit-msp',
label: 'Minneapolis (Metro Transit)', label: 'Minneapolis (Metro Transit)',
lat: 44.9778, lat: 44.9778,
lon: -93.265, lon: -93.265,
@@ -27,6 +30,7 @@ const LIVE_CITIES = [
}, },
{ {
id: 'helsinki', id: 'helsinki',
feedId: 'hsl-helsinki',
label: 'Helsinki (HSL)', label: 'Helsinki (HSL)',
lat: 60.1699, lat: 60.1699,
lon: 24.9384, lon: 24.9384,
@@ -35,6 +39,7 @@ const LIVE_CITIES = [
}, },
{ {
id: 'oslo', id: 'oslo',
feedId: 'entur-norway',
label: 'Oslo (Entur)', label: 'Oslo (Entur)',
lat: 59.9139, lat: 59.9139,
lon: 10.7522, lon: 10.7522,
+29 -7
View File
@@ -266,6 +266,7 @@ const browser = await puppeteer.launch({
executablePath: chromeExecutablePath(), executablePath: chromeExecutablePath(),
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--disable-backgrounding-occluded-windows',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
'--window-size=1920,1080', '--window-size=1920,1080',
@@ -391,22 +392,42 @@ try {
orientation: { heading: 0, pitch: (-40 * Math.PI) / 180, roll: 0 }, orientation: { heading: 0, pitch: (-40 * Math.PI) / 180, roll: 0 },
}); });
}, city); }, city);
await page.waitForFunction( const arrived = await page
() => .waitForFunction(
(window.__godsEyeView.dataManager.layers (feedId) =>
[
...window.__godsEyeView.dataManager.layers
.get('transit') .get('transit')
.module.getStats().count || 0) > 0, .module._transitStateForTest()
._vehicles.keys(),
].some((key) => key.startsWith(`${feedId}:`)),
{ timeout: 90_000 }, { timeout: 90_000 },
city.feedId,
)
.then(
() => true,
() => false,
); );
if (!arrived) {
check(
`${city.label}: live feed arrived`,
false,
`No vehicles from ${city.feedId}; retained vehicles from other cities cannot qualify this view`,
{ unexercised: true },
);
continue;
}
await wait(9_000); await wait(9_000);
const fleet = await page.evaluate(() => { const fleet = await page.evaluate((feedId) => {
const layer = const layer =
window.__godsEyeView.dataManager.layers.get('transit').module; window.__godsEyeView.dataManager.layers.get('transit').module;
return { return {
count: layer._transitStateForTest()._vehicles.size, count: [...layer._transitStateForTest()._vehicles.keys()].filter(
(key) => key.startsWith(`${feedId}:`),
).length,
stats: layer.getStats(), stats: layer.getStats(),
}; };
}); }, city.feedId);
check( check(
`${city.label}: vehicles are rendered`, `${city.label}: vehicles are rendered`,
fleet.count >= 20, fleet.count >= 20,
@@ -428,6 +449,7 @@ try {
.module._transitStateForTest(); .module._transitStateForTest();
let best = null; let best = null;
for (const entry of state._vehicles.values()) { for (const entry of state._vehicles.values()) {
if (!entry.key.startsWith(`${c.feedId}:`)) continue;
const d = Math.hypot(entry.to.lat - c.lat, entry.to.lon - c.lon); const d = Math.hypot(entry.to.lat - c.lat, entry.to.lon - c.lon);
if (!best || d < best.d) if (!best || d < best.d)
best = { d, lat: entry.to.lat, lon: entry.to.lon, key: entry.key }; best = { d, lat: entry.to.lat, lon: entry.to.lon, key: entry.key };
+13
View File
@@ -1,6 +1,7 @@
import test from 'node:test'; import test from 'node:test';
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs'; import { readFileSync } from 'node:fs';
import { getTransitFeed } from '../data/transitFeeds.js';
import { import {
chooseLiveCities, chooseLiveCities,
selectStyle, selectStyle,
@@ -101,6 +102,18 @@ test('live visual cities follow local operating hours in summer and winter', ()
const cities = chooseLiveCities(now); const cities = chooseLiveCities(now);
assert.ok(cities.length >= 1); assert.ok(cities.length >= 1);
for (const city of cities) { for (const city of cities) {
const feed = getTransitFeed(city.feedId);
assert.ok(feed, `${city.id} has a registered feed`);
const distanceKm =
111.32 *
Math.hypot(
feed.center.lat - city.lat,
(feed.center.lon - city.lon) * Math.cos((city.lat * Math.PI) / 180),
);
assert.ok(
distanceKm < feed.loadRadiusKm,
`${city.id} is inside its feed's admission region`,
);
const local = Number( const local = Number(
new Intl.DateTimeFormat('en', { new Intl.DateTimeFormat('en', {
timeZone: city.zone, timeZone: city.zone,