2026-04-06 19:36:13 +02:00
|
|
|
import { spawn } from 'child_process';
|
|
|
|
|
import { join, dirname } from 'path';
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
|
|
|
|
|
// Path to sitespeed.io — works both locally and in Docker
|
|
|
|
|
const SITESPEED_BIN = process.env.SITESPEED_BIN ||
|
|
|
|
|
join(__dirname, '..', 'sitespeed.io', 'bin', 'sitespeed.js');
|
|
|
|
|
|
|
|
|
|
export function runTest(job, onLine) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const outputFolder = join(__dirname, 'reports', job.id);
|
|
|
|
|
|
|
|
|
|
const args = [
|
|
|
|
|
SITESPEED_BIN,
|
|
|
|
|
job.url,
|
|
|
|
|
'--browser', job.browser,
|
|
|
|
|
'-n', String(job.runs),
|
|
|
|
|
'--outputFolder', outputFolder,
|
|
|
|
|
'--json',
|
|
|
|
|
'--sustainable.enable',
|
|
|
|
|
'--axe.enable',
|
|
|
|
|
'--coach',
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (job.mobile) {
|
|
|
|
|
args.push('--mobile');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 19:54:19 +02:00
|
|
|
// Chrome needs these flags when running inside Docker
|
2026-04-06 19:36:13 +02:00
|
|
|
if (process.env.IN_DOCKER) {
|
|
|
|
|
args.push('--browsertime.chrome.args', 'no-sandbox');
|
|
|
|
|
args.push('--browsertime.chrome.args', 'disable-dev-shm-usage');
|
2026-04-06 19:54:19 +02:00
|
|
|
// Disable GPU compositing — not needed in Xvfb and can cause crashes
|
|
|
|
|
args.push('--browsertime.chrome.args', 'disable-gpu');
|
2026-04-06 19:36:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onLine(`[runner] Starting: node ${args.slice(0, 3).join(' ')} ...`);
|
|
|
|
|
onLine(`[runner] Output folder: ${outputFolder}`);
|
|
|
|
|
|
|
|
|
|
const child = spawn('node', args, {
|
|
|
|
|
cwd: __dirname,
|
|
|
|
|
env: { ...process.env, DISPLAY: process.env.DISPLAY || ':99' },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.stdout.on('data', (data) => {
|
|
|
|
|
const lines = data.toString().split('\n').filter(Boolean);
|
|
|
|
|
for (const line of lines) onLine(line);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.stderr.on('data', (data) => {
|
|
|
|
|
const lines = data.toString().split('\n').filter(Boolean);
|
|
|
|
|
for (const line of lines) onLine(`[stderr] ${line}`);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.on('close', (code) => {
|
|
|
|
|
if (code === 0) {
|
|
|
|
|
resolve(outputFolder);
|
|
|
|
|
} else {
|
|
|
|
|
reject(new Error(`sitespeed.io exited with code ${code}`));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.on('error', (err) => {
|
|
|
|
|
reject(new Error(`Failed to spawn sitespeed.io: ${err.message}`));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|