import assert from 'node:assert/strict'; export async function checkMultimedia(browser, port, out, errors) { for (const mobile of [false, true]) { const page = await browser.newPage({ viewport: mobile ? { width: 844, height: 390 } : { width: 1280, height: 860 }, hasTouch: mobile, isMobile: mobile }); page.on('pageerror', e => errors.push(e.message)); await page.goto(`http://127.0.0.1:${port}/`); await page.click('#splash'); await page.click('#new-game'); await page.click('[data-rione="campomigliaio"]'); await page.click('#skip-intro'); const settle = async () => { for (let i = 0; i < 600; i++) { if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space'); // Il risveglio dell'inizio è un primo piano che va avanti da solo: si aspetta che finisca. if (await page.evaluate(() => (game.closeup && game.story.closeups[game.closeup.id].type !== 'risveglio') || game.idle)) return; await page.waitForTimeout(30); } throw new Error('la sequenza non si ferma'); }; await settle(); await page.waitForFunction(() => ambience.audible && ambience.context.state === 'running'); await page.click('#mute'); assert.equal(await page.evaluate(() => ambience.nodes.size), 0, 'muto immediato'); await page.click('#mute'); await page.click('#pause-button'); await page.locator('#effects-volume').fill('65'); assert.equal(await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-audio')).effects), 0.65); assert.equal(await page.evaluate(() => ambience.nodes.size), 0, 'in pausa nessun suono'); await page.screenshot({ path: `${out}/media-pausa-${mobile ? 'telefono' : 'desktop'}.png` }); await page.click('#resume'); if (mobile) { // Il libro già esistente deve continuare a sfogliarsi anche sul telefono. await page.evaluate(() => { game.state.inventory.push('libro'); game.act('open', 'libro'); }); await page.waitForFunction(() => game.closeup?.step === 'cover'); const r = await page.locator('#scene').boundingBox(); await page.touchscreen.tap(r.x + r.width / 2, r.y + r.height * 0.6); await page.waitForFunction(() => game.closeup.step === 'title'); await page.touchscreen.tap(r.x + r.width / 2, r.y + r.height * 0.6); await page.waitForFunction(() => game.closeup.step === 'end'); await page.screenshot({ path: `${out}/media-libro-telefono.png` }); await page.tap('#closeup-close'); await settle(); } await page.evaluate(() => { game.state.flags.indizio_regolo = true; game.state.flags.casetta_aperta = true; game.state.inventory.push('torcia'); game.go('fortezza', [462, 310]); }); await settle(); const at = async ([x, y]) => { const r = await page.locator('#scene').boundingBox(); return [r.x + x / 640 * r.width, r.y + y / 360 * r.height]; }; const openTorch = async () => { await page.click('#verbs [data-verb="use"]'); await page.click('#inventory [data-item="torcia"]'); await page.mouse.click(...await at([501, 285])); await settle(); await page.waitForFunction(() => game.closeup?.id === 'fessura'); }; await openTorch(); await page.keyboard.press('Escape'); await settle(); assert.equal(await page.evaluate(() => game.state.ended), false, 'uscire presto non vince'); await openTorch(); const saved = await page.evaluate(() => game.savable()); await page.click('#pause-button'); const age = await page.evaluate(() => game.closeup.age); await page.waitForTimeout(150); assert.equal(await page.evaluate(() => game.closeup.age), age, 'il primo piano è fermo in pausa'); await page.click('#resume'); const origin = await page.evaluate(() => [game.state.x, game.state.y]); if (mobile) { const cdp = await page.context().newCDPSession(page); const [x, y] = await at([320, 180]), [tx, ty] = await at([300, 90]); await cdp.send('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x, y }] }); await cdp.send('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [{ x: tx, y: ty }] }); await cdp.send('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] }); const beam = await page.evaluate(() => game.closeup.beam); assert.ok(Math.abs(beam[0] - 300) < 2 && Math.abs(beam[1] - 90) < 2, 'trascinare il dito sposta la torcia'); await cdp.detach(); } for (let i = 0; i < 3; i++) { const target = await page.evaluate(i => game.story.closeups.fessura.targets[i].at, i); if (mobile) await page.touchscreen.tap(...await at(target)); else if (i === 0) await page.keyboard.press('Enter'); else await page.mouse.move(...await at(target)); await page.waitForFunction(() => game.closeup?.step === 'reveal'); await page.screenshot({ path: `${out}/media-torcia-${mobile ? 'telefono' : 'desktop'}-${i}.png` }); for (const fine = Date.now() + 30000; Date.now() < fine;) { // con la macchina carica la rivelazione dura di più if (await page.evaluate(i => game.closeup.found > i, i)) break; if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space'); await page.waitForTimeout(25); } assert.equal(await page.evaluate(() => game.closeup.found), i + 1); } assert.deepEqual(await page.evaluate(() => [game.state.x, game.state.y]), origin, 'il puntatore non muove Andrea'); assert.deepEqual(await page.evaluate(() => game.savable()), saved, 'durante il primo piano resta il punto fermo'); if (mobile) await page.tap('#closeup-close'); else await page.keyboard.press('Enter'); await settle(); await page.waitForSelector('#ending:not([hidden])'); assert.ok(await page.evaluate(() => game.checkpoint.ended)); assert.equal(await page.evaluate(() => ambience.nodes.size), 0, 'alla fine non resta il vento della fessura'); // Misura il segnale realmente prodotto da Web Audio: udibile, finito, senza saturazione. if (!mobile) { const levels = await page.evaluate(async () => { const { Soundscape } = await import('/src/engine/soundscape.js'); const results = {}; for (const kind of ['page', 'card-slide', 'card-tap', 'card-shuffle', 'wood', 'drip', 'bird', 'cricket', 'bell', 'glass', 'water', 'wind']) { const ctx = new OfflineAudioContext(2, 44100 * 2, 44100); const s = new Soundscape({ createContext: () => ctx }); s.unlock(); s.configure({ active: true }); if (kind === 'water' || kind === 'wind') s.update(kind === 'water' ? 'acqua' : 'fortezza', 'sera', 0.1); else s.play(kind); const data = (await ctx.startRendering()).getChannelData(0); let peak = 0, sum = 0; for (const v of data) { peak = Math.max(peak, Math.abs(v)); sum += v * v; } results[kind] = { peak, rms: Math.sqrt(sum / data.length) }; } return results; }); for (const [kind, { peak, rms }] of Object.entries(levels)) { assert.ok(rms > 0.00001 && peak < 0.5, `${kind}: audio non valido ${JSON.stringify({ peak, rms })}`); } } await page.reload(); await page.waitForFunction(() => window.ambience?.volume === 0.65); await page.close(); } console.log('Multimedia OK: torcia con mouse, tastiera e touch; uscita e pausa; audio sintetizzato e volume salvato.'); }