Playwright: Testing E2E Moderno e Affidabile
La Sfida del Testing End-to-End
Il testing end-to-end rappresenta uno dei pilastri fondamentali per garantire la qualità delle applicazioni web moderne. Simulando l’esperienza utente reale e verificando il flusso completo dell’applicazione, i test E2E forniscono un livello di confidenza impossibile da ottenere con test unitari o di integrazione isolati.
Tuttavia, secondo la Test Automation Pyramid, i test E2E occupano il vertice della piramide proprio per le loro caratteristiche problematiche.

Vantaggi
I test E2E offrono benefici significativi:
Confidenza nel Rilascio
- Simulano l’esperienza utente reale attraverso il browser
- Verificano il flusso completo dell’applicazione, dall’interfaccia al database
- Aumentano drasticamente la fiducia nel processo di deployment
Copertura Completa
- Testano l’integrazione di tutti i componenti dell’applicazione
- Rilevano bug che sfuggono ai test unitari (problemi di integrazione, race conditions, timing issues)
- Coprono scenari complessi e realistici che riflettono il comportamento degli utenti
Qualità Percepita
- Garantiscono il corretto funzionamento delle funzionalità critiche per il business
- Migliorano la qualità percepita dall’utente finale
- Riducono significativamente i rischi di regressione in produzione
Le Sfide
Nonostante i vantaggi, i test E2E presentano problematiche ben note che ne hanno limitato l’adozione:
Costi Operativi Elevati
L’implementazione di test E2E richiede investimenti considerevoli:
- Setup complesso: configurazione di ambienti di test completi con tutti i servizi necessari
- Gestione dati: creazione e manutenzione di dataset di test realistici
- Manutenzione alta: i test tendono a rompersi frequentemente con modifiche all’UI
- Infrastruttura dedicata: necessità di risorse per eseguire browser completi
Performance Problematiche
I test E2E sono notoriamente lenti:
- Browser rendering: tempo necessario per il rendering completo delle pagine
- Network calls: latenza delle chiamate HTTP/API
- Attese UI: tempi di caricamento, animazioni, transizioni
- Esecuzione sequenziale: difficoltà nell’eseguire test in parallelo
Fragilità e Flakiness
Il problema più critico dei test E2E tradizionali è la loro inaffidabilità:
// Approccio tradizionale con Selenium
const button = driver.findElement(By.css('.submit-button'));
await button.click(); // Può fallire se l'elemento non è ancora visibile/cliccabile
// Workaround comuni (anti-pattern)
await driver.sleep(3000); // Hard-coded wait - fragile e lento
await driver.wait(until.elementLocated(By.css('.result')), 5000); // Timeout arbitrario
Questi problemi portano a test “flaky” che falliscono in modo non deterministico, causando:
- False positives: test che passano quando l’applicazione è difettosa
- False negatives: test che falliscono quando l’applicazione funziona correttamente
- Perdita di fiducia: i team iniziano a ignorare i risultati dei test
- Debugging costoso: ore perse a investigare fallimenti non riproducibili
Playwright: Un Approccio Moderno
Playwright è un framework open source sviluppato da Microsoft che risolve sistematicamente le problematiche tradizionali del testing E2E attraverso un’architettura moderna e un’eccezionale developer experience.
Adozione e Ecosistema
L’adozione di Playwright è cresciuta esponenzialmente dal suo rilascio nel 2020. Confrontato con alternative consolidate come Selenium e Cypress, Playwright ha rapidamente superato la concorrenza in termini di popolarità GitHub (oltre 70.000 stars) e trend di adozione.

