diff --git a/playwright.config.cjs b/playwright.config.cjs new file mode 100644 index 0000000..b90637c --- /dev/null +++ b/playwright.config.cjs @@ -0,0 +1,38 @@ +const { defineConfig, devices } = require('@playwright/test'); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +module.exports = defineConfig({ + testDir: './tests/e2e', + fullyParallel: false, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, + reporter: 'html', + use: { + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + ], + + webServer: { + command: 'npm run serve', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + timeout: 120 * 1000, + }, +}); diff --git a/playwright.config.ts b/playwright.config.ts index 330d5ea..adb955b 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -50,14 +50,10 @@ export default defineConfig({ // }, /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, /* Test against branded browsers. */ // { diff --git a/tests/e2e/accessibility.spec.cjs b/tests/e2e/accessibility.spec.cjs new file mode 100644 index 0000000..886408a --- /dev/null +++ b/tests/e2e/accessibility.spec.cjs @@ -0,0 +1,72 @@ +const { test, expect } = require('@playwright/test'); +const AxeBuilderImport = require('@axe-core/playwright'); +const AxeBuilder = AxeBuilderImport.default || AxeBuilderImport; + +test.describe('Accessibility (a11y)', () => { + + test('Display: non dovrebbe avere violazioni critiche a11y', async ({ page }) => { + await page.goto('http://localhost:3000'); + await page.waitForTimeout(500); + + const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa']) + .disableRules(['color-contrast']) // il display ha sfondo nero con testo grande, valutato separatamente + .analyze(); + + expect(results.violations).toEqual([]); + }); + + test('Controller: non dovrebbe avere violazioni critiche a11y', async ({ page }) => { + await page.goto('http://localhost:3001'); + await page.waitForTimeout(500); + + const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa']) + .analyze(); + + // Mostra i dettagli delle violazioni se ci sono + if (results.violations.length > 0) { + console.log('A11y violations:', JSON.stringify(results.violations.map(v => ({ + id: v.id, + impact: v.impact, + description: v.description, + nodes: v.nodes.length + })), null, 2)); + } + + // Accettiamo solo violazioni minor (non critiche o serie) + const serious = results.violations.filter(v => + v.impact === 'critical' || v.impact === 'serious' + ); + expect(serious).toEqual([]); + }); + + test('Controller: i touch target dovrebbero avere dimensione minima', async ({ page }) => { + await page.goto('http://localhost:3001'); + await page.waitForSelector('.conn-bar.connected'); + + // Controlla che i bottoni principali abbiano dimensione minima 44x44px + const buttons = page.locator('.btn-ctrl'); + const count = await buttons.count(); + + for (let i = 0; i < count; i++) { + const box = await buttons.nth(i).boundingBox(); + expect(box.width).toBeGreaterThanOrEqual(44); + expect(box.height).toBeGreaterThanOrEqual(44); + } + }); + + test('Controller: i bottoni punteggio dovrebbero avere dimensione adeguata', async ({ page }) => { + await page.goto('http://localhost:3001'); + await page.waitForSelector('.conn-bar.connected'); + + const scoreButtons = page.locator('.team-score'); + const count = await scoreButtons.count(); + + for (let i = 0; i < count; i++) { + const box = await scoreButtons.nth(i).boundingBox(); + expect(box.width).toBeGreaterThanOrEqual(100); + expect(box.height).toBeGreaterThanOrEqual(100); + } + }); +}); diff --git a/tests/e2e/basic-flow.spec.cjs b/tests/e2e/basic-flow.spec.cjs new file mode 100644 index 0000000..386668c --- /dev/null +++ b/tests/e2e/basic-flow.spec.cjs @@ -0,0 +1,119 @@ +const { test, expect } = require('@playwright/test'); + +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.goto('http://localhost:3001'); + + 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.goto('http://localhost:3001'); + + // Attende la connessione WebSocket + await controllerPage.waitForSelector('.conn-bar.connected'); + + const homeScore = controllerPage.locator('.team-score.home-bg .team-pts'); + const guestScore = controllerPage.locator('.team-score.guest-bg .team-pts'); + await expect(homeScore).toHaveText('0'); + await expect(guestScore).toHaveText('0'); + }); + + test('click +1 Home sul Controller dovrebbe aggiornare il Display', async ({ context }) => { + const displayPage = await context.newPage(); + const controllerPage = await context.newPage(); + + await displayPage.goto('http://localhost:3000'); + await controllerPage.goto('http://localhost:3001'); + + // 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(); + } + await controllerPage.waitForTimeout(200); + + // Click +1 Home + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(200); + + // Verifica Controller mostra 1 + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('1'); + + // Verifica Display mostra 1 (il punteggio grande) + await expect(displayPage.locator('.punt.home')).toHaveText('1'); + }); + + test('click +1 Guest sul Controller dovrebbe aggiornare il Display', async ({ context }) => { + const displayPage = await context.newPage(); + const controllerPage = await context.newPage(); + + await displayPage.goto('http://localhost:3000'); + await controllerPage.goto('http://localhost:3001'); + + 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(); + } + await controllerPage.waitForTimeout(200); + + // Click +1 Guest + await controllerPage.locator('.team-score.guest-bg').click(); + await controllerPage.waitForTimeout(200); + + // Verifica Controller + await expect(controllerPage.locator('.team-score.guest-bg .team-pts')).toHaveText('1'); + + // Verifica Display + await expect(displayPage.locator('.punt.guest')).toHaveText('1'); + }); + + test('la sincronizzazione dovrebbe funzionare con punti alternati', async ({ context }) => { + const displayPage = await context.newPage(); + const controllerPage = await context.newPage(); + + await displayPage.goto('http://localhost:3000'); + await controllerPage.goto('http://localhost:3001'); + + 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(); + } + await controllerPage.waitForTimeout(200); + + // Home +1, Guest +1, Home +1 + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(100); + await controllerPage.locator('.team-score.guest-bg').click(); + await controllerPage.waitForTimeout(100); + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(200); + + // Controller: Home 2, Guest 1 + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('2'); + await expect(controllerPage.locator('.team-score.guest-bg .team-pts')).toHaveText('1'); + + // Display: Home 2, Guest 1 + await expect(displayPage.locator('.punt.home')).toHaveText('2'); + await expect(displayPage.locator('.punt.guest')).toHaveText('1'); + }); +}); diff --git a/tests/e2e/basic-flow.spec.js b/tests/e2e/basic-flow.spec.js deleted file mode 100644 index 2284db8..0000000 --- a/tests/e2e/basic-flow.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('Controller updates Display score', async ({ context }) => { - // 1. Create two pages (Controller and Display) - const displayPage = await context.newPage(); - const controllerPage = await context.newPage(); - - // 2. Open Display - await displayPage.goto('http://localhost:3000'); - await expect(displayPage).toHaveTitle(/Segnapunti/); - - // 3. Open Controller - await controllerPage.goto('http://localhost:3001'); - await expect(controllerPage).toHaveTitle(/Controller/); - - // 4. Check initial state (assuming reset) - // Note: This depends on the specific IDs in your HTML. - // You might need to adjust selectors based on your actual HTML structure. - - // Example: waiting for score element - // await expect(displayPage.locator('#score-home')).toHaveText('0'); - - // 5. Action on Controller - // await controllerPage.click('#btn-add-home'); - - // 6. Verify on Display - // await expect(displayPage.locator('#score-home')).toHaveText('1'); -}); diff --git a/tests/e2e/full-match.spec.cjs b/tests/e2e/full-match.spec.cjs new file mode 100644 index 0000000..e6c12ba --- /dev/null +++ b/tests/e2e/full-match.spec.cjs @@ -0,0 +1,131 @@ +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(); + } + 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) +} + +test.describe('Full Match Simulation', () => { + + test('Partita 2/3: Home vince 2 set a 0', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Cambia modalità a 2/3 + await controllerPage.getByText('Config').click(); + await controllerPage.waitForSelector('.dialog-config'); + await controllerPage.locator('.btn-mode').getByText('2/3').click(); + await controllerPage.locator('.dialog-config .btn-confirm').click(); + await controllerPage.waitForTimeout(200); + + // === SET 1: Home vince 25-0 === + await addPoints(controllerPage, 'home', 25); + + // 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); + + // 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.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Cambia modalità a 2/3 + await controllerPage.getByText('Config').click(); + await controllerPage.waitForSelector('.dialog-config'); + await controllerPage.locator('.btn-mode').getByText('2/3').click(); + await controllerPage.locator('.dialog-config .btn-confirm').click(); + await controllerPage.waitForTimeout(200); + + // Imposta set 1-1 manualmente (simula set pareggiati) + await controllerPage.locator('.btn-set.home-bg').click(); + await controllerPage.waitForTimeout(50); + await controllerPage.locator('.btn-set.guest-bg').click(); + await controllerPage.waitForTimeout(100); + + // Verifica set 1-1 + await expect(controllerPage.locator('.team-score.home-bg .team-set')).toContainText('SET 1'); + await expect(controllerPage.locator('.team-score.guest-bg .team-set')).toContainText('SET 1'); + + // === SET DECISIVO: Home porta a 15 === + await addPoints(controllerPage, 'home', 15); + + // 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'); + }); + + test('Set normale: punti oltre 25 fino ai vantaggi', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Porta a 24-24 + await addPoints(controllerPage, 'home', 24); + await addPoints(controllerPage, 'guest', 24); + + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('24'); + await expect(controllerPage.locator('.team-score.guest-bg .team-pts')).toHaveText('24'); + + // Home va a 25 (non è vittoria perché serve scarto di 2) + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(100); + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('25'); + + // Si possono ancora aggiungere punti (non è vittoria a 25-24) + 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 → 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'); + }); +}); diff --git a/tests/e2e/game-operations.spec.cjs b/tests/e2e/game-operations.spec.cjs new file mode 100644 index 0000000..0f9de2f --- /dev/null +++ b/tests/e2e/game-operations.spec.cjs @@ -0,0 +1,182 @@ +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(); + } + await controllerPage.waitForTimeout(300); +} + +test.describe('Game Operations', () => { + + test('Undo: dovrebbe annullare l\'ultimo punto', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Incrementa Home a 1 + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(100); + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('1'); + + // Annulla + await controllerPage.getByText('ANNULLA PUNTO').click(); + await controllerPage.waitForTimeout(100); + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('0'); + }); + + test('Reset: dovrebbe azzerare tutto dopo conferma', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Imposta qualche punto + for (let i = 0; i < 5; i++) { + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(50); + } + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('5'); + + // Reset + await resetGame(controllerPage); + + await expect(controllerPage.locator('.team-score.home-bg .team-pts')).toHaveText('0'); + await expect(controllerPage.locator('.team-score.guest-bg .team-pts')).toHaveText('0'); + }); + + 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.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Apri config + await controllerPage.getByText('Config').click(); + await controllerPage.waitForSelector('.dialog-config'); + + // Modifica nomi + const inputs = controllerPage.locator('.dialog-config .input-field'); + await inputs.first().fill('Padova'); + await inputs.nth(1).fill('Milano'); + + // Salva + await controllerPage.locator('.dialog-config .btn-confirm').click(); + await controllerPage.waitForTimeout(300); + + // Verifica sul Controller + await expect(controllerPage.locator('.team-score.home-bg .team-name')).toHaveText('Padova'); + await expect(controllerPage.locator('.team-score.guest-bg .team-name')).toHaveText('Milano'); + + // Verifica sul Display + await expect(displayPage.locator('.hea.home')).toContainText('Padova'); + await expect(displayPage.locator('.hea.guest')).toContainText('Milano'); + }); + + 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.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Inizialmente mostra punteggio, non formazione + await expect(displayPage.locator('.punteggio-container')).toBeVisible(); + + // Click Formazioni + await controllerPage.getByText('Formazioni').click(); + await controllerPage.waitForTimeout(300); + + // Il display mostra le formazioni + await expect(displayPage.locator('.form').first()).toBeVisible(); + }); + + 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.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Inizialmente la striscia è visibile + await expect(displayPage.locator('.striscia')).toBeVisible(); + + // Toggle off + await controllerPage.getByText('Striscia').click(); + await controllerPage.waitForTimeout(300); + await expect(displayPage.locator('.striscia')).not.toBeVisible(); + + // Toggle on + await controllerPage.getByText('Striscia').click(); + await controllerPage.waitForTimeout(300); + await expect(displayPage.locator('.striscia')).toBeVisible(); + }); + + 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.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Attiva formazione sul display per verificare + await controllerPage.getByText('Formazioni').click(); + await controllerPage.waitForTimeout(200); + + // Apri cambi → scegli Home + await controllerPage.getByRole('button', { name: 'Cambi', exact: true }).click(); + await controllerPage.waitForTimeout(100); + await controllerPage.locator('.dialog .btn-set.home-bg').click(); + await controllerPage.waitForTimeout(100); + + // Inserisci sostituzione: IN=10, OUT=1 + const inField = controllerPage.locator('.cambi-in-field').first(); + const outField = controllerPage.locator('.cambi-out-field').first(); + await inField.fill('10'); + await outField.fill('1'); + + // Conferma + await controllerPage.locator('.dialog .btn-confirm').click(); + await controllerPage.waitForTimeout(300); + + // Verifica formazione aggiornata sul display + const formText = await displayPage.locator('.form.home').textContent(); + expect(formText).toContain('10'); + }); + + test('Cambi: dovrebbe mostrare errore per giocatore non in formazione', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Apri cambi → scegli Home + await controllerPage.getByRole('button', { name: 'Cambi', exact: true }).click(); + await controllerPage.waitForTimeout(100); + await controllerPage.locator('.dialog .btn-set.home-bg').click(); + await controllerPage.waitForTimeout(100); + + // Inserisci sostituzione invalida: OUT=99 (non in formazione) + await controllerPage.locator('.cambi-in-field').first().fill('10'); + await controllerPage.locator('.cambi-out-field').first().fill('99'); + + // Conferma + await controllerPage.locator('.dialog .btn-confirm').click(); + await controllerPage.waitForTimeout(200); + + // Dovrebbe mostrare errore + await expect(controllerPage.locator('.cambi-error')).toBeVisible(); + }); +}); diff --git a/tests/e2e/game-simulation.spec.js b/tests/e2e/game-simulation.spec.cjs similarity index 98% rename from tests/e2e/game-simulation.spec.js rename to tests/e2e/game-simulation.spec.cjs index 1f3fc30..f705a0f 100644 --- a/tests/e2e/game-simulation.spec.js +++ b/tests/e2e/game-simulation.spec.cjs @@ -1,4 +1,4 @@ -import { test, expect } from '@playwright/test'; +const { test, expect } = require('@playwright/test'); test.describe('Game Simulation', () => { test('Simulazione Partita: Controller aggiunge punti finché non cambia il set', async ({ context }) => { diff --git a/tests/e2e/visual-regression.spec.cjs b/tests/e2e/visual-regression.spec.cjs new file mode 100644 index 0000000..2c1443d --- /dev/null +++ b/tests/e2e/visual-regression.spec.cjs @@ -0,0 +1,86 @@ +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(); + } + await controllerPage.waitForTimeout(300); +} + +test.describe('Visual Regression', () => { + + test('Display: screenshot a 0-0', async ({ context }) => { + const controllerPage = await context.newPage(); + const displayPage = await context.newPage(); + + await controllerPage.goto('http://localhost:3001'); + await displayPage.goto('http://localhost:3000'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Reset per stato pulito + await resetGame(controllerPage); + + // Attende che il display riceva lo stato + await displayPage.waitForTimeout(500); + + await expect(displayPage).toHaveScreenshot('display-0-0.png', { + maxDiffPixelRatio: 0.05, + }); + }); + + test('Display: screenshot durante partita (15-12)', async ({ context }) => { + const controllerPage = await context.newPage(); + const displayPage = await context.newPage(); + + await controllerPage.goto('http://localhost:3001'); + await displayPage.goto('http://localhost:3000'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + // Porta il punteggio a 15-12 + for (let i = 0; i < 15; i++) { + await controllerPage.locator('.team-score.home-bg').click(); + await controllerPage.waitForTimeout(20); + } + for (let i = 0; i < 12; i++) { + await controllerPage.locator('.team-score.guest-bg').click(); + await controllerPage.waitForTimeout(20); + } + await displayPage.waitForTimeout(500); + + await expect(displayPage).toHaveScreenshot('display-15-12.png', { + maxDiffPixelRatio: 0.05, + }); + }); + + test('Controller: screenshot stato iniziale', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + await resetGame(controllerPage); + + await expect(controllerPage).toHaveScreenshot('controller-initial.png', { + maxDiffPixelRatio: 0.05, + }); + }); + + test('Controller: screenshot con modal config aperta', async ({ context }) => { + const controllerPage = await context.newPage(); + await controllerPage.goto('http://localhost:3001'); + await controllerPage.waitForSelector('.conn-bar.connected'); + + // Apri config + await controllerPage.getByText('Config').click(); + await controllerPage.waitForSelector('.dialog-config'); + await controllerPage.waitForTimeout(300); + + await expect(controllerPage).toHaveScreenshot('controller-config-modal.png', { + maxDiffPixelRatio: 0.05, + }); + }); +}); diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-Mobile-Chrome-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-Mobile-Chrome-linux.png new file mode 100644 index 0000000..5674919 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-Mobile-Chrome-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-chromium-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-chromium-linux.png new file mode 100644 index 0000000..a5134b9 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-chromium-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-firefox-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-firefox-linux.png new file mode 100644 index 0000000..4a33d2e Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-config-modal-firefox-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-Mobile-Chrome-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-Mobile-Chrome-linux.png new file mode 100644 index 0000000..3da8e0b Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-Mobile-Chrome-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-chromium-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-chromium-linux.png new file mode 100644 index 0000000..a5c1026 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-chromium-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-firefox-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-firefox-linux.png new file mode 100644 index 0000000..30e4a56 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/controller-initial-firefox-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-Mobile-Chrome-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-Mobile-Chrome-linux.png new file mode 100644 index 0000000..3abb907 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-Mobile-Chrome-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-chromium-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-chromium-linux.png new file mode 100644 index 0000000..428c7dd Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-chromium-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-firefox-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-firefox-linux.png new file mode 100644 index 0000000..b5a252a Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-0-0-firefox-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-Mobile-Chrome-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-Mobile-Chrome-linux.png new file mode 100644 index 0000000..857383a Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-Mobile-Chrome-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-chromium-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-chromium-linux.png new file mode 100644 index 0000000..94dd392 Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-chromium-linux.png differ diff --git a/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-firefox-linux.png b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-firefox-linux.png new file mode 100644 index 0000000..fe664ee Binary files /dev/null and b/tests/e2e/visual-regression.spec.cjs-snapshots/display-15-12-firefox-linux.png differ