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. Authenticate through Gatekeeper login form console.log(`\n๐Ÿ›ก๏ธ Authenticating through Gatekeeper...`); try { // Navigate to a protected page so Gatekeeper redirects us to the login screen await page.goto(contactUrl, { waitUntil: 'networkidle0', timeout: 30000 }); // Check if we landed on the Gatekeeper login page const isGatekeeperPage = await page.$('input[name="password"]'); if (isGatekeeperPage) { await page.type('input[name="password"]', gatekeeperPassword); await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle0', timeout: 30000 }), page.click('button[type="submit"]'), ]); console.log(`โœ… Gatekeeper authentication successful!`); } else { console.log(`โœ… Already authenticated (no Gatekeeper gate detected).`); } } catch (err: any) { console.error(`โŒ Gatekeeper authentication failed: ${err.message}`); await browser.close(); process.exit(1); } 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; } // 5. Cleanup: Delete test submissions from Payload CMS console.log(`\n๐Ÿงน Starting cleanup of test submissions...`); try { const apiUrl = `${targetUrl.replace(/\/$/, '')}/api/form-submissions`; const searchUrl = `${apiUrl}?where[email][equals]=testing@mintel.me`; // Fetch test submissions const searchResponse = await axios.get(searchUrl, { headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, }); const testSubmissions = searchResponse.data.docs || []; console.log(` Found ${testSubmissions.length} test submissions to clean up.`); for (const doc of testSubmissions) { try { await axios.delete(`${apiUrl}/${doc.id}`, { headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` }, }); console.log(` โœ… Deleted submission: ${doc.id}`); } catch (delErr: any) { console.error(` โŒ Failed to delete submission ${doc.id}: ${delErr.message}`); } } } catch (err: any) { console.error(`โŒ Cleanup failed: ${err.message}`); // Don't mark the whole test as failed just because cleanup failed } await browser.close(); // 6. 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();