---
title: "Puppeteer vs Playwright: ¿cuál deberías elegir?"
description: "Puppeteer controla Chrome y Firefox desde Node.js; Playwright suma WebKit, cuatro lenguajes y un ejecutor de pruebas. Comparamos proxies, esperas y MCP."
url: https://proxynet.io/es/blog/puppeteer-vs-playwright
date: 2026-10-06
author: "Acar Diveroli"
category: "Comparativas, Web scraping"
lang: es
---

# Puppeteer vs Playwright: ¿cuál deberías elegir?

Necesitas un navegador real desde Node.js: la página que quieres leer construye su contenido con JavaScript, o un informe HTML tiene que convertirse en PDF. Dos nombres salen primero, Puppeteer y Playwright. Los dos son gratuitos y sus API se parecen (`page.goto()`, `page.click()`, `page.pdf()`), así que la sintaxis no es lo que decide. La elección depende de los navegadores y lenguajes que necesitas, de cómo se configura el proxy y de lo que esperas de las herramientas de pruebas y de agentes de IA.

Este artículo compara los dos en navegadores y protocolo, lenguajes, espera, configuración del proxy, herramientas de pruebas y servidores MCP. Ejecutamos el mismo trabajo en ambos con Puppeteer 25.12.0 (Chrome for Testing 154) y Playwright 1.63.0 (Chromium 153) en Node.js 24 y Windows 11, a través de un proxy local que pide usuario y contraseña. Qué herramienta notan menos los sitios web no es tema de este artículo: las dos manejan un navegador real, y respetar las reglas del sitio te toca a ti en cualquiera de ellas.

> **Nota: Respuesta breve**
>
> Puppeteer es una biblioteca de Node.js que maneja Chrome mediante el Chrome DevTools Protocol y Firefox mediante WebDriver BiDi; para un script solo de Chrome que extrae datos, toma capturas de pantalla o imprime PDF, es suficiente. Playwright maneja Chromium, Firefox y WebKit con una sola API, tiene bibliotecas oficiales para JavaScript, Python, Java y .NET, e incluye un ejecutor de pruebas, un generador de código y un visor de trazas. Playwright toma el usuario y la contraseña del proxy dentro de su opción `proxy`, por navegador o por contexto; Puppeteer toma la dirección como argumento de arranque u opción de contexto, y las credenciales con `page.authenticate()`, que Chrome luego guarda en caché para todo el contexto.

## ¿Qué son Puppeteer y Playwright?

Puppeteer es una biblioteca de JavaScript con una API de alto nivel para controlar Chrome o Firefox. Funciona sobre Node.js y no tiene biblioteca oficial en otro lenguaje; existen adaptaciones a Python, pero son proyectos de terceros. `npm i puppeteer` también descarga una compilación de Chrome for Testing que encaja con la versión y el binario más pequeño `chrome-headless-shell` en `~/.cache/puppeteer`, mientras que `puppeteer-core` es la misma biblioteca sin esa descarga.

Playwright es la biblioteca de automatización de código abierto de Microsoft. Maneja Chromium, Firefox y WebKit, el motor de Safari, con una sola API, y tiene bibliotecas oficiales para JavaScript y TypeScript, Python, Java y .NET. A su lado está Playwright Test, un ejecutor de pruebas con aserciones y ejecuciones en paralelo. Los navegadores son un paso aparte: `npx playwright install chromium` descarga la compilación fijada a tu versión de Playwright.

Las dos sirven para páginas cuyo contenido aparece solo después de que corre el JavaScript. Si el dato ya está en el código fuente de la página o en un endpoint JSON, un cliente HTTP simple necesita mucha menos memoria.

## ¿Cómo hablan con el navegador?

El protocolo que hay debajo de cada herramienta explica casi todo su comportamiento. Un script de Puppeteer funciona así:

