Files
infrapuzzle/projects/sasch40/frontend/scripts/playtest.mjs
T

550 lines
21 KiB
JavaScript

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();