La prova nel browser in tre processi paralleli (principale, minigiochi, telefoni), ognuno col suo server e il suo Chromium: da 525 a circa 275 secondi, tre giri su tre verdi; --parte= per una sola. Le attese della torcia hanno una scadenza di 30 s invece di 200 giri, che con la macchina carica non bastavano (Andrea)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
@@ -47,7 +47,7 @@ test: ## Test della logica, della storia, della soluzione completa e del generat
|
|||||||
$(NODE) --test tests/*.test.mjs
|
$(NODE) --test tests/*.test.mjs
|
||||||
$(PYTHON) -m unittest discover -s tests -p "test_*.py" -q
|
$(PYTHON) -m unittest discover -s tests -p "test_*.py" -q
|
||||||
|
|
||||||
browser: ## Partita completa nel browser con Playwright
|
browser: ## Partita completa nel browser con Playwright (tre parti in processi paralleli, ~4,5 min)
|
||||||
$(NODE) tests/browser.mjs
|
$(NODE) tests/browser.mjs
|
||||||
|
|
||||||
verify: test browser check-public ## Tutti i controlli
|
verify: test browser check-public ## Tutti i controlli
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ Oppure `make up` (Docker, su http://localhost:8080). Non aprire `index.html` con
|
|||||||
- `assets/`: sfondi, sprite, musiche, voci, carattere, video.
|
- `assets/`: sfondi, sprite, musiche, voci, carattere, video.
|
||||||
- `tools/`: gli strumenti (sprite dai fogli, voci con XTTS, musiche sintetizzate, favicon, taratura dei minigiochi).
|
- `tools/`: gli strumenti (sprite dai fogli, voci con XTTS, musiche sintetizzate, favicon, taratura dei minigiochi).
|
||||||
- `tests/`: `npm test` (unità: regole, storia coerente, voci, salvataggi) e `npm run browser` (Playwright: percorre
|
- `tests/`: `npm test` (unità: regole, storia coerente, voci, salvataggi) e `npm run browser` (Playwright: percorre
|
||||||
tutta la soluzione dell'avventura, su desktop e telefono); `npm run browser:briscola`, `:tressette`, `:scopa`,
|
tutta la soluzione dell'avventura, su desktop e telefono, in tre processi paralleli; `--parte=` per una sola);
|
||||||
`:biliardo`, `:effects` per le parti singole.
|
`npm run browser:briscola`, `:tressette`, `:scopa`, `:biliardo`, `:effects` per le parti singole.
|
||||||
- `video/`: i copioni dei video «Come si gioca» e della demo, girati in una macchina virtuale.
|
- `video/`: i copioni dei video «Come si gioca» e della demo, girati in una macchina virtuale.
|
||||||
- `docs/`: `PROGETTO.md` (l'idea, i personaggi, la mappa) e `STATO.md` (dove siamo, cosa manca).
|
- `docs/`: `PROGETTO.md` (l'idea, i personaggi, la mappa) e `STATO.md` (dove siamo, cosa manca).
|
||||||
|
|
||||||
|
|||||||
+275
-250
@@ -1,6 +1,8 @@
|
|||||||
// Prova nel browser vero (Chromium headless con swiftshader): titolo, partita giocata con clic veri
|
// 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).
|
// fino alla fine, salvataggio e impaginazione su telefono. Schermate in $OUT (predefinito: /tmp/regolo-shots).
|
||||||
// npm run browser
|
// npm run browser: le tre parti (--parte=principale, minigiochi, telefoni) girano insieme come processi separati, ognuna
|
||||||
|
// col suo server e il suo Chromium; la prova passa quasi tutto il tempo ad aspettare animazioni in tempo reale, e così le
|
||||||
|
// attese si sovrappongono. Una parte sola: node tests/browser.mjs --parte=minigiochi (--media-only: solo multimedia).
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import { staticServer } from './helpers.mjs';
|
import { staticServer } from './helpers.mjs';
|
||||||
import { mkdirSync, readFileSync } from 'node:fs';
|
import { mkdirSync, readFileSync } from 'node:fs';
|
||||||
@@ -12,6 +14,22 @@ import { checkMultimedia } from './multimedia.browser.mjs';
|
|||||||
|
|
||||||
const OUT = process.env.OUT ?? '/tmp/regolo-shots';
|
const OUT = process.env.OUT ?? '/tmp/regolo-shots';
|
||||||
mkdirSync(OUT, { recursive: true });
|
mkdirSync(OUT, { recursive: true });
|
||||||
|
const PARTI = ['principale', 'minigiochi', 'telefoni'];
|
||||||
|
const PARTE = process.argv.find(a => a.startsWith('--parte='))?.slice('--parte='.length);
|
||||||
|
if (PARTE && !PARTI.includes(PARTE)) { console.error(`parte sconosciuta: ${PARTE} (${PARTI.join(', ')})`); process.exit(2); }
|
||||||
|
if (!PARTE && !process.argv.includes('--media-only')) {
|
||||||
|
const { spawn } = await import('node:child_process');
|
||||||
|
const t0 = Date.now();
|
||||||
|
const esiti = await Promise.all(PARTI.map(parte => new Promise(resolve => {
|
||||||
|
const p = spawn(process.execPath, [process.argv[1], `--parte=${parte}`], { stdio: ['ignore', 'pipe', 'pipe'] });
|
||||||
|
let resto = '';
|
||||||
|
const riga = chunk => { const righe = (resto + chunk).split('\n'); resto = righe.pop(); for (const r of righe) console.log(`[${parte}] ${r}`); };
|
||||||
|
p.stdout.on('data', riga); p.stderr.on('data', riga);
|
||||||
|
p.on('close', code => { if (resto) console.log(`[${parte}] ${resto}`); resolve({ parte, code, s: Math.round((Date.now() - t0) / 1000) }); });
|
||||||
|
})));
|
||||||
|
for (const e of esiti) console.log(`${e.code === 0 ? 'OK ' : 'FAIL'} ${e.parte} (${e.s} s)`);
|
||||||
|
process.exit(esiti.every(e => e.code === 0) ? 0 : 1);
|
||||||
|
}
|
||||||
const server = await staticServer(), PORT = server.port;
|
const server = await staticServer(), PORT = server.port;
|
||||||
const browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
const browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
||||||
const errors = [];
|
const errors = [];
|
||||||
@@ -49,9 +67,261 @@ const hotspotCenter = async (page, id) => {
|
|||||||
return c;
|
return c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Le parti che aprono una pagina propria (col suo salvataggio), ognuna nel suo processo (vedi in cima).
|
||||||
|
async function minigiochi() {
|
||||||
|
// Il libro dell'oratorio: preso, il primo piano; si sfoglia coi clic veri, «FINE» senza che nessuno lo dica,
|
||||||
|
// Esc chiude; dall'inventario «Apri» lo riapre e il pulsante Chiudi lo chiude. Intanto la scena dietro non risponde.
|
||||||
|
{
|
||||||
|
const book = await open({ width: 1280, height: 860 });
|
||||||
|
await book.click('#splash');
|
||||||
|
await book.click('#new-game');
|
||||||
|
await book.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await book.click('#skip-intro');
|
||||||
|
await settle(book);
|
||||||
|
await book.evaluate(() => game.go('oratorio', [540, 300]));
|
||||||
|
await settle(book);
|
||||||
|
await book.click('#verbs [data-verb="take"]');
|
||||||
|
await clickScene(book, await hotspotCenter(book, 'libro_dimenticato'));
|
||||||
|
await book.waitForFunction(() => game.closeup?.step === 'cover', null, { timeout: 15000 });
|
||||||
|
await book.waitForTimeout(600);
|
||||||
|
await book.screenshot({ path: `${OUT}/03a-libro-copertina.png` });
|
||||||
|
const where = await book.evaluate(() => [game.state.x, game.state.y]);
|
||||||
|
await clickScene(book, [320, 200]);
|
||||||
|
assert.equal(await book.evaluate(() => game.closeup.step), 'title', 'un clic apre il libro');
|
||||||
|
await clickScene(book, [320, 200]);
|
||||||
|
assert.equal(await book.evaluate(() => game.closeup.step), 'turn', 'un altro gira la pagina');
|
||||||
|
await book.waitForFunction(() => game.closeup?.step === 'end');
|
||||||
|
assert.equal(await book.evaluate(() => game.speech), null, '«FINE» non lo dice nessuno');
|
||||||
|
// Prima la battuta, poi la schermata: con swiftshader una schermata può durare più della battuta.
|
||||||
|
await book.waitForFunction(() => game.speech?.text === 'Mi ricordavo un finale diverso.', null, { timeout: 5000 });
|
||||||
|
await book.screenshot({ path: `${OUT}/03b-libro-fine.png` });
|
||||||
|
assert.deepEqual(await book.evaluate(() => [game.state.x, game.state.y]), where, 'i clic sul libro non muovono Andrea');
|
||||||
|
await book.keyboard.press('Space');
|
||||||
|
await book.keyboard.press('Escape');
|
||||||
|
assert.equal(await book.evaluate(() => game.closeup), null, 'Esc chiude il libro');
|
||||||
|
await book.waitForSelector('#closeup-close', { state: 'hidden' });
|
||||||
|
await settle(book);
|
||||||
|
await book.click('#verbs [data-verb="open"]');
|
||||||
|
await book.click('#inventory button');
|
||||||
|
await book.waitForFunction(() => game.closeup?.step === 'cover');
|
||||||
|
assert.ok(await book.isVisible('#closeup-close'), 'si vede il pulsante Chiudi');
|
||||||
|
await book.click('#closeup-close');
|
||||||
|
assert.equal(await book.evaluate(() => game.closeup), null, 'Chiudi chiude il libro');
|
||||||
|
await book.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Le bocce al pallaio: col mouse tenuto premuto la forza sale, lasciando la boccia parte; la scena dietro è ferma.
|
||||||
|
{
|
||||||
|
const bocce = await open({ width: 1280, height: 860 });
|
||||||
|
await bocce.click('#splash');
|
||||||
|
await bocce.click('#new-game');
|
||||||
|
await bocce.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await bocce.click('#skip-intro');
|
||||||
|
await settle(bocce);
|
||||||
|
await bocce.evaluate(() => game.go('tennis_chiesa', [430, 310]));
|
||||||
|
await settle(bocce);
|
||||||
|
await clickScene(bocce, await hotspotCenter(bocce, 'mariano'));
|
||||||
|
await bocce.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
||||||
|
await bocce.locator('#choices li', { hasText: 'Facciamo una partita' }).click();
|
||||||
|
await bocce.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
||||||
|
await bocce.locator('#choices li', { hasText: 'Seria.' }).click();
|
||||||
|
await bocce.waitForFunction(() => game.closeup?.id === 'bocce' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
||||||
|
const r = await bocce.locator('#scene').boundingBox();
|
||||||
|
await bocce.mouse.move(r.x + r.width / 2, r.y + r.height / 2);
|
||||||
|
await bocce.mouse.down();
|
||||||
|
await bocce.waitForTimeout(500);
|
||||||
|
assert.ok(await bocce.evaluate(() => game.closeup.power > 0.2), 'tenendo premuto la forza sale');
|
||||||
|
await bocce.screenshot({ path: `${OUT}/03c-bocce-forza.png` });
|
||||||
|
await bocce.mouse.up();
|
||||||
|
assert.equal(await bocce.evaluate(() => game.closeup.balls.length), 1, 'lasciando, la boccia parte');
|
||||||
|
await bocce.waitForFunction(() => game.closeup.balls.length === 2, null, { timeout: 15000 });
|
||||||
|
// Il clic fissa la mira: andando verso la forza o «Lancia» il puntatore attraversa il campo e non la sposta.
|
||||||
|
await bocce.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'player', null, { timeout: 30000 });
|
||||||
|
await bocce.mouse.click(r.x + r.width * 0.45, r.y + r.height * 0.6);
|
||||||
|
const fixed = await bocce.evaluate(() => JSON.stringify(game.closeup.aim));
|
||||||
|
await bocce.mouse.move(r.x + r.width * 0.5, r.y + r.height * 0.95, { steps: 8 });
|
||||||
|
assert.equal(await bocce.evaluate(() => JSON.stringify(game.closeup.aim)), fixed, 'la mira resta dove si è cliccato');
|
||||||
|
await bocce.keyboard.press('Escape');
|
||||||
|
await bocce.waitForFunction(() => game.speech?.text.startsWith('Già stanco?'), null, { timeout: 5000 });
|
||||||
|
await bocce.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// «Fai due tiri» al campo del Circolo: il mouse mira, tenendo premuto la forza sale, lasciando la palla vola; Esc esce.
|
||||||
|
{
|
||||||
|
const k = await open({ width: 1280, height: 860 });
|
||||||
|
await k.click('#splash');
|
||||||
|
await k.click('#new-game');
|
||||||
|
await k.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await k.click('#skip-intro');
|
||||||
|
await settle(k);
|
||||||
|
await k.evaluate(() => game.go('via_cipriani', [340, 316]));
|
||||||
|
await settle(k);
|
||||||
|
await k.evaluate(() => game.act('use', 'campo_basket'));
|
||||||
|
await k.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
||||||
|
await k.locator('#choices li', { hasText: 'Due tiri seri.' }).click();
|
||||||
|
await k.waitForFunction(() => game.closeup?.id === 'basket' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
||||||
|
const r = await k.locator('#scene').boundingBox();
|
||||||
|
const at = (x, y) => [r.x + x / 640 * r.width, r.y + y / 360 * r.height];
|
||||||
|
await k.mouse.move(...at(560, 60));
|
||||||
|
const high = await k.evaluate(() => game.closeup.angle);
|
||||||
|
await k.mouse.move(...at(600, 200));
|
||||||
|
assert.ok(await k.evaluate(() => game.closeup.angle) < high, 'la mira segue il mouse');
|
||||||
|
await k.mouse.down();
|
||||||
|
await k.waitForTimeout(500);
|
||||||
|
assert.ok(await k.evaluate(() => game.closeup.power > 0.2 && game.closeup.holding), 'tenendo premuto la forza sale');
|
||||||
|
await k.screenshot({ path: `${OUT}/03e-basket-carica.png` });
|
||||||
|
await k.mouse.up();
|
||||||
|
assert.equal(await k.evaluate(() => game.closeup.step), 'flight', 'lasciando, la palla parte');
|
||||||
|
await k.waitForTimeout(350);
|
||||||
|
await k.screenshot({ path: `${OUT}/03f-basket-volo.png` });
|
||||||
|
await k.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.thrown === 1, null, { timeout: 20000 });
|
||||||
|
await k.keyboard.press('Escape');
|
||||||
|
await k.waitForFunction(() => game.speech?.text.startsWith('Lascio il pallone'), null, { timeout: 5000 });
|
||||||
|
await k.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// La pesca al lago dell'Anatre: coi ragazzi in Fortezza «S'ha andà a fallo?!?», poi il lancio tenendo premuto e l'attesa.
|
||||||
|
{
|
||||||
|
const k = await open({ width: 1280, height: 860 });
|
||||||
|
await k.click('#splash');
|
||||||
|
await k.click('#new-game');
|
||||||
|
await k.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await k.click('#skip-intro');
|
||||||
|
await settle(k);
|
||||||
|
await k.evaluate(() => game.go('fortezza', [240, 334]));
|
||||||
|
await settle(k);
|
||||||
|
await clickScene(k, await hotspotCenter(k, 'pescatori'));
|
||||||
|
await k.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 40000 });
|
||||||
|
await k.locator('#choices li', { hasText: 'S’ha andà a fallo?!?' }).or(k.locator('#choices li', { hasText: "S'ha andà a fallo?!?" })).first().click();
|
||||||
|
await k.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
||||||
|
await k.locator('#choices li', { hasText: 'Seria.' }).click();
|
||||||
|
await k.waitForFunction(() => game.closeup?.id === 'pesca' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
||||||
|
const r = await k.locator('#scene').boundingBox();
|
||||||
|
await k.mouse.move(r.x + r.width * 0.7, r.y + r.height * 0.7);
|
||||||
|
assert.ok(await k.evaluate(() => game.closeup.power > 0.5), 'toccando l\'acqua al largo la forza sale');
|
||||||
|
await k.mouse.down();
|
||||||
|
await k.waitForTimeout(500);
|
||||||
|
assert.ok(await k.evaluate(() => game.closeup.holding && game.closeup.power > 0.2), 'tenendo premuto si carica il lancio');
|
||||||
|
await k.mouse.up();
|
||||||
|
await k.waitForFunction(() => game.closeup.step === 'wait', null, { timeout: 5000 });
|
||||||
|
await k.waitForTimeout(600);
|
||||||
|
await k.screenshot({ path: `${OUT}/03g-pesca-attesa.png` });
|
||||||
|
assert.equal(await k.textContent('#bocce-throw'), 'Ferra');
|
||||||
|
await k.keyboard.press('Escape');
|
||||||
|
await k.waitForFunction(() => game.speech?.text.startsWith('Lascio la canna'), null, { timeout: 5000 });
|
||||||
|
await k.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Il biliardo del Circolo 4 Maggio: la sala, poi la partita; un tocco sul tavolo mira, tenendo premuto la forza sale.
|
||||||
|
{
|
||||||
|
const b = await open({ width: 1280, height: 860 });
|
||||||
|
await b.click('#splash');
|
||||||
|
await b.click('#new-game');
|
||||||
|
await b.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await b.click('#skip-intro');
|
||||||
|
await settle(b);
|
||||||
|
await b.evaluate(() => game.go('circolo_4_maggio'));
|
||||||
|
await settle(b);
|
||||||
|
await b.waitForTimeout(1500); // la dissolvenza del cambio scena
|
||||||
|
await b.screenshot({ path: `${OUT}/03d-circolo.png` });
|
||||||
|
await clickScene(b, await hotspotCenter(b, 'ing_tozzi'));
|
||||||
|
await b.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
||||||
|
await b.locator('#choices li', { hasText: 'una partita ai birilli' }).click();
|
||||||
|
await b.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
||||||
|
await b.locator('#choices li', { hasText: 'Seria.' }).click();
|
||||||
|
await b.waitForFunction(() => game.closeup?.id === 'biliardo' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
||||||
|
const r = await b.locator('#scene').boundingBox();
|
||||||
|
const [tx, ty] = await b.evaluate(async () => { const { VIEW } = await import('/src/engine/biliardo.js'); const V = game.closeup.balls.cpu; return [VIEW.x + V.x * VIEW.k, VIEW.y + V.y * VIEW.k]; });
|
||||||
|
await b.mouse.move(r.x + tx / 640 * r.width, r.y + ty / 360 * r.height);
|
||||||
|
await b.mouse.down();
|
||||||
|
await b.waitForTimeout(500);
|
||||||
|
assert.ok(await b.evaluate(() => game.closeup.power > 0.2), 'tenendo premuto la forza sale');
|
||||||
|
await b.screenshot({ path: `${OUT}/03e-biliardo-forza.png` });
|
||||||
|
await b.mouse.up();
|
||||||
|
assert.equal(await b.evaluate(() => game.closeup.step), 'rolling', 'lasciando, la biglia parte');
|
||||||
|
await b.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'cpu' || game.closeup.step === 'rolling' && game.closeup.shot?.shooter === 'cpu', null, { timeout: 20000 });
|
||||||
|
await b.screenshot({ path: `${OUT}/03f-biliardo-socio.png` });
|
||||||
|
await b.keyboard.press('Escape');
|
||||||
|
await b.waitForFunction(() => game.speech?.text.startsWith('Lasci a metà?'), null, { timeout: 5000 });
|
||||||
|
await b.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function telefoni() {
|
||||||
|
const phone = await open({ width: 390, height: 844 });
|
||||||
|
await phone.click('#splash'); // un clic salta il cartello
|
||||||
|
assert.ok(await phone.isVisible('#title'), 'il clic sul cartello porta al titolo');
|
||||||
|
await phone.click('#new-game');
|
||||||
|
await phone.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await phone.click('#skip-intro');
|
||||||
|
await settle(phone);
|
||||||
|
const overflow = await phone.evaluate(() => document.documentElement.scrollWidth > innerWidth);
|
||||||
|
// Ogni verbo si vede tutto: niente pulsanti tagliati dall'inventario.
|
||||||
|
assert.ok(await phone.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.getBoundingClientRect().right <= innerWidth)), 'verbi tagliati sul telefono');
|
||||||
|
assert.equal(overflow, false, 'scorrimento orizzontale su telefono');
|
||||||
|
await phone.screenshot({ path: `${OUT}/08-telefono.png` });
|
||||||
|
// Telefono stretto in orizzontale: niente deve allargare la pagina oltre lo schermo (il browser la rimpicciolirebbe
|
||||||
|
// e taglierebbe verbi e inventario a destra), e la pausa sta tutta dentro la scena.
|
||||||
|
const small = await browser.newPage({ viewport: { width: 740, height: 330 }, deviceScaleFactor: 2.4, hasTouch: true, isMobile: true });
|
||||||
|
small.on('pageerror', e => errors.push(e.message));
|
||||||
|
await small.goto(`http://127.0.0.1:${PORT}/`);
|
||||||
|
await small.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
|
||||||
|
await small.click('#new-game');
|
||||||
|
await small.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await small.click('#skip-intro');
|
||||||
|
await settle(small);
|
||||||
|
assert.equal(await small.evaluate(() => innerWidth), 740, 'la pagina è più larga dello schermo del telefono');
|
||||||
|
await small.click('#pause-button');
|
||||||
|
assert.ok(await small.evaluate(() => document.getElementById('to-title').getBoundingClientRect().bottom <= document.getElementById('stage').getBoundingClientRect().bottom), 'la pausa esce dalla scena');
|
||||||
|
await small.screenshot({ path: `${OUT}/08b-telefono-stretto-pausa.png` });
|
||||||
|
// Telefono in verticale: la Pausa prende tutto lo schermo, e versione e spiegazione non finiscono sopra i verbi.
|
||||||
|
{
|
||||||
|
const tall = await browser.newPage({ viewport: { width: 412, height: 915 }, deviceScaleFactor: 2, hasTouch: true, isMobile: true });
|
||||||
|
await tall.goto(`http://127.0.0.1:${PORT}/`);
|
||||||
|
await tall.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
|
||||||
|
await tall.click('#new-game');
|
||||||
|
await tall.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await tall.click('#skip-intro');
|
||||||
|
await settle(tall);
|
||||||
|
await tall.click('#pause-button');
|
||||||
|
const fit = await tall.evaluate(() => {
|
||||||
|
const p = document.getElementById('pause'), v = p.querySelector('.version').getBoundingClientRect();
|
||||||
|
return { inside: p.scrollHeight <= p.clientHeight + 1, top: document.elementFromPoint(v.x + v.width / 2, v.y + v.height / 2)?.closest('#pause') !== null };
|
||||||
|
});
|
||||||
|
assert.deepEqual(fit, { inside: true, top: true }, 'in verticale la pausa esce dallo schermo o finisce sotto i verbi');
|
||||||
|
await tall.close();
|
||||||
|
}
|
||||||
|
const landscape = await open({ width: 844, height: 390 });
|
||||||
|
await landscape.click('#new-game');
|
||||||
|
await landscape.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await landscape.click('#skip-intro');
|
||||||
|
await settle(landscape);
|
||||||
|
assert.equal(await landscape.evaluate(() => document.documentElement.scrollWidth > innerWidth), false);
|
||||||
|
assert.ok(await landscape.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.getBoundingClientRect().height >= 26)), 'in orizzontale i verbi si leggono interi e il dito li prende');
|
||||||
|
await landscape.screenshot({ path: `${OUT}/09-orizzontale.png`, fullPage: true });
|
||||||
|
|
||||||
|
// Debug (?debug): area calpestabile e hotspot disegnati; Maiusc+clic segna punti senza muovere Andrea, X li azzera.
|
||||||
|
const dbg = await open({ width: 1280, height: 860 }, '?debug');
|
||||||
|
await dbg.click('#new-game');
|
||||||
|
await dbg.click('#rione [data-rione="campomigliaio"]');
|
||||||
|
await dbg.click('#skip-intro');
|
||||||
|
await settle(dbg);
|
||||||
|
const start = await dbg.evaluate(() => [game.state.x, game.state.y]);
|
||||||
|
await dbg.keyboard.down('Shift');
|
||||||
|
await clickScene(dbg, [100, 300]);
|
||||||
|
await clickScene(dbg, [200, 320]);
|
||||||
|
await dbg.keyboard.up('Shift');
|
||||||
|
await dbg.mouse.move(700, 400);
|
||||||
|
await dbg.waitForTimeout(200);
|
||||||
|
assert.deepEqual(await dbg.evaluate(() => [game.state.x, game.state.y]), start, 'Maiusc+clic non muove Andrea');
|
||||||
|
await dbg.screenshot({ path: `${OUT}/10-debug.png` });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await checkMultimedia(browser, PORT, OUT, errors);
|
if (process.argv.includes('--media-only')) await checkMultimedia(browser, PORT, OUT, errors);
|
||||||
if (!process.argv.includes('--media-only')) {
|
else if (PARTE === 'minigiochi') { await minigiochi(); console.log('Browser OK: libro, bocce, tiri, pesca, biliardo.'); }
|
||||||
|
else if (PARTE === 'telefoni') { await checkMultimedia(browser, PORT, OUT, errors); await telefoni(); console.log('Browser OK: telefoni e debug.'); }
|
||||||
|
else {
|
||||||
const page = await open({ width: 1280, height: 860 });
|
const page = await open({ width: 1280, height: 860 });
|
||||||
// Il cartello d'apertura, poi da solo il titolo.
|
// Il cartello d'apertura, poi da solo il titolo.
|
||||||
assert.match(await page.textContent('#splash'), /Manzolo\s*\(Poca\)\s*Art\s*Presents/);
|
assert.match(await page.textContent('#splash'), /Manzolo\s*\(Poca\)\s*Art\s*Presents/);
|
||||||
@@ -287,7 +557,7 @@ try {
|
|||||||
await page.mouse.move(box.x + at[0] / 640 * box.width, box.y + at[1] / 360 * box.height);
|
await page.mouse.move(box.x + at[0] / 640 * box.width, box.y + at[1] / 360 * box.height);
|
||||||
await page.waitForFunction(() => game.closeup?.step === 'reveal');
|
await page.waitForFunction(() => game.closeup?.step === 'reveal');
|
||||||
await page.screenshot({ path: `${OUT}/05b-fessura-${i}.png` });
|
await page.screenshot({ path: `${OUT}/05b-fessura-${i}.png` });
|
||||||
for (let n = 0; n < 200; n++) {
|
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(i => game.closeup.found > i, i)) break;
|
||||||
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
||||||
await page.waitForTimeout(30);
|
await page.waitForTimeout(30);
|
||||||
@@ -375,253 +645,8 @@ try {
|
|||||||
await page.click('#close-map');
|
await page.click('#close-map');
|
||||||
await page.setViewportSize({ width: 1280, height: 860 });
|
await page.setViewportSize({ width: 1280, height: 860 });
|
||||||
|
|
||||||
// Il libro dell'oratorio: preso, il primo piano; si sfoglia coi clic veri, «FINE» senza che nessuno lo dica,
|
|
||||||
// Esc chiude; dall'inventario «Apri» lo riapre e il pulsante Chiudi lo chiude. Intanto la scena dietro non risponde.
|
|
||||||
{
|
|
||||||
const book = await open({ width: 1280, height: 860 });
|
|
||||||
await book.click('#splash');
|
|
||||||
await book.click('#new-game');
|
|
||||||
await book.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await book.click('#skip-intro');
|
|
||||||
await settle(book);
|
|
||||||
await book.evaluate(() => game.go('oratorio', [540, 300]));
|
|
||||||
await settle(book);
|
|
||||||
await book.click('#verbs [data-verb="take"]');
|
|
||||||
await clickScene(book, await hotspotCenter(book, 'libro_dimenticato'));
|
|
||||||
await book.waitForFunction(() => game.closeup?.step === 'cover', null, { timeout: 15000 });
|
|
||||||
await book.waitForTimeout(600);
|
|
||||||
await book.screenshot({ path: `${OUT}/03a-libro-copertina.png` });
|
|
||||||
const where = await book.evaluate(() => [game.state.x, game.state.y]);
|
|
||||||
await clickScene(book, [320, 200]);
|
|
||||||
assert.equal(await book.evaluate(() => game.closeup.step), 'title', 'un clic apre il libro');
|
|
||||||
await clickScene(book, [320, 200]);
|
|
||||||
assert.equal(await book.evaluate(() => game.closeup.step), 'turn', 'un altro gira la pagina');
|
|
||||||
await book.waitForFunction(() => game.closeup?.step === 'end');
|
|
||||||
assert.equal(await book.evaluate(() => game.speech), null, '«FINE» non lo dice nessuno');
|
|
||||||
// Prima la battuta, poi la schermata: con swiftshader una schermata può durare più della battuta.
|
|
||||||
await book.waitForFunction(() => game.speech?.text === 'Mi ricordavo un finale diverso.', null, { timeout: 5000 });
|
|
||||||
await book.screenshot({ path: `${OUT}/03b-libro-fine.png` });
|
|
||||||
assert.deepEqual(await book.evaluate(() => [game.state.x, game.state.y]), where, 'i clic sul libro non muovono Andrea');
|
|
||||||
await book.keyboard.press('Space');
|
|
||||||
await book.keyboard.press('Escape');
|
|
||||||
assert.equal(await book.evaluate(() => game.closeup), null, 'Esc chiude il libro');
|
|
||||||
await book.waitForSelector('#closeup-close', { state: 'hidden' });
|
|
||||||
await settle(book);
|
|
||||||
await book.click('#verbs [data-verb="open"]');
|
|
||||||
await book.click('#inventory button');
|
|
||||||
await book.waitForFunction(() => game.closeup?.step === 'cover');
|
|
||||||
assert.ok(await book.isVisible('#closeup-close'), 'si vede il pulsante Chiudi');
|
|
||||||
await book.click('#closeup-close');
|
|
||||||
assert.equal(await book.evaluate(() => game.closeup), null, 'Chiudi chiude il libro');
|
|
||||||
await book.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Le bocce al pallaio: col mouse tenuto premuto la forza sale, lasciando la boccia parte; la scena dietro è ferma.
|
|
||||||
{
|
|
||||||
const bocce = await open({ width: 1280, height: 860 });
|
|
||||||
await bocce.click('#splash');
|
|
||||||
await bocce.click('#new-game');
|
|
||||||
await bocce.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await bocce.click('#skip-intro');
|
|
||||||
await settle(bocce);
|
|
||||||
await bocce.evaluate(() => game.go('tennis_chiesa', [430, 310]));
|
|
||||||
await settle(bocce);
|
|
||||||
await clickScene(bocce, await hotspotCenter(bocce, 'mariano'));
|
|
||||||
await bocce.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
|
||||||
await bocce.locator('#choices li', { hasText: 'Facciamo una partita' }).click();
|
|
||||||
await bocce.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
|
||||||
await bocce.locator('#choices li', { hasText: 'Seria.' }).click();
|
|
||||||
await bocce.waitForFunction(() => game.closeup?.id === 'bocce' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
|
||||||
const r = await bocce.locator('#scene').boundingBox();
|
|
||||||
await bocce.mouse.move(r.x + r.width / 2, r.y + r.height / 2);
|
|
||||||
await bocce.mouse.down();
|
|
||||||
await bocce.waitForTimeout(500);
|
|
||||||
assert.ok(await bocce.evaluate(() => game.closeup.power > 0.2), 'tenendo premuto la forza sale');
|
|
||||||
await bocce.screenshot({ path: `${OUT}/03c-bocce-forza.png` });
|
|
||||||
await bocce.mouse.up();
|
|
||||||
assert.equal(await bocce.evaluate(() => game.closeup.balls.length), 1, 'lasciando, la boccia parte');
|
|
||||||
await bocce.waitForFunction(() => game.closeup.balls.length === 2, null, { timeout: 15000 });
|
|
||||||
// Il clic fissa la mira: andando verso la forza o «Lancia» il puntatore attraversa il campo e non la sposta.
|
|
||||||
await bocce.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'player', null, { timeout: 30000 });
|
|
||||||
await bocce.mouse.click(r.x + r.width * 0.45, r.y + r.height * 0.6);
|
|
||||||
const fixed = await bocce.evaluate(() => JSON.stringify(game.closeup.aim));
|
|
||||||
await bocce.mouse.move(r.x + r.width * 0.5, r.y + r.height * 0.95, { steps: 8 });
|
|
||||||
assert.equal(await bocce.evaluate(() => JSON.stringify(game.closeup.aim)), fixed, 'la mira resta dove si è cliccato');
|
|
||||||
await bocce.keyboard.press('Escape');
|
|
||||||
await bocce.waitForFunction(() => game.speech?.text.startsWith('Già stanco?'), null, { timeout: 5000 });
|
|
||||||
await bocce.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// «Fai due tiri» al campo del Circolo: il mouse mira, tenendo premuto la forza sale, lasciando la palla vola; Esc esce.
|
|
||||||
{
|
|
||||||
const k = await open({ width: 1280, height: 860 });
|
|
||||||
await k.click('#splash');
|
|
||||||
await k.click('#new-game');
|
|
||||||
await k.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await k.click('#skip-intro');
|
|
||||||
await settle(k);
|
|
||||||
await k.evaluate(() => game.go('via_cipriani', [340, 316]));
|
|
||||||
await settle(k);
|
|
||||||
await k.evaluate(() => game.act('use', 'campo_basket'));
|
|
||||||
await k.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
|
||||||
await k.locator('#choices li', { hasText: 'Due tiri seri.' }).click();
|
|
||||||
await k.waitForFunction(() => game.closeup?.id === 'basket' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
|
||||||
const r = await k.locator('#scene').boundingBox();
|
|
||||||
const at = (x, y) => [r.x + x / 640 * r.width, r.y + y / 360 * r.height];
|
|
||||||
await k.mouse.move(...at(560, 60));
|
|
||||||
const high = await k.evaluate(() => game.closeup.angle);
|
|
||||||
await k.mouse.move(...at(600, 200));
|
|
||||||
assert.ok(await k.evaluate(() => game.closeup.angle) < high, 'la mira segue il mouse');
|
|
||||||
await k.mouse.down();
|
|
||||||
await k.waitForTimeout(500);
|
|
||||||
assert.ok(await k.evaluate(() => game.closeup.power > 0.2 && game.closeup.holding), 'tenendo premuto la forza sale');
|
|
||||||
await k.screenshot({ path: `${OUT}/03e-basket-carica.png` });
|
|
||||||
await k.mouse.up();
|
|
||||||
assert.equal(await k.evaluate(() => game.closeup.step), 'flight', 'lasciando, la palla parte');
|
|
||||||
await k.waitForTimeout(350);
|
|
||||||
await k.screenshot({ path: `${OUT}/03f-basket-volo.png` });
|
|
||||||
await k.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.thrown === 1, null, { timeout: 20000 });
|
|
||||||
await k.keyboard.press('Escape');
|
|
||||||
await k.waitForFunction(() => game.speech?.text.startsWith('Lascio il pallone'), null, { timeout: 5000 });
|
|
||||||
await k.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// La pesca al lago dell'Anatre: coi ragazzi in Fortezza «S'ha andà a fallo?!?», poi il lancio tenendo premuto e l'attesa.
|
|
||||||
{
|
|
||||||
const k = await open({ width: 1280, height: 860 });
|
|
||||||
await k.click('#splash');
|
|
||||||
await k.click('#new-game');
|
|
||||||
await k.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await k.click('#skip-intro');
|
|
||||||
await settle(k);
|
|
||||||
await k.evaluate(() => game.go('fortezza', [240, 334]));
|
|
||||||
await settle(k);
|
|
||||||
await clickScene(k, await hotspotCenter(k, 'pescatori'));
|
|
||||||
await k.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 40000 });
|
|
||||||
await k.locator('#choices li', { hasText: 'S’ha andà a fallo?!?' }).or(k.locator('#choices li', { hasText: "S'ha andà a fallo?!?" })).first().click();
|
|
||||||
await k.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
|
||||||
await k.locator('#choices li', { hasText: 'Seria.' }).click();
|
|
||||||
await k.waitForFunction(() => game.closeup?.id === 'pesca' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
|
||||||
const r = await k.locator('#scene').boundingBox();
|
|
||||||
await k.mouse.move(r.x + r.width * 0.7, r.y + r.height * 0.7);
|
|
||||||
assert.ok(await k.evaluate(() => game.closeup.power > 0.5), 'toccando l\'acqua al largo la forza sale');
|
|
||||||
await k.mouse.down();
|
|
||||||
await k.waitForTimeout(500);
|
|
||||||
assert.ok(await k.evaluate(() => game.closeup.holding && game.closeup.power > 0.2), 'tenendo premuto si carica il lancio');
|
|
||||||
await k.mouse.up();
|
|
||||||
await k.waitForFunction(() => game.closeup.step === 'wait', null, { timeout: 5000 });
|
|
||||||
await k.waitForTimeout(600);
|
|
||||||
await k.screenshot({ path: `${OUT}/03g-pesca-attesa.png` });
|
|
||||||
assert.equal(await k.textContent('#bocce-throw'), 'Ferra');
|
|
||||||
await k.keyboard.press('Escape');
|
|
||||||
await k.waitForFunction(() => game.speech?.text.startsWith('Lascio la canna'), null, { timeout: 5000 });
|
|
||||||
await k.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Il biliardo del Circolo 4 Maggio: la sala, poi la partita; un tocco sul tavolo mira, tenendo premuto la forza sale.
|
|
||||||
{
|
|
||||||
const b = await open({ width: 1280, height: 860 });
|
|
||||||
await b.click('#splash');
|
|
||||||
await b.click('#new-game');
|
|
||||||
await b.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await b.click('#skip-intro');
|
|
||||||
await settle(b);
|
|
||||||
await b.evaluate(() => game.go('circolo_4_maggio'));
|
|
||||||
await settle(b);
|
|
||||||
await b.waitForTimeout(1500); // la dissolvenza del cambio scena
|
|
||||||
await b.screenshot({ path: `${OUT}/03d-circolo.png` });
|
|
||||||
await clickScene(b, await hotspotCenter(b, 'ing_tozzi'));
|
|
||||||
await b.waitForFunction(() => game.dialogue && !game.busy, null, { timeout: 20000 });
|
|
||||||
await b.locator('#choices li', { hasText: 'una partita ai birilli' }).click();
|
|
||||||
await b.waitForFunction(() => game.options().some(o => o.text === 'Seria.'), null, { timeout: 30000 });
|
|
||||||
await b.locator('#choices li', { hasText: 'Seria.' }).click();
|
|
||||||
await b.waitForFunction(() => game.closeup?.id === 'biliardo' && game.closeup.step === 'aim' && !game.speech, null, { timeout: 30000 });
|
|
||||||
const r = await b.locator('#scene').boundingBox();
|
|
||||||
const [tx, ty] = await b.evaluate(async () => { const { VIEW } = await import('/src/engine/biliardo.js'); const V = game.closeup.balls.cpu; return [VIEW.x + V.x * VIEW.k, VIEW.y + V.y * VIEW.k]; });
|
|
||||||
await b.mouse.move(r.x + tx / 640 * r.width, r.y + ty / 360 * r.height);
|
|
||||||
await b.mouse.down();
|
|
||||||
await b.waitForTimeout(500);
|
|
||||||
assert.ok(await b.evaluate(() => game.closeup.power > 0.2), 'tenendo premuto la forza sale');
|
|
||||||
await b.screenshot({ path: `${OUT}/03e-biliardo-forza.png` });
|
|
||||||
await b.mouse.up();
|
|
||||||
assert.equal(await b.evaluate(() => game.closeup.step), 'rolling', 'lasciando, la biglia parte');
|
|
||||||
await b.waitForFunction(() => game.closeup.step === 'aim' && game.closeup.current === 'cpu' || game.closeup.step === 'rolling' && game.closeup.shot?.shooter === 'cpu', null, { timeout: 20000 });
|
|
||||||
await b.screenshot({ path: `${OUT}/03f-biliardo-socio.png` });
|
|
||||||
await b.keyboard.press('Escape');
|
|
||||||
await b.waitForFunction(() => game.speech?.text.startsWith('Lasci a metà?'), null, { timeout: 5000 });
|
|
||||||
await b.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
const phone = await open({ width: 390, height: 844 });
|
|
||||||
await phone.click('#splash'); // un clic salta il cartello
|
|
||||||
assert.ok(await phone.isVisible('#title'), 'il clic sul cartello porta al titolo');
|
|
||||||
await phone.click('#new-game');
|
|
||||||
await phone.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await phone.click('#skip-intro');
|
|
||||||
await settle(phone);
|
|
||||||
const overflow = await phone.evaluate(() => document.documentElement.scrollWidth > innerWidth);
|
|
||||||
// Ogni verbo si vede tutto: niente pulsanti tagliati dall'inventario.
|
|
||||||
assert.ok(await phone.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.getBoundingClientRect().right <= innerWidth)), 'verbi tagliati sul telefono');
|
|
||||||
assert.equal(overflow, false, 'scorrimento orizzontale su telefono');
|
|
||||||
await phone.screenshot({ path: `${OUT}/08-telefono.png` });
|
|
||||||
// Telefono stretto in orizzontale: niente deve allargare la pagina oltre lo schermo (il browser la rimpicciolirebbe
|
|
||||||
// e taglierebbe verbi e inventario a destra), e la pausa sta tutta dentro la scena.
|
|
||||||
const small = await browser.newPage({ viewport: { width: 740, height: 330 }, deviceScaleFactor: 2.4, hasTouch: true, isMobile: true });
|
|
||||||
small.on('pageerror', e => errors.push(e.message));
|
|
||||||
await small.goto(`http://127.0.0.1:${PORT}/`);
|
|
||||||
await small.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
|
|
||||||
await small.click('#new-game');
|
|
||||||
await small.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await small.click('#skip-intro');
|
|
||||||
await settle(small);
|
|
||||||
assert.equal(await small.evaluate(() => innerWidth), 740, 'la pagina è più larga dello schermo del telefono');
|
|
||||||
await small.click('#pause-button');
|
|
||||||
assert.ok(await small.evaluate(() => document.getElementById('to-title').getBoundingClientRect().bottom <= document.getElementById('stage').getBoundingClientRect().bottom), 'la pausa esce dalla scena');
|
|
||||||
await small.screenshot({ path: `${OUT}/08b-telefono-stretto-pausa.png` });
|
|
||||||
// Telefono in verticale: la Pausa prende tutto lo schermo, e versione e spiegazione non finiscono sopra i verbi.
|
|
||||||
{
|
|
||||||
const tall = await browser.newPage({ viewport: { width: 412, height: 915 }, deviceScaleFactor: 2, hasTouch: true, isMobile: true });
|
|
||||||
await tall.goto(`http://127.0.0.1:${PORT}/`);
|
|
||||||
await tall.waitForSelector('#splash', { state: 'hidden', timeout: 15000 });
|
|
||||||
await tall.click('#new-game');
|
|
||||||
await tall.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await tall.click('#skip-intro');
|
|
||||||
await settle(tall);
|
|
||||||
await tall.click('#pause-button');
|
|
||||||
const fit = await tall.evaluate(() => {
|
|
||||||
const p = document.getElementById('pause'), v = p.querySelector('.version').getBoundingClientRect();
|
|
||||||
return { inside: p.scrollHeight <= p.clientHeight + 1, top: document.elementFromPoint(v.x + v.width / 2, v.y + v.height / 2)?.closest('#pause') !== null };
|
|
||||||
});
|
|
||||||
assert.deepEqual(fit, { inside: true, top: true }, 'in verticale la pausa esce dallo schermo o finisce sotto i verbi');
|
|
||||||
await tall.close();
|
|
||||||
}
|
|
||||||
const landscape = await open({ width: 844, height: 390 });
|
|
||||||
await landscape.click('#new-game');
|
|
||||||
await landscape.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await landscape.click('#skip-intro');
|
|
||||||
await settle(landscape);
|
|
||||||
assert.equal(await landscape.evaluate(() => document.documentElement.scrollWidth > innerWidth), false);
|
|
||||||
assert.ok(await landscape.evaluate(() => [...document.querySelectorAll('#verbs button')].every(b => b.scrollWidth <= b.clientWidth + 1 && b.getBoundingClientRect().height >= 26)), 'in orizzontale i verbi si leggono interi e il dito li prende');
|
|
||||||
await landscape.screenshot({ path: `${OUT}/09-orizzontale.png`, fullPage: true });
|
|
||||||
|
|
||||||
// Debug (?debug): area calpestabile e hotspot disegnati; Maiusc+clic segna punti senza muovere Andrea, X li azzera.
|
|
||||||
const dbg = await open({ width: 1280, height: 860 }, '?debug');
|
|
||||||
await dbg.click('#new-game');
|
|
||||||
await dbg.click('#rione [data-rione="campomigliaio"]');
|
|
||||||
await dbg.click('#skip-intro');
|
|
||||||
await settle(dbg);
|
|
||||||
const start = await dbg.evaluate(() => [game.state.x, game.state.y]);
|
|
||||||
await dbg.keyboard.down('Shift');
|
|
||||||
await clickScene(dbg, [100, 300]);
|
|
||||||
await clickScene(dbg, [200, 320]);
|
|
||||||
await dbg.keyboard.up('Shift');
|
|
||||||
await dbg.mouse.move(700, 400);
|
|
||||||
await dbg.waitForTimeout(200);
|
|
||||||
assert.deepEqual(await dbg.evaluate(() => [game.state.x, game.state.y]), start, 'Maiusc+clic non muove Andrea');
|
|
||||||
await dbg.screenshot({ path: `${OUT}/10-debug.png` });
|
|
||||||
|
|
||||||
assert.deepEqual(errors, [], 'errori nella console');
|
assert.deepEqual(errors, [], 'errori nella console');
|
||||||
console.log(`Browser OK: soluzione completa, ripresa del salvataggio, telefono. Schermate in ${OUT}`);
|
console.log(`Browser OK: soluzione completa, ripresa del salvataggio. Schermate in ${OUT}`);
|
||||||
}
|
}
|
||||||
assert.deepEqual(errors, [], 'errori nella console');
|
assert.deepEqual(errors, [], 'errori nella console');
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export async function checkMultimedia(browser, port, out, errors) {
|
|||||||
else await page.mouse.move(...await at(target));
|
else await page.mouse.move(...await at(target));
|
||||||
await page.waitForFunction(() => game.closeup?.step === 'reveal');
|
await page.waitForFunction(() => game.closeup?.step === 'reveal');
|
||||||
await page.screenshot({ path: `${out}/media-torcia-${mobile ? 'telefono' : 'desktop'}-${i}.png` });
|
await page.screenshot({ path: `${out}/media-torcia-${mobile ? 'telefono' : 'desktop'}-${i}.png` });
|
||||||
for (let n = 0; n < 200; n++) {
|
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(i => game.closeup.found > i, i)) break;
|
||||||
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
if (await page.evaluate(() => !!game.speech)) await page.keyboard.press('Space');
|
||||||
await page.waitForTimeout(25);
|
await page.waitForTimeout(25);
|
||||||
|
|||||||
Reference in New Issue
Block a user