test: restore minimized Transit windows before screenshots

This commit is contained in:
Said committed 2026-10-20 15:12:33 +01:00
1 parent 72f1625257
commit 56be69d065
6 files changed
+101 -15

No files matched your search

+1
View File
@@ -4,3 +4,4 @@
- Bound the Transit card-anchor probe by observed poll coverage instead of a fixed 20-second window (asturksever, #1282). - Bound the Transit card-anchor probe by observed poll coverage instead of a fixed 20-second window (asturksever, #1282).
- Keep visible-page Transit probes focused through the browser testing API (asturksever, #1282). - Keep visible-page Transit probes focused through the browser testing API (asturksever, #1282).
- Add a real-browser regression gate for hidden-tab periodic work, explicit refresh, resume and teardown (asturksever, #1282). - Add a real-browser regression gate for hidden-tab periodic work, explicit refresh, resume and teardown (asturksever, #1282).
- Restore minimized test windows before screenshots so native compositor pauses cannot stall browser acceptance (asturksever, #1282).
+27
View File
@@ -18,3 +18,30 @@ export function chromeExecutablePath({
if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH; if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
return exists(MACOS_CHROME) ? MACOS_CHROME : undefined; return exists(MACOS_CHROME) ? MACOS_CHROME : undefined;
} }
/** Restore an owned test window before capture: focus emulation alone does not
* unminimize the native compositor, so CDP screenshots can otherwise stall. */
export async function captureQaScreenshot(
page,
options,
{ headful = true } = {},
) {
if (headful) {
const session = await page.createCDPSession();
try {
const { windowId, bounds } = await session.send(
'Browser.getWindowForTarget',
);
if (bounds.windowState === 'minimized') {
await session.send('Browser.setWindowBounds', {
windowId,
bounds: { windowState: 'normal' },
});
}
await page.bringToFront();
} finally {
await session.detach();
}
}
return page.screenshot(options);
}
+2 -2
View File
@@ -1,6 +1,6 @@
/** Camera-jump regression against an existing server; never starts a server. */ /** Camera-jump regression against an existing server; never starts a server. */
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs'; import { chromeExecutablePath, captureQaScreenshot } from './qa-chrome.mjs';
import { mkdir, writeFile } from 'node:fs/promises'; import { mkdir, writeFile } from 'node:fs/promises';
import { reduceTrailPixels } from '../src/layers/transit/qaMetrics.js'; import { reduceTrailPixels } from '../src/layers/transit/qaMetrics.js';
@@ -307,7 +307,7 @@ try {
result.pixelPass = pixels.pass; result.pixelPass = pixels.pass;
result.pixelDetails = pixels; result.pixelDetails = pixels;
result.errors = errors; result.errors = errors;
await page.screenshot({ path: `${shots}/recovery.png` }); await captureQaScreenshot(page, { path: `${shots}/recovery.png` });
await writeFile(`${shots}/recovery.json`, JSON.stringify(result, null, 2)); await writeFile(`${shots}/recovery.json`, JSON.stringify(result, null, 2));
for (const key of ['on', 'off', 'onAgain', 'offAgain']) delete result[key]; for (const key of ['on', 'off', 'onAgain', 'offAgain']) delete result[key];
const pass = const pass =
+7 -6
View File
@@ -1,3 +1,4 @@
import { captureQaScreenshot } from './qa-chrome.mjs';
import { getRegisteredTransitFeed } from '../src/data/transitFeeds.js'; import { getRegisteredTransitFeed } from '../src/data/transitFeeds.js';
// Not registered or active: camera discovery and live polls never own this feed. // Not registered or active: camera discovery and live polls never own this feed.
@@ -219,7 +220,7 @@ export async function runFleetBudgets({
allocations.layerFrameBytes / allocationTrace.rows.length <= 8192, allocations.layerFrameBytes / allocationTrace.rows.length <= 8192,
`${allocations.layerFrameBytes / allocationTrace.rows.length} transit-authored bytes/frame; whole frame ${allocations.frameBytes / allocationTrace.rows.length}; top three ${JSON.stringify(details.topAllocatorsPerFrame)}; collected objects included. Full profile saved.`, `${allocations.layerFrameBytes / allocationTrace.rows.length} transit-authored bytes/frame; whole frame ${allocations.frameBytes / allocationTrace.rows.length}; top three ${JSON.stringify(details.topAllocatorsPerFrame)}; collected objects included. Full profile saved.`,
); );
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-fleet-${count}.jpg`, path: `${shots}/${tag}-fleet-${count}.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 85, quality: 85,
@@ -392,7 +393,7 @@ export async function runBostonMatrix({
JSON.stringify({ pixels, contrast }), JSON.stringify({ pixels, contrast }),
{ unexercised: pixels.length < 6 }, { unexercised: pixels.length < 6 },
); );
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-matrix-${background.name}-${look.name}-fleet.jpg`, path: `${shots}/${tag}-matrix-${background.name}-${look.name}-fleet.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 90, quality: 90,
@@ -407,7 +408,7 @@ export async function runBostonMatrix({
target.key, target.key,
); );
await wait(500); await wait(500);
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-matrix-${background.name}-${look.name}-${target.route}-selected.jpg`, path: `${shots}/${tag}-matrix-${background.name}-${look.name}-${target.route}-selected.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 90, quality: 90,
@@ -431,7 +432,7 @@ export async function runBostonMatrix({
); );
} }
await wait(300); await wait(300);
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-matrix-${background.name}-${look.name}-restored.jpg`, path: `${shots}/${tag}-matrix-${background.name}-${look.name}-restored.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 90, quality: 90,
@@ -579,7 +580,7 @@ export async function runBostonLive({
JSON.stringify(selection), JSON.stringify(selection),
{ unexercised: !target }, { unexercised: !target },
); );
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-boston-live-${view.name}.jpg`, path: `${shots}/${tag}-boston-live-${view.name}.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 90, quality: 90,
@@ -1010,7 +1011,7 @@ export async function runTrailVisibility({
stable: pixels.stable[i], stable: pixels.stable[i],
eligible: pixels.eligible[i], eligible: pixels.eligible[i],
})); }));
await page.screenshot({ await captureQaScreenshot(page, {
path: `${shots}/${tag}-${altitude}m-trail-visible.jpg`, path: `${shots}/${tag}-${altitude}m-trail-visible.jpg`,
type: 'jpeg', type: 'jpeg',
quality: 95, quality: 95,
+8 -6
View File
@@ -36,7 +36,7 @@ import {
*/ */
import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { mkdir, readFile, writeFile } from 'node:fs/promises';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs'; import { chromeExecutablePath, captureQaScreenshot } from './qa-chrome.mjs';
import { createTransitHistory } from '../server/providers/transitHistory.js'; import { createTransitHistory } from '../server/providers/transitHistory.js';
import { DETECTION_THEME_MAP } from '../src/overlays/worldOverlayTokens.js'; import { DETECTION_THEME_MAP } from '../src/overlays/worldOverlayTokens.js';
import { transitModeTier } from '../src/data/transitPresetStyle.js'; import { transitModeTier } from '../src/data/transitPresetStyle.js';
@@ -294,11 +294,13 @@ page.on('request', (request) => {
}); });
const shot = (name, caption) => const shot = (name, caption) =>
page captureQaScreenshot(page, {
.screenshot({ path: `${SHOTS}/${name}`, type: 'jpeg', quality: 82 }) path: `${SHOTS}/${name}`,
.then(() => { type: 'jpeg',
console.log(` shot ${name} — ${caption}`); quality: 82,
}); }).then(() => {
console.log(` shot ${name} — ${caption}`);
});
const view = (city) => const view = (city) =>
`${BASE}/?welcome=0#lat=${city.lat}&lon=${city.lon}&alt=600&heading=${city.heading}&pitch=-45`; `${BASE}/?welcome=0#lat=${city.lat}&lon=${city.lon}&alt=600&heading=${city.heading}&pitch=-45`;
+56 -1
View File
@@ -1,6 +1,10 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { MACOS_CHROME, chromeExecutablePath } from '../scripts/qa-chrome.mjs'; import {
MACOS_CHROME,
chromeExecutablePath,
captureQaScreenshot,
} from '../scripts/qa-chrome.mjs';
test('gates keep macOS Chrome where it is installed', () => { test('gates keep macOS Chrome where it is installed', () => {
assert.equal( assert.equal(
@@ -25,3 +29,54 @@ test('an explicit executable path wins', () => {
'C:\\Chrome\\chrome.exe', 'C:\\Chrome\\chrome.exe',
); );
}); });
test('screenshots restore minimized test windows before capture and detach their session', async () => {
const calls = [];
const session = {
send: async (method, params) => {
calls.push([method, params]);
return { windowId: 7, bounds: { windowState: 'minimized' } };
},
detach: async () => calls.push(['detach']),
};
const page = {
createCDPSession: async () => session,
bringToFront: async () => calls.push(['front']),
screenshot: async (options) => {
calls.push(['capture', options]);
return 'pixels';
},
};
assert.equal(
await captureQaScreenshot(page, { path: 'proof.png' }),
'pixels',
);
assert.deepEqual(
calls.map((c) => c[0]),
[
'Browser.getWindowForTarget',
'Browser.setWindowBounds',
'front',
'detach',
'capture',
],
);
assert.deepEqual(calls[1][1], {
windowId: 7,
bounds: { windowState: 'normal' },
});
});
test('headless capture does not manipulate a native window', async () => {
const options = { path: 'proof.png' };
const page = {
screenshot: async (supplied) => {
assert.equal(supplied, options);
return 'pixels';
},
};
assert.equal(
await captureQaScreenshot(page, options, { headful: false }),
'pixels',
);
});