fix(qa): run the macOS-only gates on Windows and Linux (#1061)

Seven QA scripts hard-coded macOS Google Chrome. They now share chromeExecutablePath(): PUPPETEER_EXECUTABLE_PATH when set, then macOS Chrome when installed, otherwise Puppeteer's managed browser. The cables screenshot path is built with fileURLToPath so it works on Windows. qa-overlay-baseline keeps its fixed Chrome because its baselines record the executable.

Co-authored-by: Sameh Khamis <[email protected]>
This commit is contained in:
quanticsoul4772andSameh Khamis authored and GitHub committed 2026-10-14 15:36:35 -07:00
1 parent 504d56ba2b
commit e50bc45674
11 files changed
+67 -15

No files matched your search

+2
View File
@@ -934,6 +934,8 @@
"scripts/qa-street-level.mjs", "scripts/qa-street-level.mjs",
"src/tooling/streetLevelQa.test.mjs", "src/tooling/streetLevelQa.test.mjs",
"scripts/qa-browserEvidence.mjs", "scripts/qa-browserEvidence.mjs",
"scripts/qa-chrome.mjs",
"src/qaChrome.test.mjs",
"scripts/fixtures/street-level/mapillaryGraph.mjs", "scripts/fixtures/street-level/mapillaryGraph.mjs",
"scripts/fixtures/street-level/kartaView.mjs", "scripts/fixtures/street-level/kartaView.mjs",
"src/layers/streetLevel/filter.test.mjs", "src/layers/streetLevel/filter.test.mjs",
+2 -2
View File
@@ -1,6 +1,7 @@
import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'; import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url'; import { fileURLToPath, pathToFileURL } from 'node:url';
import { chromeExecutablePath } from './qa-chrome.mjs';
import { import {
planeSupportPoints, planeSupportPoints,
poseHash, poseHash,
@@ -389,8 +390,7 @@ async function main() {
const { default: puppeteer } = await import('puppeteer'); const { default: puppeteer } = await import('puppeteer');
browser = await puppeteer.launch({ browser = await puppeteer.launch({
headless: false, headless: false,
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
protocolTimeout: 600000, protocolTimeout: 600000,
args: [ args: [
'--no-sandbox', '--no-sandbox',
+4 -1
View File
@@ -6,6 +6,7 @@
*/ */
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { mkdirSync } from 'node:fs'; import { mkdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const argv = process.argv; const argv = process.argv;
const url = argv.includes('--url') ? argv[argv.indexOf('--url') + 1] : 'http://localhost:4214'; const url = argv.includes('--url') ? argv[argv.indexOf('--url') + 1] : 'http://localhost:4214';
@@ -65,7 +66,9 @@ try {
requestAnimationFrame(tick); requestAnimationFrame(tick);
})); }));
await new Promise((r) => setTimeout(r, 1_000)); await new Promise((r) => setTimeout(r, 1_000));
const path = new URL(`../qa-shots/cables-${tag}-${view.name}.png`, import.meta.url).pathname; const path = fileURLToPath(
new URL(`../qa-shots/cables-${tag}-${view.name}.png`, import.meta.url),
);
await page.screenshot({ path }); await page.screenshot({ path });
console.log(`saved ${path}`); console.log(`saved ${path}`);
} }
+20
View File
@@ -0,0 +1,20 @@
/**
* The browser for gates that drive an installed Chrome. These gates were
* written against macOS Google Chrome, which Windows and Linux do not have.
*/
import { existsSync } from 'node:fs';
export const MACOS_CHROME =
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
/**
* PUPPETEER_EXECUTABLE_PATH when set, then macOS Google Chrome when it is
* installed. Otherwise undefined, so Puppeteer launches the browser it manages.
*/
export function chromeExecutablePath({
env = process.env,
exists = existsSync,
} = {}) {
if (env.PUPPETEER_EXECUTABLE_PATH) return env.PUPPETEER_EXECUTABLE_PATH;
return exists(MACOS_CHROME) ? MACOS_CHROME : undefined;
}
+2 -2
View File
@@ -10,6 +10,7 @@ import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const args = process.argv.slice(2); const args = process.argv.slice(2);
@@ -35,8 +36,7 @@ const check = (name, passed, detail = '') => {
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: false, headless: false,
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
+2 -2
View File
@@ -3,6 +3,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs';
const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173'; const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173';
const SHOT_DIR = process.env.QA_SHOT_DIR || 'qa-shots/draw'; const SHOT_DIR = process.env.QA_SHOT_DIR || 'qa-shots/draw';
@@ -22,8 +23,7 @@ const browser = await puppeteer.launch({
headless: !HEADFUL, headless: !HEADFUL,
...(HEADFUL ...(HEADFUL
? { ? {
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
} }
: {}), : {}),
args: [ args: [
+2 -2
View File
@@ -20,6 +20,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs';
const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173'; const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:4173';
const SHOT_DIR = process.env.QA_SHOT_DIR || 'qa-shots/recent-imagery'; const SHOT_DIR = process.env.QA_SHOT_DIR || 'qa-shots/recent-imagery';
@@ -54,8 +55,7 @@ const browser = await puppeteer.launch({
headless: !HEADFUL, headless: !HEADFUL,
...(HEADFUL ...(HEADFUL
? { ? {
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
} }
: {}), : {}),
args: [ args: [
+2 -2
View File
@@ -1,11 +1,11 @@
import puppeteer from 'puppeteer'; import puppeteer from 'puppeteer';
import { chromeExecutablePath } from './qa-chrome.mjs';
/** Prove parked and moving headings in the real scene, without live feeds. */ /** Prove parked and moving headings in the real scene, without live feeds. */
export async function runTransitHeadingRegression(base, check) { export async function runTransitHeadingRegression(base, check) {
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: true, headless: true,
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
args: ['--no-sandbox', '--disable-background-timer-throttling'], args: ['--no-sandbox', '--disable-background-timer-throttling'],
}); });
try { try {
+2 -2
View File
@@ -1,5 +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 { 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';
@@ -8,8 +9,7 @@ const shots = process.env.QA_SHOTS || 'qa-shots/transit-recovery';
await mkdir(shots, { recursive: true }); await mkdir(shots, { recursive: true });
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: false, headless: false,
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
args: [ args: [
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
'--disable-renderer-backgrounding', '--disable-renderer-backgrounding',
+2 -2
View File
@@ -36,6 +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 { 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';
@@ -262,8 +263,7 @@ await mkdir(SHOTS, { recursive: true });
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: false, headless: false,
protocolTimeout: 150000, protocolTimeout: 150000,
executablePath: executablePath: chromeExecutablePath(),
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--disable-background-timer-throttling', '--disable-background-timer-throttling',
+27
View File
@@ -0,0 +1,27 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { MACOS_CHROME, chromeExecutablePath } from '../scripts/qa-chrome.mjs';
test('gates keep macOS Chrome where it is installed', () => {
assert.equal(
chromeExecutablePath({ env: {}, exists: (p) => p === MACOS_CHROME }),
MACOS_CHROME,
);
});
test('gates fall back to Puppeteer’s browser without macOS Chrome', () => {
assert.equal(
chromeExecutablePath({ env: {}, exists: () => false }),
undefined,
);
});
test('an explicit executable path wins', () => {
assert.equal(
chromeExecutablePath({
env: { PUPPETEER_EXECUTABLE_PATH: 'C:\\Chrome\\chrome.exe' },
exists: () => true,
}),
'C:\\Chrome\\chrome.exe',
);
});