Funciona en tu máquina, falla en CI y vuelve a pasar cuando lo intentas otra vez. Ese patrón parece un problema de velocidad, pero muchas veces es un problema de estado. Una prueba modifica un registro que otra reutiliza, dos ejecuciones escriben en el mismo archivo o un fixture conserva una cuenta que debería ser exclusiva.
Playwright crea un BrowserContext aislado para cada prueba. Eso protege las cookies, el storage y las páginas. No protege automáticamente la base de datos, la cola, la cuenta de prueba, el sistema de archivos ni el estado global que carga tu aplicación.
Este artículo parte de la guía de pruebas end-to-end con Playwright y responde una pregunta más concreta: ¿cómo encuentras y eliminas el estado compartido que CI expone cuando añade workers?
Respuesta corta
- Da a cada prueba un identificador y datos que ninguna otra ejecución pueda reutilizar.
- Usa fixtures con alcance de worker solo para recursos que se puedan compartir sin riesgo.
- Escribe informes y archivos en rutas propias de la prueba.
- Usa
workers: 1y los retries para probar una hipótesis, no para ocultar la causa.
¿Por qué una prueba pasa localmente y falla en CI?
En 2026, la documentación de paralelismo de Playwright explica que los archivos de prueba se ejecutan en paralelo por defecto y que la inestabilidad aparece cuando el estado vive fuera de una prueba. La diferencia entre tu portátil y CI suele ser el número de workers, el orden de ejecución y la presión sobre servicios compartidos.
El navegador puede estar completamente aislado mientras dos peticiones crean el mismo pedido en el backend. Una suite local con un worker ejecuta esas peticiones en secuencia. CI ejecuta los archivos al mismo tiempo y convierte una dependencia invisible en una carrera observable.
Hay cuatro fronteras que conviene separar durante la investigación:
| Frontera | Qué aísla Playwright | Qué todavía debes aislar |
|---|---|---|
| Navegador | cookies, storage y páginas de BrowserContext |
nada, salvo que crees contextos manualmente |
| Backend | nada por defecto | el usuario, pedido, documento o clave que usa la prueba |
| Worker | el proceso y los fixtures con alcance propio | una cuenta o servicio reutilizado por varios archivos |
| Salida | el directorio de artefactos del runner | los nombres de archivo que escribe tu código |
El diagnóstico más útil no empieza con "¿qué timeout debo aumentar?". Empieza con "¿qué estado existe fuera de esta prueba y quién más puede tocarlo?". Esa pregunta reduce la investigación antes de cambiar la configuración.
¿Qué aísla Playwright desde el principio?
En 2026, la página de aislamiento por BrowserContext describe cada contexto como un perfil limpio con local storage, session storage y cookies propios. Esa garantía cubre la sesión del navegador. No convierte un entorno de staging compartido en un entorno desechable.
Una page nueva no borra una fila creada por otra prueba. Tampoco cambia el usuario fijo que comparte todo el equipo, limpia un mensaje de una cola ni impide que dos workers escriban en exports/result.csv. La palabra "aislado" siempre debe indicar qué capa está aislada.
Este es un ejemplo mínimo. Es ilustrativo y usa una función de preparación que pertenece al proyecto real:
import { test, expect } from "@playwright/test";
test("edita solo el pedido creado por esta prueba", async ({ page }, testInfo) => {
const orderId = `order-${testInfo.testId}`;
await seedOrder({ id: orderId, status: "draft" });
await page.goto(`/orders/${orderId}/edit`);
await page.getByLabel("Status").selectOption("approved");
await page.getByRole("button", { name: "Save" }).click();
await expect(page.getByRole("status")).toHaveText("Order saved");
await expect(page.getByTestId("order-status")).toHaveText("approved");
});
El valor no está en el prefijo order-. Está en derivar los datos de testInfo.testId, ejecutar el escenario con esos datos y comprobar una consecuencia que pertenece al mismo pedido. La guía de paralelismo de Playwright usa este tipo de identificador para evitar que las pruebas que editan registros compitan entre sí.
¿Cómo crear datos únicos para cada prueba?
En 2026, las buenas prácticas de Playwright recomiendan controlar los propios datos cuando las pruebas usan una base de datos. La forma más rápida de aplicar la regla suele ser preparar el escenario mediante una API o una función de seed antes de abrir la página. Crear la precondición haciendo clic en la interfaz añade tiempo y mezcla la preparación con la verificación.
Empieza por enumerar los recursos que toca el caso: cuenta, pedido, cliente, archivo, cola y feature flag. Para cada recurso, elige una de estas estrategias:
- Genera un identificador a partir de
testInfo.testIdcuando el recurso pertenece a una sola prueba. - Genera un identificador a partir de
workerInfo.workerIndexcuando un fixture se puede compartir sin riesgo entre las pruebas de ese worker. - Usa un entorno desechable cuando el recurso no se pueda particionar.
- Usa un mock para una dependencia externa que no sea el objeto de la prueba, como se explica en cómo mockear APIs en Playwright sin falsos positivos.
No hagas que los datos sean únicos solo en el nombre. Confirma que la aplicación filtra realmente por ese identificador y que la preparación falla cuando el recurso no se puede crear. Una prueba que genera order-abc pero sigue leyendo "el último pedido" todavía depende de estado compartido.
¿Cuándo es seguro usar un fixture por worker?
En 2026, la documentación de fixtures de Playwright define los fixtures con alcance de worker como recursos creados una vez para ese proceso. Sirven para un servidor local o una cuenta propia del worker. Son arriesgados para una cuenta global que reutilizan todos los workers.
Un fixture de worker debe responder tres preguntas: qué índice identifica al worker, qué pruebas pueden usar el recurso al mismo tiempo y cómo se eliminará el recurso cuando termine el worker. Si una prueba cambia lo que otra espera, el recurso no se puede compartir, aunque sea caro de crear.
import { test as base } from "@playwright/test";
type Account = { id: string; username: string };
export const test = base.extend<{}, { account: Account }>({
account: [async ({}, use, workerInfo) => {
const username = `ci-user-${workerInfo.workerIndex}`;
const account = await createAccount({ username });
await use(account);
await deleteAccount(account.id);
}, { scope: "worker" }],
});
El código es ilustrativo: createAccount y deleteAccount deben existir en tu entorno de pruebas. La frontera es lo importante. Cada worker recibe su propia cuenta, mientras que cada prueba todavía debe evitar cambiar datos que otra prueba del mismo worker necesite leer. Cuando eso no sea posible, reduce el alcance a la prueba.
No conviertas beforeAll en un almacén de estado mutable. Preparar una configuración inmutable una vez puede ser correcto. Crear un pedido, cambiar su estado y esperar que todas las pruebas lo encuentren igual es una dependencia del orden.
¿Cómo evitar conflictos en archivos y artefactos?
En 2026, la guía de paralelismo de Playwright recomienda testInfo.outputPath() para obtener rutas únicas por prueba. La misma regla se aplica a exportaciones, descargas, snapshots auxiliares y archivos temporales que la aplicación crea durante el caso.
import { test, expect } from "@playwright/test";
import fs from "node:fs/promises";
test("exporta el informe de este pedido", async ({ page }, testInfo) => {
const output = testInfo.outputPath("report.csv");
await page.getByRole("button", { name: "Export" }).click();
await expect(page.getByRole("status")).toHaveText("Export ready");
await fs.writeFile(output, "order_id,status\nabc,approved\n", "utf8");
});
El ejemplo escribe el archivo directamente para que la ruta quede visible. En una prueba real, puedes usar el directorio de descargas del contexto, una función de exportación de la aplicación o el artefacto producido por CI. La regla no cambia: dos pruebas no deben poder sobrescribir la misma ruta y después discutir qué contenido encontraron.
Revisa también las variables globales del código de prueba. Un array de resultados en el alcance del módulo, un cliente singleton con una caché mutable o un token guardado en un archivo puede cruzar fronteras aunque las páginas parezcan independientes.
¿Por qué workers: 1 y los retries no son la solución?
En 2026, Playwright documenta el modo serial, pero recomienda pruebas aisladas en vez de una suite que dependa del orden. Configurar workers: 1 puede confirmar que el fallo nace de la concurrencia. No demuestra que la prueba sea correcta, porque CI volverá a mostrar el defecto cuando regrese el paralelismo.
Lo mismo ocurre con los retries. La guía de retries clasifica como flaky la prueba que falla en el primer intento y pasa después. Repetir es una forma de recoger una señal. Es una mala forma de declarar que el comportamiento es confiable.
Usa esta secuencia de diagnóstico:
- Ejecuta el caso solo en el mismo commit y con los mismos datos.
- Ejecútalo varias veces con
--workers=1. - Ejecuta el mismo conjunto con dos o más workers.
- Cambia solo la estrategia de aislamiento de datos y repite la ejecución.
- Compara el primer intento, no solo el resultado después del retry.
Si el fallo desaparece con un worker, busca estado externo antes de aumentar timeouts. Si también aparece de forma aislada, investiga esperas, locators, entorno, red o una expectativa equivocada. La guía de aserciones recomienda matchers web-first que esperan la condición esperada. page.waitForTimeout() aparece documentado como inadecuado para estabilizar pruebas de producción.
¿Cómo demostrar la causa de un fallo inestable?
En 2026, Playwright recomienda conservar artefactos de ejecución y usar aserciones que esperen estados observables. La evidencia de una corrección debe mostrar qué cambió en la frontera de estado y qué ejecución habría fallado antes. Un resultado verde nuevo, por sí solo, no explica la causa.
Registra al menos:
- el commit, archivo y nombre completo de la prueba;
- el worker y el intento que ejecutaron el caso;
- los identificadores de los datos creados, sin credenciales;
- trace, consola, red y captura cuando el caso falle;
- la diferencia entre el primer intento y el retry;
- el comando usado para repetir con un worker y con paralelismo.
Si la ejecución depende de un servicio externo, no uses la estabilidad del servicio como prueba de tu aplicación. Las buenas prácticas de Playwright recomiendan probar lo que controlas y garantizar respuestas de terceros cuando no son el objeto de la prueba.
Una corrección pertenece a la suite cuando cambia una frontera que puedes nombrar. "Aumentamos el timeout y pasó" describe un resultado. "Cada prueba crea ahora su propio pedido y ya no necesita el primer retry" describe una hipótesis comprobable. Solo la segunda frase ayuda a mantener confiable la suite.
Lista para revisar pruebas Playwright en CI
Antes de ejecutar una prueba en paralelo, pregunta:
- ¿La prueba crea o identifica los datos que necesita, o lee un registro que dejó otro caso?
- ¿El usuario, token, cuenta y feature flag pertenecen a la prueba o al worker?
- ¿La prueba puede cambiar un estado que otra prueba lee?
- ¿El mock de API cubre una dependencia externa o esconde el contrato que deberías verificar?
- ¿Cada archivo, descarga, snapshot o exportación tiene su propia ruta?
- ¿Hay un singleton o una variable de módulo mutable?
- ¿
workers: 1es un diagnóstico temporal o un parche permanente? - ¿Un retry marca la inestabilidad cuando falla el primer intento?
- ¿El informe conserva evidencia suficiente para separar un bug, una prueba, los datos y el entorno?
- ¿La prueba espera una condición observable o duerme durante un tiempo fijo?
Si dos respuestas apuntan al mismo recurso compartido, corrige esa frontera antes de reescribir el locator. Si todas las dependencias están aisladas y el fallo continúa, investiga sincronización, infraestructura o una regla del producto.
Preguntas frecuentes
¿BrowserContext no aísla ya todas las pruebas?
Aísla cookies, storage, páginas y otros datos del navegador. No aísla automáticamente un pedido del backend, una cuenta fija, una cola, un archivo o un singleton de tu código. La prueba debe crear o particionar cada recurso externo que otra ejecución pueda leer o modificar.
¿Puedo dejar todas las pruebas con workers: 1?
Puede ser una decisión temporal mientras el equipo encuentra una carrera. Playwright recomienda pruebas independientes, no una suite que dependa de la ejecución serial. Si un worker elimina el fallo, trátalo como evidencia de una dependencia de estado u orden y continúa la investigación.
¿Un fixture por worker es mejor que uno por prueba?
No por defecto. El alcance de worker reduce el costo de preparación cuando el recurso es caro y se puede compartir sin mutación. El alcance de prueba es más seguro para datos que cambian durante el escenario. Elige según el aislamiento necesario, no solo por el tiempo de preparación.
¿Debo aumentar los retries cuando una prueba falla en CI?
Usa retries para conservar evidencia e identificar inestabilidad, no para convertir silenciosamente un fallo en aprobación. Una prueba que solo pasa después de un retry sigue siendo inestable. Revisa datos, cuentas, archivos, llamadas externas, esperas y concurrencia. Después decide si un retry limitado tiene valor operativo.
Conclusión
Las pruebas Playwright en CI son más confiables cuando cada capa tiene un responsable claro. BrowserContext se ocupa de la sesión del navegador. Tu código debe ocuparse de los registros, cuentas, archivos, colas y efectos globales que quedan fuera.
Empieza por el caso que falla y escribe qué datos necesita realmente. Haz que esos datos sean únicos, usa fixtures por worker solo para recursos seguros, prefiere aserciones que esperen condiciones reales y conserva el primer intento. El paralelismo y los retries son herramientas de diagnóstico. La corrección consiste en eliminar la dependencia invisible.
Fuentes consultadas
- Playwright, "Parallelism", consultado en 2026-08-18
- Playwright, "Isolation", consultado en 2026-08-18
- Playwright, "Fixtures", consultado en 2026-08-18
- Playwright, "Best Practices", consultado en 2026-08-18
- Playwright, "Assertions", consultado en 2026-08-18
- Playwright, "Retries", consultado en 2026-08-18
- Playwright, "Page API: waitForTimeout", consultado en 2026-08-18