Cos'è il Test Parallelism in Playwright
Test parallelism è la capacità di eseguire più test contemporaneamente su worker isolati, riducendo drasticamente il tempo totale di esecuzione della suite. Playwright supporta il parallelismo attraverso le shard strategies, permettendoti di distribuire i tuoi 1000+ test su più processi e machine in modo intelligente e prevedibile.
Questo articolo ti guida attraverso le strategie di sharding supportate da Playwright, con configurazioni pratiche, trade-off, e best practice per ottenere il massimo dalle tue suite di test.
Perché il Parallelismo Importa
Senza parallelismo, una suite di 500 test con 100ms ciascuno richiede ~50 secondi sequenziali. Con parallelismo (10 worker), lo stesso tempo scende a ~5 secondi: 10x più veloce.
💡 Benefici del Parallelismo
- Feedback più rapido: sviluppatori ricevono risultati test in secondi, non minuti
- CI/CD efficiente: pipeline completano in tempi brevi, costi infrastructure ridotti
- Scalabilità: aggiungere test non aumenta il tempo di esecuzione linearmente
- Isolamento: ogni worker ha context browser isolato, nessuna contaminazione cross-test
Le Shard Strategies di Playwright
Playwright offre tre strategie di sharding, ognuna con differenti compromessi tra semplicità, distribuzione uniforme e controllo fine. Scopriamole in dettaglio.
1. Shard per File Test (Default)
La strategia file-based sharding distribuisce i file test interi su worker diversi. Ogni file rimane assegnato a un singolo worker, garantendo coesione.
{
"testDir": "./tests",
"fullyParallel": true,
"workers": 4,
"shard": [1, 4]
}
Nel tuo CI/CD (GitHub Actions, GitLab CI), lancia 4 processi, ognuno con shard diverso:
jobs:
test:
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
Vantaggi: Semplice, coesione file mantenuta, perfetto per <10 file test.
Svantaggi: Carico sbilanciato se alcuni file hanno 500 test e altri 5.
2. Shard per Test (Fine-Grained)
Playwright distribuisce il carico a livello di singolo test, non di file. Ogni test viene assegnato indipendentemente al shard in cui eseguirlo, garantendo distribuzione quasi-uniforme.
# Esegui solo i test nel shard 2 di 4
npx playwright test --shard=2/4
Internamente, Playwright raccoglie tutti i test (es. 800 test), assegna ogni test a uno shard usando hash deterministico, e ogni processo esegue solo i test del suo shard.
🔍 Dettagli Tecnici
- Algoritmo:
hash(test.file + test.title) % shardTotal === shardIndex - 1 - Risultato: distribuzione uniforme indipendentemente dalla grandezza file
- Determinismo: stessi test sempre nello stesso shard (ripetibilità build)
3. Shard per Progetto (Multi-Project)
Se la tua suite usa progetti Playwright (es. browser Chrome, Firefox, Safari),
puoi schedulare ogni progetto su worker diversi usando il flag --grep combinato con sharding.
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: 4,
projects: [
{ name: 'chromium', use: { browserName: 'chromium' } },
{ name: 'firefox', use: { browserName: 'firefox' } },
{ name: 'webkit', use: { browserName: 'webkit' } },
],
});
Nel CI, puoi lanciare 3 container: uno per Chrome, uno per Firefox, uno per Safari. Ogni container esegue la suite completa contro browser diverso, in parallelo.
Strategie Ibride: Combinare Sharding e Worker
La vera potenza emerge quando combini sharding orizzontale (tra macchine/container) con parallelismo verticale (worker su singola macchina).
jobs:
test:
strategy:
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
runs-on: ubuntu-latest
steps:
- run: npm ci
- run: npx playwright test \
--shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} \
--workers=2
# Ogni shard container ha 2 worker locali
# Totale: 4 shard × 2 worker = 8 worker paralleli
Configurazione Completa in playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: process.env.CI ? 1 : 4,
timeout: 30 * 1000,
expect: { timeout: 5000 },
retries: process.env.CI ? 2 : 0,
reporter: [
['html'],
['github'],
['junit', { outputFile: 'junit.xml' }],
],
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});
Gestione della Condivisione Stato tra Shard
Quando usi il parallelismo, non puoi condividere stato tra test (database, cache) senza rischiare race condition. Ecco il pattern corretto:
// test.ts
let sharedCounter = 0; // ❌ Ogni shard ha copia diversa
test('test 1', async ({ page }) => {
sharedCounter++;
// Shard 1 legge 1, Shard 2 legge 1 (non 2!)
});
// tests/setup.ts
import { test as base } from '@playwright/test';
type Fixtures = {
shardData: Record;
};
export const test = base.extend({
shardData: async ({ }, use) => {
const shardId = process.env.SHARD_ID || '1';
const data = { shardId, uniqueId: `user_${shardId}_${Date.now()}` };
await use(data);
},
});
// test.ts
import { test } from './setup';
test('test 1', async ({ page, shardData }) => {
console.log(`Shard ${shardData.shardId}, user: ${shardData.uniqueId}`);
// Ogni shard genera dati propri, no conflict
});
Monitoraggio e Troubleshooting
Distribuire il Carico Uniformemente
Se alcuni shard impiegano 2 minuti e altri 30 secondi, il collo di bottiglia è il shard più lento. Analizza i tempi:
# HTML report mostra timing per test
npx playwright show-report
# Alternativa: grep nei log
grep "passed\|failed" trace.log | wc -l
echo "Total test count per shard"
Se lo sbilanciamento è eccessivo, considera:
- Tag test lenti:
@slow, isolali in shard dedicati - Parallelismo interno: aumenta
workers: 8su worker veloci - Ribilanciamento dinamico: usa tool come Buildkite con
queueper assegnare test su richiesta
Best Practice
✅ Do's
- Usa sharding fine-grained per suite >100 test
- Isola database/cache per shard (diverse istanze, diverse credenziali)
- Monitora tempo shard nel CI, ribilancia se outlier >30% media
- Usa
fullyParallel: trueper massimizzare parallelismo - Retry solo in CI con
retries: 2, no flakiness
❌ Don'ts
- Non condividere stato tra shard (database thread-unsafe, cache conflicts)
- Non usare seed globale fisso (
Math.random()→ test impredetti) - Non assumere ordine esecuzione test (parallelismo = ordine non deterministico)
Conclusione
Test parallelism con shard strategies riduce il tempo di feedback da minuti a secondi, riducendo costi CI e migliorando developer experience. Scegli la strategia giusta (file-based per semplicità, fine-grained per carico uniforme, multi-project per cross-browser) e monitora il carico dei shard nel tempo.
🎯 Punti Chiave
- Parallelismo Playwright riduce tempo esecuzione di 10x+ con sharding corretto
- 3 strategie sharding: file-based (semplice), fine-grained (uniforme), multi-project (cross-browser)
- Isola stato per shard, no shared database o cache cross-shard
- Monitora distribuzione carico, ribilancia se shard outlier
Domande frequenti
Qual è la differenza tra sharding file-based e fine-grained?
File-based sharding distribuisce file interi su shard, file-based-fine-grained distribuisce singoli test. Fine-grained garantisce carico più uniforme, file-based è più semplice per suite piccole.
Posso condividere database tra shard?
No. Ogni shard deve avere istanza database isolata o tabelle separate. Condividere database causa race condition e flakiness impredetto.
Come posso monitorare se la distribuzione carico è uniforme?
Usa HTML report (npx playwright show-report) per visualizzare tempo per test. Se uno shard impiega significativamente più tempo, ribilancia tag o aumenta worker su quel shard.
Qual è il numero ideale di shard e worker?
Dipende dalla suite e dal budget CI. Regola empirica: num_shard × workers ≤ 2×num_test. Inizio con 4 shard × 2 worker, monitora tempo totale, scala da lì.

Commenti
Caricamento commenti...
Accedi per commentare