Files
speedboard/runner.js
Malin 7865252fa2 fix: add --plugins.add analysisstorer to sitespeed.io args
analysisstorer is not in the default plugin set, so pageSummary JSON
files were never written. This plugin writes all message data to disk
(browsertime.pageSummary.json, coach.pageSummary.json, etc.) which
our parser reads to extract metrics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 11:29:41 +02:00

104 lines
3.5 KiB
JavaScript

import { spawn, execSync } from 'child_process';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { existsSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const LOCAL_BIN = join(__dirname, '..', 'sitespeed.io', 'bin', 'sitespeed.js');
const REPORTS_DIR = process.env.REPORTS_DIR || join(__dirname, 'reports');
export function runTest(job, onLine) {
return new Promise((resolve, reject) => {
const outputFolder = join(REPORTS_DIR, job.id);
const isDocker = !!process.env.IN_DOCKER;
// Log to docker logs (stdout of main process) so it appears in `docker logs`
console.log(`[runner] REPORTS_DIR=${REPORTS_DIR}`);
console.log(`[runner] outputFolder=${outputFolder}`);
const sitespeedArgs = [
job.url,
'--browser', job.browser,
'-n', String(job.runs),
'--outputFolder', outputFolder,
'--plugins.add', 'analysisstorer', // not in default set — needed to write pageSummary JSON
'--sustainable.enable',
'--sustainable.useGreenWebHostingAPI',
'--axe.enable',
'--coach',
];
if (job.mobile) sitespeedArgs.push('--mobile');
if (isDocker) {
sitespeedArgs.push('--browsertime.chrome.args', 'no-sandbox');
sitespeedArgs.push('--browsertime.chrome.args', 'disable-dev-shm-usage');
sitespeedArgs.push('--browsertime.chrome.args', 'disable-gpu');
}
const env = { ...process.env };
let child;
if (isDocker) {
const bin = process.env.SITESPEED_BIN;
if (!bin) {
return reject(new Error(
'SITESPEED_BIN is not set. Check build logs for "Build-time sitespeed.js found at:"'
));
}
console.log(`[runner] spawning: node ${bin} ${sitespeedArgs.slice(0,3).join(' ')} ...`);
child = spawn('node', [bin, ...sitespeedArgs], { cwd: __dirname, env });
} else {
if (!existsSync(LOCAL_BIN)) {
return reject(new Error(`Local sitespeed.io not found at ${LOCAL_BIN}`));
}
child = spawn('node', [LOCAL_BIN, ...sitespeedArgs], { cwd: __dirname, env });
}
const allLines = [];
child.stdout.on('data', (data) => {
const lines = data.toString().split('\n').filter(Boolean);
for (const line of lines) {
allLines.push(line);
onLine(line);
// Mirror INFO/ERROR lines to docker logs too
if (line.includes('INFO:') || line.includes('ERROR:') || line.includes('stored in')) {
console.log('[sitespeed]', line);
}
}
});
child.stderr.on('data', (data) => {
const lines = data.toString().split('\n').filter(Boolean);
for (const line of lines) {
allLines.push('[stderr] ' + line);
onLine('[stderr] ' + line);
if (line.includes('ERROR:')) console.error('[sitespeed stderr]', line);
}
});
child.on('close', (code) => {
if (code === 0) {
// Log what was actually written so we can debug the parser
try {
const found = execSync(
`find "${outputFolder}" -name "*.json" 2>/dev/null | head -30`,
{ encoding: 'utf8' }
).trim();
console.log(`[runner] JSON files written:\n${found || '(none found)'}`);
} catch {}
resolve(outputFolder);
} else {
const tail = allLines.slice(-20).join('\n');
reject(new Error(`sitespeed.io exited with code ${code}\n${tail}`));
}
});
child.on('error', (err) => {
reject(new Error(`Failed to spawn process: ${err.message}`));
});
});
}