Los binarios del navegador de Playwright dependen de unas dos docenas de bibliotecas del sistema, y las diferencias entre las versiones de esas bibliotecas en tu laptop y en CI causan fallos que no tienen nada que ver con tus tests. La imagen Docker oficial de Playwright en mcr.microsoft.com/playwright empaqueta los tres navegadores y sus dependencias en una sola imagen que corre de forma idéntica en cualquier lugar, siempre que el tag de la imagen coincida con la versión de @playwright/test en tu package.json. Esta guía cubre cómo escribir un Dockerfile, cómo montar los reportes de tests de vuelta al host, Docker Compose para testear contra un stack de aplicación local, y cómo integrar el contenedor en GitHub Actions.
Por qué Docker importa para QA
Los suites de tests de navegador son inusualmente sensibles a su entorno. Playwright instala binarios de Chromium, Firefox y WebKit que dependen de bibliotecas específicas del sistema: libglib, libnss, libatk, y alrededor de dos docenas más. En tu laptop probablemente están en la versión correcta porque instalaste Playwright recientemente. En la computadora de un compañero del 2022, puede que no. En un runner de CI configurado hace seis meses con una imagen de Ubuntu diferente, la incompatibilidad se convierte en un fallo de build a las 2am.
La solución tradicional es una página de wiki llamada "Configuración del entorno de desarrollo" que queda desactualizada en el momento en que alguien la escribe. La solución de Docker es un archivo en tu repositorio llamado Dockerfile que describe exactamente el entorno que necesitan tus tests. Cualquiera que ejecute docker build obtiene el mismo entorno, sin importar qué sistema operativo use o qué instaló la semana pasada.
Para QA específicamente, Docker trae tres beneficios concretos. Primero, paridad de entorno: los tests corren dentro del mismo contenedor localmente y en CI, así que un test que pasa localmente significa algo. Segundo, sin overhead de configuración: un nuevo integrante del equipo puede ejecutar todo el suite de tests con dos comandos sin instalar Node, Playwright ni ninguna dependencia de navegador. Tercero, aislamiento: las ejecuciones de tests no interfieren entre sí ni con lo demás que esté instalado en la máquina host.
Conceptos básicos de Docker para testers
Si nunca trabajaste con Docker, el modelo mental es directo. Una imagen es una instantánea de un sistema de archivos: un sistema operativo, paquetes instalados, tu código y variables de entorno, todo congelado en un punto en el tiempo. Un contenedor es una instancia en ejecución de una imagen: piensa en la imagen como una clase y en el contenedor como un objeto.
Construyes una imagen a partir de un Dockerfile, un archivo de texto que lista los pasos para construirla. Ejecutas un contenedor desde una imagen con docker run. Cuando el contenedor termina, se detiene. Nada de lo que hizo cambia la imagen.
# Descargar una imagen existente de un registro
docker pull node:20-slim
# Ejecutar un contenedor desde esa imagen y ejecutar un comando
docker run node:20-slim node --version
# Construir una imagen desde un Dockerfile en el directorio actual
docker build -t my-playwright-tests .
# Ejecutar un contenedor desde la imagen que acabás de construir
docker run my-playwright-testsEl concepto clave para los flujos de trabajo de testing es que los contenedores son efímeros por defecto. Los archivos creados dentro de un contenedor desaparecen cuando se detiene, incluidos los reportes de tests. Esto se resuelve con volúmenes montados, que cubrimos en la sección sobre ejecución de tests.
Las imágenes se componen de capas. Cada instrucción en un Dockerfile crea una nueva capa, y Docker cachea las capas que no cambiaron. Esto es lo que hace que las reconstrucciones sean rápidas: si cambias el código de los tests pero no las dependencias, Docker reutiliza la capa cacheada que instaló esas dependencias y solo reconstruye las capas que cambiaron.
La imagen Docker oficial de Playwright
El equipo de Playwright publica y mantiene una imagen Docker oficial en mcr.microsoft.com/playwright. Esta imagen viene con todo lo que los tests de navegador necesitan: Node.js, el paquete de Playwright y los tres binarios de navegador (Chromium, Firefox, WebKit) con sus dependencias de bibliotecas del sistema preinstaladas.
# Descargar la imagen que coincide con tu versión de Playwright
docker pull mcr.microsoft.com/playwright:v1.52.0-jammy
# Ver qué tiene adentro
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy node --version
docker run --rm mcr.microsoft.com/playwright:v1.52.0-jammy npx playwright --versionEl formato del tag es v{versión-playwright}-{nombre-ubuntu}. jammy es Ubuntu 22.04, la opción más estable y ampliamente usada. También existe una variante noble para Ubuntu 24.04.
La imagen es grande (alrededor de 1.8GB) porque contiene tres instalaciones completas de navegador. Ese tamaño es el precio de tener todo preinstalado sin necesidad de ejecutar playwright install --with-deps al iniciar el contenedor. En entornos de CI donde descargás la imagen repetidamente, vale la pena porque el caché de capas de Docker significa que solo la descargás una vez por runner, a menos que cambie el tag.
La regla crítica: el tag de la imagen debe coincidir con tu versión de @playwright/test en package.json. Si usas v1.52.0-jammy pero tu proyecto tiene "@playwright/test": "1.50.0", las incompatibilidades de API causarán fallos confusos. Fijá los dos.
Escribir un Dockerfile para tu proyecto de Playwright
Empieza con la imagen oficial de Playwright como base. Acá hay un Dockerfile completo y listo para producción:
# Usar la imagen oficial de Playwright: fijar la versión para que coincida con package.json
FROM mcr.microsoft.com/playwright:v1.52.0-jammy
# Establecer el directorio de trabajo dentro del contenedor
WORKDIR /app
# Copiar los archivos de paquetes primero para mejor caché de capas
# Esta capa se reconstruye solo cuando cambian las dependencias
COPY package.json package-lock.json ./
# Instalar dependencias del proyecto (los navegadores ya están en la imagen base)
RUN npm ci
# Copiar el resto del proyecto
COPY . .
# Comando por defecto: ejecutar todos los tests
CMD ["npx", "playwright", "test"]El orden de las instrucciones COPY y RUN importa para el caché. Al copiar package.json y package-lock.json primero y ejecutar npm ci antes de copiar el resto del código fuente, se garantiza que la capa de instalación de dependencias se cachee y reutilice cuando cambien los archivos de tests. Si copiás todo primero y después ejecutás npm ci, cualquier cambio en cualquier archivo invalida el caché de dependencias.
Notá que no hay un paso npx playwright install. La imagen base ya tiene los navegadores. Esto hace el build significativamente más rápido que una configuración que empieza desde una imagen de Node simple.
Agregá un archivo .dockerignore junto a tu Dockerfile para excluir archivos que no pertenecen a la imagen:
# .dockerignore
node_modules
playwright-report
test-results
.git
.github
*.mdExcluir node_modules es especialmente importante. Sin esto, Docker copiaría los node_modules locales dentro de la imagen antes de ejecutar npm ci, lo que desperdicia tiempo y puede introducir binarios específicos de plataforma que no funcionan dentro del contenedor Linux.
Construir la imagen:
docker build -t playwright-tests:local .El flag -t etiqueta la imagen con un nombre. playwright-tests:local es una convención para distinguir imágenes construidas localmente de imágenes de registro.
Ejecutar tests en un contenedor
Una vez construida la imagen, ejecutar tests es un solo comando:
docker run --rm playwright-tests:local--rm elimina automáticamente el contenedor cuando termina. Sin él, los contenedores detenidos se acumulan y consumen espacio en disco.
El problema es que los reportes de tests se escriben dentro del contenedor y desaparecen cuando se detiene. Corregilo con un volumen montado usando -v:
docker run --rm \
-v $(pwd)/playwright-report:/app/playwright-report \
-v $(pwd)/test-results:/app/test-results \
playwright-tests:localEl flag -v mapea un directorio del host a un directorio del contenedor. Cuando Playwright escribe el reporte HTML en /app/playwright-report dentro del contenedor, en realidad escribe en $(pwd)/playwright-report en tu máquina host. Después de que el contenedor se detiene, el reporte está ahí, listo para abrir.
En Windows, reemplaza $(pwd) con %cd% en el Símbolo del sistema o ${PWD} en PowerShell:
docker run --rm `
-v ${PWD}/playwright-report:/app/playwright-report `
-v ${PWD}/test-results:/app/test-results `
playwright-tests:localPara ejecutar un subconjunto de tests, sobreescribe el CMD por defecto agregando argumentos:
# Ejecutar solo tests con un tag específico
docker run --rm playwright-tests:local npx playwright test --grep @smoke
# Ejecutar solo Chromium
docker run --rm playwright-tests:local npx playwright test --project=chromium
# Ejecutar un archivo de test específico
docker run --rm playwright-tests:local npx playwright test tests/login.spec.tsPasa variables de entorno con -e:
docker run --rm \
-e BASE_URL=https://staging.example.com \
-e TEST_USER=testuser \
-e TEST_PASSWORD=secret \
-v $(pwd)/playwright-report:/app/playwright-report \
playwright-tests:localpackage.json para el comando completo docker run con los flags de volumen, para que el equipo no tenga que recordarlos. npm run test:docker es más fácil de documentar y más difícil de tipear mal que el comando completo.docker-compose para ejecuciones locales de tests
Cuando los tests corren contra una aplicación, necesitás tanto la app como el contenedor de tests corriendo al mismo tiempo con acceso de red entre ellos. docker-compose gestiona configuraciones de múltiples contenedores con un solo archivo.
Acá hay un docker-compose.yml para una configuración típica donde los tests de Playwright corren contra una aplicación web local:
# docker-compose.yml
version: '3.8'
services:
# Tu aplicación bajo prueba
app:
image: your-app:latest
ports:
- '3000:3000'
environment:
NODE_ENV: test
DATABASE_URL: postgres://user:pass@db:5432/testdb
depends_on:
db:
condition: service_healthy
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 5s
timeout: 5s
retries: 10
# Base de datos (si la app la necesita)
db:
image: postgres:16
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: testdb
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U user -d testdb']
interval: 3s
timeout: 3s
retries: 15
# Contenedor de tests de Playwright
tests:
build:
context: .
dockerfile: Dockerfile
environment:
BASE_URL: http://app:3000
depends_on:
app:
condition: service_healthy
volumes:
- ./playwright-report:/app/playwright-report
- ./test-results:/app/test-results
# No iniciar los tests automáticamente: ejecutar bajo demanda
profiles:
- testingEl profiles: [testing] en el servicio de tests significa que no se inicia cuando ejecutas docker-compose up solo (que iniciaría la app y la base de datos). Para ejecutar los tests:
# Iniciar la app y la base de datos
docker-compose up -d app db
# Ejecutar los tests
docker-compose run --rm tests
# O iniciar todo y ejecutar los tests en un solo comando
docker-compose --profile testing run --rm tests
# Bajar todo
docker-compose downLos servicios se comunican usando sus nombres de servicio como hostnames. El contenedor de tests configura BASE_URL: http://app:3000, y el DNS interno de Docker resuelve app a la IP del contenedor de la aplicación. Tu playwright.config.ts lee process.env.BASE_URL, así que localmente apunta a localhost:3000 y dentro del contenedor apunta a http://app:3000.
El depends_on con condition: service_healthy es importante. Sin él, Playwright arrancaría antes de que la app esté lista para aceptar conexiones y todos los tests fallarían con un error de conexión. El healthcheck consulta hasta que la app responde, luego Docker inicia el contenedor dependiente.
Integrar Docker en GitHub Actions
GitHub Actions puede usar un contenedor Docker como entorno de ejecución para un job completo, o podés ejecutar docker build y docker run como pasos. Para Playwright específicamente, usar el contenedor directamente como entorno del job es el enfoque más limpio.
# .github/workflows/playwright.yml
name: Playwright Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
# Usar la imagen de Playwright como contenedor del job
container:
image: mcr.microsoft.com/playwright:v1.52.0-jammy
options: --user 1001
steps:
- name: Checkout del código
uses: actions/checkout@v4
- name: Instalar dependencias
run: npm ci
- name: Ejecutar tests de Playwright
run: npx playwright test
env:
BASE_URL: ${{ vars.BASE_URL }}
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
- name: Subir reporte de tests
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 14El bloque container: a nivel del job le dice a GitHub Actions que ejecute cada paso dentro del contenedor Docker especificado en lugar de directamente sobre el runner de Ubuntu. Los navegadores ya están instalados en la imagen, así que no hace falta el paso npx playwright install.
--user 1001 establece el usuario del contenedor para que coincida con el UID del runner de GitHub Actions. Sin esto, pueden ocurrir errores de permisos de archivos cuando Actions intenta escribir artefactos.
Para equipos que también quieren construir y subir su propia imagen de tests a un registro:
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout del código
uses: actions/checkout@v4
- name: Construir imagen de tests
run: docker build -t playwright-tests:${{ github.sha }} .
- name: Ejecutar tests
run: |
docker run --rm \
-e BASE_URL=${{ vars.BASE_URL }} \
-e TEST_USER=${{ secrets.TEST_USER }} \
-e TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} \
-v ${{ github.workspace }}/playwright-report:/app/playwright-report \
playwright-tests:${{ github.sha }}
- name: Subir reporte de tests
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 14Usar ${{ github.sha }} como tag de imagen vincula la imagen al commit exacto, lo que facilita correlacionar ejecuciones de tests con versiones de código si estás subiendo a un registro.
Problemas comunes y cómo resolverlos
Los contenedores Docker introducen un pequeño conjunto de problemas que aparecen regularmente al ejecutar Playwright.
Errores de permisos de archivos en los reportes. Cuando Playwright escribe en un directorio con volumen montado, los archivos son propiedad de cualquier usuario que corra el contenedor (a menudoroot). Tu usuario del host no puede modificarlos ni eliminarlos sin sudo. La corrección es establecer el usuario del contenedor en tiempo de ejecución:
docker run --rm \
--user $(id -u):$(id -g) \
-v $(pwd)/playwright-report:/app/playwright-report \
playwright-tests:localEsto ejecuta el contenedor como tu usuario actual del host, así que los archivos del reporte son tuyos. En CI, usa --user 1001 como se muestra en el workflow anterior.
# Lento: cualquier cambio de archivo invalida el caché de npm ci
COPY . .
RUN npm ci
# Rápido: solo los cambios en los archivos de paquetes invalidan el caché de npm ci
COPY package.json package-lock.json ./
RUN npm ci
COPY . .En CI, el caché de capas de Docker requiere configuración explícita. Agregá esto al workflow:
- name: Configurar Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Construir imagen de tests
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: playwright-tests:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxLas líneas cache-from: type=gha y cache-to: type=gha,mode=max usan el almacenamiento de caché de GitHub Actions para el caché de capas de Docker. Sin esto, cada ejecución de CI construye la imagen desde cero.
localhost se refiere al propio contenedor, no a tu máquina host. Si la app corre en tu host en localhost:3000, el contenedor no puede alcanzarla en esa dirección. Usá la IP del bridge de Docker de tu host (172.17.0.1 en Linux, host.docker.internal en Mac y Windows):
docker run --rm \
-e BASE_URL=http://host.docker.internal:3000 \
-v $(pwd)/playwright-report:/app/playwright-report \
playwright-tests:localAl usar docker-compose, este problema desaparece porque todos los servicios comparten una red y se comunican por nombre de servicio.
FAQ
¿Necesito Docker si ya uso GitHub Actions?No necesariamente. Los runners de GitHub Actions funcionan bien para Playwright sin Docker: instalás Node, ejecutás playwright install --with-deps y listo. Docker se vuelve valioso cuando querés el mismo entorno localmente y en CI, cuando los tests corren contra un stack de múltiples servicios (app + base de datos), o cuando incorporás nuevos integrantes del equipo que necesitan una forma de ejecutar los tests sin configuración.
Empieza con la imagen oficial como base de tu FROM. Obtenés actualizaciones gratuitas a versiones del navegador cuando actualizás el tag, y la imagen la mantienen personas que conocen las dependencias de Playwright. Construí sobre ella solo cuando necesitás adiciones específicas del proyecto, como fuentes personalizadas, una versión específica de Node, o datos de test precargados.
Monta el directorio test-results y habilita la grabación de video en tu config de Playwright para tests fallidos. Después de la ejecución, la carpeta test-results/ en tu host contendrá videos y traces. Abre el trace con npx playwright show-trace test-results/.../trace.zip para ver exactamente qué ocurrió.
Sí. La configuración workers de Playwright controla el paralelismo y funciona igual dentro de un contenedor. El límite práctico es la cantidad de CPUs del contenedor. Para suites grandes, ejecutá múltiples contenedores en paralelo (mediante sharding con matrix de GitHub Actions) en lugar de maximizar los workers dentro de un solo contenedor. Es más fácil de entender y de escalar.
Esto generalmente significa que hay una incompatibilidad de versión entre el tag de la imagen y tu versión de @playwright/test. Verificá que mcr.microsoft.com/playwright:v1.52.0-jammy coincida con "@playwright/test": "1.52.0" en package.json. Si las versiones no coinciden, Playwright busca los navegadores en una ruta que no existe en la imagen.
.env al contenedor?
Usa --env-file con docker run:
docker run --rm \
--env-file .env.test \
-v $(pwd)/playwright-report:/app/playwright-report \
playwright-tests:localNunca subas archivos .env al repositorio. Agrega .env* al .gitignore y usa secretos de CI para los valores sensibles.