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)); } // 5b. TAVERN: NEBENRAUM (Amy painting & Zuluman Mirror) console.log('🎨 Navigating to Nebenraum (Kemenate / Atelier)...'); const nebenraumDoor = await page.$('.hotspot-door-nebenraum'); if (nebenraumDoor) { await nebenraumDoor.click(); } else { const nrTab = await page.$('.room-tab:nth-child(2)'); if (nrTab) await nrTab.click(); } await new Promise(r => setTimeout(r, 1000)); console.log('πŸ“Έ 05b: Capturing Nebenraum (Atelier & Mirror)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05b_tavern_nebenraum.png') }); // Test Amy in Nebenraum console.log('🎨 Testing Amy Dialog in Nebenraum...'); const amyBtn = await page.$('.hotspot-amy-nebenraum'); if (amyBtn) { await amyBtn.click(); await new Promise(r => setTimeout(r, 700)); console.log('πŸ“Έ 05c: Capturing Amy Dialog...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05c_tavern_amy_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 400)); } // Test Zuluman Mirror in Nebenraum console.log('πŸͺž Testing Zuluman Magic 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)); } // 6. TAVERN: MARKTPLATZ (Mario with spinning chainsaws & NAT on stage) 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, 1000)); console.log('πŸ“Έ 08: Capturing Marktplatz (Mario chainsaws & NAT stage)...'); 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) & Test Wildschwein Duell 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') }); // Start Wild Boar Duel console.log('πŸ— Launching Wildschwein-Schnick-Schnack-Schnuck Duel...'); const startBoarBtn = await page.$('.dialog-actions .action-btn'); if (startBoarBtn) { await startBoarBtn.click(); await new Promise(r => setTimeout(r, 800)); console.log('πŸ“Έ 09c: Capturing Wildschwein Duel Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09c_tavern_boar_duel.png') }); // Play a move: 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 Clash...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09d_tavern_boar_duel_round.png') }); } // Close Boar Duel modal const closeBoarBtn = await page.$('.boar-duel-box .duel-close-btn'); if (closeBoarBtn) { await closeBoarBtn.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(4)'); 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 Luke zum Hinterraum) console.log('πŸ—οΈ Returning to Schankraum to check Luke zum Hinterraum (trapdoor)...'); const schankraumTab = await page.$('.room-tab:nth-child(1)'); if (schankraumTab) { await schankraumTab.click(); await new Promise(r => setTimeout(r, 600)); } console.log('πŸͺ© Triggering Luke zum Hinterraum Easter Egg...'); const trapdoor = await page.$('.trapdoor-hotspot'); if (trapdoor) { await trapdoor.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. GSTANZL DUEL console.log('βš”οΈ Testing Gstanzl-Duel and [βœ•] Exit Button...'); const backToGaststube = await page.$('.hotspot-door-back-vip'); if (backToGaststube) { await backToGaststube.click(); await new Promise(r => setTimeout(r, 600)); } 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();