Playwright: CI/CD con GitHub Actions — pipeline, errores reales y reporte público

GitHub Actions + GitHub Pages para Playwright. Errores de storageState, visual tests en Linux, reporte HTML público. Contraste con Selenium CI/CD.

Reporte Playwright público en cesarbeassuarez.github.io mostrando 17 tests 12 passed 5 failed en chromium con e2e clientes y api-clientes
El reporte público. Cualquiera puede ver qué tests pasaron, cuáles fallaron y por qué.

Contexto: por qué CI/CD cierra el ciclo

Este post documenta cómo configuré CI/CD con GitHub Actions para mi framework de Playwright + TypeScript. Es parte de mi serie de Playwright.

Tengo un framework con tests de login, validación de grilla contra Excel, API testing, network interception, visual testing. Todo corre en mi máquina. Pero eso no es suficiente.

Un framework que solo corre local es un proyecto personal. Un framework con CI/CD es un framework de producción.

CI/CD (Continuous Integration / Continuous Delivery) hace que los tests corran automáticamente cada vez que pusheo código. No tengo que acordarme de ejecutarlos. No tengo que abrir la terminal. Push y listo — GitHub levanta una máquina, instala todo, corre los tests y me dice si algo se rompió.

Ya hice esto en la serie de Selenium — CI/CD con GitHub Actions para Selenium - documentó todo el proceso con Allure Reports y GitHub Pages. Acá es el mismo concepto, pero más simple. Mucho más simple.


El punto de partida

Cuando creé el proyecto con npm init playwright@latest, Playwright preguntó:

✔ Add a GitHub Actions workflow? → true

Eso generó .github/workflows/playwright.yml automáticamente. Un workflow básico que instala dependencias, browsers, y corre los tests.

VS Code mostrando playwright.yml original de 27 líneas con estructura del workflow checkout setup-node npm ci install browsers run tests upload-artifact
El workflow que Playwright generó automáticamente. 27 líneas, sin deploy de reporte ni permisos para GitHub Pages.

El workflow corría en cada push a main. El problema: nunca pasó.

Pestaña Actions de GitHub mostrando 16 workflow runs de Playwright Tests casi todos con status rojo, último verde en run 9
16 runs. Casi todos rojos. Desde que el framework creció, el pipeline no pasaba.

16 runs. Casi todos rojos. El último verde fue el run #9, cuando el framework era más simple. Desde que agregué Excel, e2e, API testing, visual testing — todo rojo.


Los errores del camino

Error 1: storageState no existe en CI

El primer error era claro:

Error: ENOENT: no such file or directory, open '.auth/user.json'
Log de GitHub Actions mostrando error ENOENT no such file or directory auth user.json en api-clientes.spec.ts línea 5 con flecha señalando el error
El error más común con storageState en CI: el archivo no existe porque setup todavía no corrió.

api-clientes.spec.ts leía .auth/user.json con fs.readFileSync en la línea 5 — fuera de cualquier test, a nivel de módulo. Eso se ejecuta cuando Playwright carga el archivo, antes de que el proyecto setup corra auth.setup.ts y genere el archivo de autenticación.

El archivo no existía todavía. Crash.

El fix: mover el readFileSync adentro de test.beforeAll.

Antes:

import { test, expect } from '@playwright/test';
import fs from 'fs';

// Esto se ejecuta al cargar el archivo — ANTES del setup
const storageState = JSON.parse(fs.readFileSync('.auth/user.json', 'utf-8'));
const csrfToken = storageState.cookies.find(c => c.name === 'CSRF-TOKEN')?.value || '';

