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 takeScreenshot(page, filename) { try { await page.screenshot({ path: path.join(SCREENSHOTS_DIR, filename), timeout: 20000 }); } catch (err) { console.warn("โš ๏ธ Warning: Screenshot " + filename + " timed out or failed:", err.message); } } 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', protocolTimeout: 60000, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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, 1000)); } // Skip NAT to Flo again await page.waitForSelector('.choice-btn', { timeout: 4000 }).catch(() => null); const natChoice2 = await page.$('.choice-btn'); if (natChoice2) { await natChoice2.click(); await new Promise(r => setTimeout(r, 800)); } // Decline Flo Maier -> Viktor arrives await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); const declineFloBtn = await page.$('.choice-btn:nth-child(2)'); if (declineFloBtn) { await declineFloBtn.click(); await new Promise(r => setTimeout(r, 1000)); } console.log('๐Ÿ“ธ 02d: Capturing Viktor Partyvampir arrives...'); await takeScreenshot(page, '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 takeScreenshot(page, '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, 1000)); } // Advance to Mop safely: NAT -> Flo (decline) -> Viktor (decline) -> Mop await page.waitForSelector('.choice-btn', { timeout: 4000 }).catch(() => null); const natC = await page.$('.choice-btn'); if (natC) { await natC.click(); await new Promise(r => setTimeout(r, 600)); } await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); const floC = await page.$('.choice-btn:nth-child(2)'); if (floC) { await floC.click(); await new Promise(r => setTimeout(r, 600)); } await page.waitForSelector('.choice-btn:nth-child(2)', { timeout: 4000 }).catch(() => null); 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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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 takeScreenshot(page, '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();