import puppeteer from 'puppeteer'; const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'klz2026'; async function run() { console.log(`🚀 Smoke Test: ${targetUrl}`); const browser = await puppeteer.launch({ headless: true, executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined, args: ['--no-sandbox', '--disable-setuid-sandbox'], }); const page = await browser.newPage(); try { // 1. Login via Gatekeeper console.log('🔑 Authenticating...'); await page.goto(targetUrl, { waitUntil: 'networkidle2' }); const isGatekeeper = await page.$('input[name="password"]'); if (isGatekeeper) { await page.type('input[name="password"]', gatekeeperPassword); await Promise.all([ page.waitForNavigation({ waitUntil: 'networkidle2' }), page.click('button[type="submit"]'), ]); } // 2. Check Key Pages & Assets const pages = ['/', '/de/kontakt', '/en/contact', '/de/blog']; for (const p of pages) { console.log(`📄 Checking ${p}...`); const res = await page.goto(`${targetUrl}${p}`, { waitUntil: 'domcontentloaded' }); const status = res?.status(); if (status !== 200 && status !== 304 && status !== 307 && status !== 308) { throw new Error(`Page ${p} returned ${status}`); } // Check for broken images (using in-page fetch to preserve session and avoid heavy navigation) const images = await page.$$eval('img', (imgs) => imgs.map((img) => img.src).filter(Boolean)); console.log(` Checking ${images.length} images...`); const brokenImages = await page.evaluate(async (urls) => { const broken: string[] = []; await Promise.all( urls.map(async (url) => { if (url.includes('openstreetmap.org') || url.includes('unpkg.com')) return; try { const res = await fetch(url, { method: 'HEAD' }); if (res.status >= 400) { // Diagnostic: check if the raw source is available const rawUrlMatch = url.match(/[?&]url=([^&]+)/); if (rawUrlMatch) { const rawPath = decodeURIComponent(rawUrlMatch[1]); const baseUrl = new URL(url).origin; const rawUrl = `${baseUrl}${rawPath}`; const rawRes = await fetch(rawUrl, { method: 'HEAD' }); broken.push(`${url} (Status: ${res.status}, Raw Status: ${rawRes.status})`); } else { broken.push(`${url} (Status: ${res.status})`); } } } catch (e) { broken.push(`${url} (Fetch failed)`); } }), ); return broken; }, images); if (brokenImages.length > 0) { throw new Error( `Found ${brokenImages.length} broken images on ${p}:\n${brokenImages.join('\n')}`, ); } } // 3. Test Contact Form console.log('📝 Testing Contact Form...'); await page.goto(`${targetUrl}/de/kontakt`, { waitUntil: 'networkidle2' }); await page.type('input[name="name"]', 'Smoke Test'); await page.type('input[name="email"]', 'smoke@mintel.me'); await page.type('textarea[name="message"]', 'Automated smoke test submission.'); await Promise.all([ page.waitForSelector('[role="alert"]', { timeout: 10000 }), page.click('button[type="submit"]'), ]); const alertText = await page.$eval('[role="alert"]', (el) => el.textContent); console.log(`🔔 Alert: ${alertText}`); if (alertText?.toLowerCase().includes('error') || alertText?.toLowerCase().includes('fehler')) { throw new Error(`Form submission failed: ${alertText}`); } console.log('✅ Smoke test passed!'); process.exit(0); } catch (err: any) { console.error(`❌ Smoke test failed: ${err.message}`); process.exit(1); } finally { await browser.close(); } } run();