Framework utilizzato da aziende come Microsoft, Airbnb, Uber, e molte altre per garantire la qualità delle loro applicazioni web mission-critical.
Setup Immediato
A differenza di tool tradizionali che richiedono configurazioni complesse, Playwright è operativo in pochi secondi:
# Inizializzazione progetto
npm init playwright@latest
# Esecuzione test
npx playwright test
L’estensione VS Code ufficiale migliora ulteriormente la developer experience, permettendo di eseguire, debuggare e visualizzare i test direttamente dall’editor.
I Tre Pilastri Architetturali
Playwright si basa su tre principi fondamentali che lo differenziano dalle soluzioni tradizionali.
1. Affidabilità: Auto-Waiting e Resilienza
Il problema della fragilità nei test E2E deriva principalmente da race conditions e timing issues. Playwright risolve questo alla radice con il meccanismo di auto-waiting.
Auto-Waiting Intelligente
Ogni azione in Playwright esegue automaticamente una serie di controlli prima di procedere:
import { test, expect } from '@playwright/test';
test('get started link', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Auto-waiting applicato automaticamente
await page.getByRole('link', { name: 'Get started' }).click();
await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});
Prima di eseguire il click, Playwright verifica automaticamente che:
- Il selettore identifichi un solo elemento (univocità)
- L’elemento sia visibile (non
display: noneovisibility: hidden) - L’elemento sia stabile (non in movimento/animazione)
- L’elemento non sia coperto da altri elementi
- L’elemento non sia disabilitato
Questi controlli vengono ripetuti in modo automatico con retry fino al timeout configurato (default 30s), eliminando completamente la necessità di sleep() o wait espliciti.
Web-First Assertions
Le assertion di Playwright sono progettate per il web e includono retry automatico:
// Aspetta fino a 30s che il testo appaia
await expect(page.getByText('Success')).toBeVisible();
// Verifica con retry automatico
await expect(page.locator('.count')).toHaveText('5');
// Aspetta navigazione con assertion sull'URL
await expect(page).toHaveURL(/dashboard/);
Secondo la documentazione ufficiale, questo approccio riduce drasticamente i test flaky, incrementando in modo considerevole l’affidabilità dei test.
2. Velocità: Parallelizzazione e Architettura Efficiente
Esecuzione Parallela Nativa
Playwright è progettato per la parallelizzazione fin dalle fondamenta:
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 2 : 4, // Worker multipli
fullyParallel: true, // Parallelizzazione completa
retries: process.env.CI ? 2 : 0, // Retry automatici in CI
});
Risultati concrete:
- Test suite di 100 test: ~10 minuti con 1 worker → ~2.5 minuti con 4 workers
- Speedup 4x out-of-the-box senza modifiche ai test
- Ogni worker esegue test in contesti completamente isolati
Sharding per CI/CD Distribuita
Per test suite molto grandi, Playwright supporta lo sharding per distribuire i test su più macchine:
# Pipeline CI/CD con 4 job paralleli
npx playwright test --shard=1/4 # Job 1
npx playwright test --shard=2/4 # Job 2
npx playwright test --shard=3/4 # Job 3
npx playwright test --shard=4/4 # Job 4
Comunicazione Efficiente
A differenza di Selenium che usa il protocollo HTTP con overhead significativo, Playwright comunica con i browser tramite WebSocket diretto utilizzando i Chrome DevTools Protocol (per Chromium) e protocolli equivalenti per Firefox e WebKit.
3. Semplicità: Developer Experience Superiore
Multi-Browser con API Unificata
Playwright supporta Chromium, Firefox e WebKit (Safari) attraverso una singola API consistente:
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'iPhone 13', use: { ...devices['iPhone 13'] } },
],
});
I test vengono eseguiti automaticamente su tutti i browser configurati senza modifiche al codice.
Selettori Semantici e Accessibilità
Playwright promuove best practice attraverso selettori basati sull’accessibility tree:
// Selettori semantici (consigliati)
await page.getByRole('button', { name: 'Submit' }); // Role-based
await page.getByLabel('Email'); // Label
await page.getByPlaceholder('Search...'); // Placeholder
await page.getByText('Login'); // Text content
// Fallback per elementi dinamici
await page.getByTestId('submit-btn'); // Test ID
Questi selettori sono:
- Resilienti: non si rompono con refactoring CSS o modifiche alla struttura DOM
- Semantici: leggibili e self-documenting
- Accessibili: seguono le best practice WCAG
- Stabili: minimizzano la manutenzione dei test
Accessibility Tree
Playwright si basa sull’Accessibility Tree del browser, una rappresentazione semantica del DOM:
<!-- HTML -->
<main>
<h1>Login</h1>
<form>
<label for="user">Username:</label>
<input id="user" type="text" placeholder="mario.rossi">
<button aria-label="Submit login form">
<svg>...</svg> Enter
</button>
</form>
</main>
Accessibility Tree:
ROLE: main
├── ROLE: heading, NAME: "Login"
└── ROLE: form
├── ROLE: textbox, NAME: "Username:"
└── ROLE: button, NAME: "Submit login form"
Tooling Avanzato e Developer Experience
Codegen: Generazione Automatica di Test
Il Code Generator di Playwright genera automaticamente test interagendo con l’applicazione:
# Avvia Codegen
npx playwright codegen http://localhost:3000
Il tool:
- Apre un browser controllato
- Registra le interazioni dell’utente
- Genera codice Playwright ottimizzato con i selettori migliori
- Supporta generazione di assertions con un click