test.describe('API testing nativo de Playwright', () => {
    // ...

Después:

Código TypeScript mostrando readFileSync de auth user.json movido dentro de test.beforeAll en vez de nivel de módulo
El fix: mover readFileSync adentro de beforeAll. Así setup corre primero y el archivo existe cuando se lee.

En local funcionaba porque .auth/user.json ya existía de corridas anteriores. En CI cada ejecución empieza de cero — no hay archivos previos. Este es el tipo de error que solo aparece en CI.

Error 2: --ignore-pattern no existe

Quise excluir los visual tests de CI con --ignore-pattern:

error: unknown option '--ignore-pattern=**/visual-regression*'

Playwright no tiene esa flag. La alternativa: --grep-invert, que excluye tests por nombre.

run: npx playwright test --project=chromium --grep-invert="visual-regression"

Error 3: permisos para GitHub Pages

El step de deploy a GitHub Pages falló:

remote: Permission to cesarbeassuarez/playwright-typescript-framework.git denied to github-actions[bot].
fatal: unable to access '...': The requested URL returned error: 403

El workflow necesita permisos explícitos para pushear a la branch gh-pages. Fix:

jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: write

Decisión: qué corre en CI y qué no

No todo tiene sentido en CI.

Tests de learning/ — son exploratorios: prueban conceptos, fuerzan errores, documentan el proceso. No son de producción. Los excluí con testIgnore en la config:

testIgnore: ['**/learning/**'],

Visual tests de e2e/ — Las golden files las generé en Windows con Chromium. CI corre en Ubuntu. El rendering de fonts, anti-aliasing y sub-pixel es diferente entre sistemas operativos. Los screenshots no matchean aunque la página sea idéntica. Los excluí con --grep-invert.

¿En producción cómo se resuelve? Se generan las golden files directamente en CI con --update-snapshots, se bajan, se commitean. Así las referencia siempre son del mismo entorno donde corren los tests. O se usa Docker local con la misma imagen de CI para que el rendering sea idéntico.

Para este framework, excluirlos y documentar por qué es la decisión correcta.


El workflow final

VS Code mostrando playwright.yml final de 42 líneas con permissions contents write grep-invert visual-regression y deploy a GitHub Pages
El workflow final. Permisos, grep-invert para excluir visual tests, y deploy del reporte a GitHub Pages.

Qué hace cada step:

  1. Checkout del código
  2. Instala Node.js (versión LTS)
  3. npm ci — instala dependencias desde package-lock.json
  4. Instala browsers de Playwright con dependencias del sistema (--with-deps instala libs de Linux que Chromium necesita)
  5. Corre los tests solo en Chromium, excluyendo visual regression
  6. Sube el reporte como artifact (descargable desde la pestaña de Actions)
  7. Publica el reporte en GitHub Pages (accesible públicamente)

Reporte público en GitHub Pages

El reporte de Playwright es un solo archivo HTML autocontenido. No necesita servidor, no necesita Java, no necesita configuración. Es un .html de ~15 MB que se abre en cualquier browser.

Para publicarlo: peaceiris/actions-gh-pages toma el contenido de playwright-report/ y lo pushea a la branch gh-pages. GitHub Pages sirve esa branch como sitio estático.

Configuración en GitHub: Settings → Pages → Branch: gh-pages/ (root) → Save.

Resultado: cesarbeassuarez.github.io/playwright-typescript-framework

Reporte Playwright público en cesarbeassuarez.github.io mostrando 17 tests 12 passed 5 failed en chromium con e2e clientes y api-clientes
El reporte público. Cualquiera puede ver qué tests pasaron, cuáles fallaron y por qué.

Cada test muestra su estado, duración, retries. Los que fallaron tienen links a View Trace — el trace completo de la ejecución con screenshots, network, console, todo.

Detalle de test fallido en reporte Playwright mostrando Expected ALFKI Received C001 con test steps Before Hooks Expect toBe y retries
Expected ALFKI, Received C001. El pipeline detectó que alguien agregó un cliente nuevo en la demo.

CI detectó un cambio real

Los 5 tests que fallan no son errores del pipeline. Son cambios reales en demo.serenity.is.

Alguien agregó un cliente nuevo: C001 — ACME. La grilla pasó de 91 a 92 clientes.

Grilla de clientes en demo.serenity.is mostrando 92 registros totales con C001 ACME resaltado entre BSBEV y CACTU
C001 — ACME. Un cliente que no existía cuando escribí los tests. El CI lo encontró.

Eso rompió:

  • El test de API que espera 91 entidades → recibe 92
  • La validación contra Excel que tiene 91 registros → la grilla tiene 92
  • El ordenamiento que espera ALFKI primero → C001 viene antes alfabéticamente
  • El test de filtros que espera volver a 91 → vuelve a 92

Esto es exactamente para lo que sirve CI/CD: detectar que algo cambió. No es un bug del framework. Es un cambio en el entorno de pruebas. En un proyecto real, este fallo dispara una pregunta: "¿se esperaba este cambio? ¿hay que actualizar los tests?" Eso es proceso de QA.

No ajusté los tests para forzar el verde. El rojo es honesto.


CI/CD en Playwright vs Selenium

En la serie de Selenium, el post de CI/CD fue el Post 12. Tardé 15+ commits en hacer funcionar el pipeline. Tuve que lidiar con headless mode, WebDriverManager, rutas de ChromeDriver, maven-surefire-plugin, Allure Reports con Java, deploy con acciones custom.

En Playwright:

Concepto Selenium + Java Playwright + TypeScript
Workflow inicial Creado desde cero Generado por npm init playwright
Headless Configurar ChromeOptions manualmente Headless por defecto en CI
Drivers WebDriverManager, problemas de versión Incluidos, npx playwright install
Reporter Allure — instalar Java, generar reporte, deploy custom HTML reporter nativo — un archivo
Deploy del reporte Allure + acciones de terceros + Java runtime peaceiris/actions-gh-pages + un HTML
Config de CI Variables de entorno para headless, chromedriver process.env.CI ya configurado
Retries Configurar en TestNG/Maven retries: process.env.CI ? 2 : 0 en config
Workers Configurar parallelism manualmente workers: process.env.CI ? 1 : undefined

El playwright.config.ts ya tenía todo preparado para CI desde el principio:

forbidOnly: !!process.env.CI,          // no dejar .only en CI
retries: process.env.CI ? 2 : 0,       // reintentar en CI
workers: process.env.CI ? 1 : undefined // secuencial en CI, paralelo en local

Esas líneas las puso Playwright cuando generó el proyecto. En Selenium tuve que descubrir cada una por mi cuenta.

Allure vs Playwright HTML Reporter

En Selenium uso Allure Reports — más configurable, con @Step, categorías, historial de ejecuciones. Requiere Java instalado y una acción custom para generarlo.

En Playwright el HTML reporter es nativo. No necesita configuración, no necesita Java, se genera automáticamente. Es un solo archivo HTML con todo: resultados, traces, screenshots, diffs de visual testing.

Para un framework de Playwright, Allure es innecesario. El reporter nativo cubre todo lo que necesito.


La config que hace funcionar todo

export default defineConfig({
  testDir: './tests',
  testIgnore: ['**/learning/**'],        // solo e2e en CI
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

  use: {
    trace: 'on-first-retry',
    locale: 'es-AR',
    baseURL: 'https://demo.serenity.is',
  },

  projects: [
    { name: 'setup', testMatch: /auth\.setup\.ts/ },
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' },
      dependencies: ['setup'],
    },
    // firefox, webkit...
  ],
});

testIgnore separa learning de producción. forbidOnly previene que un .only olvidado llegue a CI. retries reintenta en CI para manejar flakiness. trace: 'on-first-retry' captura traces solo cuando un test falla la primera vez — información de debug sin overhead innecesario.

Los projects con dependencies: ['setup'] aseguran que auth.setup.ts corra primero y genere .auth/user.json antes de cualquier test.


Estado actual

Pipeline de CI/CD corriendo en cada push a main. 17 tests ejecutándose en Chromium, reporte HTML publicado automáticamente en GitHub Pages. 12 tests verdes, 5 rojos por cambio de datos en el entorno de prueba.

El framework tiene el ciclo completo: código → push → tests automáticos → reporte público.

Próximo post: Selenium vs Playwright — el comparativo final con evidencia real de ambas series.

🔗 Reporte público: cesarbeassuarez.github.io/playwright-typescript-framework

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