Test layers on one engine and share selection and shader helpers

Layer display tests no longer re-check Cesium primitives in every layer:
they run on the fake engine, and one Cesium translation check per ground
kind lives in the Cesium engine's ground tests. Their harnesses are shared,
and the fake-engine, Cesium-engine and flights tests that repeated the
contract suite are gone, 340 tests in all.

The press-travel measurement is shared by the selection dispatcher and
CCTV, which loses its ScreenSpaceEventHandler shim and world-click module.
ALPR, installations and bikeshare share one selection keeper and selected
marker write. Layers with their own click rules keep their handlers.

Navara's pulse is a class like the ground pass, the ground and imagery
passes share the surface uniforms, and the colour, log-depth and occlusion
GLSL lives in one chunk module, each caller keeping its depth bias. The
ground suite runs inside the contract suite (--only=ground:), and fixture
and script names say what they test.

Behaviour is unchanged on both engines.
This commit is contained in:
German Khokhlov committed 2026-11-08 17:06:10 +04:00
1 parent 7f478e3815
commit b4556f9763
101 files changed
+1095 -4071

No files matched your search

+3 -3
View File
@@ -7,10 +7,10 @@
"navaraKindChecks": 1,
"engineSniffCalls": 0,
"engineExtensionReads": 3,
"groundEffectPasses": 1,
"groundEffectPasses": 0,
"cesiumSdkLoads": 3,
"screenSpaceHandlerFiles": 4,
"screenSpaceHandlerFiles": 2,
"engineNameLines": 27
},
"measuredAt": "Drop leftover viewer paths"
"measuredAt": "One selection path and test level"
}
@@ -13,7 +13,7 @@ export const cameras = [
].map((position, i) => ({
...position,
osmId: 9000000 + i,
operator: i % 3 ? 'W2 fixture' : 'Cámara pública',
operator: i % 3 ? 'Deterministic fixture' : 'Cámara pública',
brand: 'Fixture camera',
cameraType: 'fixed',
direction: i % 5 ? String((i * 37) % 360) : '',
@@ -2,8 +2,8 @@ import sharp from 'sharp';
// Transport fixtures are parsed by the real CCTV catalog and media owners.
export const cameras = Array.from({ length: 25 }, (_, i) => ({
id: `w2-cctv-${String(i).padStart(3, '0')}`,
name: i === 12 ? 'Cámara pública — Congress' : `W2 camera ${i}`,
id: `fixture-cctv-${String(i).padStart(3, '0')}`,
name: i === 12 ? 'Cámara pública — Congress' : `Fixture camera ${i}`,
cityId: 'austin',
city: 'Austin',
lat: 30.2672 + (Math.floor(i / 5) - 2) * 0.0018,
@@ -17,7 +17,7 @@ export const cameras = Array.from({ length: 25 }, (_, i) => ({
headingConfidence: i % 3 ? 'medium' : 'low',
sourceKind: 'configured',
feedType: 'image',
provider: 'W2 deterministic fixture',
provider: 'Deterministic fixture',
url: 'https://cctv-fixture.invalid/frame.png',
}));
@@ -51,8 +51,8 @@ export async function installCctvFixture(page, { video } = {}) {
if (video)
catalog.push({
...cameras[0],
id: 'w2-cctv-video',
name: 'W2 moving video',
id: 'fixture-cctv-video',
name: 'Fixture moving video',
lat: 30.2718,
lon: -97.744,
feedType: 'video',
@@ -116,7 +116,7 @@ export async function installCctvFixture(page, { video } = {}) {
id,
status: 'online',
sourceKind: feedType,
label: 'W2 deterministic fixture',
label: 'Deterministic fixture',
updatedAt: Date.now(),
})),
});
@@ -126,7 +126,7 @@ export async function installCctvFixture(page, { video } = {}) {
const id = decodeURIComponent(media[2]);
if (
!ids.has(id) ||
(media[1] === 'media' && (id !== 'w2-cctv-video' || !video))
(media[1] === 'media' && (id !== 'fixture-cctv-video' || !video))
) {
return request.respond({ status: 404, body: 'Unknown fixture camera' });
}
@@ -4,8 +4,8 @@ const grid = (i, cols, spacing) => ({
lon: ORIGIN.lon + ((i % cols) - cols / 2) * spacing,
});
const bikes = Array.from({ length: 200 }, (_, i) => ({
station_id: `w2-bike-${i}`,
name: `W2 station ${i}`,
station_id: `fixture-bike-${i}`,
name: `Fixture station ${i}`,
capacity: 20,
...grid(i, 15, 0.0015),
}));
File renamed without changes.
@@ -78,7 +78,7 @@ export async function installRadioFixture(page) {
stale: false,
degraded: false,
acceptedGeneration: qa.generation,
catalogInstance: 'w2-radio-fixture',
catalogInstance: 'fixture-radio-fixture',
};
const mode = qa.response;
const delay = qa.delay;
@@ -6,7 +6,7 @@ export function transitSnapshot(step = 8) {
fetchedAt,
vehicles: [
...Array.from({ length: 12 }, (_, i) => ({
id: `w2-${i}`,
id: `fixture-${i}`,
routeId: routes[i % 6],
tripId: `trip-${i}`,
lat:
+8 -9
View File
@@ -1212,6 +1212,7 @@
"src/testSupport/cockpitEngine.mjs",
"src/layers/aircraft/render/contract.js",
"src/layers/shared/color.test.mjs",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/shared/viewRectangle.js",
"src/layers/military/render/display.js",
@@ -1222,7 +1223,6 @@
"src/layers/vessels/renderer.test.mjs",
"src/layers/vessels/testFixtures.mjs",
"src/layers/shared/render/engineView.js",
"src/globe/navara/ground/effect.js",
"src/layers/shared/render/moverBank.js",
"src/layers/shared/render/imageAtlas.js",
"src/layers/bikeshare/presentation.js",
@@ -1233,8 +1233,8 @@
"src/layers/cctv/lifecycle.test.mjs",
"src/layers/cctv/projectionMedia.test.mjs",
"src/layers/cctv/ingestion.test.mjs",
"scripts/fixtures/w2/alpr.mjs",
"scripts/fixtures/w2/cctv.mjs",
"scripts/fixtures/layers/alpr.mjs",
"scripts/fixtures/layers/cctv.mjs",
"src/layers/radio/screenClusters.js",
"src/layers/radio/screenClusters.test.mjs",
"src/globe/cesium/mapScene.js",
@@ -1258,7 +1258,7 @@
"src/globe/cesium/testing/cameraViewer.mjs",
"src/app/globeEngine.js",
"src/app/globeEngine.test.mjs",
"scripts/fixtures/w2/transit.mjs",
"scripts/fixtures/layers/transit.mjs",
"src/layers/localAdsb/render/display.js",
"src/layers/localAdsb/renderer.test.mjs",
"build/navaraAssets.js",
@@ -1381,16 +1381,16 @@
"src/globe/navara/movers/frameCamera.test.mjs",
"src/layers/localAdsb/qaDrawn.js",
"src/layers/localAdsb/qaDrawn.test.mjs",
"src/tooling/w1RuntimeEvidence.test.mjs",
"src/tooling/browserRuntimeEvidence.test.mjs",
"src/layers/firms/rectangle.js",
"src/layers/firms/rectangle.test.mjs",
"src/layers/firms/viewport.test.mjs",
"scripts/fixtures/w2/firms.mjs",
"scripts/fixtures/layers/firms.mjs",
"src/layers/firms/lifecycle.test.mjs",
"scripts/fixtures/w2/radio.mjs",
"scripts/fixtures/layers/radio.mjs",
"scripts/qa-map-policy.mjs",
"scripts/qa-map-policy.test.mjs",
"scripts/qa-w2-imagery.mjs",
"scripts/qa-imagery-contract.mjs",
"src/globe/navara/postSamplingPass.test.mjs",
"src/globe/navara/emptySceneRender.test.mjs",
"src/data/firmsIntensity.js",
@@ -1579,7 +1579,6 @@
"src/globe/navara/ground/pack.js",
"src/globe/navara/ground/pack.test.mjs",
"src/globe/navara/ground/pass.js",
"scripts/qa-ground-suite.mjs",
"docs/GLOBE-PRIMITIVES.md",
"src/layers/airQuality/render/display.js",
"src/layers/airQuality/render/display.test.mjs",
+36 -20
View File
@@ -32,6 +32,7 @@
"src/geo/",
"src/layers/infrastructure/index.js",
"src/layers/infrastructure/render/display.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/sources/requestDeadline.js",
"src/overlays/bindOverlayHost.js"
@@ -1423,8 +1424,6 @@
"src/data/routePlausible.js",
"src/geoDistance.js",
"src/data/trackedModelRegime.js",
"src/data/trackingClickGesture.js",
"src/data/trackingGesture.js",
"src/layers/flights/controller.js",
"src/layers/flights/enrichment.js",
"src/layers/flights/evidence.js",
@@ -1465,9 +1464,11 @@
"src/geo/",
"src/time/clock.js",
"src/globe/contract.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/aircraft/qaDrawn.js",
"src/overlays/bindOverlayHost.js"
"src/overlays/bindOverlayHost.js",
"src/data/trackingGesture.js"
],
"external": ["cesium"]
},
@@ -1495,8 +1496,6 @@
"src/data/motionModel.js",
"src/data/renderAltitude.js",
"src/data/trackedModelRegime.js",
"src/data/trackingClickGesture.js",
"src/data/trackingGesture.js",
"src/layers/military/controller.js",
"src/layers/military/index.js",
"src/layers/military/ingestion.js",
@@ -1530,18 +1529,21 @@
"src/i18n/locales/zh-Hant.json",
"src/globe/cesium/sceneProjection.js",
"src/globe/cesium/screenEvents.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/time/clock.js",
"src/globe/contract.js",
"src/layers/aircraft/qaDrawn.js",
"src/overlays/bindOverlayHost.js"
"src/overlays/bindOverlayHost.js",
"src/data/trackingGesture.js"
],
"external": ["cesium"]
},
"aircraft-mechanics": {
"exports": ["./aircraft"],
"modules": [
"src/data/trackingGesture.js",
"src/i18n/marker.js",
"src/data/aircraftClass.js",
"src/data/aircraftIcons.js",
@@ -1563,8 +1565,6 @@
"src/data/routePlausible.js",
"src/geoDistance.js",
"src/data/trackedModelRegime.js",
"src/data/trackingClickGesture.js",
"src/data/trackingGesture.js",
"src/layers/aircraft/classification.js",
"src/layers/aircraft/index.js",
"src/data/inputOwnership.js",
@@ -1614,6 +1614,7 @@
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/vessels/icons.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/globe/cesium/sceneProjection.js",
"src/geo/",
@@ -1630,6 +1631,7 @@
"src/layers/earthquakes/source.js",
"src/layers/earthquakes/records.js",
"src/geo/",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/earthquakes/render/display.js",
"src/overlays/bindOverlayHost.js"
@@ -1700,6 +1702,7 @@
"src/i18n/locales/zh-Hant.json",
"src/geo/",
"src/layers/firms/rectangle.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/overlays/bindOverlayHost.js"
],
@@ -1743,6 +1746,7 @@
"src/ui/unitLabels.js",
"src/geo/",
"src/time/clock.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/overlays/bindOverlayHost.js"
],
@@ -1785,6 +1789,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/time/clock.js",
@@ -1837,6 +1842,7 @@
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/installations/classLabel.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/globe/contract.js"
@@ -1900,6 +1906,7 @@
"traffic-layer": {
"exports": ["./layers/traffic"],
"modules": [
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/data/cameraSensitivity.js",
"src/data/flowMatch.js",
@@ -1992,6 +1999,7 @@
"src/i18n/locales/zh-Hant.json",
"src/layers/bikeshare/selectionModel.js",
"src/layers/bikeshare/presentation.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/globe/contract.js",
@@ -2051,6 +2059,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/overlays/bindOverlayHost.js"
@@ -2128,6 +2137,7 @@
"src/i18n/locales/zh-Hant.json",
"src/layers/streetLevel/providers/mapillary/policy.js",
"src/geo/",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/globe/cameraMath.js",
"src/layers/streetLevel/render/display.js",
@@ -2246,6 +2256,7 @@
"src/i18n/locales/zh-Hant.json",
"src/geo/",
"src/sources/requestDeadline.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/submarineCables/render/display.js",
"src/overlays/bindOverlayHost.js"
@@ -2610,7 +2621,6 @@
"src/data/neighborhoodPolygons.js",
"src/data/pickRegistry.js",
"src/data/sceneServices.js",
"src/data/trackingGesture.js",
"src/data/radio.js",
"src/data/radioCountry.js",
"src/data/regionalBrief.js",
@@ -2633,7 +2643,6 @@
"src/data/trackedCamera.js",
"src/data/trackedModelRegime.js",
"src/data/trackedReadout.js",
"src/data/trackingClickGesture.js",
"src/data/traffic.js",
"src/data/trafficBounds.js",
"src/data/trafficFlowStyle.js",
@@ -3496,6 +3505,7 @@
"src/layers/flights/render/display.js",
"src/layers/military/render/display.js",
"src/data/selectionDispatcher.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/aircraft/qaDrawn.js",
"src/layers/localAdsb/render/display.js",
@@ -3503,6 +3513,7 @@
"src/globe/cesium/celestial.js",
"src/celestialRing/view.js",
"src/globe/navara/ground/pulse.js",
"src/globe/navara/shaderChunks.js",
"src/globe/navara/sceneDepth.js",
"src/layers/bhoteKoshi/render/story.js",
"src/globe/navara/windFlowMesh.js",
@@ -3513,7 +3524,6 @@
"src/layers/radio/screenClusters.js",
"src/layers/shared/render/imageAtlas.js",
"src/globe/navara/ground/surface.js",
"src/globe/navara/ground/effect.js",
"src/annotations/outlineRungs.js",
"src/sources/openFreeMapOutlines.js",
"src/sources/nominatimOutlines.js",
@@ -3581,7 +3591,6 @@
"src/globe/navara/ground/index.js",
"src/layers/cctv/render/meshes.js",
"src/layers/cctv/render/display.js",
"src/layers/cctv/worldClick.js",
"src/layers/transit/render/display.js",
"src/layers/earthquakes/render/display.js",
"src/layers/airQuality/render/display.js",
@@ -3625,7 +3634,8 @@
"src/globe/exclusivePointer.js",
"src/globe/cesium/followOffset.js",
"src/overlays/bindOverlayHost.js",
"src/ui/releaseTrackers.js"
"src/ui/releaseTrackers.js",
"src/data/trackingGesture.js"
],
"external": [
"hls.js",
@@ -3764,7 +3774,6 @@
"src/data/neighborhoodPolygons.js",
"src/data/pickRegistry.js",
"src/data/sceneServices.js",
"src/data/trackingGesture.js",
"src/data/radioCountry.js",
"src/data/renderAltitude.js",
"src/data/bundledJson.js",
@@ -3778,7 +3787,6 @@
"src/data/trackedCamera.js",
"src/data/trackedModelRegime.js",
"src/data/trackedReadout.js",
"src/data/trackingClickGesture.js",
"src/data/trafficBounds.js",
"src/data/trafficFlowStyle.js",
"src/data/trafficPresetStyle.js",
@@ -4275,6 +4283,7 @@
"src/app/layers/displays.js",
"src/layers/aircraft/render/display.js",
"src/data/selectionDispatcher.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/flights/render/display.js",
"src/layers/military/render/display.js",
@@ -4336,7 +4345,6 @@
"src/layers/cctv/render/style.js",
"src/layers/cctv/render/meshes.js",
"src/layers/cctv/render/display.js",
"src/layers/cctv/worldClick.js",
"src/layers/transit/render/display.js",
"src/layers/earthquakes/render/display.js",
"src/layers/airQuality/render/display.js",
@@ -4368,7 +4376,6 @@
"src/layers/bhoteKoshi/render/story.js",
"src/globe/navara/ground/surface.js",
"src/globe/navara/windFlowMesh.js",
"src/globe/navara/ground/effect.js",
"src/globe/navara/ground/pulse.js",
"src/globe/cesium/windFlow.js",
"src/globe/cesium/postStages.js",
@@ -4386,7 +4393,8 @@
"src/globe/meshes.js",
"src/globe/exclusivePointer.js",
"src/globe/cesium/followOffset.js",
"src/overlays/bindOverlayHost.js"
"src/overlays/bindOverlayHost.js",
"src/data/trackingGesture.js"
],
"external": [
"hls.js",
@@ -4730,6 +4738,7 @@
"src/data/distanceFormat.js",
"src/i18n/marker.js",
"src/layers/directions/index.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/data/pointerOwnerNames.js",
"src/layers/directions/source.js",
@@ -5051,7 +5060,6 @@
"src/data/meshFloorSampler.js",
"src/data/pickRegistry.js",
"src/data/sceneServices.js",
"src/data/trackingGesture.js",
"src/data/scenePick.js",
"src/data/spriteOrder.js",
"src/data/terrainHeights.js",
@@ -5132,6 +5140,7 @@
"src/celestialRing/view.js",
"src/layers/shared/render/engineView.js",
"src/globe/cesium/sceneProjection.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/time/clock.js",
@@ -5156,7 +5165,8 @@
"src/data/selectionDispatcher.js",
"src/overlays/bindOverlayHost.js",
"src/data/contextStore.js",
"src/data/trackedReadout.js"
"src/data/trackedReadout.js",
"src/data/trackingGesture.js"
],
"external": ["cesium", "egm96-universal", "pbf"]
},
@@ -5208,6 +5218,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/",
"src/globe/contract.js",
@@ -5276,6 +5287,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/perimeters/render/display.js",
"src/overlays/bindOverlayHost.js"
@@ -5306,6 +5318,7 @@
"src/ui/statusText.js",
"src/ui/layerNames.js",
"src/layers/shared/viewRectangle.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/geo/math.js",
"src/geo/cartesian2.js",
@@ -5439,6 +5452,7 @@
"src/i18n/locales/uk.json",
"src/i18n/locales/zh-CN.json",
"src/i18n/locales/zh-Hant.json",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/droneAirspace/render/display.js",
"src/overlays/bindOverlayHost.js"
@@ -5645,6 +5659,7 @@
"src/i18n/locales/zh-Hant.json",
"src/ui/statusText.js",
"src/ui/layerNames.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/layers/airQuality/render/display.js",
"src/overlays/bindOverlayHost.js"
@@ -5725,6 +5740,7 @@
"src/globe/cesium/credits.js",
"src/layers/wildlifeSightings/display.js",
"src/data/spriteOrder.js",
"src/layers/shared/selectionKeeper.js",
"src/layers/shared/color.js",
"src/overlays/bindOverlayHost.js"
],
@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* qa-heading-b3.mjs — throwaway QA harness for skylight Task 4
* qa-aircraft-heading.mjs — aircraft display-heading checks
* (path-derived, rate-limited display course; Batch 3 overnight run).
*
* The permanent track-regression harness flies planes STRAIGHT, so it cannot
@@ -45,7 +45,7 @@
* normalized nose -X, offset 180°).
*
* VISUAL — with the turning flights plane tracked in 3D, capture screenshots
* (two zoom levels × two orbit angles) into qa-shots/b3/ for human review:
* (two zoom levels × two orbit angles) into qa-shots/aircraft-heading/ for human review:
* the nose must lead the curved trail, never sideways/backwards.
*
* LOW-SPEED (heading-v2, 2026-07-03 field-test regression) — two extra
@@ -93,7 +93,7 @@
* exceeds 15°/s (truth is 2°/s; the pre-fix snap replayed ~120° of frozen
* divergence at the 60°/s slew cap).
*
* Run: node scripts/qa-heading-b3.mjs --url http://localhost:4300
* Run: node scripts/qa-aircraft-heading.mjs --url http://localhost:4300
* Exits non-zero if any assertion fails. Never commits anything.
*/
@@ -120,7 +120,7 @@ if (requestedEngine) {
}
const APP_URL = appUrl.href;
const HEADFUL = getFlag('--headful');
const SHOT_DIR = path.resolve('qa-shots/b3');
const SHOT_DIR = path.resolve('qa-shots/aircraft-heading');
const SHOT_DIR_V2 = path.resolve('qa-shots/heading-v2');
const CHROME_EXECUTABLE_CANDIDATES = [
@@ -774,7 +774,7 @@ async function main() {
}
// ---- Screenshots: two zoom levels × two orbit angles --------------------
console.log('\n Screenshots (turning plane tracked in 3D) → qa-shots/b3/');
console.log('\n Screenshots (turning plane tracked in 3D) → qa-shots/aircraft-heading/');
await page.evaluate(() => {
window.__godsEyeView.styleManager._setDetectionMode('DENSE');
});
@@ -1,12 +1,12 @@
#!/usr/bin/env node
/**
* qa-sprites-b5.mjs — throwaway QA harness for skylight Tasks 8+9
* qa-aircraft-sprites.mjs — aircraft sprite checks
* (type-aware billboard silhouettes + per-class 2D/3D scale; Batch 5).
*
* Injects synthetic STRAIGHT-FLYING aircraft covering ALL 8 classifyAircraft
* classes — the flights layer via OpenSky extended categories (state[17]),
* the military layer via adsb.lol type codes — using the same fetch-shim +
* history-priming pattern as scripts/qa-heading-b3.mjs (positions analytic at
* history-priming pattern as scripts/qa-aircraft-heading.mjs (positions analytic at
* serve time so every fix is self-consistent through the render delay).
*
* Machine assertions:
@@ -15,17 +15,17 @@
* per-class glyph (image data-URI === aircraftIcon(klass),
* imported from src) and per-class scale (flights:
* CLASS_SCALE_2D; military: 0.7 × CLASS_SCALE_2D)
* B3 models : with 3D on, fleet Cesium.Model.scale === the layer's
* Model scales : with 3D on, fleet Cesium.Model.scale === the layer's
* MODEL_SCALE (both 1; legacy scale is baked into the GLB)
* × CLASS_SCALE_3D[klass] per icao; tracked too
* B4 clean : no console errors
*
* Visual output (qa-shots/b5/, untracked): top-down 2D spreads of both rows,
* Visual output (qa-shots/aircraft-sprites/, untracked): top-down 2D spreads of both rows,
* per-row close-ups, a tracked-cyan tint shot, and 3D cluster shots at two
* zooms per layer showing per-class model scale differences. The PNGs are the
* acceptance test — read them.
*
* Run: node scripts/qa-sprites-b5.mjs --url http://localhost:4300
* Run: node scripts/qa-aircraft-sprites.mjs --url http://localhost:4300
* Exits non-zero if any assertion fails. Never commits anything.
*/
@@ -44,7 +44,7 @@ const militaryWantScale = (klass) => (CLASS_MODEL_REAL[klass] ? 1
import { aircraftIcon } from '../src/data/aircraftIcons.js';
// ---------------------------------------------------------------------------
// Args (same shape as qa-heading-b3.mjs)
// Args (same shape as qa-aircraft-heading.mjs)
// ---------------------------------------------------------------------------
const argv = process.argv.slice(2);
const getFlag = (name) => argv.includes(name);
@@ -61,7 +61,7 @@ const ENGINE = (argv.find((arg) => arg.startsWith('--engine=')) ?? '--engine=ces
const APP_URL =
ENGINE === 'cesium' ? BASE_URL : `${BASE_URL.replace(/\/$/, '')}/?engine=${ENGINE}`;
const HEADFUL = getFlag('--headful');
const SHOT_DIR = path.resolve(ENGINE === 'cesium' ? 'qa-shots/b5' : `qa-shots/b5-${ENGINE}`);
const SHOT_DIR = path.resolve(ENGINE === 'cesium' ? 'qa-shots/aircraft-sprites' : `qa-shots/aircraft-sprites-${ENGINE}`);
const FL_MODEL_SCALE = 1; // flights.js (airplane.glb is transform-applied and meter-scale)
const MIL_PLANE_MODEL_SCALE = 1; // military heavy classes share the same baked airplane.glb
@@ -137,7 +137,7 @@ const SPRITES = {
{ hex: 'dda006', flight: 'ML-QUAD', t: 'B744', lon0: ROW_LON0 + 5 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 13000 },
{ hex: 'dda007', flight: 'ML-HEL', t: 'H60', lon0: ROW_LON0 + 6 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 40, altFt: 3000 },
// course 0 like the rest — keeps the row a clean size-comparison line (2D
// rotation is demoed by caa004 and gated by qa-heading-b3 anyway).
// rotation is demoed by caa004 and gated by qa-aircraft-heading anyway).
{ hex: 'dda008', flight: 'ML-FJ', t: 'F16', lon0: ROW_LON0 + 7 * ROW_STEP_DEG, lat0: 30.28, courseDeg: 0, speedMps: 80, altFt: 15000 },
// 3D cluster: C17 vs F16 (tracked) vs B744 vs C130. jet.glb is a
// real-world-scale asset (native bounding radius ~29.8) rendered at
@@ -332,7 +332,7 @@ async function main() {
};
});
// ---- Prime history through the render delay (same pattern as b3) -------
// ---- Prime history through the render delay (same pattern as the heading checks) -------
console.log('Priming straight-flight history through the render delay (30 s / 15 s)...');
const primed = await page.evaluate(async () => {
const dm = window.__godsEyeView.dataManager;
@@ -394,7 +394,7 @@ async function main() {
// ========================================================================
// 2D screenshots
// ========================================================================
console.log('\n2D screenshots → qa-shots/b5/');
console.log('\n2D screenshots → qa-shots/aircraft-sprites/');
const topDown = async (lat, lon, height) => {
await page.evaluate(async ({ lat, lon, height }) => {
const camera = window.__godsEyeView.engine.camera;
@@ -429,21 +429,21 @@ async function main() {
const flRowLat = await rowLat('flights', 2, 30);
const milRowLat = await rowLat('military', 4, 15);
await topDown((flRowLat + milRowLat) / 2, ROW_LON0 + 3 * ROW_STEP_DEG, 16000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-spread-bothrows.png') });
console.log(' saved b5-2d-spread-bothrows.png (flights row top, military row bottom)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-2d-spread-bothrows.png') });
console.log(' saved aircraft-sprites-2d-spread-bothrows.png (flights row top, military row bottom)');
// Row widths: military spans 7 steps (~8.1 km) → frame ~13.9 km at 12 km up;
// flights spans 5 steps (~5.8 km, +caa004's eastward drift) → ~10.4 km at 9 km up.
if (hasMilitary) {
await topDown(await rowLat('military', 4, 15), ROW_LON0 + 3.5 * ROW_STEP_DEG, 17000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-military-row.png') });
console.log(' saved b5-2d-military-row.png (8 classes, amber)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-2d-military-row.png') });
console.log(' saved aircraft-sprites-2d-military-row.png (8 classes, amber)');
console.log(` glyph positions: ${await page.evaluate(() => window.__planeCanvasCoords('military', 15, 'ft'))}`);
}
await topDown(await rowLat('flights', 2, 30), ROW_LON0 + 2.5 * ROW_STEP_DEG, 12000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-flights-row.png') });
console.log(' saved b5-2d-flights-row.png (6 category classes, white)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-2d-flights-row.png') });
console.log(' saved aircraft-sprites-2d-flights-row.png (6 category classes, white)');
console.log(` glyph positions: ${await page.evaluate(() => window.__planeCanvasCoords('flights', 30, 'm'))}`);
// Tracked tint: commercial tracked plane goes cyan; neighbors stay white.
@@ -451,11 +451,11 @@ async function main() {
window.__godsEyeView.dataManager.layers.get('flights').module.trackById(icao);
}, 'caa002');
await sleep(3000);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-2d-tracked-cyan.png') });
console.log(' saved b5-2d-tracked-cyan.png (tracked airliner cyan, follow camera)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-2d-tracked-cyan.png') });
console.log(' saved aircraft-sprites-2d-tracked-cyan.png (tracked airliner cyan, follow camera)');
// ========================================================================
// B3 + 3D screenshots — flights cluster
// Model scale and 3D screenshots — flights cluster
// ========================================================================
console.log('\n3D — flights cluster (airplane.glb, per-class scale)');
await page.evaluate(() => {
@@ -492,15 +492,15 @@ async function main() {
await sleep(1500);
await zoomToRange(900);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-flights-mid.png') });
console.log(' saved b5-3d-flights-mid.png (900 m range — near the min-pixel clamp)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-3d-flights-mid.png') });
console.log(' saved aircraft-sprites-3d-flights-mid.png (900 m range — near the min-pixel clamp)');
await zoomToRange(450);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-flights-close.png') });
console.log(' saved b5-3d-flights-close.png (450 m range: widebody left, airliner center, fastjet right)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-3d-flights-close.png') });
console.log(' saved aircraft-sprites-3d-flights-close.png (450 m range: widebody left, airliner center, fastjet right)');
}
// ========================================================================
// B3 + 3D screenshots — military cluster
// Model scale and 3D screenshots — military cluster
// ========================================================================
if (!hasMilitary) {
console.log(` [SKIP] 3D military — no military layer on ${ENGINE}`);
@@ -543,11 +543,11 @@ async function main() {
// jet.glb at MODEL_SCALE 1 is real-world size — same shot ranges as the
// flights cluster (close zoom no longer puts the camera inside a model).
await zoomToRange(900);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-military-far.png') });
console.log(' saved b5-3d-military-far.png (900 m range: C130/C17 left, F16 center, B744 right)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-3d-military-far.png') });
console.log(' saved aircraft-sprites-3d-military-far.png (900 m range: C130/C17 left, F16 center, B744 right)');
await zoomToRange(450);
await page.screenshot({ path: path.join(SHOT_DIR, 'b5-3d-military-close.png') });
console.log(' saved b5-3d-military-close.png (450 m range: C17 left, F16 center, B744 right)');
await page.screenshot({ path: path.join(SHOT_DIR, 'aircraft-sprites-3d-military-close.png') });
console.log(' saved aircraft-sprites-3d-military-close.png (450 m range: C17 left, F16 center, B744 right)');
}
}
+1 -1
View File
@@ -334,7 +334,7 @@ const runs = [
['--panoramax'],
],
['radio-engine-switch', 'radio-engine-switch', ['radio']],
['w2-imagery', 'w2-imagery', ['recent-imagery']],
['imagery-contract', 'imagery-contract', ['recent-imagery']],
['fire-camera-switch', 'fire-camera-switch', ['alpr-cameras']],
[
'fire-camera-switch--fires',
+1 -1
View File
@@ -41,7 +41,7 @@ import path from 'node:path';
import puppeteer from 'puppeteer';
import { blockPaidQaMaps } from './qa-map-policy.mjs';
import sharp from 'sharp';
import { installAlprFixture } from './fixtures/w2/alpr.mjs';
import { installAlprFixture } from './fixtures/layers/alpr.mjs';
import { redact, isExpectedPreviewSetupError } from './qa-browserEvidence.mjs';
import {
createScreencast,
+5 -1
View File
@@ -47,7 +47,11 @@ export function isExpectedPreviewSetupError(
// The runtime evidence gate shares one strict runtime verdict across its main page and
// every share-link page. Response diagnostics never retain query credentials.
export function watchW1Page(page, report, { name, preview = false, baseUrl }) {
export function watchBrowserPage(
page,
report,
{ name, preview = false, baseUrl },
) {
const errors = (report.browserErrors ??= []);
report.expectedPreviewErrors ??= [];
report.failedResponses ??= [];
+6 -5
View File
@@ -2,7 +2,7 @@ import { assertKeylessPreview } from './qa-strict-harness.mjs';
import assert from 'node:assert/strict';
import { promisify } from 'node:util';
import { execFile } from 'node:child_process';
import { installCctvFixture } from './fixtures/w2/cctv.mjs';
import { installCctvFixture } from './fixtures/layers/cctv.mjs';
import { createStrict, localPreview } from './qa-strict-harness.mjs';
const url = localPreview(process.argv[2]);
url.search = '?engine=navara&welcome=0';
@@ -64,7 +64,7 @@ await strict.run({}, async (b) => {
await dataManager.setEnabled('cctv', true, { origin: 'user' });
window.qaCctv = dataManager.layers.get('cctv').module;
window.qaCctv.setParams({
selectedCameraId: 'w2-cctv-video',
selectedCameraId: 'fixture-cctv-video',
showProjection: true,
coverageMode: 'viewshed',
calibration: { patch: { heightM: 31, pitchDeg: 20 } },
@@ -101,11 +101,12 @@ await strict.run({}, async (b) => {
params: window.qaCctv.getParams(),
plane: !!window.qaCctv
._cctvRecordsForTest()
.find((r) => r.camera.id === 'w2-cctv-video').projection?.planeEntity,
.find((r) => r.camera.id === 'fixture-cctv-video').projection
?.planeEntity,
projection: (() => {
const r = window.qaCctv
._cctvRecordsForTest()
.find((r) => r.camera.id === 'w2-cctv-video');
.find((r) => r.camera.id === 'fixture-cctv-video');
return {
show: r.projection?.planeEntity?.show === true,
visible: r.projection?.planeVisible === true,
@@ -123,7 +124,7 @@ await strict.run({}, async (b) => {
v.params.coverageMode,
);
assert.ok(v.same && v.media && v.plane && !v.paused);
assert.equal(v.params.selectedCameraId, 'w2-cctv-video');
assert.equal(v.params.selectedCameraId, 'fixture-cctv-video');
assert.equal(v.params.coverageMode, 'viewshed');
assert.equal(catalogs, count);
await strict.passed(
+3 -3
View File
@@ -77,9 +77,9 @@
*/
import puppeteer from 'puppeteer';
import { watchW1Page, redact } from './qa-browserEvidence.mjs';
import { watchBrowserPage, redact } from './qa-browserEvidence.mjs';
import { blockPaidQaMaps } from './qa-map-policy.mjs';
import { installCctvFixture } from './fixtures/w2/cctv.mjs';
import { installCctvFixture } from './fixtures/layers/cctv.mjs';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -581,7 +581,7 @@ async function main() {
if (terrainRequests.delete(request)) terrainActivityAt = Date.now();
});
await page.setViewport({ width: 1280, height: 800 });
watchW1Page(page, evidence, {
watchBrowserPage(page, evidence, {
name: 'cctv-legacy',
baseUrl: APP_URL,
preview: getFlag('--preview'),
+58
View File
@@ -90,6 +90,41 @@ async function runOn(browser, kind) {
content:
'* { pointer-events: none !important; } [data-contract-target] { pointer-events: auto !important; }',
});
// The globe drawn and its tiles loaded: a restored view hides the globe
// for a moment after the loading screen goes.
await page.evaluate(async () => {
const { engine } = window.__godsEyeView;
const canvas = engine.host.canvas;
const probe = document.createElement('canvas');
probe.width = 1;
probe.height = 1;
const context = probe.getContext('2d', { willReadFrequently: true });
const started = performance.now();
let settled = 0;
while (performance.now() - started < 120_000 && settled < 20) {
await new Promise((resolve) => {
const remove = engine.frames.postRender.addEventListener(() => {
remove();
resolve();
});
engine.render.request('contract-suite-settle');
});
context.drawImage(
canvas,
canvas.width / 2,
canvas.height / 2,
1,
1,
0,
0,
1,
1,
);
const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
settled =
r + g + b > 60 && engine.surface.tilesLoaded() ? settled + 1 : 0;
}
});
// A real left click and pointer move at a canvas position.
await page.exposeFunction('__contractPointer', async (type, x, y) => {
const box = await page.evaluate(() => {
@@ -127,9 +162,32 @@ async function runOn(browser, kind) {
// A real 2D canvas: a sprite atlas the engines can draw and pick.
const makeCanvas = (width, height) =>
Object.assign(document.createElement('canvas'), { width, height });
// RGBA of the last drawn frame (both engines keep their drawing buffer).
const scratch = document.createElement('canvas');
const context = scratch.getContext('2d', { willReadFrequently: true });
const readRegion = (x, y, width, height) => {
const canvas = engine.host.canvas;
const scale = canvas.width / canvas.clientWidth;
scratch.width = width;
scratch.height = height;
context.clearRect(0, 0, width, height);
context.drawImage(
canvas,
x * scale,
y * scale,
width * scale,
height * scale,
0,
0,
width,
height,
);
return context.getImageData(0, 0, width, height).data;
};
for (const { name, run } of createContractSuite(engine, {
waitFrames,
pointer,
readRegion,
makeCanvas,
})) {
if (only && !only.some((prefix) => name.startsWith(prefix))) continue;
+3 -3
View File
@@ -11,7 +11,7 @@
* them real types AMBIENTLY (nothing is ever tracked, 3D stays off), while
* honoring the politeness bounds.
*
* Fetch shim (installed before app code, same pattern as qa-sprites-b5.mjs):
* Fetch shim (installed before app code, same pattern as qa-aircraft-sprites.mjs):
* - /api/flights → 12 straight-flying planes, category 0
* - /api/adsbdb/type/:hex → varied REAL type codes (C172, B744, DH8D, H60,
* F16, GLID, B77W, A320, B738, PC12, R44) + one
@@ -57,7 +57,7 @@ import { classifyAircraft, CLASS_SCALE_2D } from '../src/data/aircraftClass.js';
import { aircraftIcon } from '../src/data/aircraftIcons.js';
// ---------------------------------------------------------------------------
// Args (same shape as qa-sprites-b5.mjs)
// Args (same shape as qa-aircraft-sprites.mjs)
// ---------------------------------------------------------------------------
const argv = process.argv.slice(2);
const getFlag = (name) => argv.includes(name);
@@ -308,7 +308,7 @@ async function main() {
await page.evaluate(() => window.__godsEyeView.styleManager.orbitController.stop());
console.log(' App globals ready.');
// ---- In-page billboard probe (same as qa-sprites-b5.mjs) ---------------
// ---- In-page billboard probe (same as qa-aircraft-sprites.mjs) ---------------
// What the flights layer draws, through the engine-neutral probe
// (layers.drawn: the layer's own sprite handles) on either engine. The
// layer owns no Cesium billboards any more: the contract's batched
+2 -2
View File
@@ -1,7 +1,7 @@
import { assertKeylessPreview } from './qa-strict-harness.mjs';
import assert from 'node:assert/strict';
import { installAlprFixture, cameras } from './fixtures/w2/alpr.mjs';
import { firmsFixture, FIRMS_VALID_COUNT } from './fixtures/w2/firms.mjs';
import { installAlprFixture, cameras } from './fixtures/layers/alpr.mjs';
import { firmsFixture, FIRMS_VALID_COUNT } from './fixtures/layers/firms.mjs';
import { createStrict, localPreview } from './qa-strict-harness.mjs';
const url = localPreview(process.argv[2]),
fire = process.argv.includes('--fires'),
+2 -2
View File
@@ -9,7 +9,7 @@ import fs from 'node:fs';
import path from 'node:path';
import puppeteer from 'puppeteer';
import sharp from 'sharp';
import { watchW1Page, redact } from './qa-browserEvidence.mjs';
import { watchBrowserPage, redact } from './qa-browserEvidence.mjs';
const urlIndex = process.argv.indexOf('--url');
if (urlIndex < 0) throw new Error('An explicit --url is required');
@@ -87,7 +87,7 @@ async function open(mode = 'normal') {
await page.bringToFront();
const errors = [];
if (extended)
watchW1Page(page, report, {
watchBrowserPage(page, report, {
name: mode,
baseUrl: appUrl,
preview: process.argv.includes('--preview'),
-204
View File
@@ -1,204 +0,0 @@
#!/usr/bin/env node
/**
* Run the contract suite's engine.ground checks in headless Chrome on both
* real engines, with what qa-contract-suite.mjs does not give the suite: a
* readRegion of the engine canvas (the pixel check) besides the real
* pointer (the click pick). Serve the app keyless:
*
* GOOGLE_MAPS_API_KEY= CESIUM_ION_TOKEN= npm run dev -- --port 4271
* node scripts/qa-ground-suite.mjs http://localhost:4271 [--engine=cesium,navara] [--only=ground:]
*
* Exits 1 when a check fails on any engine.
*/
import puppeteer from 'puppeteer';
const BLOCKED_HOSTS =
/(^|\.)(googleapis\.com|google\.com|gstatic\.com|cesium\.com)$/;
const args = process.argv.slice(2);
const origin = args.find((arg) => !arg.startsWith('--'));
const option = (name) =>
args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);
if (!origin) {
console.error(
'Usage: node scripts/qa-ground-suite.mjs <origin> [--engine=cesium,navara] [--only=prefix]',
);
process.exit(2);
}
const engines = (option('engine') ?? 'cesium,navara').split(',');
const only = (option('only') ?? 'ground:').split(',');
async function runOn(browser, kind) {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
const pageErrors = [];
page.on('pageerror', (error) => pageErrors.push(error.message));
await page.setRequestInterception(true);
page.on('request', (request) => {
if (BLOCKED_HOSTS.test(new URL(request.url()).hostname)) request.abort();
else request.continue();
});
await page.goto(
`${origin}/?welcome=0&engine=${kind}#v=2&lat=10&lon=20&alt=1000000&heading=0&pitch=-90&map=osm&l=`,
);
await page.waitForFunction(
(expected) =>
window.__godsEyeView?.engine?.kind === expected &&
document.getElementById('loading-screen')?.classList.contains('hidden'),
{ timeout: 300_000, polling: 250 },
kind,
);
// The app restores the view in the link with a flight that can start
// after the loading screen goes: wait until the camera is there and still,
// or that flight lands in the middle of a check.
await page.waitForFunction(
() => {
const { camera } = window.__godsEyeView.engine;
return (
!camera.flightActive() && Math.abs(camera.height() - 1_000_000) < 1000
);
},
{ timeout: 120_000, polling: 250 },
);
// The globe drawn and its tiles loaded: a restored view hides the globe
// for a moment after the loading screen goes.
await page.evaluate(async () => {
const { engine } = window.__godsEyeView;
const canvas = engine.host.canvas;
const probe = document.createElement('canvas');
probe.width = 1;
probe.height = 1;
const context = probe.getContext('2d', { willReadFrequently: true });
const started = performance.now();
let settled = 0;
while (performance.now() - started < 120_000 && settled < 20) {
await new Promise((resolve) => {
const remove = engine.frames.postRender.addEventListener(() => {
remove();
resolve();
});
engine.render.request('ground-suite-settle');
});
context.drawImage(
canvas,
canvas.width / 2,
canvas.height / 2,
1,
1,
0,
0,
1,
1,
);
const [r, g, b] = context.getImageData(0, 0, 1, 1).data;
settled =
r + g + b > 60 && engine.surface.tilesLoaded() ? settled + 1 : 0;
}
});
await page.evaluate(() =>
window.__godsEyeView.engine.host.canvas.setAttribute(
'data-contract-target',
'',
),
);
await page.addStyleTag({
content:
'* { pointer-events: none !important; } [data-contract-target] { pointer-events: auto !important; }',
});
await page.exposeFunction('__contractPointer', async (type, x, y) => {
const box = await page.evaluate(() => {
const rect =
window.__godsEyeView.engine.host.canvas.getBoundingClientRect();
return { left: rect.left, top: rect.top };
});
const at = [box.left + x, box.top + y];
if (type === 'click') await page.mouse.click(...at);
else {
await page.mouse.move(at[0] - 1, at[1]);
await page.mouse.move(...at);
}
});
const results = await page.evaluate(async (only) => {
const { engine } = window.__godsEyeView;
const waitFrames = (count) =>
new Promise((resolve) => {
let left = count;
const remove = engine.frames.postRender.addEventListener(() => {
if (--left > 0) return;
remove();
resolve();
});
engine.render.request('ground-suite');
});
const pointer = {
click: (x, y) => window.__contractPointer('click', x, y),
move: (x, y) => window.__contractPointer('move', x, y),
};
// RGBA of the last drawn frame (both engines keep their drawing buffer).
const scratch = document.createElement('canvas');
const context = scratch.getContext('2d', { willReadFrequently: true });
const readRegion = (x, y, width, height) => {
const canvas = engine.host.canvas;
const scale = canvas.width / canvas.clientWidth;
scratch.width = width;
scratch.height = height;
context.clearRect(0, 0, width, height);
context.drawImage(
canvas,
x * scale,
y * scale,
width * scale,
height * scale,
0,
0,
width,
height,
);
return context.getImageData(0, 0, width, height).data;
};
const makeCanvas = (width, height) =>
Object.assign(document.createElement('canvas'), { width, height });
const { createContractSuite } =
await import('/src/globe/testing/contractSuite.mjs');
const outcome = [];
for (const { name, run } of createContractSuite(engine, {
waitFrames,
pointer,
readRegion,
makeCanvas,
})) {
if (!only.some((prefix) => name.startsWith(prefix))) continue;
try {
await run();
outcome.push({ name, ok: true });
} catch (error) {
outcome.push({
name,
ok: false,
error: String(error?.message ?? error),
});
}
}
return outcome;
}, only);
await page.close();
return { engine: kind, results, pageErrors };
}
const browser = await puppeteer.launch({ headless: true });
let failed = false;
try {
for (const kind of engines) {
const { results, pageErrors } = await runOn(browser, kind);
const passed = results.filter((result) => result.ok).length;
console.log(`${kind}: ${passed}/${results.length} checks pass`);
for (const result of results)
if (!result.ok) {
failed = true;
console.log(` FAIL ${result.name}: ${result.error}`);
} else console.log(` ok ${result.name}`);
for (const message of pageErrors) console.log(` page error: ${message}`);
}
} finally {
await browser.close();
}
process.exit(failed ? 1 : 0);
@@ -4,7 +4,7 @@
// pixels; both pinned dates survive a switch to Cesium and back. The report
// and its screenshots go next to the evidence file (or to the temp directory).
//
// node scripts/qa-w2-imagery.mjs http://127.0.0.1:<PORT>
// node scripts/qa-imagery-contract.mjs http://127.0.0.1:<PORT>
// [--layers=recent-imagery] [--evidence=FILE]
import fs from 'node:fs/promises';
import os from 'node:os';
+14 -4
View File
@@ -1086,15 +1086,25 @@ check({
heavy: true, run: harness({ id: 'D1', script: 'track-regression.mjs', args: ['--url', APP_URL], timeoutMs: 1200000 }),
});
check({
id: 'D2', group: 'D', desc: 'qa-heading-b3 — path-derived display heading',
heavy: true, run: harness({ id: 'D2', script: 'qa-heading-b3.mjs', args: ['--url', APP_URL], timeoutMs: 900000 }),
id: 'D2',
group: 'D',
desc: 'qa-aircraft-heading — path-derived display heading',
heavy: true,
run: harness({
id: 'D2',
script: 'qa-aircraft-heading.mjs',
args: ['--url', APP_URL],
timeoutMs: 900000,
}),
});
check({
id: 'D3', group: 'D', desc: 'qa-sprites-b5 — per-class billboard silhouettes',
id: 'D3',
group: 'D',
desc: 'qa-aircraft-sprites — per-class billboard silhouettes',
heavy: true,
run: harness({
id: 'D3',
script: 'qa-sprites-b5.mjs',
script: 'qa-aircraft-sprites.mjs',
args: ['--url', APP_URL],
timeoutMs: 900000,
knownConditions: [{
+1 -1
View File
@@ -1,5 +1,5 @@
import assert from 'node:assert/strict';
import { stations, installRadioFixture } from './fixtures/w2/radio.mjs';
import { stations, installRadioFixture } from './fixtures/layers/radio.mjs';
import { createStrict, localPreview, openPage, waitForEngine } from './qa-strict-harness.mjs';
const url = localPreview(process.argv[2]);
// The long soak and switch timings live in qa-strict-soak.mjs.
+1 -1
View File
@@ -15,7 +15,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer';
import { stations } from './fixtures/w2/radio.mjs';
import { stations } from './fixtures/layers/radio.mjs';
import { isExpectedPreviewSetupError } from './qa-browserEvidence.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
+1 -1
View File
@@ -44,7 +44,7 @@ import {
slope,
startSession,
} from './qa-strict-session.mjs';
import { installRadioFixture, stations } from './fixtures/w2/radio.mjs';
import { installRadioFixture, stations } from './fixtures/layers/radio.mjs';
import { trafficFixtureResponse } from './traffic-fixtures.mjs';
import { POSITION_HISTORY_LIMIT } from '../src/layers/flights/recordPolicy.js';
import { heapSizesByType } from './qa/heapSnapshotSizes.mjs';
+2 -2
View File
@@ -1,11 +1,11 @@
import assert from 'node:assert/strict';
import { fromDegrees } from '../src/geo/cartesian3.js';
import { createStrict, localPreview, openPage, waitForEngine } from './qa-strict-harness.mjs';
import { installTransitFixture } from './fixtures/w2/transit.mjs';
import { installTransitFixture } from './fixtures/layers/transit.mjs';
import {
combinedPayloads,
installCombinedFixture,
} from './fixtures/w2/combined.mjs';
} from './fixtures/layers/combined.mjs';
const url = localPreview(process.argv[2]),
bus = process.argv.includes('--transit'),
id = bus ? 'transit' : 'bikeshare';