1. `puppeteer.launch()` arranca Chrome for Testing desde la caché, o Firefox si pasas `browser: "firefox"`.
2. Puppeteer se conecta por el Chrome DevTools Protocol (CDP), el protocolo de depuración propio de Chrome, o por WebDriver BiDi, el estándar del W3C para la automatización bidireccional de navegadores.
3. Cada llamada como `page.goto()` se convierte en una serie de mensajes del protocolo.
4. Los eventos llegan sin que los pidas: peticiones, respuestas, mensajes de consola, diálogos.

La [página de WebDriver BiDi de Puppeteer](https://pptr.dev/webdriver-bidi) explica el reparto: BiDi es el protocolo por defecto para Firefox, mientras que Chrome sigue en CDP porque no todas las funciones de CDP existen todavía en BiDi. Desde la versión 23, Puppeteer funciona con la versión estable de Firefox y no con una compilación especial.

Playwright también habla CDP con Chromium. Para Firefox y WebKit trae compilaciones parcheadas, y su documentación indica que no funciona con el Firefox ni el Safari de marca porque depende de esos parches; Google Chrome y Microsoft Edge están disponibles con la opción `channel`. En Python, Java y .NET, cada llamada pasa por un driver de Node.js incluido en el paquete, así que el lado del navegador se comporta igual en cada lenguaje. Para automatizar el Firefox que instalan tus usuarios, Puppeteer está más cerca; WebKit solo lo tiene Playwright.

## Puppeteer vs Playwright: tabla comparativa

| | Puppeteer | Playwright |
|---|---|---|
| Navegadores | Chrome, Firefox estable | Chromium, Firefox parcheado, WebKit; Chrome y Edge con `channel` |
| Protocolo | CDP para Chrome, WebDriver BiDi para Firefox | CDP para Chromium; Firefox y WebKit con compilaciones parcheadas |
| Lenguajes oficiales | JavaScript y TypeScript | JavaScript y TypeScript, Python, Java, .NET |
| Espera | Los locators esperan antes de las acciones | Los locators esperan antes de las acciones; las aserciones de prueba reintentan |
| Ejecutor de pruebas | Ninguno integrado | Playwright Test |
| Grabación | Exportación desde el Recorder de Chrome DevTools | `playwright codegen` |
| Revisar una ejecución | Traza de rendimiento para DevTools | Trace Viewer |
| Proxy por navegador | Argumento de arranque `--proxy-server` | `proxy` en `launch()` |
| Proxy por contexto | `proxyServer` en `createBrowserContext()` | `proxy` en `newContext()` |
| Usuario y contraseña del proxy | `page.authenticate()` en una página, en caché por contexto | Campos `username` y `password` |
| Servidor MCP | Chrome DevTools MCP (construido sobre Puppeteer) | Playwright MCP |

## ¿Cómo se ve el mismo trabajo en las dos herramientas?

Nuestra página de prueba, `shop.test`, es una pequeña página local que imprime seis tarjetas de producto con JavaScript un segundo y medio después de cargar y luego muestra un enlace "Next". El trabajo: abrirla a través de un proxy que exige usuario y contraseña, leer los nombres de los productos, hacer clic en "Next" y confirmar la nueva dirección. En tu propio código, pon tu objetivo en lugar de `shop.test`.

Con Puppeteer:

```js
import puppeteer from "puppeteer";

const URL = "http://shop.test/"; // nuestra página de prueba local; pon aquí tu objetivo

const browser = await puppeteer.launch({
  args: ["--proxy-server=http://pr.proxynet.io:8000"],
});
try {
  const page = await browser.newPage();
  await page.authenticate({ username: "user", password: "pass" });
  const response = await page.goto(URL);
  console.log("status:", response.status());
  await page.waitForSelector("div.product"); // la lista la dibuja JavaScript
  const names = await page.$$eval("div.product h2", (els) => els.map((el) => el.textContent));
  console.log(names.length, "products, first:", names[0]);
  await Promise.all([
    page.waitForNavigation(),
    page.locator("a.next").click(), // el locator espera hasta que se puede hacer clic en el enlace
  ]);
  console.log(page.url());
} finally {
  await browser.close();
}
```

Con Playwright:

```js
import { chromium } from "playwright";

const URL = "http://shop.test/"; // nuestra página de prueba local; pon aquí tu objetivo

const browser = await chromium.launch({
  proxy: { server: "http://pr.proxynet.io:8000", username: "user", password: "pass" },
});
try {
  const page = await browser.newPage();
  const response = await page.goto(URL);
  console.log("status:", response.status());
  const products = page.locator("div.product h2");
  await products.first().waitFor(); // count() y allTextContents() no esperan
  const names = await products.allTextContents();
  console.log(names.length, "products, first:", names[0]);
  await page.getByRole("link", { name: "Next" }).click(); // espera por sí solo
  await page.waitForURL("**/page/2/");
  console.log(page.url());
} finally {
  await browser.close();
}
```

Los dos scripts imprimieron las mismas tres líneas a través de nuestro proxy de prueba local:

```text
status: 200
6 products, first: Desk lamp
http://shop.test/page/2/
```

Puppeteer toma la dirección del proxy como argumento de Chrome y las credenciales en una llamada aparte; Playwright toma los tres datos en un solo objeto. Puppeteer empareja el clic con `page.waitForNavigation()` para no leer la URL demasiado pronto, mientras que Playwright encuentra el enlace por su nombre accesible y espera el patrón de la URL.

El registro del proxy mostró una diferencia más. El modo headless por defecto de Puppeteer ejecuta la compilación completa de Chrome for Testing, que además envió peticiones a servicios de Google (`update.googleapis.com`, `accounts.google.com` y otros) a través del proxy. Con `headless: "shell"`, el binario más ligero `chrome-headless-shell`, desaparecieron; el headless shell que Playwright usa por defecto solo envió las peticiones de la propia página. Si pagas el tráfico por gigabyte, como con los [Proxies residenciales](https://proxynet.io/es/residential-proxy), vale la pena revisar ese tráfico de fondo en tu panel.

## ¿Cómo funciona la espera automática en cada una?

La mayoría de los errores en páginas dinámicas viene del momento en que pasan las cosas: el código busca un elemento que JavaScript todavía no ha dibujado. En las acciones, las dos herramientas lo resuelven de forma parecida.

En Puppeteer el camino recomendado es el locator. Antes de un clic, un locator comprueba que el elemento está en el viewport, es visible, está habilitado y tiene un recuadro delimitador estable durante dos fotogramas de animación; si no lo consigue dentro del tiempo de espera de la página, lanza un `TimeoutError`. El código antiguo basado en `page.$()` y `page.click(selector)` no espera a que aparezca el elemento, por eso ese estilo llama antes a `page.waitForSelector()`.

Playwright comprueba que el elemento es visible, está estable, puede recibir eventos y está habilitado. Añade dos cosas: locators que encuentran un elemento por rol, etiqueta o texto, y que sobreviven a una clase CSS renombrada, y, en Playwright Test, aserciones como `expect(locator).toHaveText()` que reintentan hasta que la condición se cumple.

Ninguna espera cuando lees una lista: `page.$$eval()` y `allTextContents()` devuelven lo que hay en la página en ese momento. Por eso los dos scripts esperan al primer producto.

## ¿En qué se diferencian los ajustes del proxy?

En el trabajo de scraping, aquí es donde las dos herramientas más se separan.

En Puppeteer el argumento `--proxy-server` ata todo el navegador. Para un proxy distinto por sesión, la [referencia de `BrowserContextOptions`](https://pptr.dev/api/puppeteer.browsercontextoptions) describe `proxyServer` y `proxyBypassList` y deja el usuario y la contraseña a `Page.authenticate`:

```js
const context = await browser.createBrowserContext({
  proxyServer: "http://pr.proxynet.io:8000",
});
const page = await context.newPage();
await page.authenticate({ username: "user", password: "pass" }); // Chrome guarda las credenciales por contexto
```

La referencia añade que `page.authenticate()` activa por detrás la interceptación de peticiones (request interception), lo que puede afectar al rendimiento. La llamada se hace en una página, pero Chrome guarda las credenciales en caché para todo el contexto de navegador: en nuestras pruebas, un contexto en el que ninguna página había hecho la llamada falló con `net::ERR_INVALID_AUTH_CREDENTIALS`, mientras que las páginas posteriores de un contexto ya autenticado pasaron. Las sesiones y la comprobación de la IP de salida están en nuestra [guía de configuración de proxy en Puppeteer](/es/blog/puppeteer-proxy).

En Playwright el objeto `proxy` va en `launch()` para todo el navegador o en `newContext()` para un contexto, con el usuario y la contraseña como campos:

```js
const context = await browser.newContext({
  proxy: { server: "http://pr.proxynet.io:8000", username: "user", password: "pass" },
});
```

La [guía de red de Playwright](https://playwright.dev/docs/network#http-proxy) muestra las dos formas y acepta servidores HTTP(S) y SOCKSv5. Como cada contexto lleva sus propias credenciales, no hay ninguna llamada por página que olvidar.

Un comportamiento que conviene conocer: cuando dejamos fuera las credenciales, Playwright no lanzó ningún error y `page.goto()` devolvió una respuesta con estado 407. Comprueba `response.status()` en lugar de tomar un `goto` silencioso como prueba de que la página cargó. La configuración completa está en [Qué es Playwright y cómo usarlo con un proxy](/es/blog/playwright-proxy).

Hay tres reglas de Chromium que valen para las dos herramientas. El [documento de proxy de Chromium](https://chromium.googlesource.com/chromium/src/+/HEAD/net/docs/proxy.md) dice que Chrome no admite ningún método de autenticación para SOCKS5, así que SOCKS5 con contraseña no funciona en Chromium con ninguna de las dos; usa una lista de IP autorizadas, que en nuestro servicio admite hasta 10 direcciones. Chrome también rechaza las credenciales escritas dentro de la dirección del proxy: en nuestra prueba con Puppeteer, `user:pass@` en `--proxy-server` hizo que Chrome 154 fallara con `net::ERR_NO_SUPPORTED_PROXIES`, así que no es un atajo. Y Chrome se salta el proxy para las direcciones de loopback, las de la propia máquina: en nuestra prueba Puppeteer abrió `127.0.0.1` directamente mientras Playwright la envió por el proxy, y con `--proxy-bypass-list=<-loopback>` Puppeteer también usó el proxy.

Si cada contexto debe salir por una IP distinta, no necesitas una lista de direcciones en tu código. Un solo gateway con los [Proxies rotativos](https://proxynet.io/es/rotating-proxy) cambia la IP de salida por ti, y una sesión fija mantiene una IP de 1 a 60 minutos.

## ¿Qué servidor MCP elegir: Playwright MCP o Chrome DevTools MCP?

Esta comparación aparece ahora a menudo cuando alguien quiere darle un navegador a un asistente de programación con IA como Claude Code o Cursor. MCP (Model Context Protocol) es la forma estándar en que esos asistentes llaman a herramientas externas, y cada biblioteca tiene un servidor construido sobre él.

Playwright MCP es el servidor de Microsoft. Entrega la página al modelo como texto del árbol de accesibilidad en lugar de capturas de pantalla, arranca con `npx @playwright/mcp@latest` y acepta los flags `--proxy-server` y `--proxy-bypass`. Su README señala que, para agentes de programación, una vía por línea de comandos gasta menos tokens, porque no carga grandes esquemas de herramientas en el contexto del modelo. La instalación y las credenciales del proxy están en [Qué es Playwright MCP](/es/blog/playwright-mcp).

[Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) permite que un agente de programación controle e inspeccione un Chrome en marcha, con Puppeteer para las acciones y DevTools para las trazas de rendimiento; arranca con `npx -y chrome-devtools-mcp@latest`. En la versión 1.10.1, `--proxyServer` se pasa a Chrome como `--proxy-server` sin ningún campo para credenciales, así que la lista de IP autorizadas es el camino práctico. Por defecto abre el Chrome estable instalado y envía estadísticas de uso a Google, salvo que añadas `--no-usage-statistics`.

Para un agente que recorre páginas haciendo clic y rellena formularios, usa Playwright MCP; para uno que lee errores de consola, peticiones de red y trazas de rendimiento de tu propio sitio, Chrome DevTools MCP. En los dos casos, limita adónde puede ir el agente (`--allowed-origins` o `--allowedUrlPattern`), deja las contraseñas fuera del prompt y aprueba las acciones que cambian algo.

## Ejecutor de pruebas, generación de código y trazas

Aquí es donde la ventaja de Playwright es mayor. Playwright Test trae el ejecutor, las aserciones, las ejecuciones en paralelo, los reintentos y un informe HTML; Python usa el plugin de pytest, y Java y .NET sus marcos de pruebas habituales. `npx playwright codegen <url>` escribe código mientras haces clic. El Trace Viewer (`npx playwright show-trace trace.zip`) reproduce una ejecución grabada con instantáneas del DOM, peticiones de red y mensajes de consola en una línea de tiempo, para que veas por qué un trabajo nocturno volvió vacío.

Puppeteer deja las pruebas en tus manos: combínalo con Jest, Mocha o el ejecutor de pruebas de Node.js. Para grabar, el panel **Recorder** (grabadora) de Chrome DevTools exporta una sesión como script de Puppeteer, como script de Puppeteer para Firefox o como uno con un análisis de Lighthouse. `page.tracing.start()` escribe una traza de rendimiento de Chrome para DevTools, que responde por qué una página va lenta, pero no es una reproducción paso a paso de tu script.

## Velocidad: por qué no damos cifras

Las cifras publicadas del tipo "X por ciento más rápido" se midieron en la máquina y en la página de otra persona. En Chromium las dos herramientas hablan CDP, y en un rastreo a través de un proxy la mayor parte del tiempo se va en la red y en el sitio objetivo. Las decisiones de configuración, como Chrome completo frente al headless shell, pesan más que la biblioteca. Si necesitas un número, mídelo en tu propio objetivo con el mismo proxy y la misma concurrencia.

## Casos de uso

- **PDF y capturas de pantalla desde un servicio de Node.js:** las dos tienen `page.pdf()` y las dos generaron un PDF desde Chromium en nuestra prueba; Puppeteer es la dependencia más ligera.
- **Pruebas de extremo a extremo en varios motores:** Playwright Test ejecuta la misma prueba en Chromium, Firefox y WebKit.
- **Recolección de datos en Python, Java o .NET:** Playwright, porque Puppeteer es solo para JavaScript.
- **Automatizar la versión estable de Firefox:** Puppeteer mediante WebDriver BiDi.

Elijas la herramienta que elijas, respeta el `robots.txt` y los términos de uso del sitio, usa la API oficial cuando exista y mantén tu ritmo de peticiones en un nivel que el sitio pueda soportar.

## Errores frecuentes

- **Abrir un contexto de Puppeteer sin `page.authenticate()`.** Chrome guarda las credenciales en caché por contexto; un contexto en el que ninguna página hizo la llamada falla con `net::ERR_INVALID_AUTH_CREDENTIALS`.
- **Esperar que Playwright lance un error cuando el proxy pide credenciales.** En nuestra prueba devolvió una respuesta 407; comprueba `response.status()`.
- **Escribir `user:pass@` en la dirección del proxy.** Chrome no toma las credenciales de la dirección; pásalas con `page.authenticate()` en Puppeteer o con los campos `username` y `password` en Playwright.
- **Usar SOCKS5 con contraseña en Chromium.** Chrome no tiene autenticación SOCKS5; usa una lista de IP autorizadas.
- **Probar un proxy contra `localhost` en Puppeteer.** Chrome se salta el proxy para las direcciones de loopback, salvo que añadas `<-loopback>`.
- **Esperar que el Firefox de Playwright sea el Firefox instalado.** Es una compilación parcheada, y WebKit no es Safari.

## Guía de decisión

| Necesidad | Recomendación |
|---|---|
| Un script de Node.js que solo necesita Chrome | Cualquiera; Puppeteer basta |
| Python, Java o .NET | Playwright |
| Pruebas en el motor WebKit | Playwright |
| Automatizar la versión estable de Firefox | Puppeteer |
| Credenciales del proxy fijadas una vez por contexto | Playwright |
| IP distintas por sesión en un mismo navegador | Cualquiera, un contexto por sesión |
| Una suite de pruebas con aserciones e informes | Playwright Test |
| Un agente que rellena formularios | Playwright MCP |
| Un agente que depura el rendimiento en Chrome | Chrome DevTools MCP |

## Preguntas frecuentes

### ¿Es Playwright mejor que Puppeteer?

No en todo. Playwright cubre más motores, lenguajes y herramientas de pruebas; Puppeteer es más pequeño y funciona con la versión estable de Firefox. Si un script de Puppeteer que ya funciona hace lo que necesitas, no hay motivo para reescribirlo.

### ¿Puedo usar Puppeteer con Python?

No de forma oficial. La única biblioteca oficial de Puppeteer es para JavaScript y TypeScript sobre Node.js; existen adaptaciones a Python, pero son proyectos de terceros fuera del repositorio de Puppeteer. Para una cadena de procesamiento en Python, Playwright tiene una biblioteca oficial con las mismas funciones de automatización de navegador que su versión de Node.js.

### ¿Puppeteer funciona con Firefox?

Sí. Desde la versión 23 funciona con la versión estable de Firefox mediante WebDriver BiDi; lo arrancas con `browser: "firefox"`. Firefox no se descarga con el paquete a menos que lo actives en la configuración de descarga, y algunas funciones que solo existen en CDP no están disponibles en BiDi.

### ¿Es difícil pasar de Puppeteer a Playwright?

Un script pequeño se pasa rápido, porque muchas llamadas se llaman igual: `goto`, `click`, `evaluate`, `pdf`. El trabajo está en tres sitios: las credenciales del proxy pasan al objeto `proxy`, las parejas con `waitForNavigation()` dejan paso a `waitForURL()` y a los locators, y los contextos de navegador se convierten en la unidad de aislamiento.

### ¿Cuál debería usar con Claude Code u otro agente de IA?

Depende de la tarea. Playwright MCP está hecho para manejar páginas a través del árbol de accesibilidad; Chrome DevTools MCP, construido sobre Puppeteer, es más fuerte inspeccionando mensajes de consola, peticiones de red y trazas de rendimiento. Puedes instalar los dos y dar a cada tarea solo el servidor que necesita.

### ¿Alguna de las dos puede usar un proxy SOCKS5 con usuario y contraseña?

En Chromium no, porque Chrome no admite autenticación SOCKS5. Usa SOCKS5 con una lista de IP autorizadas, o un proxy HTTP con usuario y contraseña. El Generador de endpoints de nuestro panel muestra el puerto SOCKS5 de cada producto.

## En resumen

Puppeteer y Playwright manejan el mismo Chromium de forma muy parecida; la diferencia está en lo que lo rodea. Puppeteer es una biblioteca de Node.js para Chrome y el Firefox estable; las credenciales del proxy pasan por `page.authenticate()` y Chrome las guarda en caché por contexto de navegador. Playwright suma WebKit, tres lenguajes oficiales más, un ejecutor de pruebas con trazas y una opción `proxy` que lleva las credenciales por contexto. Para un trabajo de Node.js solo con Chrome, Puppeteer basta; para varios lenguajes, motores o una suite de pruebas, Playwright encaja mejor. Los tipos de proxy que funcionan con las dos los encuentras en nuestros [servicios de proxy](/es/proxy).
