test(e2e): implement full sitemap testing logic as per klz-2026 standards
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 6s
Build & Deploy / 🧪 QA (push) Failing after 1m23s
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 6s
Build & Deploy / 🧪 QA (push) Failing after 1m23s
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:
@@ -3,10 +3,44 @@ 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 E2E Form Submission Check for: ${targetUrl}`);
|
||||
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:
|
||||
@@ -26,23 +60,74 @@ async function main() {
|
||||
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Enable console logging from the page for debugging
|
||||
page.on("console", (msg) => console.log(` [PAGE] ${msg.text()}`));
|
||||
page.on("pageerror", (err: Error) =>
|
||||
console.error(` [PAGE ERROR] ${err.message}`),
|
||||
);
|
||||
page.on("requestfailed", (req) =>
|
||||
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] ${req.url()} - ${req.failure()?.errorText}`,
|
||||
),
|
||||
` [REQUEST FAILED] ${response.url()} - Status: ${status} (${type})`,
|
||||
);
|
||||
hasBrokenAssets = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// Authenticate through Gatekeeper
|
||||
console.log(`\n🛡️ Authenticating through Gatekeeper...`);
|
||||
console.log(` Navigating to: ${targetUrl}`);
|
||||
console.log(` Navigating to: ${urls[0]}`);
|
||||
|
||||
const response = await page.goto(targetUrl, {
|
||||
const response = await page.goto(urls[0], {
|
||||
waitUntil: "domcontentloaded",
|
||||
timeout: 60000,
|
||||
});
|
||||
@@ -69,33 +154,74 @@ async function main() {
|
||||
console.log(`✅ Already authenticated (no Gatekeeper gate detected).`);
|
||||
}
|
||||
|
||||
// Basic smoke test
|
||||
console.log(`\n🧪 Testing page load...`);
|
||||
// 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: 60000 });
|
||||
|
||||
// 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.toLowerCase().includes("mintel")) {
|
||||
console.log(`✅ Basic smoke test passed!`);
|
||||
} else {
|
||||
throw new Error(`Page title mismatch: "${title}"`);
|
||||
if (!title) {
|
||||
throw new Error(`Page title is missing.`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error(`❌ Test Failed: ${err.message}`);
|
||||
// Take a screenshot for debugging
|
||||
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 screenshot errors */
|
||||
/* ignore */
|
||||
}
|
||||
console.log(` Current URL: ${page.url()}`);
|
||||
await browser.close();
|
||||
process.exit(1);
|
||||
hasBrokenAssets = true;
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log(`\n🎉 SUCCESS: E2E smoke test passed!`);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user