Prove nel browser sulla scheda video: tests/chromium.mjs usa la GPU se c'è (schermata 41 ms invece di 633; tutte e venti in ~4 min invece di ~16, sei alla volta), altrimenti swiftshader; la pipeline dà la GPU ai job, runner a 6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiECd9RWGMKV7n3K1mdy8T
This commit is contained in:
2026-11-01 17:47:10 +01:00
co-authored by Claude Opus 5.5
parent 8319f58f1e
commit a926ad6423
26 changed files with 85 additions and 28 deletions
+2 -1
View File
@@ -3,6 +3,7 @@
// npm run browser:effects — immagini in /tmp/regolo-effects (oppure $OUT).
import { chromium } from 'playwright';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
import { mkdirSync, writeFileSync } from 'node:fs';
import assert from 'node:assert/strict';
@@ -11,7 +12,7 @@ mkdirSync(out, { recursive: true });
const server = await staticServer(), port = server.port;
let browser;
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
const page = await browser.newPage({ viewport: { width: 640, height: 360 } });
const errors = [];
page.on('pageerror', e => errors.push(e.message));
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync, writeFileSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-biliardo';
mkdirSync(out, { recursive: true });
@@ -64,7 +65,7 @@ async function checkArtwork(page, name) {
}
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
for (const [name, mobile, viewport] of [
['desktop', false, { width: 1280, height: 860 }],
['touch', true, { width: 844, height: 390 }],
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync, writeFileSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = '/tmp/regolo-shots'; mkdirSync(out, { recursive: true });
const server = await staticServer(), errors = [];
@@ -51,7 +52,7 @@ async function artwork(page) {
for (const [name, png] of Object.entries(pictures)) writeFileSync(`${out}/briscola-${name}.png`, Buffer.from(png, 'base64'));
}
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
const scenarios = ['mouse', 'touch', 'keyboard'].flatMap(mode => [
{ name: `desktop-${mode}`, mode, viewport: { width: 1280, height: 860 } },
{ name: `telefono-${mode}`, mode, viewport: { width: 390, height: 844 } },
+2 -1
View File
@@ -5,6 +5,7 @@
// attese si sovrappongono. Una parte sola: node tests/browser.mjs --parte=minigiochi (--media-only: solo multimedia).
import { chromium } from 'playwright';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
import { mkdirSync, readFileSync } from 'node:fs';
import assert from 'node:assert/strict';
import { solution, route } from '../src/story/solution.js';
@@ -31,7 +32,7 @@ if (!PARTE && !process.argv.includes('--media-only')) {
process.exit(esiti.every(e => e.code === 0) ? 0 : 1);
}
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: ARGS });
const errors = [];
async function open(viewport, query = '') {
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-bruciate';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
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));
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-campana';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
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));
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-cerbottana';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
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));
+10
View File
@@ -0,0 +1,10 @@
// Come si apre Chromium nelle prove nel browser (solo da node: helpers.mjs lo carica anche la pagina).
// Con una scheda video (/dev/dri o /dev/nvidia0, anche nei contenitori lanciati con --gpus) si disegna lì: una
// schermata costa ~45 ms invece di ~600 con swiftshader, e le prove girano 5-10 volte più in fretta. Senza, swiftshader.
// REGOLO_GPU=0 forza swiftshader, REGOLO_GPU=1 la scheda video.
import { existsSync } from 'node:fs';
export const GPU = process.env.REGOLO_GPU ? process.env.REGOLO_GPU === '1' : ['/dev/dri/renderD128', '/dev/nvidia0'].some(existsSync);
const GL = GPU ? ['--use-gl=angle', '--use-angle=gl-egl', '--ignore-gpu-blocklist', '--enable-gpu-rasterization']
: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'];
export const ARGS = ['--autoplay-policy=no-user-gesture-required', ...GL];
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-condor';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
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));
+12 -3
View File
@@ -5,6 +5,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-flipper';
mkdirSync(out, { recursive: true });
@@ -12,7 +13,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
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));
@@ -45,9 +46,10 @@ try {
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();
// Da tastiera con la freccia giù (il mouse tiene la molla come il dito, provato sul telefono).
await page.keyboard.down('ArrowDown');
await page.waitForFunction(() => game.closeup.holding && game.closeup.power > 0.3, null, { polling: 'raf' });
await page.mouse.up();
await page.keyboard.up('ArrowDown');
}
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.
@@ -83,6 +85,13 @@ try {
await page.keyboard.up('ArrowLeft');
await page.click('#resume');
await page.waitForFunction(() => document.querySelector('#pause').hidden);
// I due Shift: il sinistro alza la paletta sinistra, il destro la destra.
await page.keyboard.down('ShiftRight');
await page.waitForFunction(() => game.closeup.flippers.right.held.includes('key-ShiftRight') && !game.closeup.flippers.left.held.length);
await page.keyboard.down('ShiftLeft');
await page.waitForFunction(() => game.closeup.flippers.left.held.includes('key-ShiftLeft'));
await page.keyboard.up('ShiftRight'); await page.keyboard.up('ShiftLeft');
await page.waitForFunction(() => !game.closeup.flippers.left.held.length && !game.closeup.flippers.right.held.length);
}
// Il traguardo aggiunge una pallina e lascia continuare la partita.
const reward = await page.evaluate(async () => {
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-gara';
mkdirSync(out, { recursive: true });
@@ -12,7 +13,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
for (const [name, mobile, viewport, level] of [
['touch', true, { width: 844, height: 390 }, 'seria'],
['portrait', true, { width: 390, height: 844 }, 'bona'],
+2 -1
View File
@@ -5,6 +5,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-ingorgo';
mkdirSync(out, { recursive: true });
@@ -12,7 +13,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
const page = await browser.newPage({ viewport: { width: 1280, height: 860 } });
page.on('pageerror', e => { errors.push(e.message); console.log('ERR', e.message); });
await page.goto(`http://127.0.0.1:${server.port}/`);
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-micro';
mkdirSync(out, { recursive: true });
@@ -12,7 +13,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
for (const [name, mobile, viewport] of [
['desktop', false, { width: 1280, height: 860 }],
['touch', true, { width: 844, height: 390 }],
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-risveglio';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
// Sul computer la versione con la sfocatura vera, sul telefono (touch) quella leggera senza filtri.
for (const mobile of [false, true]) {
const page = await browser.newPage(mobile ? { viewport: { width: 844, height: 390 }, hasTouch: true, isMobile: true } : { viewport: { width: 1280, height: 860 } });
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = '/tmp/regolo-shots'; mkdirSync(out, { recursive: true });
const server = await staticServer(), errors = [];
@@ -44,7 +45,7 @@ async function wait(page) {
});
}
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
const scenarios = [
{ name: 'desktop-mouse', mode: 'mouse', viewport: { width: 1280, height: 860 }, full: true },
{ name: 'telefono-touch', mode: 'touch', viewport: { width: 390, height: 844 } },
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-smarronata';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
const page = await browser.newPage({ viewport: { width: 1280, height: 860 } });
page.on('pageerror', e => { errors.push(e.message); console.log('ERR', e.message); });
await page.goto(`http://127.0.0.1:${server.port}/`);
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-telefono';
mkdirSync(out, { recursive: true });
@@ -11,7 +12,7 @@ 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'] });
browser = await chromium.launch({ args: ARGS });
const page = await browser.newPage({ viewport: { width: 1280, height: 860 } });
page.on('pageerror', e => { errors.push(e.message); console.log('ERR', e.message); });
await page.goto(`http://127.0.0.1:${server.port}/`);
+2 -1
View File
@@ -4,6 +4,7 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = '/tmp/regolo-shots'; mkdirSync(out, { recursive: true });
const server = await staticServer(), errors = [];
@@ -46,7 +47,7 @@ async function wait(page) {
});
}
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
const scenarios = [
{ name: 'desktop-mouse', mode: 'mouse', viewport: { width: 1280, height: 860 }, full: true },
{ name: 'telefono-touch', mode: 'touch', viewport: { width: 390, height: 844 } },
+2 -1
View File
@@ -4,13 +4,14 @@ import { chromium } from 'playwright';
import { mkdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import { staticServer } from './helpers.mjs';
import { ARGS } from './chromium.mjs';
const out = process.env.OUT ?? '/tmp/regolo-shots';
mkdirSync(out, { recursive: true });
const server = await staticServer();
let browser;
try {
browser = await chromium.launch({ args: ['--autoplay-policy=no-user-gesture-required', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
browser = await chromium.launch({ args: ARGS });
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
const errors = [];
page.on('pageerror', e => errors.push(e.message));
+1 -1
View File
@@ -10,7 +10,7 @@ const FILES = ['index.html', 'style.css', 'src/main.js', 'src/version.js', 'pack
'src/engine/briscola.js', 'src/engine/briscola-render.js', 'src/engine/flipper.js', 'src/engine/corsa.js', 'src/story/piloti.js',
'src/story/story.js', 'src/story/solution.js', 'assets/scenes/piazza.png', 'assets/closeups/micro-parcheggio.png',
'assets/closeups/gp/boccino.png', 'assets/muro/index.html', 'assets/video/briscola.mp4', 'docs/BRISCOLA.md', 'docs/STATO.md',
'tests/helpers.mjs', 'tests/browser.mjs', 'tests/briscola.browser.mjs', 'tests/briscola.test.mjs', 'tests/story.test.mjs',
'tests/helpers.mjs', 'tests/chromium.mjs', 'tests/browser.mjs', 'tests/briscola.browser.mjs', 'tests/briscola.test.mjs', 'tests/story.test.mjs',
'scripts/verify.mjs', 'scripts/bump.sh', 'tools/slice_sprites.py', '.verify-ok.json'];
const suitesWith = path => Object.keys(SUITES).filter(n => inputsOf(n, FILES).includes(path));
const islands = Object.keys(SUITES).filter(n => !SUITES[n].core), cores = Object.keys(SUITES).filter(n => SUITES[n].core);