Files
mintel.me/apps/web/scripts/check-forms.ts
Marc Mintel a1c0736274
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 12s
Nightly QA / 🔗 Links & Deps (push) Has been cancelled
Nightly QA / 🔔 Notify (push) Has been cancelled
Nightly QA / 🔍 Static Analysis (push) Has been cancelled
Nightly QA / 🎭 Lighthouse (push) Has been cancelled
Nightly QA / 📝 E2E (push) Has been cancelled
Build & Deploy / 🧪 QA (push) Successful in 1m16s
Build & Deploy / 🏗️ Build (push) Successful in 14m28s
Build & Deploy / 🚀 Deploy (push) Successful in 22s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 2m27s
Build & Deploy / 🔔 Notify (push) Successful in 1s
ci(deploy): increase E2E timeout and add continue-on-error to smoke test
2026-03-05 12:08:49 +01:00

229 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import puppeteer from "puppeteer";
const targetUrl = process.env.TEST_URL || "http://localhost:3000";
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || "secret";
async function fetchSitemapUrls(baseUrl: string): Promise<string[]> {
const sitemapUrl = `${baseUrl.replace(/\/$/, "")}/sitemap.xml`;
console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
try {
const response = await fetch(sitemapUrl);
const text = await response.text();
// Simple regex to extract loc tags
const matches = text.matchAll(/<loc>(.*?)<\/loc>/g);
let urls = Array.from(matches, (m) => m[1]);
// Normalize to target URL instance
const urlPattern = /https?:\/\/[^\/]+/;
urls = [...new Set(urls)]
.filter((u) => u.startsWith("http"))
.map((u) => u.replace(urlPattern, baseUrl.replace(/\/$/, "")))
.sort();
console.log(`✅ Found ${urls.length} target URLs.`);
return urls;
} catch (err: any) {
console.error(`❌ Failed to fetch sitemap: ${err.message}`);
return [];
}
}
async function main() {
console.log(`\n🚀 Starting Strict Asset Integrity Check for: ${targetUrl}`);
let urls = await fetchSitemapUrls(targetUrl);
if (urls.length === 0) {
console.warn(`⚠️ Falling back to just the homepage.`);
urls = [targetUrl];
}
// Launch browser with KLZ pattern: use system chromium via env
console.log(`\n🕷 Launching Puppeteer Headless Engine...`);
const browser = await puppeteer.launch({
headless: true,
executablePath:
process.env.PUPPETEER_EXECUTABLE_PATH ||
process.env.CHROME_PATH ||
undefined,
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--ignore-certificate-errors",
"--disable-web-security",
"--disable-features=IsolateOrigins,site-per-process",
],
});
const page = await browser.newPage();
let hasBrokenAssets = false;
let currentScannedUrl = urls[0] || "";
// Listen for console logging from the page for debugging
page.on("console", (msg) => {
const type = msg.type();
// Only capture errors and warnings, not info/logs
if (type === "error" || type === "warn") {
const text = msg.text();
// Exclude common noise
if (
text.includes("google-analytics") ||
text.includes("googletagmanager") ||
text.includes("Fast Refresh")
)
return;
console.log(` [PAGE ${type.toUpperCase()}] ${text}`);
}
});
page.on("pageerror", (err: Error) => {
if (currentScannedUrl.includes("showcase")) return;
console.error(` [PAGE EXCEPTION] ${err.message}`);
});
// Listen to ALL network responses to catch broken assets (404/500)
page.on("response", (response) => {
const status = response.status();
// Catch classic 404s and 500s on ANY fetch/image/script
if (
status >= 400 &&
status !== 429 &&
status !== 999 &&
!response.url().includes("google-analytics") &&
!response.url().includes("googletagmanager")
) {
const type = response.request().resourceType();
// We explicitly care about images, scripts, stylesheets, and fetches getting 404/500s.
if (
["image", "script", "stylesheet", "fetch", "xhr", "document"].includes(
type,
)
) {
// Exclude showcase routes from strict sub-asset checking since they proxy external content
if (
(currentScannedUrl.includes("showcase") ||
response.url().includes("showcase")) &&
type !== "document"
) {
return;
}
console.error(
` [REQUEST FAILED] ${response.url()} - Status: ${status} (${type})`,
);
hasBrokenAssets = true;
}
}
});
try {
// Authenticate through Gatekeeper
console.log(`\n🛡 Authenticating through Gatekeeper...`);
console.log(` Navigating to: ${urls[0]}`);
const response = await page.goto(urls[0], {
waitUntil: "domcontentloaded",
timeout: 120000,
});
// Give Gatekeeper a second to redirect if needed
console.log(` Waiting for potential Gatekeeper redirect...`);
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(` Response status: ${response?.status()}`);
console.log(` Response URL: ${response?.url()}`);
const isGatekeeperPage = await page.$('input[name="password"]');
if (isGatekeeperPage) {
await page.type('input[name="password"]', gatekeeperPassword);
await Promise.all([
page.waitForNavigation({
waitUntil: "domcontentloaded",
timeout: 120000,
}),
page.click('button[type="submit"]'),
]);
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log(`✅ Gatekeeper authentication successful!`);
} else {
console.log(`✅ Already authenticated (no Gatekeeper gate detected).`);
}
// Scan each page
console.log(`\n🧪 Testing all ${urls.length} pages...`);
for (let i = 0; i < urls.length; i++) {
const u = urls[i];
currentScannedUrl = u;
console.log(`\n[${i + 1}/${urls.length}] Scanning: ${u}`);
try {
await page.goto(u, { waitUntil: "domcontentloaded", timeout: 120000 });
// Simulate a scroll to bottom to trigger lazy-loads if necessary
await page.evaluate(async () => {
await new Promise<void>((resolve) => {
let totalHeight = 0;
const distance = 500;
const timer = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
totalHeight += distance;
// Stop scrolling if we reached the bottom or scrolled for more than 5 seconds
if (totalHeight >= scrollHeight || totalHeight > 10000) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
// Small delay for final hydration and asynchronous asset loading
await new Promise((resolve) => setTimeout(resolve, 1500));
const title = await page.title();
console.log(` ✅ Page Title: ${title}`);
if (!title) {
throw new Error(`Page title is missing.`);
}
} catch (err: any) {
console.error(
` ❌ Timeout or navigation error on ${u}: ${err.message}`,
);
hasBrokenAssets = true;
}
}
} catch (err: any) {
console.error(`\n❌ Fatal Test Error: ${err.message}`);
// Take a screenshot for debugging on crash
try {
const screenshotPath = "/tmp/e2e-failure.png";
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log(`📸 Screenshot saved to ${screenshotPath}`);
} catch {
/* ignore */
}
hasBrokenAssets = true;
}
await browser.close();
if (hasBrokenAssets) {
console.error(
`\n🚨 The CI build will now fail to prevent bad code from reaching production.`,
);
process.exit(1);
}
console.log(
`\n🎉 SUCCESS: All ${urls.length} pages rendered perfectly with 0 broken assets!`,
);
process.exit(0);
}
main();