Codice generato automaticamente:
await page.goto('http://localhost:3000/');
await page.getByRole('link', { name: 'Products' }).click();
await page.getByPlaceholder('Search...').fill('laptop');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByRole('heading', { name: 'Laptop Pro' })).toBeVisible();

L’integrazione con l’estensione VS Code permette di avviare Codegen direttamente dall’editor e inserire il codice generato nel file di test.
UI Mode: Debugging Interattivo
La UI Mode fornisce un’interfaccia grafica completa per esplorare, eseguire e debuggare i test:
npx playwright test --ui

Funzionalità principali:
- Watch mode: esecuzione automatica dei test al salvataggio del file
- Timeline visuale: visualizzazione cronologica di navigazioni e azioni
- Inspector integrato: snapshot del DOM per ogni azione
- Pick Locator: tool per identificare selettori ottimali con hover
- Network tab: monitoring delle richieste HTTP
- Console logs: output del browser e degli script di test
- Screenshots step-by-step: stato visivo dell’applicazione ad ogni step
Trace Viewer: Post-Mortem Analysis
Il Trace Viewer permette l’analisi dettagliata di test falliti attraverso registrazioni complete dell’esecuzione:
// playwright.config.ts
export default defineConfig({
use: {
trace: 'on-first-retry', // Registra trace sui retry
},
});
Visualizzazione della trace:
npx playwright show-trace trace.zip
La trace include:
- Timeline completa delle azioni
- Screenshots prima/dopo ogni azione
- Network activity
- Console output
- Metadata del test (browser, viewport, durata)
Reporter Avanzati
Playwright supporta multiple opzioni di reporting:
export default defineConfig({
reporter: [
['html'], // HTML report interattivo
['json'], // Output JSON per integrazione
['junit'], // JUnit XML per CI/CD
['allure-playwright'] // Allure reporter
],
});
Implementazione Pratica: Workshop E-Commerce
Per dimostrare l’applicazione pratica di Playwright, consideriamo un’applicazione e-commerce realistica: TechStore.
Architettura dell’Applicazione di Test
TechStore (Demo App)
├── Backend: Express.js
│ ├── REST API (prodotti, carrello, autenticazione, checkout)
│ ├── Session management
│ └── In-memory data storage
├── Frontend: Vanilla HTML/CSS/JS
│ ├── data-testid attributes per selettori stabili
│ ├── Loading states per operazioni async
│ └── Form validation
└── API Documentation: Swagger UI
Avvio dell’applicazione:
cd demo-app
docker-compose up -d # Oppure: npm install && npm start
# Applicazione disponibile su http://localhost:3000
# API docs: http://localhost:3000/api-docs
Credenziali di test:
- Email:
test@example.com - Password:
password123
Test Base: Login Flow
import { test, expect } from '@playwright/test';
test('login con successo', async ({ page }) => {
// Navigazione
await page.goto('/login');
// Interazione con form
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
// Submit
await page.getByRole('button', { name: 'Login' }).click();
// Assertions
await expect(page).toHaveURL(/dashboard/);
await expect(page.getByText('Benvenuto')).toBeVisible();
});
Visual Regression Testing
Playwright include supporto nativo per visual regression testing:
test('homepage layout', async ({ page }) => {
await page.goto('/');
// Primo run: crea screenshot di riferimento
// Run successivi: confronta con riferimento
await expect(page).toHaveScreenshot('homepage.png');
});

