// Runtime smoke test: load the real game in headless Chromium and drive it. const puppeteer = require('puppeteer'); const path = require('path'); (async () => { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '--disable-setuid-sandbox', '--use-gl=swiftshader', '--enable-unsafe-swiftshader', '--disable-gpu-sandbox'], }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 720 }); const errors = []; page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message)); page.on('console', m => { if (m.type() === 'error') errors.push('CONSOLE: ' + m.text()); }); const url = 'file://' + path.resolve(__dirname, 'index.html'); await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 }); await new Promise(r => setTimeout(r, 1500)); // basic state let st = await page.evaluate(() => ({ hasG: typeof G !== 'undefined', state: G.state, three: typeof THREE !== 'undefined', cars: G.cars ? G.cars.length : 0, })); console.log('BOOT:', JSON.stringify(st)); // start race await page.evaluate(() => startRace()); await new Promise(r => setTimeout(r, 500)); st = await page.evaluate(() => ({ state: G.state, raceTime: G.raceTime.toFixed(2) })); console.log('AFTER START:', JSON.stringify(st)); // hold gas + steer a bit for ~6 simulated seconds await page.keyboard.down('ArrowUp'); await new Promise(r => setTimeout(r, 3000)); await page.keyboard.down('ArrowLeft'); await new Promise(r => setTimeout(r, 1500)); await page.keyboard.up('ArrowLeft'); // use nitro briefly await page.keyboard.down('Shift'); await new Promise(r => setTimeout(r, 1200)); await page.keyboard.up('Shift'); await new Promise(r => setTimeout(r, 1500)); st = await page.evaluate(() => { const p = G.player; return { state: G.state, raceTime: +G.raceTime.toFixed(1), speed: +(Math.hypot(p.vx, p.vz) * 3.6).toFixed(0), lap: p.lap, s: Math.round(p.s), x: +p.x.toFixed(1), z: +p.z.toFixed(1), nitro: +p.nitro.toFixed(2), aiSpeeds: G.cars.filter(c => !c.isPlayer).map(a => +(a.v * 3.6).toFixed(0)), rank: getRank(), hudSpeed: document.getElementById('speed').textContent, }; }); console.log('AFTER DRIVING:', JSON.stringify(st)); await page.keyboard.up('ArrowUp'); // run 20 more seconds idle (AI keeps going), check laps progress & no NaN await new Promise(r => setTimeout(r, 20000)); st = await page.evaluate(() => { const p = G.player; const bad = [p.x, p.z, p.vx, p.vz, p.heading].some(v => !isFinite(v)); return { raceTime: +G.raceTime.toFixed(1), aiLaps: G.cars.filter(c => !c.isPlayer).map(a => a.lap), playerPos: [ +p.x.toFixed(0), +p.z.toFixed(0) ], anyNaN: bad, cam: [ +camera.position.x.toFixed(0), +camera.position.y.toFixed(0), +camera.position.z.toFixed(0) ], }; }); console.log('AFTER 20s IDLE:', JSON.stringify(st)); await page.screenshot({ path: 'shot_menu_race.png' }); console.log('ERRORS(' + errors.length + '):'); errors.slice(0, 20).forEach(e => console.log(' ' + e)); await browser.close(); process.exit(errors.length ? 1 : 0); })().catch(e => { console.error('FATAL', e); process.exit(2); });