Il vostro frontend funziona in locale. Poi arriva un deploy in produzione: un bottone cambia classe, un popup non si chiude, un utente reale sbatte contro un errore 500. I test manuali li fate quando vi ricordate. Selenium è lento e ballerino. Cypress? bello, ma solo su Chrome e con quel lo stesso tab che si ricarica. Serve altro.
Noi, di Meteora Web, costruiamo applicazioni da anni e abbiamo visto troppe suite di test abbandonate perché fragili, lente o impossibili da mantenere. Playwright — il framework open source di Microsoft — è quello che usiamo oggi per i test end-to-end su progetti reali. E non torniamo indietro.
Questa guida parte dal codice e arriva alla CI, senza giri di parole. Se non avete mai usato Playwright, troverete tutto quello che serve per partire. Se già lo conoscete, troverete strategie che abbiamo messo a punto su clienti con budget e deadline strette.
Perché Playwright supera Selenium e Cypress nei test E2E?
Il problema classico dei test E2E è la fragilità: un elemento non è ancora caricato, un timing sbagliato, un selettore CSS che cambia. Selenium chiede di scrivere espliciti wait e sleep. Cypress ha auto-wait ma gira in un browser solo (Chromium di default) e non supporta contesti multipli.
Playwright risolve alla radice: attese automatiche. Ogni comando aspetta che l'elemento sia visibile, abilitato e stabile prima di interagirci. Zero sleep nel codice. In più:
Sponsored Protocol
- Multi-browser: Chromium, Firefox, WebKit con la stessa API. Test su Safari e Edge senza emulatori.
- Contesti isolati: potete aprire più browser context nella stessa sessione, utili per testare flussi con autenticazione e guest contemporaneamente.
- Network interception: mock di API e risposte direttamente dal test, senza bisogno di un server esterno.
- Varie opzioni di esecuzione: headless, headed, parziale su CI. Con parallelismo nativo.
Noi abbiamo migrato un cliente da un mix di Cypress + Selenium Grid a Playwright: il tempo di esecuzione della suite è passato da 45 minuti a 12. E i falsi positivi sono scesi a zero.
Come scrivere test E2E con Playwright?
Partiamo da un esempio reale: testare il login di un'applicazione Laravel/Vue che usa JWT. Installate Playwright in un progetto Node:
npm init playwright@latest
Questo crea la struttura base con tests/, playwright.config.ts. Configuriamo il file per usare più browser:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: 1,
use: {
baseURL: 'http://localhost:8000',
viewport: { width: 1280, height: 720 },
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
],
});
Ora un test E2E per il login:
Sponsored Protocol
// tests/login.spec.ts
import { test, expect } from '@playwright/test';
test('login with valid credentials', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// Playwright aspetta automaticamente la navigazione dopo il submit
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.locator('.welcome')).toContainText('Benvenuto');
});
test('login with wrong password shows error', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'wrongpass');
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toBeVisible();
});
Notate: nessun await page.waitForTimeout(). Playwright aspetta che il bottone sia cliccabile, che il campo sia visibile, che la nuova URL sia caricata. I test sono deterministici.
Sponsored Protocol
Selettori robusti
Evitate selettori fragili come #app > div:nth-child(2). Usate attributi data-testid nel vostro frontend:
<button data-testid="submit-login">Accedi</button>
await page.click('[data-testid="submit-login"]');
Playwright supporta anche selettori per testo, placeholder, role ARIA. Scegliete quelli che meno cambiano con il design.
Mock di chiamate API
Per testare scenari senza backend reale, intercettate le richieste:
await page.route('**/api/login', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ token: 'fake-jwt', user: { name: 'Test' } }),
});
});
Così il test è veloce e non dipende da ambienti esterni.
Playwright in CI/CD — Integrazione senza intoppi
Il vero valore dei test E2E emerge quando girano su ogni push. Con Playwright l'integrazione è semplice. Ecco un esempio per GitHub Actions:
# .github/workflows/e2e.yml
name: Playwright E2E
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run build && npm run start &
working-directory: ./frontend
- run: npx playwright test
Noi usiamo anche il report HTML generato da Playwright: basta aggiungere --reporter=html e pubblicare la cartella come artifact. I fallimenti diventano visibili con screenshot, trace e video.
Sponsored Protocol
Parallelismo e sharding
Per suite grandi, attivate il parallelismo su più worker. Con fullyParallel: true ogni file gira in parallelo. Se avete molti test, usate lo sharding per distribuire i test su più job CI:
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3
Con tre job paralleli, la suite da 30 minuti passa a 10. E il costo CI non esplode.
Come gestire gli stati e le attese nei test E2E?
Anche con le attese automatiche, ci sono situazioni delicate: popup modali che appaiono dopo un timer, animazioni CSS, chiamate AJAX asincrone. Playwright offre strumenti specifici:
- Expect configurabile:
await expect(locator).toBeVisible({ timeout: 10000 })per aumentare il timeout solo su quel punto. - Wait for event:
await page.waitForResponse('**/api/save')per attendere che una specifica richiesta sia completata. - page.waitForFunction: per condizioni JavaScript custom, ad esempio quando un contatore arriva a zero.
Esempio pratico: attendere la chiusura di un toast dopo un'operazione:
Sponsored Protocol
await page.locator('.toast-success').waitFor({ state: 'hidden' });
Non scrivete mai await page.waitForTimeout(3000). Se un test fallisce per timing, non allungate il timeout: rivedete la logica di attesa. Usate i metodi nativi di Playwright.
Cosa fare adesso
- Installate Playwright in un progetto frontend esistente con
npm init playwright@latest. - Scrivete il vostro primo test per la pagina di login o per un flusso critico (carrello, checkout, form di contatto).
- Aggiungete il test alla CI con una pipeline GitHub Actions o GitLab CI. Iniziate con un browser solo, poi aggiungete i multi-browser.
- Introducete i data-testid nei componenti Vue/React per rendere i selettori immutabili.
- Rimuovete ogni sleep dal codice: sostituitelo con attese basate su eventi o locator.
I test E2E non sono un lusso: sono la rete di sicurezza che trasforma un deploy in un atto di fiducia e non in un terno al lotto. Noi, di Meteora Web, lo vediamo ogni giorno sui progetti che seguiamo. Se avete bisogno di una mano a impostare la strategia di testing, partite dalla nostra guida pillar sul testing e poi scriveteci.