Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions frontend/scripts/axe-run.cjs
Original file line number Diff line number Diff line change
@@ -1,45 +1,67 @@
#!/usr/bin/env node
/**
* CommonJS Axe + Puppeteer runner to support Node with type: module
* CommonJS Axe + Puppeteer runner to support Node with type: module.
* Launches a headless browser, injects axe-core, runs an accessibility audit,
* and saves the JSON report to the lighthouse/ output directory.
*
* Usage: node scripts/axe-run.cjs <URL>
*/
const fs = require("fs");
const path = require("path");
const puppeteer = require("puppeteer");
const axeSource = require("axe-core").source;

async function run(url) {
// Main runner — opens the URL, injects axe-core, and collects the audit result
const run = async (url) => {
// Launch headless browser with sandbox flags suitable for CI environments
const browser = await puppeteer.launch({
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();

// Navigate to the target URL and wait until network is idle
await page.goto(url, { waitUntil: "networkidle0" });
// Inject axe

// Inject axe-core library into the page context
await page.evaluate(axeSource);
// Run axe with default options

// Run the full axe accessibility audit and collect results
const result = await page.evaluate(async () => await window.axe.run());

// Ensure the output directory exists before writing the report
const outDir = path.resolve(__dirname, "..", "lighthouse");
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });

// Generate a timestamped filename so reports don't overwrite each other
const ts = new Date().toISOString().replace(/[:.]/g, "-");
const jsonPath = path.join(outDir, `axe-${ts}.json`);

// Persist the full axe result as JSON for later review
fs.writeFileSync(jsonPath, JSON.stringify(result, null, 2));
await browser.close();

// Print summary to stdout
console.log("Axe report saved to", jsonPath);
console.log("Violations:", result.violations.length);

// Log each violation with its impact level and affected nodes, then exit with code 2
if (result.violations.length > 0) {
result.violations.forEach((v) => {
console.log("\n", v.id, "-", v.impact, "-", v.help);
v.nodes.forEach((n) => console.log(" - ", n.target.join(", ")));
});
process.exit(2);
}
}
};

// Parse CLI arguments — require exactly one URL argument
const args = process.argv.slice(2);
if (args.length < 1) {
console.error("Usage: node scripts/axe-run.cjs <URL>");
process.exit(1);
}

// Execute the runner and handle any unexpected errors
run(args[0]).catch((err) => {
console.error(err);
process.exit(1);
Expand Down
32 changes: 27 additions & 5 deletions frontend/scripts/lighthouse-run.cjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
#!/usr/bin/env node
/* CommonJS version for environments with "type": "module"
Runs Lighthouse in headless Chrome using chrome-launcher
*/
/**
* CommonJS Lighthouse runner for environments with "type": "module".
* Launches Chromium via chrome-launcher, runs Lighthouse against the given URL,
* and saves HTML + JSON reports to the lighthouse/ output directory.
*
* Usage: node scripts/lighthouse-run.cjs <URL> [--mobile|--desktop]
*/
const fs = require("fs");
const path = require("path");
const lighthouseModule = require("lighthouse");

// Support both default and named exports depending on the installed version
const lighthouse = lighthouseModule.default || lighthouseModule;
const chromeLauncher = require("chrome-launcher");

async function run(url, opts = {}) {
// Main runner — launches Chrome, runs Lighthouse, and saves reports
const run = async (url, opts = {}) => {
// Launch headless Chromium with flags suitable for CI and containerised environments
const chrome = await chromeLauncher.launch({
chromeFlags: [
"--headless=new",
Expand All @@ -20,21 +28,29 @@ async function run(url, opts = {}) {
"--disable-features=IsolateOrigins,site-per-process",
],
});

// Build Lighthouse options — default to desktop form factor if not specified
const options = {
port: chrome.port,
output: "html",
onlyCategories: ["performance", "accessibility", "best-practices", "seo"],
emulatedFormFactor: opts.formFactor || "desktop",
};

console.log(
`Running Lighthouse on ${url} (${options.emulatedFormFactor})...`,
);

// Execute the Lighthouse audit and extract HTML + JSON reports
const runnerResult = await lighthouse(url, options);
const reportHtml = runnerResult.report;
const reportJson = runnerResult.lhr;

// Ensure the output directory exists before writing reports
const outDir = path.resolve(__dirname, "..", "lighthouse");
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });

// Generate timestamped filenames so reports don't overwrite each other
const ts = new Date().toISOString().replace(/[:.]/g, "-");
const htmlPath = path.join(
outDir,
Expand All @@ -44,9 +60,12 @@ async function run(url, opts = {}) {
outDir,
`lighthouse-${options.emulatedFormFactor}-${ts}.json`,
);

// Write both the human-readable HTML report and the machine-readable JSON
fs.writeFileSync(htmlPath, reportHtml);
fs.writeFileSync(jsonPath, JSON.stringify(reportJson, null, 2));

// Print report paths and a compact category score summary
console.log(`Saved reports: ${htmlPath} and ${jsonPath}`);
console.log("----- Scores -----");
console.log(
Expand All @@ -63,8 +82,9 @@ async function run(url, opts = {}) {
);

await chrome.kill();
}
};

// Parse CLI arguments — require exactly one URL; form factor defaults to desktop
const args = process.argv.slice(2);
if (args.length < 1) {
console.error(
Expand All @@ -74,6 +94,8 @@ if (args.length < 1) {
}
const url = args[0];
const formFactor = args.includes("--mobile") ? "mobile" : "desktop";

// Execute the runner and handle any unexpected errors
run(url, { formFactor }).catch((err) => {
console.error(err);
process.exit(1);
Expand Down