Playwright + TypeScript: por qué este stack, setup completo y primer test funcionando en 3 browsers

Por qué Playwright sobre Cypress, TypeScript sobre JavaScript. Setup completo, 9 tests en 3 browsers contra app real y diferencias con Selenium.

Reporte HTML de Playwright mostrando 9 tests pasados en Chromium Firefox y WebKit incluyendo test propio contra Saucedemo
9 tests, 3 browsers, todo verde. Los 6 de ejemplo + 3 del test propio contra Saucedemo.

Contexto: por qué Playwright, por qué ahora

Tengo un framework de Selenium + Java en producción: +96 tests automatizados, Allure Reports, GitHub Actions, CI/CD, Page Object Model. Funciona y resuelve. Pero el mercado ya no pide un solo stack.

Evalué Playwright como segundo stack y esto es lo que un QA con experiencia en ambos necesita saber.


Decisiones técnicas

Por qué Playwright (y no Cypress primero)

Ambos están en mi roadmap. Pero arranco con Playwright por estas razones:

1. Mercado laboral Playwright creció más que Cypress en ofertas laborales en el último año. Cada vez más empresas lo adoptan para proyectos nuevos.

2. Multi-browser real Playwright testea en Chromium, Firefox y WebKit (Safari) de forma nativa. Cypress tiene limitaciones con Firefox y no soporta WebKit.

3. Multi-lenguaje Playwright soporta TypeScript, JavaScript, Python, Java, C#. Cypress: solo JavaScript/TypeScript.

4. Arquitectura más poderosa Playwright puede:

  • Manejar múltiples tabs y ventanas
  • Interceptar requests de red
  • Hacer API testing nativo (sin herramientas extra)
  • Testing de mobile viewports
  • Visual testing con screenshots comparison

Cypress tiene limitaciones arquitecturales con tabs, iframes y multi-dominios.

5. Auto-waiting inteligente Playwright espera automáticamente a que los elementos estén listos antes de interactuar. No necesitás configurar waits explícitos como en Selenium.

En Selenium, si no ponés un wait explícito, el test falla porque el elemento no cargó. En Playwright, el framework maneja eso solo.

Por qué NO Cypress primero:

  • Playwright tiene más demanda y está creciendo más rápido
  • Arquitectura más completa (tabs, iframes, multi-dominio)
  • Multi-browser real vs limitaciones de Cypress
  • Después de aprender Playwright, Cypress se aprende rápido

Cypress queda para después. No lo descarto, lo ordeno.

Por qué TypeScript (y no JavaScript)

Playwright soporta ambos. Elegí TypeScript.

Tipado estático TypeScript detecta errores antes de ejecutar. Si escribo mal un método o paso un tipo incorrecto, el IDE me avisa antes de correr el test.

JavaScript no. El error aparece en runtime, cuando el test ya está corriendo.

Para automation esto importa mucho: si tenés 200 tests, no querés descubrir errores de tipo cuando ejecutás la suite completa.

Autocompletado superior Con TypeScript, VS Code o cualquier IDE te sugiere métodos, propiedades, parámetros. Escribís más rápido y con menos errores.

Estándar en la industria La mayoría de los frameworks modernos de testing usan TypeScript: Playwright lo recomienda por defecto, Cypress lo soporta, muchas empresas lo adoptan.

Transición desde Java Vengo de Java (tipado estático). TypeScript tiene una lógica similar: definís tipos, interfaces, el compilador te avisa errores. JavaScript sería un paso atrás en rigurosidad.

Si venís de Java, TypeScript se siente natural. El tipado estático, las interfaces, el compilador que te avisa errores antes de ejecutar: es la misma lógica con sintaxis distinta. La curva de transición es corta."


Setup

Requisitos previos

Node.js instalado. Verifiqué mis versiones:

node --version   → v24.14.0
npm --version    → 11.9.0

Si no tenés Node.js: nodejs.org, versión LTS.

Crear carpeta e inicializar

Desde la raíz de qa-automation-lab:

mkdir playwright-typescript
cd playwright-typescript
npm init playwright@latest

El instalador pregunta 4 cosas:

✔ Do you want to use TypeScript or JavaScript? → TypeScript
✔ Where to put your end-to-end tests? → tests
✔ Add a GitHub Actions workflow? → true
✔ Install Playwright browsers? → true
Terminal mostrando instalación de Playwright con TypeScript tests GitHub Actions y browsers seleccionados
npm init playwright@latest: TypeScript, carpeta tests, GitHub Actions y browsers. Cuatro preguntas y listo.

