// La pista col gesso nel browser vero, su desktop e touch: la scena di Cardetole col Chiaru, la mira, la spinta // tenendo premuto e i comandi del pannello. npm run browser:micro — schermate in /tmp/regolo-micro. import { chromium } from 'playwright'; import { mkdirSync } from 'node:fs'; import assert from 'node:assert/strict'; import { staticServer } from './helpers.mjs'; const out = process.env.OUT ?? '/tmp/regolo-micro'; mkdirSync(out, { recursive: true }); const server = await staticServer(); let browser; const errors = []; try { browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] }); for (const [name, mobile, viewport] of [ ['desktop', false, { width: 1280, height: 860 }], ['touch', true, { width: 844, height: 390 }], ['portrait', true, { width: 390, height: 844 }], ]) { const page = await browser.newPage({ viewport, hasTouch: mobile, isMobile: mobile }); page.on('pageerror', e => errors.push(e.message)); await page.goto(`http://127.0.0.1:${server.port}/`); await page.click('#splash'); await page.click('#new-game'); await page.click('#rione [data-rione="campomigliaio"]'); await page.click('#skip-intro'); await page.evaluate(async () => { const { settle } = await import('/tests/helpers.mjs'); settle(game); game.go('cardetole'); settle(game); }); await page.waitForTimeout(1200); if (name === 'desktop') await page.screenshot({ path: `${out}/${name}-cardetole.png` }); await page.evaluate(async () => { const { settle } = await import('/tests/helpers.mjs'); game.act('talk', 'chiaru'); settle(game); game.choose(game.options().findIndex(o => o.text === 'Si fa una gara alle macchinine?!?!')); settle(game); game.choose(game.options().findIndex(o => o.text === 'Alla bona.')); settle(game); }); await page.waitForSelector('#bocce-controls:not([hidden])'); await page.waitForFunction(async () => { const { hasBackground } = await import('/src/engine/render.js'); return hasBackground('assets/closeups/micro-parcheggio.png'); }); // Prima si sceglie la macchinina: la viola, toccandola. assert.equal(await page.locator('#bocce-throw').textContent(), 'Scegli'); await page.waitForFunction(async () => (await import('/src/engine/render.js')).hasBackground('assets/closeups/micro/viola-grande.png')); if (name === 'desktop') { await page.waitForTimeout(1600); await page.screenshot({ path: `${out}/${name}-sceglie.png` }); } { const box = await page.locator('#scene').boundingBox(), [vx, vy] = [480, 220]; const at = { x: box.x + vx / 640 * box.width, y: box.y + vy / 360 * box.height }; if (mobile) await page.touchscreen.tap(at.x, at.y); else await page.mouse.click(at.x, at.y); } assert.deepEqual(await page.evaluate(() => game.closeup.colors), { player: 'viola', cpu: 'blu' }); await page.waitForTimeout(1600); if (name === 'desktop') await page.screenshot({ path: `${out}/${name}-scelta.png` }); await page.waitForFunction(() => document.querySelector('#bocce-throw').textContent === 'Spingi'); assert.ok(await page.locator('#micro-help').isVisible(), 'il «Come si gioca» delle macchinine'); const exit = await page.locator('#closeup-close').boundingBox(), canvas = await page.locator('#scene').boundingBox(); assert.ok(exit.y >= canvas.y + canvas.height, 'l\'uscita sta fuori dalla pista'); await page.waitForFunction(async () => (await import('/src/engine/render.js')).hasBackground('assets/closeups/micro/viola.png')); const state = () => page.evaluate(() => ({ angle: game.closeup.angle, power: game.closeup.power, step: game.closeup.step, x: game.closeup.cars.player.x })); // Un punto della pista davanti alla macchinina di Andrea, in pixel della pagina. const ahead = await page.evaluate(() => { const c = game.closeup, i = (c.cars.player.idx + 50) % c.track.n; return c.track.pts[i]; }); const target = { x: canvas.x + ahead[0] / 640 * canvas.width, y: canvas.y + ahead[1] / 360 * canvas.height }; if (!mobile) { const before = (await state()).angle; await page.mouse.move(target.x, target.y + 30, { steps: 4 }); assert.notEqual((await state()).angle, before, 'senza clic la mira segue il mouse'); await page.mouse.click(target.x, target.y); } else await page.touchscreen.tap(target.x, target.y); const aimed = await state(); assert.equal(aimed.step, 'aim', 'un tocco mira senza spingere'); await page.screenshot({ path: `${out}/${name}-mira.png` }); // Si spinge: col mouse tenendo premuto, col dito dal pulsante dopo aver scelto la forza. if (!mobile) { // Si lascia appena la carica arriva: restando troppo alla carica piena sarebbe un accompagnè. await page.mouse.move(target.x, target.y); await page.mouse.down(); await page.waitForFunction(() => game.closeup.charging && game.closeup.power >= 0.75); await page.mouse.up(); } else { await page.locator('#bocce-power').fill('70'); await page.locator('#bocce-throw').tap(); } await page.waitForTimeout(250); const rolling = await state(); assert.equal(rolling.step, 'rolling', 'la macchinina parte'); await page.screenshot({ path: `${out}/${name}-corsa.png` }); // Poi spinge il Chiaru, e si torna a mirare. await page.waitForFunction(() => game.closeup.current === 'player' && game.closeup.step === 'aim', null, { timeout: 15000 }); assert.notEqual((await state()).x, aimed.x, 'la macchinina di Andrea si è mossa'); await page.screenshot({ path: `${out}/${name}-dopo.png` }); await page.close(); } assert.deepEqual(errors, []); console.log(`ok: schermate in ${out}`); } finally { await browser?.close(); server.kill(); }