Olvidar --with-deps al instalar Playwright en GitHub Actions es la razón más común por la que los tests pasan localmente pero fallan en CI: las bibliotecas del sistema como libgtk y libnss no están instaladas en los runners de Ubuntu recién creados. El segundo error más común es omitir if: always() en el paso de subida del artefacto, lo que significa que pierdes el reporte HTML justo cuando los tests fallan y más los necesitas. Esta guía cubre la configuración completa: workflow mínimo funcional, secretos, caché de navegadores, filtros de ramas, ejecuciones cross-browser con matrix, sharding entre jobs paralelos, y comentarios de PR con resultados de tests.
Por qué GitHub Actions
GitHub Actions es la plataforma de CI dominante para proyectos nuevos. La encuesta State of CI/CD 2025 de JetBrains encontró que el 62% de los desarrolladores la usan en proyectos personales y el 41% en sus organizaciones, cifras superiores a cualquier otra herramienta.
Las razones prácticas son directas: es gratuita para repositorios públicos, gratuita para 2.000 minutos por mes en los privados, y está integrada en GitHub. No hay servidor separado que aprovisionar, ni cuenta de terceros que conectar, ni configuración de webhooks. Subes un archivo YAML y los tests empiezan a correr.
Para Playwright específicamente, GitHub Actions funciona bien porque los runners de Ubuntu que provee tienen todo lo que Playwright necesita. Las dependencias del navegador, fuentes y bibliotecas del sistema están disponibles vía --with-deps. El equipo oficial de Playwright testea contra estos runners. Estás en el camino soportado.
El workflow mínimo funcional
Crea el archivo .github/workflows/playwright.yml en la raíz de tu proyecto. Ahí es donde GitHub busca las definiciones de workflow.
mkdir -p .github/workflowsAcá está el workflow mínimo completo:
# .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
steps:
- name: Checkout del código
uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Instalar navegadores de Playwright
run: npx playwright install --with-deps
- name: Ejecutar tests de Playwright
run: npx playwright test
- name: Subir reporte de tests
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 14Commitea este archivo, súbelo, y abre la pestaña Actions en tu repositorio de GitHub. Verás el workflow aparecer y correr en segundos.
npm ci en lugar de npm install instala exactamente lo que está en package-lock.json sin modificarlo. Este es el comando correcto para entornos CI porque da builds reproducibles. npx playwright install --with-deps instala los navegadores junto con sus dependencias a nivel del sistema (libgtk, libnss, etc.) que no están presentes en un runner de Ubuntu recién creado. Olvidar --with-deps es la razón más común por la que Playwright falla en CI cuando funciona localmente.
El if: always() en el paso de subida es esencial. Sin él, el artefacto solo se sube cuando el workflow tiene éxito, lo que significa que pierdes el reporte justo cuando los tests fallan y más lo necesitas.
El timeout-minutes: 60 en el job es una salvaguarda. Un test colgado puede consumir toda tu cuota mensual de minutos antes de que te des cuenta.
Almacenar y descargar reportes de tests
El paso upload-artifact en el workflow de arriba guarda el directorio completo playwright-report/ como un zip descargable. Después de que una ejecución termina, ve a la página de la ejecución del workflow, luego Summary, luego Artifacts, luego playwright-report. Descarga y descomprime, luego abre index.html en un navegador.
El reporte HTML muestra qué tests pasaron, cuáles fallaron, capturas de pantalla de los fallos, traces, y video si tienes grabación habilitada. Así es como investigás fallos sin acceder directamente al runner de CI.
Si estás generando múltiples reportes (por ejemplo, uno por navegador en una ejecución de matrix), dale a cada artefacto un nombre único:
- name: Subir reporte de tests
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.browser }}
path: playwright-report/
retention-days: 14retention-days: 14 conserva los reportes durante dos semanas, suficiente para la mayoría de los flujos de depuración sin acumular costos innecesarios de almacenamiento. GitHub permite hasta 90 días.
Variables de entorno y secretos
Nunca pongas credenciales ni URLs específicas del entorno en el archivo YAML del workflow. Los Secrets de GitHub son el lugar correcto para cualquier cosa sensible.
Ve a tu repositorio, luego Settings, luego Secrets and variables, luego Actions, luego New repository secret. Agrega los secretos que necesiten tus tests: TEST_USER, TEST_PASSWORD y cualquier clave de API.
Referencialos en el workflow bajo env: en el paso de tests:
- name: Ejecutar tests de Playwright
run: npx playwright test
env:
BASE_URL: https://staging.example.com
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}En tus tests, leelos con process.env:
const baseURL = process.env.BASE_URL ?? 'http://localhost:3000';
const user = process.env.TEST_USER ?? '';
const password = process.env.TEST_PASSWORD ?? '';GitHub enmascara los valores de los secretos en los logs. Si un secreto aparece en el output, se reemplaza con *. Los valores no sensibles como BASE_URL también pueden guardarse como variables del repositorio (no secretos), que son visibles en la UI. Usa variables para URLs y feature flags, secretos para credenciales.
pull_request_target, pero entiende las implicaciones de seguridad antes de usarlo.Caché para ejecuciones más rápidas
Una ejecución de workflow recién creada instala los módulos de Node y los navegadores de Playwright desde cero cada vez. En un runner frío, eso son 2-3 minutos antes de que corra un solo test. El caché reduce eso significativamente.
La acción setup-node con cache: 'npm' ya maneja el caché de node_modules automáticamente. Para los navegadores de Playwright, agrega un paso de caché dedicado:
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Cachear navegadores de Playwright
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Instalar navegadores de Playwright
run: npx playwright install --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'La clave del caché incluye un hash de package-lock.json. Cuando actualizas versiones de Playwright, el archivo de lock cambia, el hash cambia, el caché falla, y los navegadores se reinstalan. Cuando nada cambia, el caché acierta y te saltás la instalación de navegadores completamente.
Esta optimización importa más en suites grandes. Un suite de 200 tests con caché habilitado puede pasar de 8 minutos a 4 minutos solo saltándose las descargas repetidas de navegadores.
Ejecutar solo en ramas específicas
La sección on: controla cuándo corre tu workflow. El ejemplo mínimo ya filtra a main y develop. Puedes ser más específico:
on:
push:
branches:
- main
- develop
- 'release/**'
pull_request:
branches:
- main
- develop
workflow_dispatch:workflow_dispatch: agrega un botón "Run workflow" en la pestaña Actions, permitiendo activarlo manualmente. Es útil para ejecutar tests contra una rama específica bajo demanda sin hacer push de un commit.
También puedes ejecutar tests con un horario programado. Las ejecuciones nocturnas de smoke tests detectan problemas que aparecen solo en ciertas ventanas de tiempo o después de que los datos se acumulan:
on:
schedule:
- cron: '0 3 * * *' # 3am UTC todas las noches
push:
branches: [main]
pull_request:
branches: [main]Dentro de un job, puedes agregar condiciones de rama en pasos específicos usando if::
- name: Ejecutar suite completo
run: npx playwright test
if: github.ref == 'refs/heads/main'
- name: Ejecutar solo smoke tests
run: npx playwright test --grep @smoke
if: github.event_name == 'pull_request'Este patrón da a los pull requests feedback rápido de un suite de smoke mientras las ejecuciones en la rama main reciben el test completo.
Estrategia de matrix para testing cross-browser
Playwright soporta Chromium, Firefox y WebKit. Correr los tres en cada PR es costoso en tiempo y minutos, pero igual querés cobertura cross-browser. La estrategia de matrix ejecuta jobs paralelos con diferentes configuraciones:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
steps:
- name: Checkout del código
uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Cachear navegadores de Playwright
uses: actions/cache@v4
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ matrix.browser }}-${{ hashFiles('package-lock.json') }}
- name: Instalar navegadores de Playwright
run: npx playwright install ${{ matrix.browser }} --with-deps
if: steps.playwright-cache.outputs.cache-hit != 'true'
- name: Ejecutar tests
run: npx playwright test --project=${{ matrix.browser }}
env:
BASE_URL: ${{ vars.BASE_URL }}
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
- name: Subir reporte
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-${{ matrix.browser }}
path: playwright-report/
retention-days: 14fail-fast: false significa que si Firefox falla, los jobs de Chromium y WebKit siguen corriendo. Con fail-fast: true (el valor por defecto), cualquier fallo en un job de la matrix cancela el resto, generalmente no lo que querés cuando depurás problemas cross-browser.
npx playwright install ${{ matrix.browser }} --with-deps instala solo el navegador para ese job, no los tres. Es más rápido que instalar todo en cada job.
if: github.ref == 'refs/heads/main' al job o usa el filtro de ramas en el trigger on.push. Tus PRs se mantienen rápidos; tu rama main obtiene cobertura completa.Sharding: dividir los tests entre jobs
El sharding divide el suite de tests entre múltiples jobs paralelos. Donde la estrategia de matrix corre diferentes configuraciones, el sharding corre los mismos tests más rápido dividiéndolos:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- name: Checkout del código
uses: actions/checkout@v4
- name: Configurar Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Instalar dependencias
run: npm ci
- name: Instalar navegadores de Playwright
run: npx playwright install chromium --with-deps
- name: Ejecutar shard
run: npx playwright test --shard=${{ matrix.shard }}/4
env:
BASE_URL: ${{ vars.BASE_URL }}
TEST_USER: ${{ secrets.TEST_USER }}
TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }}
- name: Subir reporte del shard
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report-shard-${{ matrix.shard }}
path: playwright-report/
retention-days: 14Playwright distribuye los tests uniformemente entre shards basándose en datos de timing de ejecuciones anteriores si proporcionás un reporte blob, o por orden de archivos en caso contrario. Con cuatro shards, un suite de 20 minutos corre en aproximadamente 5 minutos.
Puedes combinar sharding con la estrategia de matrix. Un patrón común es cuatro shards por navegador para suites grandes:
strategy:
matrix:
browser: [chromium, firefox]
shard: [1, 2, 3, 4]Esto crea 8 jobs paralelos. Ten en cuenta que cada job usa minutos del runner. 8 jobs corriendo durante 5 minutos cada uno cuestan 40 minutos de tu cuota, igual que correr todo en serie. La ventaja es el tiempo de pared del reloj, no el consumo de cuota.
Fusionar los reportes de shards en un solo reporte HTML requiere el reporter blob. Configura reporter: 'blob' en tu config de Playwright para las ejecuciones de CI, luego agrega un job de fusión:
merge-reports:
if: always()
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- name: Descargar reportes blob
uses: actions/download-artifact@v4
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Fusionar en reporte HTML
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Subir reporte fusionado
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 14Publicar resultados en comentarios de PR
Hacer que los resultados de los tests aparezcan directamente en el pull request hace que los fallos sean mucho más visibles. Esto requiere una acción separada después de que termine la ejecución de tests.
El enfoque más simple usa la acción dawidd6/action-junit-report, que lee el output XML de JUnit y publica un comentario. Primero, agrega el reporter JUnit a tu config de Playwright:
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
reporter: [
['html'],
['junit', { outputFile: 'results.xml' }],
],
});Luego agrega un paso de reporte a tu workflow:
- 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: Publicar resultados de tests en el PR
uses: dawidd6/action-junit-report@v4
if: always()
with:
report_paths: results.xml
github_token: ${{ secrets.GITHUB_TOKEN }}
check_name: Playwright Test ResultsGITHUB_TOKEN está disponible automáticamente en cada workflow. No se necesita configuración manual de secretos.
El comentario muestra conteos de pasa/falla, nombres de tests y mensajes de error directamente en la conversación del PR. Los revisores no necesitan navegar a la pestaña Actions para saber si los tests pasaron.
FAQ
Los tests pasan localmente pero fallan en GitHub Actions. ¿Por dónde empiezo?Verifica estas cosas en orden: variables de entorno (un secreto no configurado en Actions será undefined, no un error), el BASE_URL (si está hardcodeado como localhost, no funcionará en CI), y el timing. Los runners de CI son más lentos que las máquinas de desarrollo. Aumentá los timeouts en tu playwright.config.ts o agregá retries: 1 específicamente para CI usando process.env.CI ? 1 : 0.
Ve a la ejecución fallida del workflow, luego Summary, luego desplazate hasta Artifacts, luego descargá playwright-report. Extraé el zip y abrí index.html en un navegador. El reporte incluye capturas de pantalla y traces para los tests fallidos.
No. El workflow los instala en tiempo de ejecución vía npx playwright install. Con el caché configurado, las ejecuciones posteriores se saltan la descarga. Commitear los binarios del navegador inflaría tu repositorio por cientos de megabytes.
El caché es la primera optimización: cachear los navegadores de Playwright ahorra 1-2 minutos por ejecución. El sharding es la segunda: dividir un suite de 15 minutos en 3 shards baja el tiempo de pared del reloj a 5 minutos. Ejecutar solo Chromium en PRs y matrix completo en main es la tercera: triplica la velocidad de cada ejecución de PR.
¿Puedo usar la imagen Docker de Playwright en lugar de instalar los navegadores?Sí. Configura container: mcr.microsoft.com/playwright:v1.52.0-jammy en el job y sáltate el paso de instalación. El tradeoff: los contenedores Docker arrancan más lento que los runners de Ubuntu sin contenedor, y estás fijado a una versión específica de Playwright en el tag de la imagen. Es una elección razonable para equipos que quieren evitar el paso de instalación completamente.
Playwright no soporta esto de forma nativa. Algunos equipos usan filtros basados en rutas en el workflow, activando el job de tests solo cuando cambian archivos en ciertos directorios. Agrega paths: bajo on.pull_request para limitar cuándo corre el workflow. La selección completa de tests basada en cambios de código requiere herramientas externas y rara vez vale la complejidad hasta que tu suite supere los 10 minutos.