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

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

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

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)

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.

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();
});
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)
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.

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