Files
mintel.me/apps/web/scripts/pagespeed-sitemap.ts
Marc Mintel ecea90dc91
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Failing after 1m27s
Build & Deploy / 🏗️ Build (push) Failing after 1m31s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
chore: stabilize apps/web (lint, build, typecheck fixes)
2026-02-11 11:56:13 +01:00

181 lines
5.9 KiB
TypeScript

import axios from "axios";
import * as cheerio from "cheerio";
import { execSync } from "child_process";
import * as fs from "fs";
import * as path from "path";
/**
* PageSpeed Test Script
*
* 1. Fetches sitemap.xml from the target URL
* 2. Extracts all URLs
* 3. Runs Lighthouse CI on those URLs
*/
const targetUrl =
process.argv[2] ||
process.env.NEXT_PUBLIC_BASE_URL ||
"https://testing.mintel.me";
const limit = process.env.PAGESPEED_LIMIT
? parseInt(process.env.PAGESPEED_LIMIT)
: 20;
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || "mintel";
const gatekeeperCookie =
process.env.AUTH_COOKIE_NAME || "mintel_gatekeeper_session";
async function main() {
console.log(`\n🚀 Starting PageSpeed test for: ${targetUrl}`);
console.log(`📊 Limit: ${limit} pages\n`);
try {
// 1. Fetch Sitemap
const sitemapUrl = `${targetUrl.replace(/\/$/, "")}/sitemap.xml`;
console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
// We might need to bypass gatekeeper for the sitemap fetch too
const response = await axios.get(sitemapUrl, {
headers: {
Cookie: `${gatekeeperCookie}=${gatekeeperPassword}`,
},
validateStatus: (status) => status < 400,
});
const $ = cheerio.load(response.data, { xmlMode: true });
let urls = $("url loc")
.map((_i, el) => $(el).text())
.get();
// Cleanup, filter and normalize domains to targetUrl
const urlPattern = /https?:\/\/[^\/]+/;
urls = [...new Set(urls)]
.filter((u) => u.startsWith("http"))
.map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, "")))
.sort();
console.log(`✅ Found ${urls.length} URLs in sitemap.`);
if (urls.length === 0) {
console.error("❌ No URLs found in sitemap. Is the site up?");
process.exit(1);
}
if (urls.length > limit) {
console.log(
`⚠️ Too many pages (${urls.length}). Limiting to ${limit} representative pages.`,
);
// Try to pick a variety: home, some products, some blog posts
const home = urls.filter(
(u) => u.endsWith("/de") || u.endsWith("/en") || u === targetUrl,
);
const others = urls.filter((u) => !home.includes(u));
urls = [...home, ...others.slice(0, limit - home.length)];
}
console.log(`🧪 Pages to be tested:`);
urls.forEach((u) => console.log(` - ${u}`));
// 2. Prepare LHCI command
// We use --collect.url multiple times
const urlArgs = urls.map((u) => `--collect.url="${u}"`).join(" ");
// Handle authentication for staging/testing
// Lighthouse can set cookies via --collect.settings.extraHeaders
const extraHeaders = JSON.stringify({
Cookie: `${gatekeeperCookie}=${gatekeeperPassword}`,
});
const chromePath =
process.env.CHROME_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
const chromePathArg = chromePath
? `--collect.chromePath="${chromePath}"`
: "";
// Clean up old reports
if (fs.existsSync(".lighthouseci")) {
fs.rmSync(".lighthouseci", { recursive: true, force: true });
}
// Using a more robust way to execute and capture output
// We remove 'npx lhci upload' to keep everything local and avoid Google-hosted reports
const lhciCommand = `npx lhci collect ${urlArgs} ${chromePathArg} --collect.settings.chromeFlags='--no-sandbox --disable-setuid-sandbox --headless' --collect.settings.extraHeaders='${extraHeaders}' && npx lhci assert`;
console.log(`💻 Executing LHCI...`);
try {
execSync(lhciCommand, {
encoding: "utf8",
stdio: "inherit",
});
} catch (err: any) {
console.warn("⚠️ LHCI assertion finished with warnings or errors.");
// We continue to show the table even if assertions failed
}
// 3. Summarize Results (Local & Independent)
const manifestPath = path.join(
process.cwd(),
".lighthouseci",
"manifest.json",
);
if (fs.existsSync(manifestPath)) {
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
console.log(`\n📊 PageSpeed Summary (FOSS - Local Report):\n`);
const summaryTable = manifest.map((entry: any) => {
const s = entry.summary;
return {
URL: entry.url.replace(targetUrl, ""),
Perf: Math.round(s.performance * 100),
Acc: Math.round(s.accessibility * 100),
BP: Math.round(s["best-practices"] * 100),
SEO: Math.round(s.seo * 100),
};
});
console.table(summaryTable);
// Calculate Average
const avg = {
Perf: Math.round(
summaryTable.reduce((acc: any, curr: any) => acc + curr.Perf, 0) /
summaryTable.length,
),
Acc: Math.round(
summaryTable.reduce((acc: any, curr: any) => acc + curr.Acc, 0) /
summaryTable.length,
),
BP: Math.round(
summaryTable.reduce((acc: any, curr: any) => acc + curr.BP, 0) /
summaryTable.length,
),
SEO: Math.round(
summaryTable.reduce((acc: any, curr: any) => acc + curr.SEO, 0) /
summaryTable.length,
),
};
console.log(`\n📈 Average Scores:`);
console.log(
` Performance: ${avg.Perf > 90 ? "✅" : "⚠️"} ${avg.Perf}`,
);
console.log(` Accessibility: ${avg.Acc > 90 ? "✅" : "⚠️"} ${avg.Acc}`);
console.log(` Best Practices: ${avg.BP > 90 ? "✅" : "⚠️"} ${avg.BP}`);
console.log(` SEO: ${avg.SEO > 90 ? "✅" : "⚠️"} ${avg.SEO}`);
}
console.log(`\n✨ PageSpeed tests completed successfully!`);
} catch (error: any) {
console.error(`\n❌ Error during PageSpeed test:`);
if (axios.isAxiosError(error)) {
console.error(`Status: ${error.response?.status}`);
console.error(`StatusText: ${error.response?.statusText}`);
console.error(`URL: ${error.config?.url}`);
} else {
console.error(error.message);
}
process.exit(1);
}
}
main();