// Il flipper del River nel browser vero: dal bar, la molla col mouse e col dito, i flipper con due dita sulla scena, // coi pulsanti e con le frecce, la pausa che lascia tutto, il risultato. npm run browser:flipper — schermate in // /tmp/regolo-flipper. 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-flipper'; 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 }]]) { 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('via_provinciale'); settle(game); game.act('use', 'river'); settle(game); game.choose(game.options().findIndex(o => o.text.startsWith('Una partita alla bona'))); settle(game); }); await page.waitForSelector('#bocce-controls:not([hidden])'); await page.waitForFunction(async () => (await import('/src/engine/render.js')).hasBackground('assets/closeups/river-flipper.png')); assert.equal(await page.locator('#bocce-throw').textContent(), 'Lancia'); assert.ok(await page.locator('#flipper-help').isVisible()); assert.equal(await page.locator('#bocce-left').textContent(), '← Flipper'); assert.ok(!(await page.locator('#bocce-left').isDisabled()), 'i pulsanti dei flipper sono attivi anche prima del lancio'); await page.screenshot({ path: `${out}/${name}-tavola.png` }); const box = await page.locator('#scene').boundingBox(), P = (x, y) => [box.x + x / 640 * box.width, box.y + y / 360 * box.height]; const cdp = mobile ? await page.context().newCDPSession(page) : null; const touch = (type, points) => cdp.send('Input.dispatchTouchEvent', { type, touchPoints: points.map(([x, y]) => ({ x, y })) }); // La molla: tenendo premuto sulla scena, col mouse e col dito. if (mobile) { await touch('touchStart', [P(440, 320)]); await page.waitForFunction(() => game.closeup.holding && game.closeup.power > 0.3, null, { polling: 'raf' }); await touch('touchEnd', []); } else { await page.mouse.move(...P(440, 320)); await page.mouse.down(); await page.waitForFunction(() => game.closeup.holding && game.closeup.power > 0.3, null, { polling: 'raf' }); await page.mouse.up(); } await page.waitForFunction(() => game.closeup && !game.closeup.ball?.waiting, null, { timeout: 5000 }); // Due dita sulla scena: sinistra, poi anche destra, poi si alza la sinistra e la destra resta su. if (mobile) { await touch('touchStart', [P(250, 200)]); await page.waitForFunction(() => game.closeup.flippers.left.held.length > 0); await touch('touchStart', [P(250, 200), P(400, 200)]); await page.waitForFunction(() => game.closeup.flippers.right.held.length > 0 && game.closeup.flippers.left.held.length > 0, null, { timeout: 3000 }); await page.screenshot({ path: `${out}/${name}-flipper.png` }); await touch('touchEnd', [P(400, 200)]); await page.waitForFunction(() => game.closeup.flippers.left.held.length === 0 && game.closeup.flippers.right.held.length > 0, null, { timeout: 3000 }); await touch('touchEnd', []); await page.waitForFunction(() => game.closeup.flippers.right.held.length === 0); // I pulsanti tenuti premuti. const b = await page.locator('#bocce-left').boundingBox(); await touch('touchStart', [[b.x + b.width / 2, b.y + b.height / 2]]); await page.waitForFunction(() => game.closeup.flippers.left.held.length > 0); await touch('touchEnd', []); await page.waitForFunction(() => game.closeup.flippers.left.held.length === 0); } else { await page.mouse.move(...P(400, 200)); await page.mouse.down(); await page.waitForFunction(() => game.closeup.flippers.right.held.length > 0); await page.mouse.down({ button: 'right' }); await page.waitForFunction(() => game.closeup.flippers.right.held.length > 1, null, { timeout: 3000 }); await page.screenshot({ path: `${out}/${name}-flipper.png` }); await page.mouse.up(); await page.mouse.up({ button: 'right' }); await page.waitForFunction(() => game.closeup.flippers.right.held.length === 0); await page.keyboard.down('ArrowLeft'); await page.waitForFunction(() => game.closeup.flippers.left.held.length > 0); // La pausa lascia tutto: tornando, il flipper è giù anche se il tasto era premuto. await page.click('#pause-button'); await page.waitForFunction(() => game.closeup.flippers.left.held.length === 0); await page.keyboard.up('ArrowLeft'); await page.click('#resume'); await page.waitForFunction(() => document.querySelector('#pause').hidden); } // La fine: si fa finire la partita e si guarda il quadro. await page.evaluate(() => { const c = game.closeup; c.score = 7350; c.step = 'result'; c.ball = null; c.result = { winner: 'player', score: 7350, goal: c.skill.goal }; }); await page.waitForFunction(() => document.querySelector('#bocce-throw').textContent === 'Finita'); assert.match(await page.locator('#bocce-status').textContent(), /7350 punti/); await page.screenshot({ path: `${out}/${name}-fine.png` }); if (mobile) await page.locator('#closeup-close').click(); else await page.keyboard.press('Enter'); // Invio chiude il risultato await page.waitForFunction(() => game.state.flags.flipper_vinto && !game.closeup); await page.close(); } assert.deepEqual(errors, []); console.log(`ok: schermate in ${out}`); } finally { await browser?.close(); server.kill(); }