Merge branch 'main' into feature/excel
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Failing after 1m57s
Build & Deploy / 🧪 QA (push) Failing after 2m6s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 9s
CI - Lint, Typecheck & Test / quality-assurance (pull_request) Failing after 1m57s
Build & Deploy / 🧪 QA (push) Failing after 2m6s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
@@ -2,11 +2,16 @@ import puppeteer, { HTTPResponse } from 'puppeteer';
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
|
||||
const targetUrl =
|
||||
process.argv.find((arg) => !arg.startsWith('--') && arg.startsWith('http')) ||
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
'http://localhost:3000';
|
||||
const limit = process.env.ASSET_CHECK_LIMIT ? parseInt(process.env.ASSET_CHECK_LIMIT) : 20;
|
||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
|
||||
|
||||
async function main() {
|
||||
console.log(`\n🚀 Starting Strict Asset Integrity Check for: ${targetUrl}`);
|
||||
console.log(`📊 Limit: ${limit} pages\n`);
|
||||
|
||||
// 1. Fetch Sitemap to discover all routes
|
||||
const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`;
|
||||
@@ -31,6 +36,17 @@ async function main() {
|
||||
.sort();
|
||||
|
||||
console.log(`✅ Found ${urls.length} target URLs.`);
|
||||
|
||||
if (urls.length > limit) {
|
||||
console.log(
|
||||
`⚠️ Too many pages (${urls.length}). Limiting to ${limit} representative pages.`,
|
||||
);
|
||||
// Simplify selection: home pages + a slice of the rest
|
||||
const homeEN = urls.filter((u) => u.endsWith('/en') || u === targetUrl);
|
||||
const homeDE = urls.filter((u) => u.endsWith('/de'));
|
||||
const others = urls.filter((u) => !homeEN.includes(u) && !homeDE.includes(u));
|
||||
urls = [...homeEN, ...homeDE, ...others.slice(0, limit - (homeEN.length + homeDE.length))];
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Failed to fetch sitemap: ${err.message}`);
|
||||
process.exit(1);
|
||||
|
||||
@@ -66,6 +66,12 @@ async function main() {
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
page.on('console', (msg) => console.log('💻 BROWSER CONSOLE:', msg.text()));
|
||||
page.on('pageerror', (error) => console.error('💻 BROWSER ERROR:', error.message));
|
||||
page.on('requestfailed', (request) => {
|
||||
console.error('💻 BROWSER REQUEST FAILED:', request.url(), request.failure()?.errorText);
|
||||
});
|
||||
|
||||
// 3. Authenticate through Gatekeeper login form
|
||||
console.log(`\n🛡️ Authenticating through Gatekeeper...`);
|
||||
try {
|
||||
@@ -109,6 +115,9 @@ async function main() {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Wait specifically for hydration logic to initialize the onSubmit handler
|
||||
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 2000)));
|
||||
|
||||
// Fill form fields
|
||||
await page.type('input[name="name"]', 'Automated E2E Test');
|
||||
await page.type('input[name="email"]', 'testing@mintel.me');
|
||||
@@ -117,14 +126,24 @@ async function main() {
|
||||
'This is an automated test verifying the contact form submission.',
|
||||
);
|
||||
|
||||
// Give state a moment to settle
|
||||
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 500)));
|
||||
|
||||
console.log(` Submitting Contact Form...`);
|
||||
|
||||
// Explicitly click submit and wait for navigation/state-change
|
||||
await Promise.all([
|
||||
page.waitForSelector('[role="alert"][aria-live="polite"]', { timeout: 15000 }),
|
||||
page.waitForSelector('[role="alert"]', { timeout: 15000 }),
|
||||
page.click('button[type="submit"]'),
|
||||
]);
|
||||
|
||||
const alertText = await page.$eval('[role="alert"]', (el) => el.textContent);
|
||||
console.log(` Alert text: ${alertText}`);
|
||||
|
||||
if (alertText?.includes('Failed') || alertText?.includes('went wrong')) {
|
||||
throw new Error(`Form submitted but showed error: ${alertText}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Contact Form submitted successfully! (Success state verified)`);
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Contact Form Test Failed: ${err.message}`);
|
||||
@@ -147,6 +166,9 @@ async function main() {
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Wait specifically for hydration logic to initialize the onSubmit handler
|
||||
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 2000)));
|
||||
|
||||
// In RequestQuoteForm, the email input is type="email" and message is a textarea.
|
||||
await page.type('form input[type="email"]', 'testing@mintel.me');
|
||||
await page.type(
|
||||
@@ -154,14 +176,24 @@ async function main() {
|
||||
'Automated request for product quote via E2E testing framework.',
|
||||
);
|
||||
|
||||
// Give state a moment to settle
|
||||
await page.evaluate(() => new Promise((resolve) => setTimeout(resolve, 500)));
|
||||
|
||||
console.log(` Submitting Product Quote Form...`);
|
||||
|
||||
// Submit and wait for success state
|
||||
await Promise.all([
|
||||
page.waitForSelector('[role="alert"][aria-live="polite"]', { timeout: 15000 }),
|
||||
page.waitForSelector('[role="alert"]', { timeout: 15000 }),
|
||||
page.click('form button[type="submit"]'),
|
||||
]);
|
||||
|
||||
const alertText = await page.$eval('[role="alert"]', (el) => el.textContent);
|
||||
console.log(` Alert text: ${alertText}`);
|
||||
|
||||
if (alertText?.includes('Failed') || alertText?.includes('went wrong')) {
|
||||
throw new Error(`Form submitted but showed error: ${alertText}`);
|
||||
}
|
||||
|
||||
console.log(`✅ Product Quote Form submitted successfully! (Success state verified)`);
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Product Quote Form Test Failed: ${err.message}`);
|
||||
@@ -189,11 +221,20 @@ async function main() {
|
||||
});
|
||||
console.log(` ✅ Deleted submission: ${doc.id}`);
|
||||
} catch (delErr: any) {
|
||||
console.error(` ❌ Failed to delete submission ${doc.id}: ${delErr.message}`);
|
||||
// Log but don't fail, 403s on Directus / Payload APIs for guest Gatekeeper sessions are normal
|
||||
console.warn(
|
||||
` ⚠️ Cleanup attempt on ${doc.id} returned an error, typically due to API Auth separation: ${delErr.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Cleanup failed: ${err.message}`);
|
||||
if (err.response?.status === 403) {
|
||||
console.warn(
|
||||
` ⚠️ Cleanup fetch failed with 403 Forbidden. This is expected if the runner lacks admin API credentials. Test submissions remain in the database.`,
|
||||
);
|
||||
} else {
|
||||
console.error(` ❌ Cleanup fetch failed: ${err.message}`);
|
||||
}
|
||||
// Don't mark the whole test as failed just because cleanup failed
|
||||
}
|
||||
|
||||
|
||||
24
scripts/lhci-puppeteer-setup.js
Normal file
24
scripts/lhci-puppeteer-setup.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* LHCI Puppeteer Setup Script
|
||||
* Sets the gatekeeper session cookie before auditing
|
||||
*/
|
||||
module.exports = async (browser, context) => {
|
||||
const page = await browser.newPage();
|
||||
// Using LHCI_URL or TARGET_URL if available
|
||||
const targetUrl =
|
||||
process.env.LHCI_URL || process.env.TARGET_URL || 'https://testing.klz-cables.com';
|
||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026';
|
||||
|
||||
console.log(`🔑 LHCI Auth: Setting gatekeeper cookie for ${new URL(targetUrl).hostname}...`);
|
||||
|
||||
await page.setCookie({
|
||||
name: 'klz_gatekeeper_session',
|
||||
value: gatekeeperPassword,
|
||||
domain: new URL(targetUrl).hostname,
|
||||
path: '/',
|
||||
httpOnly: true,
|
||||
secure: targetUrl.startsWith('https://'),
|
||||
});
|
||||
|
||||
await page.close();
|
||||
};
|
||||
@@ -12,7 +12,11 @@ import * as path from 'path';
|
||||
* 3. Runs Lighthouse CI on those URLs
|
||||
*/
|
||||
|
||||
const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
|
||||
const targetUrl =
|
||||
process.argv.find((arg) => !arg.startsWith('--') && arg.startsWith('http')) ||
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
process.env.LHCI_URL ||
|
||||
'http://localhost:3000';
|
||||
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';
|
||||
|
||||
@@ -76,7 +80,56 @@ async function main() {
|
||||
Cookie: `klz_gatekeeper_session=${gatekeeperPassword}`,
|
||||
});
|
||||
|
||||
const chromePath = process.env.CHROME_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
|
||||
// Detect Chrome path from Puppeteer installation if not provided
|
||||
let chromePath = process.env.CHROME_PATH || process.env.PUPPETEER_EXECUTABLE_PATH;
|
||||
if (!chromePath) {
|
||||
try {
|
||||
console.log('🔍 Attempting to detect Puppeteer Chrome path...');
|
||||
const puppeteerInfo = execSync('npx puppeteer browsers latest chrome', {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
console.log(`📦 Puppeteer info: ${puppeteerInfo}`);
|
||||
const match = puppeteerInfo.match(/executablePath: (.*)/);
|
||||
if (match && match[1]) {
|
||||
chromePath = match[1].trim();
|
||||
console.log(`✅ Detected Puppeteer Chrome at: ${chromePath}`);
|
||||
}
|
||||
} catch (e: any) {
|
||||
console.warn(`⚠️ Could not detect Puppeteer Chrome path via command: ${e.message}`);
|
||||
}
|
||||
|
||||
// Fallback to known paths if still not found
|
||||
if (!chromePath) {
|
||||
const fallbacks = [
|
||||
'/root/.cache/puppeteer/chrome/linux-145.0.7632.77/chrome-linux64/chrome',
|
||||
'/home/runner/.cache/puppeteer/chrome/linux-145.0.7632.77/chrome-linux64/chrome',
|
||||
path.join(
|
||||
process.cwd(),
|
||||
'node_modules',
|
||||
'.puppeteer',
|
||||
'chrome',
|
||||
'linux-145.0.7632.77',
|
||||
'chrome-linux64',
|
||||
'chrome',
|
||||
),
|
||||
];
|
||||
|
||||
for (const fallback of fallbacks) {
|
||||
if (fs.existsSync(fallback)) {
|
||||
chromePath = fallback;
|
||||
console.log(`✅ Found Puppeteer Chrome at fallback: ${chromePath}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`ℹ️ Using existing Chrome path: ${chromePath}`);
|
||||
}
|
||||
|
||||
if (!chromePath) {
|
||||
console.warn('❌ CHROME_PATH is still undefined. Lighthouse might fail.');
|
||||
}
|
||||
|
||||
const chromePathArg = chromePath ? `--collect.chromePath="${chromePath}"` : '';
|
||||
|
||||
// Clean up old reports
|
||||
@@ -85,15 +138,16 @@ async function main() {
|
||||
}
|
||||
|
||||
// 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} --config=config/lighthouserc.json --collect.settings.extraHeaders='${extraHeaders}' --collect.settings.chromeFlags="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage" && npx lhci assert --config=config/lighthouserc.json`;
|
||||
// We use a puppeteer script to set cookies which is more reliable than extraHeaders for LHCI
|
||||
const lhciCommand = `npx lhci collect ${urlArgs} ${chromePathArg} --config=config/lighthouserc.json --collect.puppeteerScript="scripts/lhci-puppeteer-setup.js" --collect.settings.chromeFlags="--no-sandbox --disable-setuid-sandbox --disable-dev-shm-usage" && npx lhci assert --config=config/lighthouserc.json`;
|
||||
|
||||
console.log(`💻 Executing LHCI...`);
|
||||
console.log(`💻 Executing LHCI with CHROME_PATH="${chromePath}" and Puppeteer Auth...`);
|
||||
|
||||
try {
|
||||
execSync(lhciCommand, {
|
||||
encoding: 'utf8',
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, CHROME_PATH: chromePath },
|
||||
});
|
||||
} catch (err: any) {
|
||||
console.warn('⚠️ LHCI assertion finished with warnings or errors.');
|
||||
|
||||
Reference in New Issue
Block a user