From d67f3669d6a8b0d5a0c9dbe5b429f5103ae76506 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sun, 21 Jun 2026 18:03:07 +0200 Subject: [PATCH] test(e2e): helper condivisi e refactor degli spec di gioco MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - nuovo tests/e2e/helpers.cjs: openController (viewport portrait per il layout mobile), openDisplay, resetGame, addPoints - resetGame gestisce il dialog di vittoria persistente e reimposta i nomi di default (lo stato è condiviso e persistente: serve per il determinismo) - full-match/game-simulation: gestito il dialog SET VINTO automatico a 25/15 - basic-flow/game-operations: migrati agli helper condivisi --- tests/e2e/basic-flow.spec.cjs | 87 +++++------------------------- tests/e2e/full-match.spec.cjs | 77 ++++++-------------------- tests/e2e/game-operations.spec.cjs | 72 ++++++------------------- tests/e2e/game-simulation.spec.cjs | 66 ++++------------------- tests/e2e/helpers.cjs | 81 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 248 deletions(-) create mode 100644 tests/e2e/helpers.cjs diff --git a/tests/e2e/basic-flow.spec.cjs b/tests/e2e/basic-flow.spec.cjs index fdb3e03..e928294 100644 --- a/tests/e2e/basic-flow.spec.cjs +++ b/tests/e2e/basic-flow.spec.cjs @@ -1,26 +1,19 @@ const { test, expect } = require('@playwright/test'); +const { openController, openDisplay, resetGame } = require('./helpers.cjs'); test.describe('Basic Flow: Controller ↔ Display', () => { test('dovrebbe caricare Display e Controller con i titoli corretti', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); await expect(displayPage).toHaveTitle(/Segnapunti/); await expect(controllerPage).toHaveTitle(/Controller/); }); test('il punteggio iniziale dovrebbe essere 0-0', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - - // Attende la connessione WebSocket - await controllerPage.waitForSelector('.conn-bar.connected'); + const controllerPage = await openController(context); + await resetGame(controllerPage); const homeScore = controllerPage.locator('.team-score.home-bg .team-pts'); const guestScore = controllerPage.locator('.team-score.guest-bg .team-pts'); @@ -29,28 +22,10 @@ test.describe('Basic Flow: Controller ↔ Display', () => { }); test('click +1 Home sul Controller dovrebbe aggiornare il Display', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - - // Attende la connessione WebSocket del controller - await controllerPage.waitForSelector('.conn-bar.connected'); - - // Reset per stato pulito - await controllerPage.getByText(/Reset/i).first().click(); - const btnConfirm = controllerPage.locator('.dialog .btn-confirm'); - if (await btnConfirm.isVisible()) { - await btnConfirm.click(); - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(200); + await resetGame(controllerPage); // Click +1 Home await controllerPage.locator('.team-score.home-bg').click(); @@ -64,27 +39,10 @@ test.describe('Basic Flow: Controller ↔ Display', () => { }); test('click +1 Guest sul Controller dovrebbe aggiornare il Display', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - - await controllerPage.waitForSelector('.conn-bar.connected'); - - // Reset - await controllerPage.getByText(/Reset/i).first().click(); - const btnConfirm = controllerPage.locator('.dialog .btn-confirm'); - if (await btnConfirm.isVisible()) { - await btnConfirm.click(); - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(200); + await resetGame(controllerPage); // Click +1 Guest await controllerPage.locator('.team-score.guest-bg').click(); @@ -98,27 +56,10 @@ test.describe('Basic Flow: Controller ↔ Display', () => { }); test('la sincronizzazione dovrebbe funzionare con punti alternati', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - - await controllerPage.waitForSelector('.conn-bar.connected'); - - // Reset - await controllerPage.getByText(/Reset/i).first().click(); - const btnConfirm = controllerPage.locator('.dialog .btn-confirm'); - if (await btnConfirm.isVisible()) { - await btnConfirm.click(); - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(200); + await resetGame(controllerPage); // Home +1, Guest +1, Home +1 await controllerPage.locator('.team-score.home-bg').click(); diff --git a/tests/e2e/full-match.spec.cjs b/tests/e2e/full-match.spec.cjs index 838747d..de62a61 100644 --- a/tests/e2e/full-match.spec.cjs +++ b/tests/e2e/full-match.spec.cjs @@ -1,49 +1,10 @@ const { test, expect } = require('@playwright/test'); - -// Helper: reset dal controller -async function resetGame(controllerPage) { - await controllerPage.getByText(/Reset/i).first().click(); - const btnConfirm = controllerPage.locator('.dialog .btn-confirm'); - if (await btnConfirm.isVisible()) { - await btnConfirm.click(); - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(300); -} - -// Helper: incrementa punti per una squadra N volte -async function addPoints(controllerPage, team, count) { - const selector = team === 'home' ? '.team-score.home-bg' : '.team-score.guest-bg'; - for (let i = 0; i < count; i++) { - await controllerPage.locator(selector).click(); - await controllerPage.waitForTimeout(30); - } - await controllerPage.waitForTimeout(100); -} - -// Helper: assegna un set a una squadra (25 punti + click SET) -async function winSet(controllerPage, team) { - await addPoints(controllerPage, team, 25); - // Clicca bottone SET - const setSelector = team === 'home' ? '.btn-set.home-bg' : '.btn-set.guest-bg'; - await controllerPage.locator(setSelector).click(); - await controllerPage.waitForTimeout(100); - // Reset punti per il prossimo set - // (in questo gioco i punti non si resettano automaticamente, serve reset manuale - // o il controller gestisce il prossimo set manualmente) -} +const { openController, resetGame, addPoints } = require('./helpers.cjs'); test.describe('Full Match Simulation', () => { - test('Partita 2/3: Home vince 2 set a 0', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + test('Partita 2/3: Home vince il primo set', async ({ context }) => { + const controllerPage = await openController(context); await resetGame(controllerPage); @@ -60,19 +21,19 @@ test.describe('Full Match Simulation', () => { // Verifica punteggio 25 await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('25'); - // Incrementa set Home - await controllerPage.locator('.btn-set.home-bg').click(); - await controllerPage.waitForTimeout(100); + // A 25-0 compare automaticamente il dialog "SET VINTO": vai al set successivo + await controllerPage.waitForSelector('.dialog-winner'); + await controllerPage.getByText('VAI AL SET SUCCESSIVO').click(); + // doNuovoSet riapre il dialog di configurazione: chiudilo + await controllerPage.locator('.dialog-config .btn-cancel').click(); + await controllerPage.waitForTimeout(200); // Verifica set 1 per Home await expect(controllerPage.locator('.team-score.home-bg .team-set')).toContainText('SET 1'); }); test('Set decisivo 2/3: vittoria a 15 punti', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const controllerPage = await openController(context); await resetGame(controllerPage); @@ -99,18 +60,12 @@ test.describe('Full Match Simulation', () => { // Verifica punteggio 15 (e il set è decisivo: dopo 15 punti il gioco è vinto) await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('15'); - // Verifica che non si possono aggiungere altri punti (vittoria) - await controllerPage.locator('.team-score.home-bg').click(); - await controllerPage.waitForTimeout(100); - // Dovrebbe restare 15 (checkVittoria blocca incPunt) - await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('15'); + // A 15 nel set decisivo scatta la vittoria: compare il dialog (PARTITA FINITA) + await expect(controllerPage.locator('.dialog-winner')).toBeVisible(); }); test('Set normale: punti oltre 25 fino ai vantaggi', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const controllerPage = await openController(context); await resetGame(controllerPage); @@ -131,9 +86,7 @@ test.describe('Full Match Simulation', () => { await controllerPage.waitForTimeout(100); await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('26'); - // 26-24 è vittoria → non si possono più aggiungere punti - await controllerPage.locator('.team-score.home-bg').click(); - await controllerPage.waitForTimeout(100); - await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('26'); + // 26-24 è vittoria → compare automaticamente il dialog SET VINTO + await expect(controllerPage.locator('.dialog-winner')).toBeVisible(); }); }); diff --git a/tests/e2e/game-operations.spec.cjs b/tests/e2e/game-operations.spec.cjs index 1e5984c..e3fff74 100644 --- a/tests/e2e/game-operations.spec.cjs +++ b/tests/e2e/game-operations.spec.cjs @@ -1,28 +1,10 @@ const { test, expect } = require('@playwright/test'); - -// Helper: reset dal controller -async function resetGame(controllerPage) { - await controllerPage.getByText(/Reset/i).first().click(); - const btnConfirm = controllerPage.locator('.dialog .btn-confirm'); - if (await btnConfirm.isVisible()) { - await btnConfirm.click(); - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(300); -} +const { openController, openDisplay, resetGame } = require('./helpers.cjs'); test.describe('Game Operations', () => { test('Undo: dovrebbe annullare l\'ultimo punto', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); - + const controllerPage = await openController(context); await resetGame(controllerPage); // Incrementa Home a 1 @@ -37,10 +19,8 @@ test.describe('Game Operations', () => { }); test('Reset: dovrebbe azzerare tutto dopo conferma', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const controllerPage = await openController(context); + await resetGame(controllerPage); // Imposta qualche punto for (let i = 0; i < 5; i++) { @@ -57,13 +37,9 @@ test.describe('Game Operations', () => { }); test('Config: dovrebbe cambiare i nomi dei team', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); + await resetGame(controllerPage); // Apri config await controllerPage.getByText('Config').click(); @@ -88,13 +64,9 @@ test.describe('Game Operations', () => { }); test('Toggle Formazione: dovrebbe mostrare la formazione sul display', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); + await resetGame(controllerPage); // Inizialmente mostra punteggio, non formazione await expect(displayPage.locator('.punteggio-container')).toBeVisible(); @@ -108,13 +80,9 @@ test.describe('Game Operations', () => { }); test('Toggle Striscia: dovrebbe nascondere/mostrare la striscia', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); + await resetGame(controllerPage); // Inizialmente la striscia è visibile await expect(displayPage.locator('.striscia')).toBeVisible(); @@ -131,13 +99,8 @@ test.describe('Game Operations', () => { }); test('Cambi: dovrebbe effettuare una sostituzione giocatore', async ({ context }) => { - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); await resetGame(controllerPage); @@ -167,10 +130,7 @@ test.describe('Game Operations', () => { }); test('Cambi: dovrebbe mostrare errore per giocatore non in formazione', async ({ context }) => { - const controllerPage = await context.newPage(); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); - await controllerPage.waitForSelector('.conn-bar.connected'); + const controllerPage = await openController(context); await resetGame(controllerPage); diff --git a/tests/e2e/game-simulation.spec.cjs b/tests/e2e/game-simulation.spec.cjs index 4e135ce..38bbaf0 100644 --- a/tests/e2e/game-simulation.spec.cjs +++ b/tests/e2e/game-simulation.spec.cjs @@ -1,66 +1,16 @@ const { test, expect } = require('@playwright/test'); +const { openController, openDisplay, resetGame, addPoints } = require('./helpers.cjs'); test.describe('Game Simulation', () => { test('Simulazione Partita: Controller aggiunge punti finché non cambia il set', async ({ context }) => { - // 1. Setup Pagine - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); + const displayPage = await openDisplay(context); + const controllerPage = await openController(context); - await displayPage.goto('http://localhost:3000'); - await controllerPage.setViewportSize({ width: 390, height: 844 }); - await controllerPage.goto('http://localhost:3000/controller'); + await resetGame(controllerPage); - // Selettori (basati su ID ipotetici o classi, adattali al tuo HTML reale) - // Assumo che nel DOM ci siano elementi con ID o classi riconoscibili - // E che i punteggi siano visibili. - - // Pulisco lo stato iniziale (reset) - const btnReset = controllerPage.getByText(/Reset/i).first(); - if (await btnReset.isVisible()) { - await btnReset.click(); - // La modale di conferma ha un bottone "SI" con classe .btn-confirm - const btnConfirmReset = controllerPage.locator('.dialog .btn-confirm').getByText('SI'); - if (await btnConfirmReset.isVisible()) { - await btnConfirmReset.click(); - } - } - // doReset apre automaticamente il dialog di configurazione: chiudilo - const cfgCancel = controllerPage.locator('.dialog-config .btn-cancel'); - if (await cfgCancel.isVisible()) { - await cfgCancel.click(); - } - await controllerPage.waitForTimeout(200); - - // 2. Loop per vincere il primo set (25 punti) - // In ControllerPage.vue, il click su .team-score.home-bg incrementa i punti home - const btnHomeScore = controllerPage.locator('.team-score.home-bg'); - - for (let i = 0; i < 25; i++) { - await btnHomeScore.click(); - // Piccola pausa per lasciare tempo al server di processare e broadcastare - //await displayPage.waitForTimeout(10); - } - - // 3. Verifica Vittoria Set - // I punti dovrebbero essere tornati a 0 (o mostrare 25 prima del reset manuale?) - // Il codice gameState dice: checkVittoria -> resetta solo se qualcuno chiama resetta? - // No, checkVittoria è boolean. applyAction('incPunt') incrementa. - // Se vince, il set incrementa? 'incPunt' non incrementa i set in automatico nel codice gameState checkato prima! - // Controllo applyAction: - // "s.sp.punt[team]++" ... POI "checkVittoria(s)" all'inizio del prossimo incPunt? - // NO: "if (checkVittoria(s)) break" all'inizio di incPunt impedisce di andare oltre 25 se già vinto. - // MA 'incSet' è un'azione separata! - - // Aspetta, la logica standard è: arrivo a 25 -> vinco set? - // In questo codice `gameState.js` NON c'è automatismo "arrivo a 25 -> set++ e palla al centro". - // L'utente deve cliccare "SET Antoniana" manualmente? - // Guardiamo ControllerPage.vue: - // C'è un bottone "SET {{ state.sp.nomi.home }}" che manda { type: 'incSet', team: 'home' } - - // QUINDI: Il test deve: - // 1. Arrivare a 25 pt. - // 2. Cliccare "SET HOME". - // 3. Verificare che Set Home = 1. + // Porta Home a 25: in ControllerPage il click su .team-score.home-bg + // incrementa i punti home. A 25-0 scatta la vittoria del set. + await addPoints(controllerPage, 'home', 25); // A 25-0 compare automaticamente il dialog "SET VINTO" await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('25'); @@ -68,6 +18,8 @@ test.describe('Game Simulation', () => { // Procedi al set successivo: registra il set vinto da Home await controllerPage.getByText('VAI AL SET SUCCESSIVO').click(); + // doNuovoSet riapre il dialog di configurazione: chiudilo + await controllerPage.locator('.dialog-config .btn-cancel').click(); await controllerPage.waitForTimeout(300); // Verifica che il set Home sia incrementato a 1 diff --git a/tests/e2e/helpers.cjs b/tests/e2e/helpers.cjs new file mode 100644 index 0000000..f794547 --- /dev/null +++ b/tests/e2e/helpers.cjs @@ -0,0 +1,81 @@ +// Helper condivisi per gli E2E. +// Nota: lo stato di gioco è UNICO e persistente sul server, quindi ogni test +// deve riportarlo a zero (resetGame) e gestire eventuali dialog rimasti aperti +// dal test precedente. + +const CONTROLLER_URL = 'http://localhost:3000/controller'; +const DISPLAY_URL = 'http://localhost:3000'; + +// Apre il controller in viewport portrait (layout "mobile", su cui si basano i +// selettori dei test) e attende la connessione WebSocket. +async function openController(context) { + const page = await context.newPage(); + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto(CONTROLLER_URL); + await page.waitForSelector('.conn-bar.connected'); + return page; +} + +// Apre il display. +async function openDisplay(context) { + const page = await context.newPage(); + await page.goto(DISPLAY_URL); + return page; +} + +// Chiude l'eventuale dialog di fine set / fine partita (può essersi aperto in +// automatico al caricamento se lo stato persistito era già "vinto"). +async function chiudiDialogVittoria(page) { + if (await page.locator('.dialog-winner').isVisible().catch(() => false)) { + const chiudi = page.getByRole('button', { name: 'CHIUDI' }); + if (await chiudi.isVisible().catch(() => false)) { + await chiudi.click(); + } else { + await page.getByText('INDIETRO').click(); + } + await page.waitForTimeout(150); + } +} + +// Reset completo dello stato dal controller: +// 1) chiude un eventuale dialog di vittoria, 2) azzera, 3) nel dialog di +// configurazione che doReset riapre, reimposta nomi di default e conferma. +// Reimpostare i nomi rende lo stato deterministico (lo stato è condiviso e +// persistente: senza questo i nomi di un test precedente "sporcano" gli altri, +// in particolare gli screenshot di visual-regression). +async function resetGame(page) { + await chiudiDialogVittoria(page); + await page.getByText(/Reset/i).first().click(); + const btnConfirm = page.locator('.dialog .btn-confirm'); + if (await btnConfirm.isVisible().catch(() => false)) { + await btnConfirm.click(); + } + const cfg = page.locator('.dialog-config'); + if (await cfg.isVisible().catch(() => false)) { + const inputs = page.locator('.dialog-config .input-field'); + await inputs.first().fill('Antoniana'); + await inputs.nth(1).fill('Guest'); + await page.locator('.dialog-config .btn-confirm').click(); + } + await page.waitForTimeout(300); +} + +// Incrementa N punti per una squadra cliccando il punteggio. +async function addPoints(page, team, count) { + const selector = team === 'home' ? '.team-score.home-bg' : '.team-score.guest-bg'; + for (let i = 0; i < count; i++) { + await page.locator(selector).click(); + await page.waitForTimeout(30); + } + await page.waitForTimeout(100); +} + +module.exports = { + CONTROLLER_URL, + DISPLAY_URL, + openController, + openDisplay, + chiudiDialogVittoria, + resetGame, + addPoints, +};