TypeScript, carpeta tests, GitHub Actions sí, browsers sí.

Terminal mostrando 6 tests pasados en 42 segundos versiones de Node npm y Playwright y estructura de carpetas
Primer run: 6 passed en 42.9s. Node v24.14.0, Playwright 1.58.2.

Elegí tests como carpeta porque es la convención de Playwright. En Selenium uso src/test/java/ porque Maven lo dicta. Acá el ecosistema es distinto.

Qué se instaló

Playwright descargó automáticamente:

Browser Versión
Chromium 145.0.7632.6 (v1208)
Firefox 146.0.1 (v1509)
WebKit 26.0 (v2248)

También FFmpeg (para grabación de video) y Winldd (utilidad de Windows).

Versión de Playwright instalada: 1.58.2

Comparación con Selenium: en Selenium tuve que agregar WebDriverManager como dependencia para que gestione los drivers automáticamente. Acá Playwright descarga e instala los browsers completos. No drivers, no versiones que se desactualizan. Todo integrado.

Estructura generada

playwright-typescript/
├── .github/
│   └── workflows/
│       └── playwright.yml       ← CI/CD listo
├── node_modules/                ← dependencias
├── playwright-report/           ← reportes HTML
├── test-results/                ← resultados de ejecución
├── tests/
│   └── example.spec.ts          ← test de ejemplo
├── .gitignore
├── package.json
├── package-lock.json
└── playwright.config.ts         ← configuración central
Explorador de archivos mostrando estructura del proyecto Playwright con carpetas github tests playwright-report y archivos de configuracion
Estructura generada por Playwright. Todo lo necesario sale de un solo comando.

La diferencia con Selenium es clara: Playwright genera config, test de ejemplo, workflow de CI/CD y carpetas de reportes en un solo comando. En mi framework de Selenium, cada una de esas piezas requirió configuración individual: Maven, TestNG, Allure, GitHub Actions. Acá el ecosistema viene integrado de entrada.


El test de ejemplo

Playwright genera un archivo tests/example.spec.ts con dos tests:

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

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Expect a title "to contain" a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

Diferencias con Selenium

Sintaxis más corta. En Selenium, abrir una página y verificar el título son varias líneas: crear driver, configurar waits, navegar, obtener título, hacer assert, cerrar driver. Acá: page.goto() + expect(page).toHaveTitle(). Dos líneas.

No hay setup ni teardown visible. En Selenium necesitás @BeforeMethod para crear el driver y @AfterMethod para cerrarlo. Playwright maneja eso internamente con fixtures. El { page } que recibe cada test es un browser context limpio que Playwright crea y destruye solo.

Locators semánticos. page.getByRole('link', { name: 'Get started' }) busca por rol y texto accesible. En Selenium, generalmente usás By.xpath() o By.cssSelector(). Los locators de Playwright están diseñados para ser más resilientes: si cambia el HTML pero el botón sigue diciendo "Get started", el test no se rompe.

Async/await. Todo es asíncrono. Cada interacción con la página usa await. Esto es TypeScript/JavaScript: el browser corre en paralelo al código, y await asegura que cada acción termine antes de seguir a la siguiente.

En Java no necesitás esto porque Selenium es sincrónico por defecto.

Assertions integradas. expect(page).toHaveTitle() viene con Playwright. No necesitás TestNG ni otra librería de assertions. En Selenium usé Assert de TestNG como capa separada.


La configuración: playwright.config.ts

export default defineConfig({
  testDir: './tests',
  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',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
  ],
});

Lo que importa de esta config

fullyParallel: true Todos los tests corren en paralelo por defecto. En Selenium tuve que configurar esto manualmente en testng.xml. Acá viene activado.

retries: process.env.CI ? 2 : 0 En local: sin reintentos (si falla, falla). En CI: 2 reintentos para manejar flakiness. Lógica inteligente que diferencia entre desarrollo y pipeline.

reporter: 'html' Reporte HTML integrado. Sin Allure, sin plugins, sin configuración extra. Un comando para generar, otro para abrir.

projects Cada "project" es un browser. El mismo test corre en Chromium, Firefox y WebKit automáticamente. Por eso mis 2 tests se convirtieron en 6: 2 tests × 3 browsers = 6 ejecuciones.

