mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
test: keep Traffic interception off workers and restore screenshot windows
This commit is contained in:
1 parent
116f28a525
commit
168d491038
6 files changed
+230
-27
No files matched your search
@@ -3,3 +3,5 @@
|
|||||||
- Establish browser-level foreground visibility for Traffic QA and reject hidden-page samples (asturksever, #1281).
|
- Establish browser-level foreground visibility for Traffic QA and reject hidden-page samples (asturksever, #1281).
|
||||||
- Supply CORS headers on cross-origin traffic fixtures and report browser exceptions without disabling browser security (asturksever, #1281).
|
- Supply CORS headers on cross-origin traffic fixtures and report browser exceptions without disabling browser security (asturksever, #1281).
|
||||||
- Isolate the Traffic keyless browser scenario from the keyed page’s workers and intercepted cache (asturksever, #1281).
|
- Isolate the Traffic keyless browser scenario from the keyed page’s workers and intercepted cache (asturksever, #1281).
|
||||||
|
- Restore minimized test windows before screenshots so native compositor pauses cannot stall browser acceptance (asturksever, #1281).
|
||||||
|
- Intercept only Traffic provider URLs, leaving Cesium worker and module loading untouched (asturksever, #1281).
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
+11
-26
@@ -31,6 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
|
import { captureQaScreenshot } from './qa-chrome.mjs';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
@@ -49,7 +50,7 @@ appUrl.searchParams.set('welcome', '0');
|
|||||||
const APP_URL = appUrl.href;
|
const APP_URL = appUrl.href;
|
||||||
const HEADFUL = argv.includes('--headful');
|
const HEADFUL = argv.includes('--headful');
|
||||||
const FIXTURES = argv.includes('--fixtures');
|
const FIXTURES = argv.includes('--fixtures');
|
||||||
const fixtureResponse = FIXTURES ? (await import('./traffic-fixtures.mjs')).trafficFixtureResponse : () => null;
|
const { installTrafficRoutes } = await import('./traffic-fixtures.mjs');
|
||||||
|
|
||||||
const CHROME_EXECUTABLE_CANDIDATES = [
|
const CHROME_EXECUTABLE_CANDIDATES = [
|
||||||
process.env.PUPPETEER_EXECUTABLE_PATH,
|
process.env.PUPPETEER_EXECUTABLE_PATH,
|
||||||
@@ -158,11 +159,6 @@ async function main() {
|
|||||||
// Chrome minimized. Use browser focus emulation, not patched DOM getters.
|
// Chrome minimized. Use browser focus emulation, not patched DOM getters.
|
||||||
const focusSession = await page.createCDPSession();
|
const focusSession = await page.createCDPSession();
|
||||||
await focusSession.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
await focusSession.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
const onFixture = (request) => {
|
|
||||||
const response = fixtureResponse(request);
|
|
||||||
if (response) void request.respond(response); else void request.continue();
|
|
||||||
};
|
|
||||||
if (FIXTURES) { await page.setCacheEnabled(false); await page.setRequestInterception(true); page.on('request', onFixture); }
|
|
||||||
await page.setViewport({ width: 1440, height: 900 });
|
await page.setViewport({ width: 1440, height: 900 });
|
||||||
|
|
||||||
// Track flow-tile requests + traffic console lines for (ii)/(iii)/(v).
|
// Track flow-tile requests + traffic console lines for (ii)/(iii)/(v).
|
||||||
@@ -172,6 +168,7 @@ async function main() {
|
|||||||
});
|
});
|
||||||
const trafficLogs = [];
|
const trafficLogs = [];
|
||||||
const pageErrors = [];
|
const pageErrors = [];
|
||||||
|
if (FIXTURES) await installTrafficRoutes(page, { origin: appUrl.origin, fixtures: true, onError: error => pageErrors.push(error.message) });
|
||||||
page.on('pageerror', error => { pageErrors.push(error.message); console.error('PAGE ERROR:', error.message); });
|
page.on('pageerror', error => { pageErrors.push(error.message); console.error('PAGE ERROR:', error.message); });
|
||||||
page.on('console', message => { if (message.type() === 'error') console.error('BROWSER ERROR:', message.text()); });
|
page.on('console', message => { if (message.type() === 'error') console.error('BROWSER ERROR:', message.text()); });
|
||||||
page.on('console', (msg) => {
|
page.on('console', (msg) => {
|
||||||
@@ -210,7 +207,7 @@ async function main() {
|
|||||||
`coverage=${mumbai.flowCoveragePct}% tiles=${mumbai.tilesFetched}`);
|
`coverage=${mumbai.flowCoveragePct}% tiles=${mumbai.tilesFetched}`);
|
||||||
if (mumbai.mode !== 'live' || !(mumbai.count > 0) || !(colored > 0)) exitCode = 1;
|
if (mumbai.mode !== 'live' || !(mumbai.count > 0) || !(colored > 0)) exitCode = 1;
|
||||||
await sleep(1200);
|
await sleep(1200);
|
||||||
await page.screenshot({ path: path.join(SHOTS_DIR, 'traffic-live-flow.png') });
|
await captureQaScreenshot(page, { path: path.join(SHOTS_DIR, 'traffic-live-flow.png') }, { headful: HEADFUL });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── (iv) uncovered-roads param — hide vs sim ─────────────────────────────
|
// ── (iv) uncovered-roads param — hide vs sim ─────────────────────────────
|
||||||
@@ -243,7 +240,7 @@ async function main() {
|
|||||||
(hid.flowBuckets?.sim || 0) === 0 && hid.count > 0,
|
(hid.flowBuckets?.sim || 0) === 0 && hid.count > 0,
|
||||||
`count=${hid.count} sim=${hid.flowBuckets?.sim}`);
|
`count=${hid.count} sim=${hid.flowBuckets?.sim}`);
|
||||||
if ((hid.flowBuckets?.sim || 0) !== 0) exitCode = 1;
|
if ((hid.flowBuckets?.sim || 0) !== 0) exitCode = 1;
|
||||||
await page.screenshot({ path: path.join(SHOTS_DIR, 'traffic-live-hide-mode.png') });
|
await captureQaScreenshot(page, { path: path.join(SHOTS_DIR, 'traffic-live-hide-mode.png') }, { headful: HEADFUL });
|
||||||
} else {
|
} else {
|
||||||
record('PARAM: hide mode renders zero sim (white) dots', null,
|
record('PARAM: hide mode renders zero sim (white) dots', null,
|
||||||
'view had 100% coverage (no sim dots to hide) — inconclusive here, covered by unit tests');
|
'view had 100% coverage (no sim dots to hide) — inconclusive here, covered by unit tests');
|
||||||
@@ -265,7 +262,7 @@ async function main() {
|
|||||||
}
|
}
|
||||||
record('C4: oblique fetch box centers on the look-at point', ok, detail);
|
record('C4: oblique fetch box centers on the look-at point', ok, detail);
|
||||||
if (!ok) exitCode = 1;
|
if (!ok) exitCode = 1;
|
||||||
await page.screenshot({ path: path.join(SHOTS_DIR, 'traffic-c4-oblique.png') });
|
await captureQaScreenshot(page, { path: path.join(SHOTS_DIR, 'traffic-c4-oblique.png') }, { headful: HEADFUL });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── (iii) budget honesty ─────────────────────────────────────────────────
|
// ── (iii) budget honesty ─────────────────────────────────────────────────
|
||||||
@@ -290,23 +287,11 @@ async function main() {
|
|||||||
await keylessFocus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
await keylessFocus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
page.on('pageerror', error => { pageErrors.push(error.message); console.error('PAGE ERROR:', error.message); });
|
page.on('pageerror', error => { pageErrors.push(error.message); console.error('PAGE ERROR:', error.message); });
|
||||||
page.on('console', message => { if (message.type() === 'error') console.error('BROWSER ERROR:', message.text()); });
|
page.on('console', message => { if (message.type() === 'error') console.error('BROWSER ERROR:', message.text()); });
|
||||||
await page.setCacheEnabled(false);
|
|
||||||
await page.setRequestInterception(true);
|
|
||||||
const keylessFlowReqs = [];
|
const keylessFlowReqs = [];
|
||||||
page.on('request', (req) => {
|
await installTrafficRoutes(page, {
|
||||||
const url = req.url();
|
origin: appUrl.origin, fixtures: FIXTURES, keyless: true,
|
||||||
if (url.includes('/api/tomtom/status')) {
|
onFlow: url => keylessFlowReqs.push(url),
|
||||||
req.respond({ status: 200, contentType: 'application/json', body: JSON.stringify({ hasKey: false }) });
|
onError: error => pageErrors.push(error.message),
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (url.includes('/api/tomtom/flow/')) {
|
|
||||||
keylessFlowReqs.push(url);
|
|
||||||
req.respond({ status: 503, contentType: 'application/json', body: JSON.stringify({ error: 'no_key' }) });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const response = fixtureResponse(req);
|
|
||||||
if (response) { void req.respond(response); return; }
|
|
||||||
try { req.continue(); } catch { /* already handled */ }
|
|
||||||
});
|
});
|
||||||
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
await page.goto(APP_URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
@@ -338,7 +323,7 @@ async function main() {
|
|||||||
exitCode = 1;
|
exitCode = 1;
|
||||||
}
|
}
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
await page.screenshot({ path: path.join(SHOTS_DIR, 'traffic-sim-keyless.png') });
|
await captureQaScreenshot(page, { path: path.join(SHOTS_DIR, 'traffic-sim-keyless.png') }, { headful: HEADFUL });
|
||||||
}
|
}
|
||||||
record('BROWSER: no uncaught page exceptions', pageErrors.length === 0, pageErrors.join('; '));
|
record('BROWSER: no uncaught page exceptions', pageErrors.length === 0, pageErrors.join('; '));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -117,3 +117,59 @@ export function trafficFixtureResponse(request) {
|
|||||||
body: trafficFixtureTile(Number(road[1])),
|
body: trafficFixtureTile(Number(road[1])),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Intercept provider URLs only; Cesium worker/module loading stays untouched. */
|
||||||
|
export async function installTrafficRoutes(
|
||||||
|
page,
|
||||||
|
{
|
||||||
|
origin,
|
||||||
|
fixtures = false,
|
||||||
|
keyless = false,
|
||||||
|
onFlow = () => {},
|
||||||
|
onError = () => {},
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const session = await page.createCDPSession();
|
||||||
|
const patterns = [
|
||||||
|
`${origin}/api/tomtom/status*`,
|
||||||
|
`${origin}/api/tomtom/flow/*`,
|
||||||
|
...(fixtures
|
||||||
|
? ['https://tiles.openfreemap.org/*', 'https://terrain.reearth.land/*']
|
||||||
|
: []),
|
||||||
|
].map((urlPattern) => ({ urlPattern, requestStage: 'Request' }));
|
||||||
|
session.on('Fetch.requestPaused', async ({ requestId, request }) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
let response;
|
||||||
|
if (keyless && url.pathname === '/api/tomtom/status') {
|
||||||
|
response = json({ hasKey: false });
|
||||||
|
} else if (keyless && url.pathname.startsWith('/api/tomtom/flow/')) {
|
||||||
|
onFlow(request.url);
|
||||||
|
response = { ...json({ error: 'no_key' }), status: 503 };
|
||||||
|
} else if (fixtures) {
|
||||||
|
response = trafficFixtureResponse({ url: () => request.url });
|
||||||
|
}
|
||||||
|
if (!response) {
|
||||||
|
await session.send('Fetch.continueRequest', { requestId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await session.send('Fetch.fulfillRequest', {
|
||||||
|
requestId,
|
||||||
|
responseCode: response.status,
|
||||||
|
responseHeaders: Object.entries({
|
||||||
|
'content-type': response.contentType,
|
||||||
|
...response.headers,
|
||||||
|
}).map(([name, value]) => ({ name, value })),
|
||||||
|
body: Buffer.from(response.body).toString('base64'),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (page.isClosed()) return;
|
||||||
|
onError(error);
|
||||||
|
await session
|
||||||
|
.send('Fetch.failRequest', { requestId, errorReason: 'Failed' })
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await session.send('Fetch.enable', { patterns });
|
||||||
|
return session;
|
||||||
|
}
|
||||||
+56
-1
@@ -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',
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -90,3 +90,81 @@ test('flat terrain fixture decodes through Cesium and advertises no network chil
|
|||||||
await new Promise((resolve) => server.close(resolve));
|
await new Promise((resolve) => server.close(resolve));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('fixture interception targets provider URLs and leaves app workers alone', async () => {
|
||||||
|
const { installTrafficRoutes } =
|
||||||
|
await import('../../scripts/traffic-fixtures.mjs');
|
||||||
|
const sent = [];
|
||||||
|
let paused;
|
||||||
|
const session = {
|
||||||
|
on: (_, handler) => {
|
||||||
|
paused = handler;
|
||||||
|
},
|
||||||
|
send: async (name, args) => {
|
||||||
|
sent.push({ name, args });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await installTrafficRoutes(
|
||||||
|
{ createCDPSession: async () => session, isClosed: () => false },
|
||||||
|
{ origin: 'http://localhost:4173', fixtures: true },
|
||||||
|
);
|
||||||
|
const patterns = sent[0].args.patterns.map((p) => p.urlPattern);
|
||||||
|
assert.deepEqual(patterns, [
|
||||||
|
'http://localhost:4173/api/tomtom/status*',
|
||||||
|
'http://localhost:4173/api/tomtom/flow/*',
|
||||||
|
'https://tiles.openfreemap.org/*',
|
||||||
|
'https://terrain.reearth.land/*',
|
||||||
|
]);
|
||||||
|
await paused({
|
||||||
|
requestId: 'terrain',
|
||||||
|
request: { url: 'https://terrain.reearth.land/layer.json' },
|
||||||
|
});
|
||||||
|
const reply = sent.at(-1);
|
||||||
|
assert.equal(reply.name, 'Fetch.fulfillRequest');
|
||||||
|
assert.equal(
|
||||||
|
JSON.parse(Buffer.from(reply.args.body, 'base64')).format,
|
||||||
|
'heightmap-1.0',
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
reply.args.responseHeaders.some(
|
||||||
|
(h) => h.name === 'access-control-allow-origin' && h.value === '*',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('keyless interception refuses and records unexpected flow requests', async () => {
|
||||||
|
const { installTrafficRoutes } =
|
||||||
|
await import('../../scripts/traffic-fixtures.mjs');
|
||||||
|
const sent = [],
|
||||||
|
flows = [];
|
||||||
|
let paused;
|
||||||
|
const session = {
|
||||||
|
on: (_, handler) => {
|
||||||
|
paused = handler;
|
||||||
|
},
|
||||||
|
send: async (name, args) => {
|
||||||
|
sent.push({ name, args });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await installTrafficRoutes(
|
||||||
|
{ createCDPSession: async () => session, isClosed: () => false },
|
||||||
|
{
|
||||||
|
origin: 'http://localhost:4173',
|
||||||
|
keyless: true,
|
||||||
|
onFlow: (url) => flows.push(url),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
await paused({
|
||||||
|
requestId: 'status',
|
||||||
|
request: { url: 'http://localhost:4173/api/tomtom/status' },
|
||||||
|
});
|
||||||
|
assert.deepEqual(JSON.parse(Buffer.from(sent.at(-1).args.body, 'base64')), {
|
||||||
|
hasKey: false,
|
||||||
|
});
|
||||||
|
await paused({
|
||||||
|
requestId: 'flow',
|
||||||
|
request: { url: 'http://localhost:4173/api/tomtom/flow/12/1/1.pbf' },
|
||||||
|
});
|
||||||
|
assert.equal(sent.at(-1).args.responseCode, 503);
|
||||||
|
assert.equal(flows.length, 1);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user