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.klz-cables.com'; const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20; // Default limit to avoid infinite runs const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; 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: `klz_gatekeeper_session=${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: `klz_gatekeeper_session=${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' --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();