diff --git a/scripts/check-forms.ts b/scripts/check-forms.ts new file mode 100644 index 00000000..691a586b --- /dev/null +++ b/scripts/check-forms.ts @@ -0,0 +1,170 @@ +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 gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; + +async function main() { + console.log(`\n๐Ÿš€ Starting E2E Form Submission Check for: ${targetUrl}`); + + // 1. Fetch Sitemap to discover the contact page and a product page + const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`; + let urls: string[] = []; + + try { + console.log(`๐Ÿ“ฅ Fetching sitemap from ${sitemapUrl}...`); + const response = await axios.get(sitemapUrl, { + headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, + }); + + const $ = cheerio.load(response.data, { xmlMode: true }); + urls = $('url loc') + .map((i, el) => $(el).text()) + .get(); + + // Normalize to target URL instance + const urlPattern = /https?:\/\/[^\/]+/; + urls = [...new Set(urls)] + .filter((u) => u.startsWith('http')) + .map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, ''))) + .sort(); + } catch (err: any) { + console.error(`โŒ Failed to fetch sitemap: ${err.message}`); + process.exit(1); + } + + const contactUrl = urls.find((u) => u.includes('/de/kontakt')); + // Ensure we select an actual product page (depth >= 7: http://host/de/produkte/category/product) + const productUrl = urls.find( + (u) => + u.includes('/de/produkte/') && new URL(u).pathname.split('/').filter(Boolean).length >= 4, + ); + + if (!contactUrl) { + console.error(`โŒ Could not find contact page in sitemap. Ensure /de/kontakt exists.`); + process.exit(1); + } + + if (!productUrl) { + console.error( + `โŒ Could not find a product page in sitemap. Form testing requires at least one product page.`, + ); + process.exit(1); + } + + console.log(`โœ… Discovered Contact Page: ${contactUrl}`); + console.log(`โœ… Discovered Product Page: ${productUrl}`); + + // 2. Launch Headless Browser + 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'], + }); + + const page = await browser.newPage(); + + // 3. Inject Gatekeeper session bypassing auth screens + console.log(`\n๐Ÿ›ก๏ธ Injecting Gatekeeper Session...`); + await page.setCookie({ + name: 'klz_gatekeeper_session', + value: gatekeeperPassword, + domain: new URL(targetUrl).hostname, + path: '/', + httpOnly: true, + secure: targetUrl.startsWith('https://'), + }); + + let hasErrors = false; + + // 4. Test Contact Form + try { + console.log(`\n๐Ÿงช Testing Contact Form on: ${contactUrl}`); + await page.goto(contactUrl, { waitUntil: 'networkidle0', timeout: 30000 }); + + // Ensure React has hydrated completely + await page.waitForNetworkIdle({ idleTime: 1000, timeout: 15000 }).catch(() => {}); + + // Ensure form is visible and interactive + try { + // Find the form input by name + await page.waitForSelector('input[name="name"]', { visible: true, timeout: 15000 }); + } catch (e) { + console.error('Failed to find Contact Form input. Page Title:', await page.title()); + throw e; + } + + // Fill form fields + await page.type('input[name="name"]', 'Automated E2E Test'); + await page.type('input[name="email"]', 'testing@mintel.me'); + await page.type( + 'textarea[name="message"]', + 'This is an automated test verifying the contact form submission.', + ); + + 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.click('button[type="submit"]'), + ]); + + console.log(`โœ… Contact Form submitted successfully! (Success state verified)`); + } catch (err: any) { + console.error(`โŒ Contact Form Test Failed: ${err.message}`); + hasErrors = true; + } + + // 4. Test Product Quote Form + try { + console.log(`\n๐Ÿงช Testing Product Quote Form on: ${productUrl}`); + await page.goto(productUrl, { waitUntil: 'networkidle0', timeout: 30000 }); + + // Ensure React has hydrated completely + await page.waitForNetworkIdle({ idleTime: 1000, timeout: 15000 }).catch(() => {}); + + // The product form uses dynamic IDs, so we select by input type in the specific form context + try { + await page.waitForSelector('form input[type="email"]', { visible: true, timeout: 15000 }); + } catch (e) { + console.error('Failed to find Product Quote Form input. Page Title:', await page.title()); + throw e; + } + + // 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( + 'form textarea', + 'Automated request for product quote via E2E testing framework.', + ); + + 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.click('form button[type="submit"]'), + ]); + + console.log(`โœ… Product Quote Form submitted successfully! (Success state verified)`); + } catch (err: any) { + console.error(`โŒ Product Quote Form Test Failed: ${err.message}`); + hasErrors = true; + } + + await browser.close(); + + // 5. Evaluation + if (hasErrors) { + console.error(`\n๐Ÿšจ IMPORTANT: Form E2E checks failed. The CI build is failing.`); + process.exit(1); + } else { + console.log(`\n๐ŸŽ‰ SUCCESS: All form submissions arrived and handled correctly!`); + process.exit(0); + } +} + +main(); diff --git a/scripts/check-html.ts b/scripts/check-html.ts index b0dcb043..b822aca3 100644 --- a/scripts/check-html.ts +++ b/scripts/check-html.ts @@ -50,7 +50,12 @@ async function main() { headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, validateStatus: (status) => status < 400, }); - const filename = `page-${i}.html`; + + // Generate a safe filename that retains URL information + const urlStr = new URL(u); + const safePath = (urlStr.pathname + urlStr.search).replace(/[^a-zA-Z0-9]/g, '_'); + const filename = `${safePath || 'index'}.html`; + fs.writeFileSync(path.join(outputDir, filename), res.data); } catch (err: any) { console.error(`โŒ HTTP Error fetching ${u}: ${err.message}`);