mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Each script launches and closes Chrome through scripts/qa/browser.mjs, waits on the application through scripts/qa/ready.mjs instead of the loading screen and fixed pauses, and reads the contract instead of Cesium where the check is not about Cesium itself.
201 lines
6.8 KiB
JavaScript
201 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/** Exercise scene-clock ownership and authored seek behavior in the composed browser app. */
|
|
import { whenSettled } from './qa/ready.mjs';
|
|
import { launchBrowser, closeBrowser } from './qa/browser.mjs';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
const shots = path.resolve('qa-shots/director-timing');
|
|
fs.mkdirSync(shots, { recursive: true });
|
|
const browser = await launchBrowser({
|
|
headless: true,
|
|
args: ['--no-sandbox', '--disable-dev-shm-usage'],
|
|
});
|
|
const page = await browser.newPage();
|
|
const errors = [];
|
|
page.on('pageerror', (error) => errors.push(error.message));
|
|
let failures = 0;
|
|
function check(name, passed) {
|
|
console.log(`[${passed ? 'PASS' : 'FAIL'}] ${name}`);
|
|
if (!passed) failures++;
|
|
}
|
|
try {
|
|
await page.setViewport({ width: 1440, height: 900 });
|
|
await page.goto(
|
|
`${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
|
|
{ waitUntil: 'domcontentloaded' },
|
|
);
|
|
await page.waitForFunction(() => window.__godsEyeView?.sceneDirector, {
|
|
timeout: 60000,
|
|
});
|
|
await whenSettled(page);
|
|
const result = await page.evaluate(async () => {
|
|
const director = window.__godsEyeView.sceneDirector;
|
|
const camera = director.styleManager.getCameraState();
|
|
const visual = director.styleManager.getVisualState();
|
|
const scene = {
|
|
id: 'timing-qa',
|
|
title: 'Timing QA',
|
|
shots: [0, 1].map((index) => ({
|
|
id: `timing-${index}`,
|
|
title: `Timing ${index}`,
|
|
durationSec: 0.4,
|
|
holdSec: 0.3,
|
|
camera: { ...camera, lon: camera.lon + index * 0.002 },
|
|
visual,
|
|
layers: {},
|
|
})),
|
|
};
|
|
director._project.scenes.push(scene);
|
|
const load = await director.loadShot(scene.id, scene.shots[0].id, {
|
|
flyDuration: 0.3,
|
|
});
|
|
const forward = await director.seekScene(scene.id, 0.9);
|
|
const forwardSnapshot = director.getPlaybackTimingState().snapshot;
|
|
const backward = await director.seekScene(scene.id, 0.1);
|
|
const backwardSnapshot = director.getPlaybackTimingState().snapshot;
|
|
const replay = await director.replayShot(scene.id, scene.shots[1].id);
|
|
director.stopScene();
|
|
await director.startScene(scene.id, { single: true, preview: false });
|
|
const completed =
|
|
!director.running && director.getPlaybackTimingState().activeTimers === 0;
|
|
scene.shots[0].holdSec = 60;
|
|
const run = director.startScene(scene.id, { single: true, preview: false });
|
|
const deadline = Date.now() + 10000;
|
|
while (
|
|
director.getPlaybackTimingState().activeTimers < 3 &&
|
|
Date.now() < deadline
|
|
)
|
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
const holding = director.getPlaybackTimingState().activeTimers >= 3;
|
|
const stoppedAt = performance.now();
|
|
director.stopScene('QA Stop during hold');
|
|
await run;
|
|
const stoppedPromptly = performance.now() - stoppedAt < 1000;
|
|
const noTimers = director.getPlaybackTimingState().activeTimers === 0;
|
|
director._project.scenes = director._project.scenes.filter(
|
|
({ id }) => id !== scene.id,
|
|
);
|
|
return {
|
|
loaded: load?.started,
|
|
forward: forward && forwardSnapshot.shotId === 'timing-1',
|
|
backward: backward && backwardSnapshot.shotId === 'timing-0',
|
|
replay: replay?.started,
|
|
completed,
|
|
holding,
|
|
stoppedPromptly,
|
|
noTimers,
|
|
};
|
|
});
|
|
for (const [name, passed] of Object.entries(result)) check(name, passed);
|
|
const visibilityRestored = await page.evaluate(async () => {
|
|
// QA layer registration exists only in Vite dev builds.
|
|
if (typeof window.__gevQaRegisterLayer !== 'function') return 'skipped';
|
|
const { sceneDirector: director, dataManager } = window.__godsEyeView;
|
|
const layerId = 'director-seek-visibility-qa';
|
|
window.__gevQaRegisterLayer(dataManager, {
|
|
id: layerId,
|
|
name: 'Director seek visibility QA',
|
|
updateInterval: -1,
|
|
init() {},
|
|
enable() {},
|
|
disable() {},
|
|
update() {},
|
|
destroy() {},
|
|
getStats: () => ({ count: 0 }),
|
|
});
|
|
const scene = {
|
|
id: 'visibility-qa',
|
|
title: 'Visibility QA',
|
|
shots: [
|
|
{
|
|
id: 'visibility-shot',
|
|
title: 'Visibility shot',
|
|
durationSec: 0.4,
|
|
holdSec: 0,
|
|
camera: director.styleManager.getCameraState(),
|
|
visual: director.styleManager.getVisualState(),
|
|
layers: { [layerId]: { enabled: true } },
|
|
},
|
|
],
|
|
};
|
|
director._project.scenes.push(scene);
|
|
try {
|
|
const loaded = await director.loadShot(scene.id, scene.shots[0].id, {
|
|
flyDuration: 0.2,
|
|
});
|
|
const disabled =
|
|
(await dataManager.setEnabled(layerId, false, { origin: 'user' })) &&
|
|
!dataManager.layers.get(layerId).enabled;
|
|
const sought = await director.seekScene(scene.id, 0.5);
|
|
return (
|
|
loaded?.started &&
|
|
disabled &&
|
|
sought &&
|
|
dataManager.layers.get(layerId).enabled
|
|
);
|
|
} finally {
|
|
director.stopScene();
|
|
director._project.scenes = director._project.scenes.filter(
|
|
({ id }) => id !== scene.id,
|
|
);
|
|
await window.__gevQaUnregisterLayer(dataManager, layerId);
|
|
}
|
|
});
|
|
if (visibilityRestored === 'skipped') {
|
|
console.log(
|
|
'[SKIP] same-shot seek visibility needs a Vite dev server (QA_BASE_URL)',
|
|
);
|
|
} else {
|
|
check(
|
|
'same-shot seek restores manually changed layer visibility',
|
|
visibilityRestored,
|
|
);
|
|
}
|
|
const content = await page.evaluate(async () => {
|
|
const d = window.__godsEyeView.sceneDirector;
|
|
const scene = d._project.scenes.find(({ title }) =>
|
|
/Nepal Flood Incident/i.test(title),
|
|
);
|
|
if (!scene) return { exists: false };
|
|
const shot =
|
|
scene.shots.find(({ title }) => /Regional Approach/i.test(title)) ||
|
|
scene.shots[0];
|
|
const loaded = await d.loadShot(scene.id, shot.id, { flyDuration: 0.3 });
|
|
return {
|
|
exists: true,
|
|
shots: scene.shots.length,
|
|
loaded: loaded?.started === true,
|
|
};
|
|
});
|
|
check(
|
|
'existing Nepal scene and authored shots load',
|
|
content.exists && content.shots >= 25 && content.loaded,
|
|
);
|
|
await whenSettled(page);
|
|
await page.screenshot({ path: path.join(shots, 'nepal-regional.png') });
|
|
await page.evaluate(() => {
|
|
const g = window.__godsEyeView;
|
|
g.sceneDirector.stopScene();
|
|
const pose = g.styleManager.getCameraState();
|
|
g.sceneDirector._setCameraView({
|
|
...pose,
|
|
heading: pose.heading + 45,
|
|
pitch: -55,
|
|
});
|
|
});
|
|
await whenSettled(page);
|
|
await page.screenshot({ path: path.join(shots, 'nepal-angle.png') });
|
|
check(
|
|
'no pending clocks after Stop',
|
|
await page.evaluate(
|
|
() =>
|
|
window.__godsEyeView.sceneDirector.getPlaybackTimingState()
|
|
.activeTimers === 0,
|
|
),
|
|
);
|
|
check('no browser exceptions', errors.length === 0);
|
|
} finally {
|
|
await closeBrowser(browser);
|
|
}
|
|
if (failures) process.exitCode = 1;
|