Playwright: ejecución paralela — workers, tiempos y bottleneck

6 corridas con distintos workers y parallel mode. El default de 2 workers fue el óptimo. Más no siempre es más rápido. Datos reales, no teoría.

Terminal mostrando npx playwright test con reporter list, Running 37 tests using 2 workers resaltado, lista completa de tests en chromium firefox y webkit
"Running 37 tests using 2 workers." Sin configurar nada, Playwright eligió 2 workers y repartió los archivos.

Nota para el lector

Este post documenta cómo funciona la ejecución paralela en Playwright — qué hace por defecto, cómo configurarlo y qué pasa cuando intentás forzar más paralelismo del que tu suite necesita.

Si querés ir al grano:


Contexto:

Con la estructura del post anterior (2 archivos de e2e, 37 tests contando los 3 browsers), la suite corría en ~2.6 minutos sin ninguna configuración de paralelismo.

Playwright ya estaba distribuyendo los archivos entre workers automáticamente. Este post mide qué pasa cuando cambiás esa configuración — y por qué más workers no siempre significa más velocidad.


Qué es ejecución paralela

Ejecutar tests en paralelo es correr varios al mismo tiempo en vez de uno atrás de otro.

Playwright usa workers — procesos independientes que agarran tests y los ejecutan simultáneamente. Cada worker levanta su propio browser context aislado. No comparten estado, no se pisan.

El comportamiento por defecto: Playwright crea tantos workers como la mitad de tus CPUs. En mi máquina (4 cores), usa 2 workers.

Y por defecto, el paralelismo es por archivo: cada archivo .spec.ts va a un worker distinto. Pero dentro de cada archivo, los tests corren en serie — uno termina, arranca el siguiente.

Confirmé esto con npx playwright test --reporter=list:

Terminal mostrando npx playwright test con reporter list, Running 37 tests using 2 workers resaltado, lista completa de tests en chromium firefox y webkit
"Running 37 tests using 2 workers." Sin configurar nada, Playwright eligió 2 workers y repartió los archivos.

"Running 37 tests using 2 workers." Playwright eligió 2 workers automáticamente.


El experimento: 6 corridas, 6 configuraciones

Quise entender qué pasaba si cambiaba la cantidad de workers y activaba el paralelismo dentro de los archivos. Hice 6 corridas con los mismos 37 tests.

Corrida 1: --workers=1 (serie pura)

npx playwright test --workers=1
Reporte HTML de Playwright con workers 1 mostrando Total time 4.7m, Failed 4, Passed 33, tests de clientes y ordenamiento visibles
1 worker: 4.7 minutos. Serie pura, todo en fila.

4.7 minutos. Todos los tests en fila. Un worker hace todo el trabajo solo.

Corrida 2: sin configuración (default, 2 workers)

npx playwright test
Reporte HTML de Playwright con 2 workers default mostrando Total time 2.6m, Failed 3, Passed 34, tests de clientes visibles
2 workers (default): 2.6 minutos. Casi la mitad que en serie.

2.6 minutos. Casi la mitad. Playwright repartió login.spec.ts a un worker y clientes.spec.ts a otro. Corrieron al mismo tiempo.

Corrida 3: --workers=4 (sin parallel mode)

npx playwright test --workers=4
Terminal mostrando npx playwright test workers 4 con resultado 2.9m, error de WOLZA doble espacio en Excel, 3 failed 34 passed
4 workers sin parallel mode: 2.9 minutos. Más lento que con 2. Workers ociosos.

2.9 minutos. Más lento que con 2 workers. ¿Por qué? Playwright solo tiene 2 archivos para repartir. 2 de los 4 workers quedaron ociosos. Y levantar 4 procesos consume más memoria y CPU que levantar 2. Overhead sin beneficio.

Corrida 4: --workers=4 + parallel mode

Agregué test.describe.configure({ mode: 'parallel' }) en ambos archivos. Esto le dice a Playwright: no esperes a que un test termine para arrancar el siguiente dentro del mismo archivo. Repartilos entre workers.

import { test, expect } from '../../fixtures/test-fixtures';

test.describe.configure({ mode: 'parallel' });

// ... resto del archivo
npx playwright test --workers=4
VS Code con login.spec.ts mostrando test.describe.configure mode parallel en línea 3 y terminal con workers 4 resultado 2.6m
Parallel mode activado + 4 workers: 2.6 minutos. Mismo tiempo que el default. El bottleneck no era la distribución.

2.6 minutos. Mismo tiempo que con 2 workers. Los 4 workers tuvieron trabajo esta vez, pero el bottleneck no era la distribución de tests.

Corrida 5: --workers=1 + parallel mode

npx playwright test --workers=1
Terminal mostrando npx playwright test workers 1 con parallel mode activado, resultado 5.1m, 3 failed 34 passed
1 worker con parallel mode: 5.1 minutos. Peor que sin parallel. Overhead sin beneficio.

5.1 minutos. Peor que la corrida 1. El parallel mode agrega overhead de scheduling (Playwright planifica cómo repartir tests individuales), pero con un solo worker no puede paralelizar nada. Costo sin beneficio.

(UPDATE de Post)_Corrida 6: 2 workers (default) + parallel mode

