Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
129 lines
7.9 KiB
JavaScript
129 lines
7.9 KiB
JavaScript
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 (let n = 0; n < 200; n++) {
|
|
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.');
|
|
}
|