// Prova nel browser vero (Chromium headless con swiftshader): titolo, partita giocata con clic veri // fino alla fine, salvataggio e impaginazione su telefono. Schermate in $OUT (predefinito: /tmp/regolo-shots). // npm run browser import { chromium } from 'playwright'; import { spawn } from 'node:child_process'; import { mkdirSync } from 'node:fs'; import assert from 'node:assert/strict'; import { solution } from '../src/story/solution.js'; const OUT = process.env.OUT ?? '/tmp/regolo-shots', PORT = 8000 + Math.floor(Math.random() * 900); mkdirSync(OUT, { recursive: true }); const server = spawn('python3', ['-m', 'http.server', String(PORT), '--bind', '127.0.0.1'], { stdio: 'ignore' }); const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); const errors = []; async function open(viewport) { const page = await browser.newPage({ viewport }); page.on('pageerror', e => errors.push(e.message)); page.on('console', m => m.type() === 'error' && errors.push(m.text())); for (let i = 0; i < 50; i++) { try { await page.goto(`http://127.0.0.1:${PORT}/`); break; } catch { await page.waitForTimeout(100); } } return page; } // Clic su un punto della scena (coordinate 640×360). async function clickScene(page, [x, y]) { const r = await page.locator('#scene').boundingBox(); await page.mouse.click(r.x + x / 640 * r.width, r.y + y / 360 * r.height); } // Aspetta che il gioco sia fermo, saltando le battute con la barra spaziatrice. async function settle(page) { for (let i = 0; i < 400; i++) { const s = await page.evaluate(() => ({ speech: !!game.speech, idle: !game.busy && !game.walking && !game.queue.length && !game.task })); if (s.speech) await page.keyboard.press('Space'); else if (s.idle) return; await page.waitForTimeout(50); } throw new Error('il gioco non si ferma'); } const hotspotCenter = (page, id) => page.evaluate(id => { const h = game.hotspot(id); return [h.box[0] + h.box[2] / 2, h.box[1] + h.box[3] / 2]; }, id); try { const page = await open({ width: 1280, height: 860 }); await page.waitForSelector('#title:not([hidden])'); await page.screenshot({ path: `${OUT}/01-titolo.png` }); await page.click('#new-game'); await page.waitForFunction(() => game.speech); await page.screenshot({ path: `${OUT}/02-inizio.png` }); const fits = async p => p.evaluate(() => document.querySelector('#panel').getBoundingClientRect().bottom <= innerHeight); assert.ok(await fits(page), 'verbi e inventario fuori dallo schermo'); await settle(page); for (const [verb, target, item] of solution) { if (verb === 'choose') { await page.locator('#choices li', { hasText: target }).click(); } else { const smart = await page.evaluate(([v, t]) => game.defaultVerb(t) === v, [verb, target]); if (item) { await page.click(`#verbs [data-verb="${verb}"]`); await page.click(`#inventory [data-item="${item}"]`); } else if (!smart) await page.click(`#verbs [data-verb="${verb}"]`); await clickScene(page, await hotspotCenter(page, target)); } await page.waitForTimeout(80); if (verb === 'talk') await page.screenshot({ path: `${OUT}/03-dialogo.png` }); await settle(page); if (target === 'cosa_luccicante') await page.screenshot({ path: `${OUT}/04-squama.png` }); if (target === 'verso_salita') await page.screenshot({ path: `${OUT}/05-salita.png` }); } await page.waitForSelector('#ending:not([hidden])'); await page.screenshot({ path: `${OUT}/06-fine.png` }); const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-save'))); assert.ok(saved.ended && saved.inventory.includes('squama'), 'salvataggio finale'); // Una partita a metà riprende da dove era rimasta. await page.click('#restart'); await settle(page); await clickScene(page, await hotspotCenter(page, 'verso_salita')); await settle(page); await page.reload(); await page.waitForSelector('#continue:not([hidden])'); await page.click('#continue'); assert.equal(await page.evaluate(() => game.state.scene), 'salita'); const phone = await open({ width: 390, height: 844 }); await phone.click('#new-game'); await settle(phone); const overflow = await phone.evaluate(() => document.documentElement.scrollWidth > innerWidth); assert.equal(overflow, false, 'scorrimento orizzontale su telefono'); await phone.screenshot({ path: `${OUT}/07-telefono.png` }); const landscape = await open({ width: 844, height: 390 }); await landscape.click('#new-game'); await settle(landscape); assert.equal(await landscape.evaluate(() => document.documentElement.scrollWidth > innerWidth), false); await landscape.screenshot({ path: `${OUT}/08-orizzontale.png`, fullPage: true }); assert.deepEqual(errors, [], 'errori nella console'); console.log(`Browser OK: soluzione completa, ripresa del salvataggio, telefono. Schermate in ${OUT}`); } finally { await browser.close(); server.kill(); }