Mirando la tabla de combinaciones de experimentos noté que faltaba una combinación: 2 workers con parallel mode. Instinto de QA — tabla de decisión para cobertura completa de las combinaciones posibles. La corrí.

npx playwright test
VS Code con login.spec.ts mostrando test.describe.configure mode parallel y terminal con 2 workers default resultado 3.8m y 5 failed
2 workers + parallel mode: 3.8 minutos, 5 failed. La peor combinación: más lenta y menos estable que el default.

3.8 minutos. 5 failed. Peor en todo. No solo subió el tiempo respecto al default (2.6m → 3.8m), sino que aparecieron 2 fallos nuevos en Firefox: "Ordenamiento por ID" y "Filtro combinado Argentina + Buenos Aires". Tests que antes pasaban sin problema.

Parallel mode con 2 workers genera más presión simultánea sobre la RAM (8GB) y sobre el servidor remoto (demo.serenity.is). Los tests no comparten browser context, pero los recursos de la máquina y el servidor sí se estresan. Resultado: flakiness.

La tabla completa

WorkersParallel modeTiempoFailed
1no4.7m4
15.1m3
2 (default)no2.6m3
23.8m5
4no2.9m3
42.6m3

El salto real es de 1 a 2 workers. Después de eso, ni más workers ni parallel mode mejoran nada. Y la combinación de 2 workers + parallel mode fue la peor: más lenta y menos estable que el default.


El bottleneck: el test de Excel

¿Por qué 4 workers con parallel mode no baja de 2.6 minutos?

Porque el test de validación contra Excel — el que lee 91 clientes y los compara campo a campo — tarda ~11-14 segundos por browser. Es un solo test pesado que no se puede partir. Mientras ese corre, los demás (que tardan 6-8 segundos) ya terminaron todos.

No importa cuántos workers pongas: el test más lento define el piso. Es como tener 8 cajeros en un supermercado con 2 clientes. Más cajeros no hacen que los clientes compren más rápido.

Esto va a cambiar cuando la suite crezca. Si tuviera 10 archivos de e2e en vez de 2, más workers sí harían diferencia. Pero hoy, con esta suite, 2 workers es el punto óptimo.


Lo que dejé y lo que saqué

Probé test.describe.configure({ mode: 'parallel' }) en ambos archivos. No mejoró tiempos y en una combinación (2 workers) introdujo flakiness real — tests que antes pasaban empezaron a fallar. Lo dejé comentado como referencia:

// test.describe.configure({ mode: 'parallel' });

No lo borré porque quiero que quede documentado: lo probé, medí, no aportó. Si la suite crece y vuelvo a evaluar, ya tengo la línea lista.

El config sigue sin cambios. No toqué fullyParallel ni workers en playwright.config.ts. El default funciona.


Opciones de configuración: el mapa completo

Aunque no las usé todas, vale documentar las opciones que Playwright ofrece.

Workers

Desde la línea de comandos:

npx playwright test --workers=4

Desde el config:

export default defineConfig({
    workers: 4,
    // ...
});

Si no ponés nada, Playwright usa la mitad de tus CPUs.

fullyParallel

export default defineConfig({
    fullyParallel: true,
    // ...
});

Esto es el equivalente global de test.describe.configure({ mode: 'parallel' }). Hace que todos los tests de todos los archivos se repartan individualmente entre workers, no por archivo. Útil si tenés muchos archivos con pocos tests cada uno, o si tus tests son completamente independientes.

Serial mode

test.describe.configure({ mode: 'serial' });

Lo opuesto: fuerza que los tests dentro de un describe corran en orden estricto. Si uno falla, los siguientes se saltan. Útil para flows donde el test B depende de que el test A haya pasado.

No lo necesité porque mis tests ya son independientes — cada uno navega desde cero con beforeEach.

Sharding

npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3

Parte la suite en pedazos y corre cada pedazo por separado. El uso real de esto es en CI/CD: corrés cada shard en una máquina distinta del pipeline. Para una máquina local con 37 tests, no tiene sentido.


Contraste con Selenium + TestNG

En mi serie de Selenium, los 96 tests corren en serie. Nunca configuré ejecución paralela. Si quisiera, tendría que:

  1. Configurar testng.xml:
<suite name="Suite" parallel="methods" thread-count="4">
  1. Resolver thread-safety: dos tests compartiendo el mismo WebDriver explota. Hay que usar ThreadLocal<WebDriver> o algún mecanismo de aislamiento.
  2. Asegurar que los Page Objects sean thread-safe.

En Playwright no tuve que pensar en nada de eso. Cada worker tiene su browser context aislado. No hay estado compartido. No hay race conditions. Funciona desde el día 1.

Es una diferencia de filosofía: en TestNG el paralelismo es un feature que activás y configurás. En Playwright es el comportamiento por defecto.


Estado actual

Con 2 archivos de e2e y 37 tests, el default de Playwright (2 workers, paralelismo por archivo) es el punto óptimo. Más workers o parallel mode no mejoran tiempos hasta que la suite crezca lo suficiente para distribuir carga real entre ellos.

No cambié nada en el config. El test.describe.configure({ mode: 'parallel' }) quedó comentado en ambos archivos como documentación del experimento.

Próximo post: API testing nativo con el objeto request de Playwright — tests de API sin levantar browser.

🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/playwright-typescript-framework