Salta al contenuto principale

Test parallelism Playwright: shard strategies

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 24 November 2027.

Cosa ne pensi di questo articolo?

Test parallelism Playwright: shard strategies
Playwright ComponentsFoto:Ruben BoekelooPexels

In Breve

Articolo della serie Playwright Component Testing Avanzato. Pubblicazione prevista 24 November 2027.

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.

Configurazione per 4 shard su 4 worker
{
  "testDir": "./tests",
  "fullyParallel": true,
  "workers": 4,
  "shard": [1, 4]
}

Nel tuo CI/CD (GitHub Actions, GitLab CI), lancia 4 processi, ognuno con shard diverso:

GitHub Actions - 4 Shard paralleli
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.

Sharding fine-grained: esecuzione
# 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.

playwright.config.ts - 3 Progetti
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).

Hybrid Setup: CI + Local Workers
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

playwright.config.ts - Setup Production-Ready
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:

❌ SBAGLIATO: Stato condiviso tra shard
// 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!)
});
✅ CORRETTO: Stato isolato per shard
// 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:

Analizzare Tempo Esecuzione per Shard
# 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: 8 su worker veloci
  • Ribilanciamento dinamico: usa tool come Buildkite con queue per 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: true per 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ì.

Fonti e approfondimenti

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti