diff --git a/changelog.d/1281-traffic-issue-fix.md b/changelog.d/1281-traffic-issue-fix.md index ade62efe4..2c4d1900c 100644 --- a/changelog.d/1281-traffic-issue-fix.md +++ b/changelog.d/1281-traffic-issue-fix.md @@ -1,2 +1,4 @@ - Hold rendering only for animated content (asturksever, #1281). - Exercise current vector road sources and published camera bounds in Traffic fixture QA (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). diff --git a/scripts/qa-traffic.mjs b/scripts/qa-traffic.mjs index 5da4b7db7..bb00596ce 100644 --- a/scripts/qa-traffic.mjs +++ b/scripts/qa-traffic.mjs @@ -82,6 +82,10 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); /** Enable traffic + teleport, then poll the layer until settled. */ async function settleTraffic(page, view, { minCount = 1, timeoutS = 30 } = {}) { + // The app deliberately stops rendering hidden pages. Programmatic camera + // changes do not activate a headful window the way a real UI click does. + await page.bringToFront(); + await page.waitForFunction(() => document.visibilityState === 'visible'); return page.evaluate(async (v, minC, tS) => { const gev = window.__godsEyeView; const dm = gev.dataManager; @@ -99,6 +103,7 @@ async function settleTraffic(page, view, { minCount = 1, timeoutS = 30 } = {}) { }); let s = null; for (let i = 0; i < tS; i++) { + if (document.visibilityState !== 'visible') throw new Error('Traffic QA page became hidden; rendering is intentionally paused'); await new Promise((r) => setTimeout(r, 1000)); s = mod.getStats(); // Retained roads from the previous city can satisfy count/loading before @@ -140,7 +145,7 @@ async function main() { '--no-sandbox', '--disable-setuid-sandbox', ...(!HEADFUL ? ['--use-gl=angle', '--use-angle=swiftshader'] : []), '--disable-backgrounding-occluded-windows', - '--disable-dev-shm-usage', '--disable-web-security', + '--disable-dev-shm-usage', '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--window-size=1440,900', ], @@ -149,6 +154,10 @@ async function main() { let exitCode = 0; try { const page = await browser.newPage(); + // Keep this visible-page regression deterministic even if the OS starts + // Chrome minimized. Use browser focus emulation, not patched DOM getters. + const focusSession = await page.createCDPSession(); + await focusSession.send('Emulation.setFocusEmulationEnabled', { enabled: true }); const onFixture = (request) => { const response = fixtureResponse(request); if (response) void request.respond(response); else void request.continue(); @@ -162,6 +171,9 @@ async function main() { if (req.url().includes('/api/tomtom/flow/')) flowRequests.push(req.url()); }); const trafficLogs = []; + const pageErrors = []; + 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', (msg) => { const t = msg.text(); if (t.includes('[Data:Traffic]')) trafficLogs.push(t); @@ -319,6 +331,7 @@ async function main() { await sleep(1000); await page.screenshot({ path: path.join(SHOTS_DIR, 'traffic-sim-keyless.png') }); } + record('BROWSER: no uncaught page exceptions', pageErrors.length === 0, pageErrors.join('; ')); } catch (e) { console.error('\x1b[31mHarness error:\x1b[0m', e); exitCode = 3; diff --git a/scripts/traffic-fixtures.mjs b/scripts/traffic-fixtures.mjs index af51487db..2f94993cc 100644 --- a/scripts/traffic-fixtures.mjs +++ b/scripts/traffic-fixtures.mjs @@ -1,7 +1,9 @@ import { PbfWriter } from 'pbf'; +const corsHeaders = { 'access-control-allow-origin': '*' }; const json = (body) => ({ status: 200, + headers: corsHeaders, contentType: 'application/json', body: JSON.stringify(body), }); @@ -80,6 +82,7 @@ export function trafficFixtureResponse(request) { return { status: 200, contentType: 'application/octet-stream', + headers: corsHeaders, body: flatTerrain, }; throw new Error(`Unexpected terrain fixture request: ${url.pathname}`); @@ -93,6 +96,7 @@ export function trafficFixtureResponse(request) { return { status: 200, contentType: 'application/x-protobuf', + headers: corsHeaders, body: trafficFixtureTile(Number(flow[1]), true), }; if (url.hostname !== 'tiles.openfreemap.org') return null; @@ -109,6 +113,7 @@ export function trafficFixtureResponse(request) { return { status: 200, contentType: 'application/x-protobuf', + headers: corsHeaders, body: trafficFixtureTile(Number(road[1])), }; } diff --git a/src/tooling/trafficFixtures.test.mjs b/src/tooling/trafficFixtures.test.mjs index 308c0aaa6..e02b959e7 100644 --- a/src/tooling/trafficFixtures.test.mjs +++ b/src/tooling/trafficFixtures.test.mjs @@ -33,9 +33,9 @@ test('traffic fixtures provide aligned current road tiles and partial flow cover test('fixture TileJSON and every road/flow request stay on intercepted sources', () => { const request = (url) => trafficFixtureResponse({ url: () => url }); - const metadata = JSON.parse( - request('https://tiles.openfreemap.org/planet').body, - ); + const tileJson = request('https://tiles.openfreemap.org/planet'); + assert.equal(tileJson.headers['access-control-allow-origin'], '*'); + const metadata = JSON.parse(tileJson.body); const tileUrl = metadata.tiles[0] .replace('{z}', '14') .replace('{x}', '935')