From dd23310ac46251d2f70cce7922bb4ba81e62c2a2 Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Sun, 1 Feb 2026 22:50:33 +0100 Subject: [PATCH] fix(ci): prioritize PPA chromium over snap wrapper and pass explicit chrome path to lhci --- .gitea/workflows/deploy.yml | 37 ++++--- scripts/pagespeed-sitemap.ts | 186 ++++++++++++++++++----------------- 2 files changed, 114 insertions(+), 109 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 88c82c3d..799ed3e2 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -358,29 +358,28 @@ jobs: echo "๐ŸŽฏ Debian detected - installing native chromium" apt-get install -y chromium else - echo "๐ŸŽฏ Ubuntu detected - adding xtradeb PPA for native chromium (non-snap)" + echo "๐ŸŽฏ Ubuntu detected - adding xtradeb PPA" mkdir -p /etc/apt/keyrings - # Robust key fetch with retries and fallback servers - for server in "hkp://keyserver.ubuntu.com:80" "hkp://keyserver.ubuntu.com:11371"; do - echo "Trying keyserver: $server" - if gpg --homedir /tmp --no-default-keyring --keyring /tmp/xtradeb-temp.gpg --keyserver "$server" --recv-keys 290D73D2240900B5; then - gpg --homedir /tmp --no-default-keyring --keyring /tmp/xtradeb-temp.gpg --export > /etc/apt/keyrings/xtradeb.gpg - break - fi - done - if [ -f /etc/apt/keyrings/xtradeb.gpg ]; then - echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list - apt-get update - apt-get install -y chromium || apt-get install -y chromium-browser - else - echo "โš ๏ธ Could not fetch GPG key, trying fallback install..." - apt-get install -y chromium || apt-get install -y chromium-browser - fi + # Use direct HTTPS fetch for GPG key (most reliable in Docker) + wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x290d73d2240900b5" | gpg --dearmor > /etc/apt/keyrings/xtradeb.gpg || true + + echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" > /etc/apt/sources.list.d/xtradeb-ppa.list + + # PRIORITY PINNING: Force Apt to use the PPA version over the Snap-filler + printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" > /etc/apt/preferences.d/xtradeb + + apt-get update + apt-get install -y --allow-downgrades chromium || apt-get install -y chromium-browser fi - # Ensure consistent paths for Lighthouse - [ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser || true + # Force clean paths (remove existing dead links/files if they are snap wrappers) + rm -f /usr/bin/google-chrome /usr/bin/chromium-browser + [ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/google-chrome + [ -f /usr/bin/chromium ] && ln -sf /usr/bin/chromium /usr/bin/chromium-browser + + echo "โœ… Binary check:" + ls -l /usr/bin/chromium* /usr/bin/google-chrome || true continue-on-error: true - name: ๐Ÿงช Run PageSpeed (Lighthouse) diff --git a/scripts/pagespeed-sitemap.ts b/scripts/pagespeed-sitemap.ts index 5cd72ae6..d4bbaff0 100644 --- a/scripts/pagespeed-sitemap.ts +++ b/scripts/pagespeed-sitemap.ts @@ -6,110 +6,116 @@ 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 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`); + 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 and filter + urls = [...new Set(urls)].filter((u) => u.startsWith('http')).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}"` : ''; + + // Using a more robust way to execute and capture output + const lhciCommand = `npx lhci collect ${urlArgs} ${chromePathArg} --collect.settings.chromeFlags='--no-sandbox --disable-setuid-sandbox' --collect.settings.extraHeaders='${extraHeaders}' && npx lhci assert && npx lhci upload`; + + console.log(`๐Ÿ’ป Executing LHCI...`); try { - // 1. Fetch Sitemap - const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`; - console.log(`๐Ÿ“ฅ Fetching sitemap from ${sitemapUrl}...`); + const output = execSync(lhciCommand, { + encoding: 'utf8', + stdio: ['inherit', 'pipe', 'inherit'], // Pipe stdout so we can parse it + }); - // 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 - }); + console.log(output); - const $ = cheerio.load(response.data, { xmlMode: true }); - let urls = $('url loc').map((i, el) => $(el).text()).get(); - - // Cleanup and filter - urls = [...new Set(urls)].filter(u => u.startsWith('http')).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}` - }); - - console.log(`\n๐Ÿ—๏ธ Running Lighthouse CI...`); - - // Using a more robust way to execute and capture output - const lhciCommand = `npx lhci collect ${urlArgs} --collect.settings.chromeFlags='--no-sandbox --disable-setuid-sandbox' --collect.settings.extraHeaders='${extraHeaders}' && npx lhci assert && npx lhci upload`; - - console.log(`๐Ÿ’ป Executing LHCI...`); - - try { - const output = execSync(lhciCommand, { - encoding: 'utf8', - stdio: ['inherit', 'pipe', 'inherit'] // Pipe stdout so we can parse it - }); - - console.log(output); - - // Extract report URL from LHCI output - const reportMatch = output.match(/Sent to (https:\/\/storage\.googleapis\.com\/lighthouse-infrastructure\.appspot\.com\/reports\/[^\s]+)/); - if (reportMatch && reportMatch[1]) { - const reportUrl = reportMatch[1]; - console.log(`\n๐Ÿ“Š Report URL: ${reportUrl}`); - fs.writeFileSync('pagespeed-report-url.txt', reportUrl); - } - - } catch (err: any) { - console.error('โŒ LHCI execution failed.'); - if (err.stdout) console.log(err.stdout); - if (err.stderr) console.error(err.stderr); - throw err; - } - - 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); + // Extract report URL from LHCI output + const reportMatch = output.match( + /Sent to (https:\/\/storage\.googleapis\.com\/lighthouse-infrastructure\.appspot\.com\/reports\/[^\s]+)/, + ); + if (reportMatch && reportMatch[1]) { + const reportUrl = reportMatch[1]; + console.log(`\n๐Ÿ“Š Report URL: ${reportUrl}`); + fs.writeFileSync('pagespeed-report-url.txt', reportUrl); + } + } catch (err: any) { + console.error('โŒ LHCI execution failed.'); + if (err.stdout) console.log(err.stdout); + if (err.stderr) console.error(err.stderr); + throw err; } + + 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();