import puppeteer from 'puppeteer-core'; import path from 'path'; import fs from 'fs'; const ARTIFACTS_DIR = '/Users/moritz/.gemini/antigravity/brain/26049ae8-3106-42e0-922f-356dbbfe162a'; const SCREENSHOTS_DIR = path.join(ARTIFACTS_DIR, 'playtest_screenshots'); if (!fs.existsSync(SCREENSHOTS_DIR)) { fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); } async function runPlaytest() { console.log('πŸš€ Starting Comprehensive Chrome Playtest for Saschas Kronkorken Kreuzzug...'); const logs = []; const errors = []; const failedRequests = []; const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: 'new', args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', '--window-size=412,869' ] }); const page = await browser.newPage(); await page.setViewport({ width: 412, height: 869, deviceScaleFactor: 2, isMobile: true, hasTouch: true }); page.on('console', msg => { const text = msg.text(); logs.push(`[${msg.type().toUpperCase()}] ${text}`); if (msg.type() === 'error') { errors.push(`Console Error: ${text}`); } }); page.on('pageerror', err => { errors.push(`Page Error: ${err.toString()}`); }); page.on('requestfailed', req => { failedRequests.push(`Failed Request: ${req.url()} (${req.failure()?.errorText || 'unknown'})`); }); page.on('response', resp => { if (resp.status() >= 400) { failedRequests.push(`HTTP ${resp.status()}: ${resp.url()}`); } }); try { console.log('🌐 Navigating to https://sasch40.haumdaucher.de...'); await page.goto('https://sasch40.haumdaucher.de', { waitUntil: 'networkidle0', timeout: 35000 }); // 1. INTRO / PREQUEL SCREEN console.log('πŸ“Έ 01: Capturing Intro Screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '01_intro_screen.png') }); // Click Start Button console.log('πŸ–±οΈ Clicking Start Button...'); await page.click('.start-btn'); await new Promise(r => setTimeout(r, 1000)); // 2. PROLOGUE SCREEN (Balcony Scene) console.log('πŸ“Έ 02: Capturing Prologue Screen (Verifying banner/portrait alignment)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02_prologue_screen.png') }); // Advance Prologue Dialogues await page.waitForSelector('.choice-btn', { timeout: 5000 }); const firstChoice = await page.$('.choice-btn'); if (firstChoice) { await firstChoice.click(); await new Promise(r => setTimeout(r, 800)); console.log('πŸ“Έ 02b: Capturing Prologue Mop Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02b_prologue_mop.png') }); } // Advance to Platformer const mopChoice = await page.$('.choice-btn'); if (mopChoice) { await mopChoice.click(); await new Promise(r => setTimeout(r, 1000)); } // 3. PLATFORMER PRE-LEVEL START SCREEN (Act II) console.log('πŸ“Έ 03: Capturing Platformer Start Screen Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03_platformer_start_modal.png') }); // Click Start Act Button on the modal const startPlatformerBtn = await page.$('.start-act-btn'); if (startPlatformerBtn) { await startPlatformerBtn.click(); await new Promise(r => setTimeout(r, 800)); } // 4. PLATFORMER GAMEPLAY console.log('πŸ“Έ 04: Capturing Platformer Gameplay...'); const btnA = await page.$('.btn-a'); if (btnA) { await btnA.click(); await new Promise(r => setTimeout(r, 300)); } await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '04_platformer_gameplay.png') }); // 5. TAVERN: SCHANKRAUM (Gaststube) console.log('🍺 Advancing to Tavern Act (Schankraum)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; if (app && app.config?.globalProperties?.$pinia) { const pinia = app.config.globalProperties.$pinia; for (const [id, store] of pinia._s.entries()) { if (id === 'game') store.setAct('tavern'); } } }); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 05: Capturing Tavern Gaststube (Schankraum)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_schankraum.png') }); // Test Verena Hotspot console.log('πŸ‘₯ Testing Verena Dialog...'); const verenaBtn = await page.$('.hotspot-verena'); if (verenaBtn) { await verenaBtn.click(); await new Promise(r => setTimeout(r, 700)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '06_tavern_verena_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } // Test Armend Hotspot console.log('πŸ”¨ Testing Armend Dialog...'); const armendBtn = await page.$('.hotspot-armend'); if (armendBtn) { await armendBtn.click(); await new Promise(r => setTimeout(r, 700)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '07_tavern_armend_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } // 6. TAVERN: MARKTPLATZ (Mario with spinning chainsaws) console.log('πŸŽͺ Navigating to Marktplatz...'); const marktplatzTab = await page.$('.room-tab:nth-child(2)'); if (marktplatzTab) { await marktplatzTab.click(); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 08: Capturing Marktplatz (Mario chainsaws)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') }); // Click Mario const marioBtn = await page.$('.hotspot-mario'); if (marioBtn) { await marioBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 09: Capturing Mario Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09_tavern_mario_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } // Click NAT (Josef) const natBtn = await page.$('.hotspot-nat'); if (natBtn) { await natBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 09b: Capturing NAT (Josef) Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } } // 7. TAVERN: HINTERLAND (Mane hiking) console.log('🌲 Navigating to Hinterland...'); const hinterlandTab = await page.$('.room-tab:nth-child(3)'); if (hinterlandTab) { await hinterlandTab.click(); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 10: Capturing Hinterland (Mane hiking)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_tavern_hinterland.png') }); // Click Mane const maneBtn = await page.$('.hotspot-mane'); if (maneBtn) { await maneBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 11: Capturing Mane Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11_tavern_mane_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } } // 8. TAVERN: SECRET HINTERZIMMER (VIP Rave via Secret Tap) console.log('πŸ—οΈ Returning to Schankraum to check Secret Tap (initially invisible)...'); const schankraumTab = await page.$('.room-tab:nth-child(1)'); if (schankraumTab) { await schankraumTab.click(); await new Promise(r => setTimeout(r, 600)); } console.log('πŸͺ© Triggering Secret Tap Easter Egg...'); const secretTap = await page.$('.secret-tap-hotspot'); if (secretTap) { await secretTap.click(); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 12: Capturing Secret VIP Hinterzimmer...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_hinterzimmer_vip.png') }); // Click PΓΌ in Hinterzimmer const pueBtn = await page.$('.hotspot-pue'); if (pueBtn) { await pueBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 13: Capturing PΓΌ Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '13_tavern_pue_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } } // 9. ZULUMAN MAGIC MIRROR & GSTANZL DUEL console.log('πŸͺž Testing Zuluman Magic Mirror...'); const backToGaststube = await page.$('.hotspot-door-back-vip'); if (backToGaststube) { await backToGaststube.click(); await new Promise(r => setTimeout(r, 600)); } const mirrorBtn = await page.$('.hotspot-mirror'); if (mirrorBtn) { await mirrorBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 14: Capturing Zuluman Mirror...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '14_tavern_zuluman_mirror.png') }); const closeBtn = await page.$('.mirror-box .action-btn'); if (closeBtn) await closeBtn.click(); await new Promise(r => setTimeout(r, 400)); } console.log('βš”οΈ Testing Gstanzl-Duel and [βœ•] Exit Button...'); const duelBtn = await page.$('.duel-trigger'); if (duelBtn) { await duelBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 15: Capturing Gstanzl Duel with [βœ•] button...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15_tavern_gstanzl_duel.png') }); // Test the [βœ•] close button const closeDuelBtn = await page.$('.duel-close-btn'); if (closeDuelBtn) { console.log('Testing duel exit via [βœ•] button...'); await closeDuelBtn.click(); await new Promise(r => setTimeout(r, 600)); const duelClosed = await page.$('.duel-box') === null; console.log('Duel modal closed successfully by [βœ•]:', duelClosed); } } // Test reload persistence in Tavern Act console.log('πŸ’Ύ Testing game state reload persistence...'); const stateBeforeReload = await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const pinia = app?.config?.globalProperties?.$pinia; const s = pinia?._s.get('game'); return s ? { currentAct: s.currentAct, kronkorken: s.kronkorken, beers: s.beers } : null; }); console.log('State before reload:', stateBeforeReload); await page.reload({ waitUntil: 'networkidle0' }); await new Promise(r => setTimeout(r, 1000)); const stateAfterReload = await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const pinia = app?.config?.globalProperties?.$pinia; const s = pinia?._s.get('game'); return s ? { currentAct: s.currentAct, kronkorken: s.kronkorken, beers: s.beers } : null; }); console.log('State after reload:', stateAfterReload); // 10. PARTY EXPRESS (Act IV) console.log('πŸš‚ Advancing to Party Express (Act IV)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; if (app && app.config?.globalProperties?.$pinia) { const pinia = app.config.globalProperties.$pinia; for (const [id, store] of pinia._s.entries()) { if (id === 'game') store.setAct('party_express'); } } }); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 16: Capturing Party Express Start Screen Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '16_party_express_start_modal.png') }); // Start Party Express const startExpressBtn = await page.$('.start-act-btn'); if (startExpressBtn) { await startExpressBtn.click(); await new Promise(r => setTimeout(r, 800)); } console.log('πŸ“Έ 17: Capturing Party Express Gameplay (Redesigned Bollerwagen)...'); // Serve beer const throwBeerBtn = await page.$('.throw-beer-btn'); if (throwBeerBtn) { await throwBeerBtn.click(); await new Promise(r => setTimeout(r, 400)); } await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '17_party_express_gameplay.png') }); // 11. FINALE / CERTIFICATE (Act V) console.log('πŸ‘‘ Advancing to Finale Certificate (Act V)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; if (app && app.config?.globalProperties?.$pinia) { const pinia = app.config.globalProperties.$pinia; for (const [id, store] of pinia._s.entries()) { if (id === 'game') { store.addKronkorken(35); store.serveGuest(); store.serveGuest(); store.setAct('finale'); } } } }); await new Promise(r => setTimeout(r, 1500)); console.log('πŸ“Έ 18: Capturing Certificate Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '18_certificate_screen.png') }); // Test NOCHMAL SPIELEN button console.log('πŸ”„ Testing NOCHMAL SPIELEN button...'); const restartBtn = await page.$('.restart-btn'); if (restartBtn) { await restartBtn.click(); await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 19: Capturing post-restart Intro screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '19_post_restart_intro.png') }); } // Verify all images loaded properly const brokenImages = await page.evaluate(() => { const imgs = Array.from(document.querySelectorAll('img')); return imgs .filter(img => !img.complete || img.naturalWidth === 0) .map(img => ({ src: img.src, alt: img.alt })); }); console.log('\n--- PLAYTEST REPORT ---'); console.log('Total Console Logs:', logs.length); console.log('Console Errors:', errors); console.log('Failed Requests:', failedRequests); console.log('Broken Images:', brokenImages); const report = { timestamp: new Date().toISOString(), errors, failedRequests, brokenImages, screenshots: fs.readdirSync(SCREENSHOTS_DIR) }; fs.writeFileSync(path.join(ARTIFACTS_DIR, 'playtest_report.json'), JSON.stringify(report, null, 2)); } catch (err) { console.error('Playtest error:', err); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, 'error_state.png') }).catch(() => {}); } finally { await browser.close(); console.log('🏁 Playtest browser closed.'); } } runPlaytest();