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('📸 02a: Capturing Prologue Screen (NAT opening)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02a_prologue_nat.png') }); // Click first choice -> Flo Maier arrives const natChoice = await page.$('.choice-btn'); if (natChoice) await natChoice.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 02b: Capturing Flo Maier Feierdeifl arrives...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02b_prologue_flo_feierdeifl.png') }); // Test Game Over branch: "Ja freilich, zünd ois o, Flo!" const fireChoice = await page.$('.choice-btn:nth-child(1)'); if (fireChoice) await fireChoice.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 02c: Capturing Flo Maier Game Over (fiery castle)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02c_prologue_flo_game_over.png') }); // Reset via Löschzug const resetFireBtn = await page.$('.game-over-btn'); if (resetFireBtn) await resetFireBtn.click(); await new Promise(r => setTimeout(r, 800)); // Skip NAT to Flo again const natChoice2 = await page.$('.choice-btn'); if (natChoice2) await natChoice2.click(); await new Promise(r => setTimeout(r, 600)); // Decline Flo Maier -> Viktor arrives const declineFloBtn = await page.$('.choice-btn:nth-child(2)'); if (declineFloBtn) await declineFloBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 02d: Capturing Viktor Partyvampir arrives...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02d_prologue_viktor_vampire.png') }); // Test Game Over branch: "Ja geil, mach mi unsterblich!" const biteChoice = await page.$('.choice-btn:nth-child(1)'); if (biteChoice) await biteChoice.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 02e: Capturing Viktor Game Over (glitter dust in sun)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02e_prologue_viktor_game_over.png') }); // Reset via Knoblauch const resetVampBtn = await page.$('.game-over-btn'); if (resetVampBtn) await resetVampBtn.click(); await new Promise(r => setTimeout(r, 800)); // Advance to Mop safely: NAT -> Flo (decline) -> Viktor (decline) -> Mop const natC = await page.$('.choice-btn'); if (natC) await natC.click(); await new Promise(r => setTimeout(r, 500)); const floC = await page.$('.choice-btn:nth-child(2)'); if (floC) await floC.click(); await new Promise(r => setTimeout(r, 500)); const vikC = await page.$('.choice-btn:nth-child(2)'); if (vikC) await vikC.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 02f: Capturing Mop Dialogue (Sister Rescue Quest)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02f_prologue_mop.png') }); // Advance to Akt II (Doodle Jump Schwesternrettung) const startAkt2Btn = await page.$('.choice-btn'); if (startAkt2Btn) await startAkt2Btn.click(); await new Promise(r => setTimeout(r, 1000)); // 3. AKT II: DOODLE JUMP SCHWESTERNRETTUNG console.log('📸 03a: Capturing Akt II Doodle Jump Start Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03a_doodle_jump_intro_modal.png') }); // Click Start Act Button on modal const startDoodleBtn = await page.$('.start-act-btn'); if (startDoodleBtn) { await startDoodleBtn.click(); await new Promise(r => setTimeout(r, 800)); } // Capture Gameplay (Bouncing up platforms) console.log('📸 03b: Capturing Doodle Jump Gameplay...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03b_doodle_jump_gameplay.png') }); // Trigger Sister 1 Rescue (Saskia) await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const comp = app?._instance?.subTree?.children?.[1]?.children?.[0]?.component?.subTree?.children?.[0]?.component?.ctx; // Alternatively trigger rescue directly via store or component const pinia = app?.config?.globalProperties?.$pinia; const store = pinia?._s.get('game'); if (store) { store.addKronkorken(3); store.addBeer(); } }); // 4. TAVERN: SCHANKRAUM (Gaststube with Seamless Oak Floor) console.log('🍺 Advancing to Tavern Act (Schankraum)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const pinia = app?.config?.globalProperties?.$pinia; const store = pinia?._s.get('game'); if (store) store.setAct('tavern'); }); await new Promise(r => setTimeout(r, 1000)); console.log('📸 05: Capturing Tavern Schankraum (seamless oak floor)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_schankraum.png') }); // Test Double-Click / Double-Tap on Trapdoor to unlock Party Bunker console.log('🚪 Testing double-click / double-tap on oak floor trapdoor...'); const trapdoor = await page.$('.trapdoor-hotspot'); if (trapdoor) { // First click: triggers subtle sound, still closed await trapdoor.click(); await new Promise(r => setTimeout(r, 150)); // Second click within 450ms: unlocks Party Bunker! await trapdoor.click(); await new Promise(r => setTimeout(r, 1000)); console.log('📸 12b: Capturing Secret VIP Party Bunker...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12b_tavern_party_bunker_vip.png') }); // Click Pü in Party Bunker 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)); } // Return to Schankraum const backToSchankraum = await page.$('.hotspot-door-back-vip'); if (backToSchankraum) { await backToSchankraum.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 12: Capturing Schankraum with revealed Luke zum Party Bunker...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_party_bunker_trapdoor.png') }); } } // Test Nebenraum (Atelier with Amy & Zuluman Mirror) console.log('🎨 Navigating to Nebenraum (Kemenate / Atelier)...'); const nrTab = await page.$('.room-tab:nth-child(2)'); if (nrTab) await nrTab.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 05b: Capturing Nebenraum (Atelier & Mirror)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05b_tavern_nebenraum.png') }); // Test Zuluman Mirror in Nebenraum const mirrorBtn = await page.$('.hotspot-mirror-nebenraum'); 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)); } // Test Marktplatz & Wildschwein Duel console.log('🎪 Navigating to Marktplatz...'); const marktplatzTab = await page.$('.room-tab:nth-child(3)'); if (marktplatzTab) await marktplatzTab.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 08: Capturing Marktplatz...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') }); const natBtn = await page.$('.hotspot-nat'); if (natBtn) { await natBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 09b: Capturing NAT Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') }); // Click "🐗 Zum Wildschwein-Duell!" -> opens INTRO MODAL const startBoarIntroBtn = await page.$('.dialog-actions .action-btn'); if (startBoarIntroBtn) { await startBoarIntroBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 09c: Capturing Wildschwein Duel INTRO modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09c_tavern_boar_duel_intro.png') }); // Start Duel from Intro const startBoarDuelBtn = await page.$('.start-duel-action-btn'); if (startBoarDuelBtn) { await startBoarDuelBtn.click(); await new Promise(r => setTimeout(r, 800)); // Play Keiler const keilerBtn = await page.$('.boar-choice-btn:nth-child(1)'); if (keilerBtn) { await keilerBtn.click(); await new Promise(r => setTimeout(r, 600)); console.log('📸 09d: Capturing Boar Duel Round Clash (verifying unhighlighted buttons)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09d_tavern_boar_duel_round.png') }); } // Close modal via ✕ const closeBoarBtn = await page.$('.boar-duel-box .duel-close-btn'); if (closeBoarBtn) { await closeBoarBtn.click(); await new Promise(r => setTimeout(r, 400)); } } } } // Test Hinterland (Mane & Hansi departing toward Münchanien) console.log('🌲 Navigating to Hinterland...'); const hinterlandTab = await page.$('.room-tab:nth-child(4)'); if (hinterlandTab) await hinterlandTab.click(); await new Promise(r => setTimeout(r, 1000)); console.log('📸 10: Capturing Hinterland (Mane & Hansi departing toward Münchanien)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_tavern_hinterland.png') }); // Click Hansi to hear about departure and evening concert const hansiBtn = await page.$('.hotspot-hansi'); if (hansiBtn) { await hansiBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 11: Capturing Hansi Departure Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11_tavern_hansi_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } // Test Münchanien Signpost Hint Dialog console.log('🪧 Testing Münchanien Signpost Hint...'); const signBtn = await page.$('.hotspot-sign-munchanien'); if (signBtn) { await signBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 11b: Capturing Münchanien Signpost Hint Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11b_tavern_munchanien_hint.png') }); const hintCloseBtn = await page.$('.intro-modal-box .action-btn'); if (hintCloseBtn) await hintCloseBtn.click(); await new Promise(r => setTimeout(r, 400)); } // Test Hinter den Bergen (Taiwan Valley & Papa Markus) console.log('🏮 Navigating to Hinter den Bergen...'); const hdbDoor = await page.$('.hotspot-door-hinter-den-bergen'); if (hdbDoor) { await hdbDoor.click(); } else { const hdbTab = await page.$('.room-tab:nth-child(5)'); if (hdbTab) await hdbTab.click(); } await new Promise(r => setTimeout(r, 1000)); console.log('📸 11c: Capturing Hinter den Bergen Scene (Taiwan Alpine Skyline & Papa Markus)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11c_tavern_hinter_den_bergen.png') }); // Click Markus for Liar's Dice const markusBtn = await page.$('.hotspot-markus'); if (markusBtn) { await markusBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 11d: Capturing Papa Markus Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11d_tavern_markus_dialog.png') }); // Click "🎲 Auf geht's Papa, Liar's Dice spielen!" -> opens intro modal const startDiceIntroBtn = await page.$('.dialog-actions .action-btn'); if (startDiceIntroBtn) { await startDiceIntroBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('📸 11e: Capturing Liar\'s Dice INTRO Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11e_tavern_liars_dice_intro.png') }); // Start Liar's Dice Match const startMatchBtn = await page.$('.start-duel-action-btn'); if (startMatchBtn) { await startMatchBtn.click(); await new Promise(r => setTimeout(r, 1200)); console.log('📸 11f: Capturing Liar\'s Dice Initial Round View...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11f_tavern_liars_dice_gameplay.png') }); // Submit player bid const bidBtn = await page.$('.bid-action-btn'); if (bidBtn) { await bidBtn.click(); await new Promise(r => setTimeout(r, 1500)); // wait for Markus response console.log('📸 11g: Capturing Liar\'s Dice Markus Counter-Bid...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11g_tavern_liars_dice_markus_turn.png') }); } // Call bluff const bluffBtn = await page.$('.call-bluff-btn'); if (bluffBtn) { await bluffBtn.click(); await new Promise(r => setTimeout(r, 1200)); // wait for shot animation console.log('📸 11h: Capturing Liar\'s Dice Bluff Resolution & Gaoliang Shot...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11h_tavern_liars_dice_bluff_call.png') }); } // Close modal const closeDiceBtn = await page.$('.liars-dice-box .duel-close-btn'); if (closeDiceBtn) { await closeDiceBtn.click(); await new Promise(r => setTimeout(r, 400)); } } } } // Return to Hinterland const backToHinterland = await page.$('.hotspot-door-back-hinterland'); if (backToHinterland) { await backToHinterland.click(); await new Promise(r => setTimeout(r, 600)); } // Test Gstanzl Duel Intro & Gameplay console.log('⚔️ Testing Gstanzl-Fechten Intro & Minigame...'); const duelBtn = await page.$('.duel-trigger'); if (duelBtn) { await duelBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 15a: Capturing Gstanzl Duel INTRO modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15a_tavern_gstanzl_intro.png') }); const startGstanzlBtn = await page.$('.start-duel-action-btn'); if (startGstanzlBtn) { await startGstanzlBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 15b: Capturing Gstanzl Duel Round...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15b_tavern_gstanzl_duel.png') }); // Close via ✕ const closeDuelBtn = await page.$('.duel-box .duel-close-btn'); if (closeDuelBtn) { await closeDuelBtn.click(); await new Promise(r => setTimeout(r, 400)); } } } // 5. AKT IV: MITTELALTER GUITAR HERO (HANSIS ABSCHIEDSKONZERT) console.log('🎸 Advancing to Akt IV (Mittelalter Guitar Hero)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const pinia = app?.config?.globalProperties?.$pinia; const store = pinia?._s.get('game'); if (store) store.setAct('party_express'); }); await new Promise(r => setTimeout(r, 1000)); console.log('📸 16: Capturing Lute Hero Start Screen Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '16_lute_hero_start_modal.png') }); // Start Concert const startConcertBtn = await page.$('.start-act-btn'); if (startConcertBtn) { await startConcertBtn.click(); await new Promise(r => setTimeout(r, 800)); } // Hit string buttons console.log('🎸 Hitting lute strings...'); const btnLane0 = await page.$('.btn-lane-0'); const btnLane1 = await page.$('.btn-lane-1'); const btnLane2 = await page.$('.btn-lane-2'); if (btnLane0) await btnLane0.click(); await new Promise(r => setTimeout(r, 200)); if (btnLane1) await btnLane1.click(); await new Promise(r => setTimeout(r, 200)); if (btnLane2) await btnLane2.click(); await new Promise(r => setTimeout(r, 300)); console.log('📸 17: Capturing Castle Stage Rock Concert Gameplay...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '17_lute_hero_gameplay.png') }); // 6. AKT V: FINALE & CERTIFICATE console.log('👑 Advancing to Finale Certificate (Act V)...'); await page.evaluate(() => { const app = document.querySelector('#app')?.__vue_app__; const pinia = app?.config?.globalProperties?.$pinia; const store = pinia?._s.get('game'); if (store) { store.addKronkorken(35); store.serveGuest(); store.serveGuest(); store.setAct('finale'); } }); await new Promise(r => setTimeout(r, 1500)); console.log('📸 18: Capturing Certificate Screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '18_certificate_screen.png') }); // Test Settings Modal (Gear icon ⚙️) console.log('⚙️ Testing Settings Modal (Pause, Mute, Reset)...'); const settingsBtn = await page.$('.settings-toggle-btn'); if (settingsBtn) { await settingsBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('📸 19: Capturing Settings & Pause Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '19_settings_pause_modal.png') }); // Click Resume const resumeBtn = await page.$('.resume-btn'); if (resumeBtn) { await resumeBtn.click(); await new Promise(r => setTimeout(r, 500)); } } // Test NOCHMAL SPIELEN button on certificate 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('📸 20: Capturing post-restart Intro screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '20_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();