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/workflows

Acá 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: 14

Commitea 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: 14

retention-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.

Los secretos no están disponibles para los workflows activados por pull requests de forks. Esto es una funcionalidad de seguridad: un PR de un fork podría de otra forma leer tus secretos. Si necesitas testear PRs de forks con secretos, investiga el evento 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: 14

fail-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.
Ejecuta la matrix solo en pushes a main, no en cada PR. Agrega 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: 14

Playwright 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: 14

Publicar 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 Results

GITHUB_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.

¿Cómo veo el reporte HTML de una ejecución fallida?

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.

¿Necesito commitear los navegadores de Playwright al repositorio?

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.

Mi workflow es lento. ¿Qué tiene más impacto?

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.

¿Cómo ejecuto solo los tests relacionados con los archivos modificados?

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.

→ See also: CI/CD para QA: GitHub Actions, Jenkins y GitLab Comparados | Depurando Tests Inestables: Una Guía Práctica | Ejecución Paralela en Playwright: Workers, Fragmentos y Fragmentación para Mayor Velocidad | Informes de Tests en Playwright: HTML Integrado vs Allure | GitLab CI + Playwright: Guía Completa de Configuración