trace: 'on-first-retry' Si un test falla y se reintenta, Playwright graba un trace completo: screenshots, red, DOM. Herramienta de debugging que en Selenium no existe nativamente.


Ejecución

npx playwright test

Resultado:

Running 6 tests using 2 workers
  6 passed (42.9s)
Terminal mostrando ejecucion de 6 tests pasados y comando para abrir reporte HTML en localhost
6 tests de ejemplo corriendo. npx playwright show-report abre el reporte en el browser.

2 tests, 3 browsers, 6 ejecuciones. Todo verde. 42.9 segundos.

El reporte HTML

npx playwright show-report

Se abre en localhost:9323. Muestra cada test con su browser, tiempo de ejecución, estado. Filtros por passed, failed, flaky, skipped.

Reporte HTML de Playwright mostrando 6 tests pasados en Chromium Firefox y WebKit con tiempos de ejecucion
El mismo test corre en 3 browsers automáticamente. 2 tests × 3 browsers = 6 ejecuciones.

Comparado con Allure (que uso en Selenium): Allure es más configurable y detallado. El reporte de Playwright es más simple pero viene sin configuración. Para empezar, es más que suficiente.


Diferencias clave entre ambos stacks: Playwright vs Selenium

Estas son las diferencias más relevantes para alguien que ya trabaja con Selenium y evalúa sumar Playwright.

Aspecto Selenium + Java Playwright + TypeScript
Setup Maven + dependencias + WebDriverManager Un comando (npm init playwright@latest)
Drivers WebDriverManager los gestiona Playwright los incluye
Browsers Chrome, Firefox, Edge (drivers separados) Chromium, Firefox, WebKit (incluidos)
Waits Explícitos (hay que configurar) Auto-waiting integrado
Paralelo Configurar manualmente en testng.xml Activado por defecto
Reportes Allure (configurar aparte) HTML reporter integrado
CI/CD Crear workflow manual Workflow generado automáticamente
Lenguaje Java (tipado estático) TypeScript (tipado estático)
Locators xpath, css, id getByRole, getByText, getByLabel + css
Curva de aprendizaje Más larga Más corta

Playwright es más moderno, más rápido de arrancar, menos configuración. Selenium tiene más madurez, más ecosistema, más ofertas laborales (por ahora).

No reemplaza a Selenium. Lo complementa.


Primer test propio: Saucedemo

Los tests de ejemplo prueban contra la web de Playwright. Sirven para verificar el setup, pero no son míos.

Escribí un test contra Saucedemo, una app de práctica de Sauce Labs:

typescript

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

test('verify Saucedemo login page loads', async ({ page }) => {
  await page.goto('https://www.saucedemo.com/');

  await expect(page).toHaveTitle(/Swag Labs/);
  await expect(page.getByPlaceholder('Username')).toBeVisible();
  await expect(page.getByPlaceholder('Password')).toBeVisible();
});
Terminal mostrando creacion de archivo saucedemo spec ts y escritura del test con assertions contra Saucedemo
Primer test propio: verificar que la página de login de Saucedemo carga correctamente.

Tres assertions: título correcto, campo de usuario visible, campo de contraseña visible.

Resultado:

Running 9 tests using 2 workers
  9 passed (37.4s)
Terminal mostrando 9 tests pasados en 37 segundos con Playwright
9 passed en 37.4s. Los 6 de ejemplo + mi test contra Saucedemo en 3 browsers.

9 tests: los 6 de ejemplo + 3 del test nuevo (uno por browser). Todo verde.

Lo que noté al escribirlo: getByPlaceholder('Username') es más legible que By.xpath("//input[@placeholder='Username']") en Selenium. El locator describe lo que el usuario ve, no la estructura del HTML.

Reporte HTML de Playwright con 9 tests verdes separados en example spec y saucedemo spec en tres browsers
Reporte final: example.spec.ts y saucedemo.spec.ts, cada uno corriendo en Chromium, Firefox y WebKit.

Estado actual del proyecto

Tengo:

  • Node v24.14.0 + npm 11.9.0
  • Playwright 1.58.2 instalado
  • 3 browsers descargados (Chromium, Firefox, WebKit)
  • 9 tests funcionando (6 de ejemplo + 3 propios)
  • Test propio contra app real (Saucedemo)
  • Reporte HTML generado
  • GitHub Actions workflow listo
  • Repo independiente en GitHub

Próximo paso: Explorar locators de Playwright a fondo y empezar a armar estructura con Page Object Model.

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