95 lines
3.1 KiB
JavaScript
95 lines
3.1 KiB
JavaScript
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||
import { tmpdir } from "node:os";
|
||
import { join } from "node:path";
|
||
import { parseArgs } from "node:util";
|
||
import lighthouse from "lighthouse";
|
||
import { launch } from "chrome-launcher";
|
||
|
||
const { values } = parseArgs({
|
||
options: {
|
||
url: { type: "string", default: "http://127.0.0.1:4173/" },
|
||
runs: { type: "string", default: "3" },
|
||
dark: { type: "boolean", default: false },
|
||
},
|
||
});
|
||
const url = new URL(values.url);
|
||
const runs = Number(values.runs);
|
||
if (!Number.isInteger(runs) || runs < 1 || runs > 10)
|
||
throw new Error("--runs must be 1–10");
|
||
const directory = ".artifacts/lighthouse";
|
||
await mkdir(directory, { recursive: true });
|
||
const name = `${url.pathname === "/admin" ? "admin" : "redeem"}-${values.dark ? "dark" : "light"}`;
|
||
const results = [];
|
||
for (let index = 1; index <= runs; index++) {
|
||
// New empty profile, no extensions, no warm cache or saved theme on every run.
|
||
const profile = await mkdtemp(join(tmpdir(), "xgift-lighthouse-"));
|
||
let chrome;
|
||
try {
|
||
chrome = await launch({
|
||
userDataDir: profile,
|
||
chromeFlags: [
|
||
"--headless",
|
||
"--disable-extensions",
|
||
...(values.dark ? ["--force-dark-mode"] : []),
|
||
],
|
||
});
|
||
const result = await lighthouse(url.href, {
|
||
port: chrome.port,
|
||
logLevel: "error",
|
||
output: ["html", "json"],
|
||
onlyCategories: ["performance", "accessibility", "best-practices", "seo"],
|
||
// Keep Lighthouse's standard mobile device, network and CPU throttling.
|
||
});
|
||
if (!result || result.lhr.runtimeError)
|
||
throw new Error(result?.lhr.runtimeError?.message || "No audit result");
|
||
const prefix = `${directory}/${name}-${index}`;
|
||
await writeFile(`${prefix}.html`, result.report[0]);
|
||
await writeFile(`${prefix}.json`, result.report[1]);
|
||
const scores = Object.fromEntries(
|
||
Object.entries(result.lhr.categories).map(([key, category]) => [
|
||
key,
|
||
Math.round(category.score * 100),
|
||
]),
|
||
);
|
||
const entry = {
|
||
run: index,
|
||
scores,
|
||
metrics: Object.fromEntries(
|
||
[
|
||
"first-contentful-paint",
|
||
"largest-contentful-paint",
|
||
"total-blocking-time",
|
||
"cumulative-layout-shift",
|
||
].map((key) => [key, result.lhr.audits[key].numericValue]),
|
||
),
|
||
report: `${prefix}.html`,
|
||
warnings: result.lhr.runWarnings,
|
||
};
|
||
results.push(entry);
|
||
console.log(JSON.stringify(entry));
|
||
if (Object.values(scores).some((score) => score < 97)) process.exitCode = 1;
|
||
} finally {
|
||
await chrome?.kill();
|
||
await rm(profile, { recursive: true, force: true });
|
||
}
|
||
}
|
||
await writeFile(
|
||
`${directory}/${name}-summary.json`,
|
||
JSON.stringify(
|
||
{
|
||
url: url.href,
|
||
theme: values.dark ? "system dark" : "system default",
|
||
profile: "new empty temporary profile for each run; extensions disabled",
|
||
preset: "Lighthouse default mobile simulated throttling",
|
||
target: 97,
|
||
results,
|
||
},
|
||
null,
|
||
2,
|
||
),
|
||
);
|
||
if (process.exitCode)
|
||
console.error(
|
||
"At least one category scored below 97. See .artifacts/lighthouse reports.",
|
||
);
|