Revisione di Codex: salvataggi a punti fermi, salva e carica da file, volume delle voci
- si salva solo a sequenza finita: uscire a metà di una risposta o della casetta riporta a prima; la squama presa resta salvata anche senza cambiare scena; test di interruzione su soluzione e catena del ghiacciolo - «Salva su file» nella pausa e «Carica» nel titolo, con controllo di versione e contenuto prima di sostituire il salvataggio del browser - cursore del volume delle voci («bla»), 50% di partenza; il muto zittisce subito la voce, la pausa la riprende - col dito un oggetto mancato di poco si prende, e i piccoli vincono sui grandi che li contengono (la squama); verbi su due colonne nel telefono in orizzontale - sfondi caricati per vicinanza alla scena di partenza, con tentativi limitati se falliscono; a gioco fermo non si ridisegna - modalità ?debug per scrivere le scene: area calpestabile, hotspot, punti, coordinate copiabili - corretto: i crediti dal titolo restavano dietro il titolo Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ApqNBvNbNCwXqR2RLo8QJh
This commit is contained in:
+54
-5
@@ -3,7 +3,7 @@
|
||||
// npm run browser
|
||||
import { chromium } from 'playwright';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { mkdirSync, readFileSync } from 'node:fs';
|
||||
import assert from 'node:assert/strict';
|
||||
import { solution } from '../src/story/solution.js';
|
||||
|
||||
@@ -13,12 +13,12 @@ const server = spawn('python3', ['-m', 'http.server', String(PORT), '--bind', '1
|
||||
const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
|
||||
const errors = [];
|
||||
|
||||
async function open(viewport) {
|
||||
const page = await browser.newPage({ viewport });
|
||||
async function open(viewport, query = '') {
|
||||
const page = await browser.newPage({ viewport, acceptDownloads: true });
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
page.on('console', m => m.type() === 'error' && errors.push(m.text()));
|
||||
for (let i = 0; i < 50; i++) {
|
||||
try { await page.goto(`http://127.0.0.1:${PORT}/`); break; } catch { await page.waitForTimeout(100); }
|
||||
try { await page.goto(`http://127.0.0.1:${PORT}/${query}`); break; } catch { await page.waitForTimeout(100); }
|
||||
}
|
||||
return page;
|
||||
}
|
||||
@@ -63,6 +63,9 @@ try {
|
||||
assert.match(await page.textContent('#credits'), /Crediti\s*Al Lele/, 'la dedica al Lele viene per prima');
|
||||
assert.match(await page.textContent('#credits'), /Yuri Baldi/);
|
||||
assert.match(await page.textContent('#credits'), /Francesco Bacci/);
|
||||
const onTop = await page.evaluate(() => { const r = document.getElementById('close-credits').getBoundingClientRect();
|
||||
return document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)?.id === 'close-credits'; });
|
||||
assert.ok(onTop, 'i crediti stanno sopra il titolo, non dietro');
|
||||
await page.screenshot({ path: `${OUT}/01c-crediti.png` });
|
||||
// L'elenco può scorrere; la dedica e il pulsante Chiudi devono restare dentro lo schermo.
|
||||
const fitsCredits = await page.evaluate(() => { const c = document.getElementById('credits').getBoundingClientRect(), b = document.getElementById('close-credits').getBoundingClientRect(); return b.bottom <= c.bottom && b.top >= c.top; });
|
||||
@@ -108,6 +111,32 @@ try {
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForSelector('#pause:not([hidden])');
|
||||
await page.screenshot({ path: `${OUT}/02b-pausa.png` });
|
||||
// Salva su file: si scarica la partita; dal titolo «Carica» la riapre anche senza il salvataggio del browser.
|
||||
const [download] = await Promise.all([page.waitForEvent('download'), page.click('#save-file')]);
|
||||
assert.match(download.suggestedFilename(), /^regolo-\d{4}-\d\d-\d\d-\d{4}\.json$/);
|
||||
const savedFile = `${OUT}/partita.json`;
|
||||
await download.saveAs(savedFile);
|
||||
const before = await page.evaluate(() => game.snapshot());
|
||||
await page.click('#to-title');
|
||||
await page.evaluate(() => localStorage.removeItem('leggenda-regolo-save'));
|
||||
await page.setInputFiles('#load-file', { name: 'altro.json', mimeType: 'application/json', buffer: Buffer.from('{"ciao":1}') });
|
||||
await page.waitForSelector('#load-error:not([hidden])');
|
||||
assert.ok(await page.isVisible('#title'), 'un file sbagliato lascia il titolo dov\'è');
|
||||
// Una scena che non esiste: rifiutato, e il salvataggio del browser resta com'era.
|
||||
await page.evaluate(() => localStorage.setItem('leggenda-regolo-save', '{"scene":"piazza","resta":true}'));
|
||||
const lost = JSON.parse(readFileSync(savedFile, 'utf8'));
|
||||
lost.state.scene = 'luna';
|
||||
await page.setInputFiles('#load-file', { name: 'luna.json', mimeType: 'application/json', buffer: Buffer.from(JSON.stringify(lost)) });
|
||||
await page.waitForFunction(() => /non torna/.test(document.getElementById('load-error').textContent));
|
||||
assert.ok(await page.isVisible('#title'));
|
||||
assert.equal(await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-save')).resta), true, 'il salvataggio del browser non è toccato');
|
||||
await page.evaluate(() => localStorage.removeItem('leggenda-regolo-save'));
|
||||
await page.setInputFiles('#load-file', savedFile);
|
||||
await page.waitForSelector('#title', { state: 'hidden' });
|
||||
assert.deepEqual(await page.evaluate(() => game.snapshot()), before, 'la partita caricata è quella salvata');
|
||||
assert.ok(await page.evaluate(() => !!localStorage.getItem('leggenda-regolo-save')), 'e diventa quella di «Continua»');
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForSelector('#pause:not([hidden])');
|
||||
await page.click('#resume');
|
||||
assert.ok(await page.isHidden('#pause'));
|
||||
await page.click('#pause-button');
|
||||
@@ -121,7 +150,9 @@ try {
|
||||
assert.ok(playing, 'musica del paese: ' + await page.evaluate(() => music.current?.src));
|
||||
await page.click('#mute');
|
||||
await page.locator('#volume').fill('30');
|
||||
assert.deepEqual(await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-audio'))), { volume: 0.3, muted: false });
|
||||
await page.locator('#voice-volume').fill('70');
|
||||
assert.deepEqual(await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-audio'))), { volume: 0.3, muted: false, voices: 0.7 });
|
||||
assert.equal(await page.evaluate(() => voices.volume), 0.7);
|
||||
|
||||
for (const [verb, target, item] of solution) {
|
||||
if (verb === 'choose') {
|
||||
@@ -144,6 +175,8 @@ try {
|
||||
await page.screenshot({ path: `${OUT}/06-fine.png` });
|
||||
await page.click('#ending .open-credits');
|
||||
assert.ok(await page.isVisible('#credits'), 'crediti anche dalla fine');
|
||||
assert.ok(await page.evaluate(() => { const r = document.getElementById('close-credits').getBoundingClientRect();
|
||||
return document.elementFromPoint(r.x + r.width / 2, r.y + r.height / 2)?.id === 'close-credits'; }), 'i crediti stanno sopra la fine');
|
||||
await page.click('#close-credits');
|
||||
assert.ok(await page.isHidden('#credits'));
|
||||
const saved = await page.evaluate(() => JSON.parse(localStorage.getItem('leggenda-regolo-save')));
|
||||
@@ -208,8 +241,24 @@ try {
|
||||
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('#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');
|
||||
console.log(`Browser OK: soluzione completa, ripresa del salvataggio, telefono. Schermate in ${OUT}`);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user