Al primo run, Playwright crea lo screenshot di riferimento in tests/__screenshots__/. Nei run successivi, confronta pixel-per-pixel e fallisce se rileva differenze superiori alla soglia configurata.
Ottimizzazione dell’Autenticazione
Per test che richiedono autenticazione, eseguire il login ad ogni test è inefficiente. Playwright supporta il pattern di setup project:
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL(/dashboard/);
// Salva stato sessione
await page.context().storageState({
path: 'playwright/.auth/user.json'
});
});
Configurazione:
// playwright.config.ts
export default defineConfig({
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium-auth',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json' // Riusa sessione
},
dependencies: ['setup'], // Esegui setup prima
},
],
});
Benefici:
- Login eseguito una sola volta per test suite
- Speedup significativo (da ~3s a ~100ms per test)
- Cookies e localStorage condivisi tra test autenticati
Concetti Avanzati
Page Object Model
Il Page Object Model incapsula la logica di interazione con le pagine:
// pages/LoginPage.ts
import { Page } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.page.getByLabel('Email').fill(email);
await this.page.getByLabel('Password').fill(password);
await this.page.getByRole('button', { name: 'Login' }).click();
}
async expectLoginSuccess() {
await expect(this.page).toHaveURL(/dashboard/);
}
}
Utilizzo:
import { LoginPage } from '../pages/LoginPage';
test('login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@test.com', 'password123');
await loginPage.expectLoginSuccess();
});
Custom Fixtures
Le fixtures custom permettono di estendere il framework con setup riutilizzabili:
// fixtures/auth.fixture.ts
import { test as base } from '@playwright/test';
export const test = base.extend({
authenticatedPage: async ({ page }, use) => {
// Setup: esegui login
await page.goto('/login');
await page.getByLabel('Email').fill('test@test.com');
await page.getByLabel('Password').fill('pass123');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL(/dashboard/);
// Fornisci page autenticata al test
await use(page);
// Teardown (opzionale): logout
}
});
Utilizzo:
test('dashboard', async ({ authenticatedPage }) => {
// Page già autenticata!
await expect(authenticatedPage.getByText('Welcome')).toBeVisible();
});
API Testing
Playwright include un client HTTP nativo per API testing:
test('GET /api/products', async ({ request }) => {
const response = await request.get('/api/products');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const products = await response.json();
expect(products).toHaveLength(10);
expect(products[0]).toHaveProperty('name');
expect(products[0]).toHaveProperty('price');
});
test('POST /api/products', async ({ request }) => {
const response = await request.post('/api/products', {
data: { name: 'New Product', price: 99.99 }
});
expect(response.status()).toBe(201);
const product = await response.json();
expect(product.id).toBeDefined();
});
Pattern avanzato: Fixture con API per isolamento completo
Per test paralleli che necessitano isolamento dei dati:
// fixtures/user.fixture.ts
export const test = base.extend({
authenticatedUser: async ({ request }, use) => {
// Crea utente unico via API
const userData = {
email: `user-${Date.now()}@test.com`,
password: 'password123'
};
const createResponse = await request.post('/api/users', { data: userData });
const user = await createResponse.json();
// Autentica via API (più veloce di UI)
const loginResponse = await request.post('/api/auth/login', { data: userData });
// Fornisci utente e sessione al test
await use({ user, sessionCookies: loginResponse.headers()['set-cookie'] });
// Cleanup: elimina utente
await request.delete(`/api/users/${user.id}`);
}
});
Questo pattern permette esecuzione parallela illimitata con isolamento completo tra test.
Mobile Testing
Playwright supporta emulazione di device mobili out-of-the-box:
import { devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'iPhone 13', use: { ...devices['iPhone 13'] } },
{ name: 'Pixel 5', use: { ...devices['Pixel 5'] } },
{ name: 'iPad Pro', use: { ...devices['iPad Pro'] } },
],
});
Test specifici per mobile:
test('mobile menu', async ({ page }) => {
// Touch events
await page.tap('#menu-button');
// Swipe gesture
await page.touchscreen.tap(100, 100);
// Geolocation
await page.context().setGeolocation({ latitude: 48.858455, longitude: 2.294474 });
});
Integrazione CI/CD
Playwright è progettato per ambienti CI/CD con Docker images ufficiali e configurazioni ottimizzate.
GitHub Actions
# .github/workflows/playwright.yml
name: Playwright Tests
on: [push, pull_request]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
GitLab CI
# .gitlab-ci.yml
playwright:
image: mcr.microsoft.com/playwright:v1.40.0-jammy
script:
- npm ci
- npx playwright test
artifacts:
when: always
paths:
- playwright-report/
expire_in: 1 week
Ottimizzazioni per CI
// playwright.config.ts
export default defineConfig({
workers: process.env.CI ? 2 : undefined, // Limita worker in CI
retries: process.env.CI ? 2 : 0, // Retry automatici
use: {
trace: 'on-first-retry', // Trace solo su retry
video: 'retain-on-failure', // Video solo se fallisce
},
});
Best Practices
Principi di Testing
Test Behaviour, Not Implementation
// ❌ Male: testa implementazione
await expect(page.locator('.user-name-class')).toHaveText('John');
// ✅ Bene: testa comportamento
await expect(page.getByRole('heading', { level: 1 })).toHaveText('John');
User-Centric Testing Scrivi test dal punto di vista dell’utente, usando selettori semantici e verificando comportamenti visibili.
Test Critical Paths First Prioritizza test per:
- User authentication
- Checkout/payment flows
- Funzionalità core business
Code Quality
DRY Principle Usa fixtures e helper functions per evitare duplicazione:
// helpers/auth.ts
export async function login(page: Page, credentials: Credentials) {
await page.goto('/login');
await page.getByLabel('Email').fill(credentials.email);
await page.getByLabel('Password').fill(credentials.password);
await page.getByRole('button', { name: 'Login' }).click();
}
Meaningful Assertions
// ❌ Male: assertion generica
await expect(page.locator('.message')).toBeVisible();
// ✅ Bene: assertion specifica
await expect(page.getByText('Order confirmed')).toBeVisible();
Maintenance
Keep Tests Updated
- Refactor test quando cambia l’UI
- Rimuovi test obsoleti
- Aggiorna selettori quando necessario
Monitor Test Health Traccia metriche come:
- Flakiness rate
- Execution time (alert se aumenta significativamente)
- Coverage delle funzionalità critiche
Performance
Test Isolation Ogni test deve essere completamente indipendente:
test.describe.configure({ mode: 'parallel' });
test('test 1', async ({ page }) => {
// Completamente isolato
});
test('test 2', async ({ page }) => {
// Non dipende da test 1
});
Fast Feedback Loop
- Usa
--grepper eseguire subset di test durante sviluppo - Configura watch mode per test specifici
- Esegui full suite solo in CI
Quando Usare Playwright
Scenari Ideali
Playwright eccelle in:
- Nuovi progetti senza legacy di test esistenti
- Web apps moderne (SPA, PWA, applicazioni React/Vue/Angular)
- Test E2E complessi con multiple interazioni utente
- Cross-browser testing critico per il business
- Pipeline CI/CD intensive con necessità di feedback rapido
- Team che prioritizza velocità di sviluppo e manutenzione
Considerazioni
Limitazioni da valutare:
- No supporto IE11 (browser deprecato)
- Solo web mobile tramite emulazione (no app native)
- Learning investment per team abituati a Selenium/Cypress
- Migration cost da framework esistenti
- Verificare requisiti enterprise specifici (es. supporto commerciale)
Risorse e Community
Documentazione Ufficiale
- Playwright Docs - Documentazione completa e aggiornata
- API Reference - Reference completo delle API
- Best Practices - Linee guida ufficiali
Tool Essenziali
- VS Code Extension - Integrazione IDE
- Trace Viewer - Debugging visuale
- Codegen - Generazione automatica test
Community
- Discord - Community attiva e supporto
- GitHub - Repository ufficiale, issues, discussions
- Stack Overflow - Q&A tecnico
Learning Path
- Fondamenti: Getting Started Guide
- Pratica: Workshop hands-on con applicazioni reali
- Avanzato: Page Object Model, fixtures, parallelizzazione
- Produzione: CI/CD integration, monitoring, best practices
Conclusioni
Il testing end-to-end ha tradizionalmente rappresentato un compromesso tra benefici (confidenza, copertura) e costi (fragilità, lentezza, manutenzione). Playwright risolve questo compromesso attraverso innovazioni architetturali fondamentali:
Auto-waiting intelligente elimina il problema della fragilità dei test, riducendo drasticamente i flaky test e la necessità di wait espliciti.
Parallelizzazione nativa e comunicazione efficiente via WebSocket forniscono performance 4x superiori rispetto a esecuzione sequenziale, rendendo praticabile l’esecuzione di suite di test estese.
Developer experience superiore attraverso tooling eccezionale (Codegen, UI Mode, Trace Viewer) e API moderne riduce significativamente i costi di creazione e manutenzione dei test.
La combinazione di affidabilità, velocità e semplicità rende Playwright la scelta ottimale per team che vogliono implementare testing E2E moderno senza i compromessi tradizionali.
Per approfondimenti pratici, il workshop repository include un’applicazione e-commerce completa con esempi di test che dimostrano pattern avanzati, visual testing, ottimizzazioni di autenticazione e integrazione CI/CD.
L’ecosistema Playwright è in rapida evoluzione con release frequenti e community attiva. Per rimanere aggiornati, seguire il blog ufficiale e partecipare alle discussioni su Discord e GitHub.
Testing E2E non deve essere sinonimo di test fragili e lenti. Con Playwright, è possibile ottenere confidenza elevata mantenendo velocità e affidabilità.
Foto di Paul Knight su Unsplash