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 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: 30000 }); // 1. INTRO SCREEN console.log('📸 Capturing Intro Screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '01_intro_screen.png') }); // Inspect layout and controls presence in Intro const introControlsVisible = await page.evaluate(() => { const dpad = document.querySelector('.dpad'); const intro = document.querySelector('.intro-screen'); const chassis = document.querySelector('.gameboy-chassis'); return { hasDpad: !!dpad, dpadOffsetHeight: dpad ? (dpad.offsetHeight > 0) : false, hasIntro: !!intro, layoutClass: chassis ? chassis.className : '' }; }); console.log('Intro Layout Check:', introControlsVisible); // Click Start Button console.log('🖱️ Clicking Start Button...'); await page.click('.start-btn'); await new Promise(r => setTimeout(r, 1000)); // 2. PROLOGUE SCREEN console.log('📸 Capturing Prologue Screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02_prologue_screen.png') }); // Advance Prologue Dialogues console.log('💬 Advancing Prologue Dialogues (clicking choice buttons)...'); // Click choice 1 on 'start' 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('📸 Capturing Mop Appears Prologue Dialogue...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02b_prologue_mop.png') }); } // Click choice on 'mop_appears' -> triggers 'startPlatformer' const mopChoice = await page.$('.choice-btn'); if (mopChoice) { await mopChoice.click(); await new Promise(r => setTimeout(r, 1200)); } // 3. PLATFORMER SCREEN console.log('📸 Capturing Platformer (Jump & Run)...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03_platformer_screen.png') }); const platformerControls = await page.evaluate(() => { const dpad = document.querySelector('.dpad'); const btnA = document.querySelector('.btn-a'); const canvas = document.querySelector('canvas'); return { hasDpad: !!dpad, hasBtnA: !!btnA, hasCanvas: !!canvas, canvasW: canvas?.width, canvasH: canvas?.height }; }); console.log('Platformer Controls Check:', platformerControls); // Test Jump & Move console.log('🎮 Testing Jump & Move Controls in Platformer...'); const btnA = await page.$('.btn-a'); if (btnA) { await btnA.click(); await new Promise(r => setTimeout(r, 400)); await btnA.click(); await new Promise(r => setTimeout(r, 400)); } const dpadRight = await page.$('.dpad-right'); if (dpadRight) { await dpadRight.hover(); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03b_platformer_jump.png') }); } // Directly set act to 'tavern' to test Tavern Point & Click console.log('🍺 Advancing to Tavern Act...'); await page.evaluate(() => { const chassis = document.querySelector('.gameboy-chassis'); // access pinia store from window if exposed, or trigger level finish // We can click the level finish or evaluate store change window.dispatchEvent(new CustomEvent('test-set-act', { detail: 'tavern' })); }); // Check if act switched, or use page.evaluate to trigger store change await page.evaluate(() => { // Find Pinia root if possible const app = document.querySelector('#app')?.__vue_app__; if (app && app.config?.globalProperties?.$pinia) { const pinia = app.config.globalProperties.$pinia; // find game store for (const [id, store] of pinia._s.entries()) { if (id === 'game') { store.setAct('tavern'); } } } }); await new Promise(r => setTimeout(r, 1000)); console.log('📸 Capturing Tavern Screen...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '04_tavern_screen.png') }); // Test Tavern Hotspots console.log('👥 Testing Verena Hotspot...'); const verenaBtn = await page.$('.hotspot-verena'); if (verenaBtn) { await verenaBtn.click(); await new Promise(r => setTimeout(r, 800)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_verena_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 500)); } console.log('🔨 Testing Armend Hotspot...'); const armendBtn = await page.$('.hotspot-armend'); if (armendBtn) { await armendBtn.click(); await new Promise(r => setTimeout(r, 800)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '06_tavern_armend_dialog.png') }); const actionBtn = await page.$('.dialog-actions .action-btn'); if (actionBtn) await actionBtn.click(); await new Promise(r => setTimeout(r, 500)); } console.log('🪞 Testing Zuluman Magic Mirror Hotspot...'); const mirrorBtn = await page.$('.hotspot-mirror'); if (mirrorBtn) { await mirrorBtn.click(); await new Promise(r => setTimeout(r, 800)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '07_tavern_zuluman_mirror.png') }); const closeBtn = await page.$('.mirror-box .action-btn'); if (closeBtn) await closeBtn.click(); await new Promise(r => setTimeout(r, 500)); } console.log('⚔️ Testing Gstanzl-Duel...'); const duelBtn = await page.$('.duel-trigger'); if (duelBtn) { await duelBtn.click(); await new Promise(r => setTimeout(r, 800)); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_gstanzl_duel.png') }); // Answer Gstanzl question const correctOption = await page.$('.option-btn'); if (correctOption) { await correctOption.click(); await new Promise(r => setTimeout(r, 600)); } } // 4. 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('📸 Capturing Party Express...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09_party_express.png') }); // 5. 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(25); store.serveGuest(); store.serveGuest(); store.setAct('finale'); } } } }); await new Promise(r => setTimeout(r, 1500)); console.log('📸 Capturing Certificate Modal...'); await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_certificate_screen.png') }); // Check images in document 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 on Certificate Page:', 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();