Playwright CLI en Claude Code: Bots de Navegador Autónomos
La primera vez que dejé a Claude Code manejar un navegador real sin supervisión, estaba viendo un formulario de onboarding de 12 preguntas fallar en cámara lenta. La página 1 funcionó. La página 2 funcionó. La página 3 — un textarea de formato largo — se congeló. El agente presionó Enter para avanzar. No pasó nada. Lo presionó de nuevo. Nada todavía. Entonces hizo algo que genuinamente no esperaba: tomó una captura de pantalla, abrió el código fuente de la página, localizó el handler de keydown, notó que el textarea estaba absorbiendo la tecla Enter en lugar de propagarla al listener de submit del formulario, parchó el handler, redesplegó, re-ejecutó el formulario y me avisó cuando las 12 preguntas se enviaron correctamente.
Todo ese bucle — probar, detectar, corregir, re-probar — tomó aproximadamente once minutos. Había estado a un metro de mi teclado todo el tiempo y contribuí cero pulsaciones de tecla.
Lo que lo hizo funcionar no fue un prompt inteligente. No fue una habilidad. Fue un cambio que había hecho una semana antes: abandonar Playwright MCP por Playwright CLI como las manos de Claude Code dentro del navegador. Ese único cambio redujo mi factura de tokens de automatización del navegador en aproximadamente 4x, hizo que las capturas de pantalla de debug fueran razonables de revisar y desbloqueó una categoría de agente que realmente no había podido construir antes — bucles de QA autónomos, scrapers que sobreviven al bloqueo de Google y bots detrás de login que permanecen autenticados entre ejecuciones.
Este es el desglose completo. Qué es Playwright CLI, por qué específicamente supera a Chrome DevTools MCP y Playwright MCP para cargas de trabajo de Claude Code, los tres patrones de producción que ahora uso y las partes feas que nadie menciona en los posts de lanzamiento.
Por Qué Existe Playwright CLI (Y Por Qué No Es Solo Otro MCP)
Microsoft lanzó Playwright CLI a principios de 2026 como un complemento deliberado — no un reemplazo — de Playwright MCP. El servidor MCP todavía existe. Todavía funciona. Pero el equipo notó algo que el resto de nosotros también estábamos notando: cuando un agente de código como Claude Code habla con un navegador a través de MCP, cada interacción con la página envía de vuelta un árbol de accesibilidad completo al contexto del modelo. En una página compleja, ese árbol son 50,000 tokens. Por clic. Por scroll. Por pulsación de tecla.
Multiplica eso por una ejecución de QA de 50 pasos y entenderás por qué mi dashboard de Anthropic estaba gritando.
Playwright CLI invierte el flujo de datos. En lugar de transmitir el árbol de accesibilidad al contexto del modelo, el CLI guarda snapshots en disco como archivos YAML compactos. El modelo lee solo la parte que solicitó, cuando la solicitó. Mismo navegador. Misma API de Playwright debajo. Diferente relación entre el modelo y los datos.
Los números de los benchmarks públicos coinciden con lo que vi en mis propias facturas:
- Playwright MCP: ~1.5M tokens por ejecución de automatización del navegador (peor caso, páginas completas, múltiples turnos)
- Chrome DevTools MCP: ~330K tokens por ejecución (mejor — snapshots con alcance definido, agrupación de llamadas en una sola ejecución)
- Playwright CLI: aproximadamente 4x menos tokens que Playwright MCP para trabajo equivalente
Eso no es una optimización marginal. Es la diferencia entre "puedo ejecutar este agente toda la noche" y "puedo ejecutar este agente durante cuarenta segundos antes de que facturación me diga que pare." Para personas que ya rastrean los costos de tokens agresivamente — y si no lo haces, mi desglose de optimización de tokens de Claude Code vale la pena leerlo antes de construir cualquiera de esto — Playwright CLI es la respuesta a un problema que quizás no sabías que tenías.
Hay una segunda razón por la que importa de la que nadie habla, y me tomó unas cuantas sesiones descubrirla. Playwright CLI es un CLI. No un daemon. No un servidor. No un protocolo. Es un binario que llamas con argumentos. Claude Code es muy bueno llamando binarios con argumentos. Es menos bueno manejando una conexión MCP de larga duración, recuperándose de timeouts de MCP y parseando árboles de accesibilidad que no pidió. Playwright CLI juega a favor de las fortalezas reales de Claude Code — bash, archivos y llamadas de herramientas pequeñas y enfocadas.
Esa alineación es lo que el post de testcollab señaló como la verdadera razón por la que los agentes de código lo prefieren. La eficiencia de tokens es el titular. El ajuste de la herramienta es la sustancia.
La Instalación (Y Por Qué Me Salto la Mitad de la Configuración Recomendada por Microsoft)
Puedes estar ejecutando Playwright CLI dentro de Claude Code en aproximadamente noventa segundos. La instalación es más limpia que Playwright MCP, que solía implicar copiar un fragmento JSON en tu mcp.json y esperar que la cadena de versión no se desviara.
La versión de la instalación que realmente uso:
# Initialize a Playwright project — creates package.json, tsconfig, example tests
npm init playwright@latest
# Install browser binaries (Chromium, Firefox, WebKit + dependencies)
npx playwright install --with-deps
# Verify the CLI works
npx playwright --version
Si quieres que esté disponible en todas partes en lugar de por proyecto:
npm install -g @playwright/cli@latest
playwright-cli install
playwright-cli install-browser
Microsoft también incluye un flag --skills (playwright-cli install --skills) que conecta Playwright al sistema de habilidades de Claude Code. Lo probé. Funciona bien. Pero prefiero hablar con el CLI directamente a través de bash porque le da a Claude superficies de error más claras — cuando algo falla en la capa de habilidades, tienes que debuggear la habilidad y el comando subyacente. Cuando algo falla en la capa del CLI, el stderr te dice exactamente qué pasó.
Una vez instalado, la superficie que Claude Code realmente usa es pequeña:
npx playwright codegen <url>— graba una sesión, produce un script de prueba funcionalnpx playwright test— ejecuta tests (headless por defecto, con interfaz visible usando--headed)npx playwright test --debug— abre el inspector, avanza paso a paso frame por framenpx playwright show-trace trace.zip— revisa una traza grabada después del hecho
El Playwright CLI propiamente dicho (el paquete @playwright/cli) añade un vocabulario diferente orientado a agentes de código — open, goto, click, type, fill, select, check, hover, drag, upload, snapshot, screenshot, close. Claude tiende a componer esos en scripts cortos en lugar de llamarlos uno por uno, lo cual es el instinto correcto.
Ahora la parte que importa: qué construyes realmente con esto.
Patrón 1: El Bucle de QA Autónomo
Este es el caso de uso que me convirtió. Tenía un formulario de onboarding de múltiples páginas — doce preguntas, seis páginas, ramificación condicional en la página cuatro, una pantalla de revisión, un flujo de editar-desde-revisión. Cosas estándar. Roto de formas no estándar.
La lista de bugs cuando empecé la ejecución:
- La tecla Enter no avanzaba el formulario en las páginas con textarea — solo el botón explícito de Siguiente lo hacía
- La página de revisión fallaba al cargar aproximadamente el 20% de las veces, devolviendo un componente en blanco
- El botón de Editar en la página de revisión quedaba bloqueado por una superposición de modal obsoleta si habías cerrado un modal antes en el flujo
Sabía del primero. Los otros dos los descubrí porque dejé al agente ejecutar.
El script que Claude Code escribió para sí mismo, ligeramente limpiado:
import { test, expect } from '@playwright/test';
test('full onboarding flow — 12 questions, 6 pages', async ({ page }) => {
await page.goto('http://localhost:3000/onboarding');
for (let pageNum = 1; pageNum <= 6; pageNum++) {
await page.screenshot({
path: `screenshots/onboarding-page-${pageNum}.png`,
fullPage: true,
});
// Fill whatever inputs exist on this page
const inputs = await page.locator('input, textarea, select').all();
for (const input of inputs) {
const type = await input.getAttribute('type');
if (type === 'email') await input.fill('[email protected]');
else if (type === 'tel') await input.fill('555-0100');
else await input.fill('automated test response');
}
// Advance — explicit button click, not Enter
await page.getByRole('button', { name: /next|continue|review/i }).click();
await page.waitForLoadState('networkidle');
}
await expect(page.getByText(/thank you|submitted/i)).toBeVisible({
timeout: 10_000,
});
});
La instrucción de Claude Code que lo dirigió fueron cuatro oraciones: "Ejecuta el test. Si falla, toma una captura del punto de fallo, lee el código fuente del componente que falló, propón una corrección, aplícala, reinicia el servidor de desarrollo y re-ejecuta. Repite hasta que el test pase tres veces seguidas. No me preguntes sobre nada por debajo de P0."
El agente ejecutó el test. Falló en la página 3 — tecla Enter, el bug que ya conocía. Abrió el componente textarea, encontró el handler onKeyDown que llamaba a event.preventDefault() incondicionalmente, lo restringió para solo prevenir Enter cuando Shift estuviera presionado (para que la entrada multilínea siguiera funcionando), guardó, reinició dev, re-ejecutó. El test pasó la página 3, falló en la página 4 — la página de revisión en blanco. El agente sospechó una carrera entre el cargador de ruta y el hook de estado del formulario, agregó un estado de carga, reintentó. Pasó. Falló en la colisión del modal al editar desde revisión. Escribió un pequeño efecto que limpiaba las superposiciones de modal en cambio de ruta. Pasó tres veces seguidas. Se detuvo. Escribió un resumen en qa-run.md.
Once minutos. Tres bugs reales encontrados y parcheados. Un humano supervisando desde el otro lado de la habitación.
El patrón, destilado:
- Probar — Playwright CLI ejecuta el script
- Detectar — En caso de fallo, captura de pantalla + leer código fuente + formar una hipótesis
- Corregir — Aplicar el parche, reiniciar lo que sea necesario
- Re-probar — Repetir hasta verde N veces seguidas, no solo una
El requisito de "N veces seguidas" está haciendo mucho trabajo. Un test inestable que pasa una vez no está corregido. Tres pases consecutivos es el tamaño de muestra más pequeño donde puedes decir creíblemente que la corrección se mantuvo.
Esto es genuinamente lo más cerca que he visto a Claude Code comportarse como un ingeniero de QA junior que realmente termina el ticket. Si quieres ver cómo este tipo de bucle se compone en un flujo de trabajo de ingeniería más amplio, el post del stack de habilidades de Claude Code desglosa las capas por encima de esta — Superpowers, Skill Creator, el resto. Playwright CLI son los ojos y las manos. Esas habilidades son el cerebro.
Patrón 2: Web Scraping Adaptativo (Cuando Google Decide Que Está Aburrido de Ti)
Diferente trabajo, diferente lección. Un amigo que gestiona un pequeño servicio de marketing dental me preguntó si podía extraer información de contacto — nombre, dirección, teléfono — de cada dentista en algunos códigos postales específicos de California. Tiempo de investigación manual por código postal: de cuatro a seis horas. Información pública, solo tediosa de recopilar.
El primer script que Claude escribió fue el obvio: buscar en Google dentist near 94110, parsear la SERP, visitar cada resultado, extraer el número de teléfono de la página de contacto. Funcionó. Durante unas treinta búsquedas. Luego Google sirvió un CAPTCHA, luego un bloqueo suave, luego un límite de velocidad duro.
El parche fue la parte interesante. Sin que yo lo indicara, Claude agregó tres comportamientos:
import { chromium } from 'playwright';
async function adaptiveSearch(query: string) {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
});
const page = await context.newPage();
// 1. Try Google first
await page.goto(`https://www.google.com/search?q=${encodeURIComponent(query)}`);
// 2. Detect blocking — captcha, "unusual traffic" page, empty results
const blocked = await page.locator('text=/unusual traffic|captcha|are you a robot/i').count();
if (blocked > 0) {
console.log('Google blocked — switching to DuckDuckGo');
await page.goto(`https://duckduckgo.com/?q=${encodeURIComponent(query)}`);
}
// 3. Random jitter between requests so the cadence doesn't look automated
await page.waitForTimeout(2000 + Math.random() * 3000);
return page;
}
Solo ese fallback — Google a DuckDuckGo cuando es bloqueado — llevó el script de "muere después de 30 búsquedas" a "ejecutó durante seis horas sin supervisión." DuckDuckGo no tiene la infraestructura anti-bot de Google, el diseño de la SERP es más simple de parsear y para una consulta como "dentist 94110" la calidad de los resultados es esencialmente equivalente.
La segunda adaptación fue más sutil. El número de teléfono era visible en la SERP para aproximadamente el 70% de los listados de dentistas — Google lo extrae de datos estructurados y lo muestra en línea. El scraper ingenuo felizmente tomaría ese número visible y seguiría adelante. El problema: ese número a veces es un número de seguimiento de marketing, no la línea real del dentista.
Así que Claude actualizó la lógica: incluso cuando un teléfono es visible en la SERP, hacer clic para ir a la página de contacto del propio dentista y tomar el número de ahí. Más lento por registro. Mayor precisión. El tipo de decisión que un humano cuidadoso tomaría y que una automatización descuidada se saltaría.
El script de recopilación completo ejecutó en menos de tres horas, alcanzó ~430 dentistas de California en cinco códigos postales y produjo un CSV con nombre, dirección, teléfono, sitio web y (cuando estaba disponible) horario de atención. Costo en tokens de API, con Playwright CLI manejando el navegador en lugar de MCP: aproximadamente $4.20.
Dos reglas prácticas que ahora aplico a cada trabajo de scraping:
- Siempre ten un motor de búsqueda de respaldo. Google es la mejor fuente hasta que es la peor fuente. Tu script debería detectar la transición sin que tú lo supervises.
- Desconfía de la SERP para cualquier dato que tenga valor comercial. Números de teléfono, precios, horarios — haz clic y verifica. La latencia extra es más barata que una lista de contactos llena de callejones sin salida.
Si estás construyendo algo más complejo que esto, mi post sobre WebMCP para agentes de IA en Chrome cubre la alternativa cuando necesitas características del protocolo específico de Chrome que Playwright no expone.
Patrón 3: Sesiones de Login Persistentes — El Bot Autenticado
Este es el patrón que realmente cambió lo que creo que Claude Code puede hacer.
Hacer scraping de páginas públicas es fácil. Cualquier cosa detrás de un login es la verdadera frontera — y la mayoría del trabajo interesante ocurre detrás de un login. Canales de Slack. Plataformas escolares. Dashboards internos. Productos SaaS que pagas. El desafío no es iniciar sesión una vez. Es mantenerse conectado, entre ejecuciones, entre días, entre reinicios del navegador, sin re-escribir credenciales cada vez.
El contexto persistente de Playwright es la respuesta, y la mayoría de los tutoriales lo explican mal porque confunden storageState con launchPersistentContext. No son lo mismo.
storageState es un snapshot de cookies + localStorage, volcado a un archivo JSON. Bueno para ejecuciones headless de CI donde inicias sesión una vez, guardas el estado y lo reutilizas en cientos de ejecuciones de test.
// Save after a successful login
await page.context().storageState({ path: 'auth.json' });
// Reuse in later runs
const context = await browser.newContext({ storageState: 'auth.json' });
launchPersistentContext es un perfil de navegador real en disco. Cookies, caché, localStorage, IndexedDB, registros de service workers, todo. Esto es lo que quieres cuando necesitas comportarte como un usuario realmente autenticado entre sesiones — no solo un ejecutor de tests autenticado.
import { chromium } from 'playwright';
const userDataDir = '/Users/mejba/.playwright-profiles/school-platform';
const context = await chromium.launchPersistentContext(userDataDir, {
headless: false, // first run only — log in by hand
viewport: { width: 1440, height: 900 },
});
const page = context.pages()[0] ?? await context.newPage();
await page.goto('https://school.example.com');
// Log in manually, complete 2FA, dismiss any onboarding modals
// Then close the browser. The profile is now persisted.
El patrón de traspaso es la parte que me tomó un par de noches dominar. Primera ejecución: con interfaz visible, login manual, 2FA manual, todo-lo-que-el-bot-no-puede-hacer manual. Ejecuciones posteriores: mismo userDataDir, pero headless, y ya estás autenticado. Las cookies, los tokens de sesión, lo de la huella digital del dispositivo — todo vive en disco en ese directorio de perfil.
Para una automatización de plataforma escolar que construí — un script diario que extrae publicaciones marcadas como "logros" por compañeros de clase, las clasifica por recencia y da like a las cinco más recientes — la ejecución se ve así:
import { chromium } from 'playwright';
(async () => {
const context = await chromium.launchPersistentContext(
'/Users/mejba/.playwright-profiles/school-platform',
{ headless: true }
);
const page = await context.newPage();
await page.goto('https://school.example.com/channels/wins');
// Filter by newest — the platform's UI tab
await page.getByRole('tab', { name: 'Newest' }).click();
await page.waitForLoadState('networkidle');
// Scroll until we have 30 posts loaded
for (let i = 0; i < 5; i++) {
await page.mouse.wheel(0, 2000);
await page.waitForTimeout(800);
}
// Like the top 5 — but throttled, because the platform crashed
// when I tried to like 5 in 2 seconds during my first run
const likeButtons = await page
.locator('[data-testid="like-button"]')
.filter({ hasNotText: 'Liked' })
.all();
for (const button of likeButtons.slice(0, 5)) {
await button.click();
await page.waitForTimeout(1500); // throttle
}
await context.close();
})();
El throttle en el bucle está ahí por un bug real que causé. Mi primera versión del script hacía clic en los cinco likes dentro de un Promise.all. El frontend de la plataforma no está construido para manejar cinco mutaciones de like concurrentes de la misma sesión y crasheó el árbol de React a mitad del render. Claude lo descubrió leyendo la captura de pantalla del estado roto, encontrando el overlay de error de React, leyendo el stack trace y decidiendo que la corrección era un delay en lugar de lógica de reintento.
Ese bucle iterativo — romper, capturar pantalla, leer el error, formular hipótesis, corregir, re-ejecutar — es exactamente el mismo bucle que el Patrón 1. Diferente dominio, forma idéntica.
Conectándose a un Navegador Ya en Ejecución (CDP)
Hay una tercera opción para el problema del "login" que vale la pena conocer incluso si no la usas a menudo: conectar Playwright a una instancia de Chrome que tú mismo iniciaste.
Lanza Chrome con el puerto de depuración abierto:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug-profile
Luego haz que Claude Code se conecte desde un script de Playwright:
import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP('http://localhost:9222');
const context = browser.contexts()[0]; // attach to the existing default context
const page = context.pages()[0] ?? await context.newPage();
Cuándo es útil: has navegado manualmente a través de un flujo de autenticación complejo de múltiples pasos (redirecciones SSO, prompts de llave de hardware, captchas) y quieres que el agente tome el control desde donde tú paraste. Modo con interfaz visible, navegador real, cookies reales, sin copia de perfil. La limitación: CDP solo funciona para navegadores basados en Chromium — no Firefox ni WebKit.
Uso esto quizás una de cada veinte automatizaciones. Pero las veces que lo hago, nada más resuelve el mismo problema.
Headed vs Headless — Una Regla, No una Preferencia
El valor por defecto en CI es headless. El valor por defecto durante desarrollo debería ser headed para la primera ejecución de cualquier nueva automatización, luego headless una vez que es estable.
La razón es la asimetría en el debugging. Cuando una ejecución headless falla, tienes una captura de pantalla, una traza y tu imaginación. Cuando una ejecución headed falla, puedes ver el navegador real cometiendo el error real en tiempo real. La diferencia entre esas dos experiencias de debugging es la diferencia entre arreglar un bug en veinte minutos y arreglarlo en tres horas.
El flag es simplemente --headed:
npx playwright test --headed --debug
--debug agrega el inspector — pausa en cada acción, avanza paso a paso, modifica selectores en vivo. Úsalo una vez. Nunca volverás a debuggear Playwright con prints.
La excepción: cada vez que el agente ejecuta sin supervisión, debe ser headless. Las ejecuciones headed necesitan un servidor de display, se matan cuando tu sesión termina y agregan overhead real. El cambio de una línea es lo que quieres — headed durante la construcción, headless durante la ejecución.
Para Qué No Usaré Playwright CLI
Esta es la parte que la mayoría de los posts se saltan. Tres cosas que probé y de las que me retracté.
Debugging pesado de red/rendimiento. Playwright CLI puede capturar logs de red y trazas, pero para debugging serio — comparar tiempos de waterfall, perfilar hot paths de JavaScript, inspeccionar eventos a nivel CDP — Chrome DevTools MCP es genuinamente mejor. La herramienta execute agrupa acciones en una sola llamada y los datos nativos de CDP son más ricos. Mantengo ambos instalados y recurro a DevTools MCP cuando la pregunta es "por qué esta página es lenta" en lugar de "¿funcionó esta página?"
Cualquier cosa dentro de iframes de un origen diferente. Playwright maneja iframes cross-origin, pero la API se pone fea rápido — cadenas de frameLocator, colocación cuidadosa de waitFor y una sospecha permanente de que los selectores no sobrevivirán al próximo deploy. Para widgets de anuncios, flujos embebidos de Stripe/Plaid o popups de login social, o intercepto a nivel de red o simplemente no automatizo ese paso. El análisis costo-beneficio no da.
Flujos de confirmación por email. Playwright felizmente hará clic en el enlace del email si le das el enlace. La parte difícil es obtener el enlace. Las APIs de buzón (Mailtrap, Mailosaur) resuelven esto; Playwright no. Intentar hacer scraping de Gmail para el email de verificación es un camino al dolor.
El resumen honesto: Playwright CLI es la opción correcta por defecto para automatización del navegador dentro de Claude Code en 2026. No es la herramienta correcta para trabajo de rendimiento, flujos exóticos de embeds o plomería de email. Saber dónde están los límites te ahorra aprenderlos a las 2 AM.
Programando los Bots — Modal, Trigger y la Trampa del Cron de Escritorio
Una automatización de navegador que solo se ejecuta cuando recuerdas ejecutarla es una demo. Las automatizaciones de navegador en producción se ejecutan con un horario.
Tres opciones, cada una adecuada a una realidad diferente:
Modal — Python serverless con soporte de primera clase para Playwright. Defines una función con @modal.function(schedule=modal.Cron("0 9 * * *")) y Modal se encarga del contenedor, los binarios del navegador, el aislamiento de ejecución, los logs. Mi bot diario de resumen de noticias corre aquí. Aproximadamente $0.40 por día en cómputo.
Trigger.dev — nativo de TypeScript, el ecosistema JS se siente más cercano si tus scripts de Playwright ya son TS. Su primitiva de browser-task está construida específicamente para cargas de trabajo de Playwright.
Cron de escritorio + headless — para automatizaciones personales en tu propio portátil. Funciona. Se cae en el momento en que tu portátil entra en suspensión, el wifi cambia o el navegador se actualiza. No lo uses para nada que importe.
Empecé todo con cron de escritorio porque era gratis. Migré a Modal después de la tercera ejecución fallida durante un vuelo. Factura mensual total entre cuatro bots programados: menos de $25. Vale la pena.
La Capa de Agente Encima
Una vez que Playwright CLI está conectado y tus primeros tres patrones funcionan, la tentación es seguir construyendo scripts más grandes. No lo hagas. El siguiente paso es construir agentes que llamen a los scripts.
Un agente de resumen diario de noticias que:
- Se despierta a las 8 AM
- Extrae titulares de tres feeds RSS por HTTP plano (sin navegador necesario)
- Le pide a Claude que los resuma y clasifique
- Llama a un script de Playwright CLI para publicar el resumen en un canal de Slack
- Observa el canal buscando respuestas durante los siguientes treinta minutos vía sesión persistente
- Le pide a Claude que redacte respuestas a cualquier cosa que necesite una
- Llama a otro script de Playwright para publicar las respuestas
Cada pieza es pequeña. Las partes del navegador son diminutas — un clic, un escribir, una captura de pantalla, una salida. El razonamiento del agente vive fuera del navegador. El navegador es solo las manos.
Esta separación es el punto central de preferir CLI sobre MCP para estos flujos de trabajo. La capa del navegador debería ser barata, rápida y tonta. La capa de razonamiento debería ser donde van los tokens. Cuando la capa del navegador también está consumiendo tokens — que es lo que Playwright MCP hace, en cada página — las matemáticas dejan de funcionar a cualquier escala no trivial.
El marco más cercano que puedo darte: Playwright CLI es a la automatización del navegador lo que bash es a la automatización del sistema de archivos. Pequeño, afilado, scripteable. Fácil de componer en algo más grande. Olvidable de la manera en que la buena infraestructura debería serlo.
Preguntas Frecuentes
¿Es Playwright CLI un reemplazo de Playwright MCP?
No — Playwright CLI es una herramienta complementaria, no un reemplazo. Usa CLI cuando un agente de código como Claude Code está manejando el navegador; usa MCP cuando un flujo de trabajo de agente autónomo necesita el protocolo estándar MCP. Microsoft mantiene ambos deliberadamente.
¿Cuánto ahorra realmente Playwright CLI en tokens vs Playwright MCP?
Los benchmarks públicos y mis propias ejecuciones muestran aproximadamente 4x menos tokens por sesión con Playwright CLI vs Playwright MCP. Los ahorros vienen de que CLI guarda snapshots en disco como YAML compacto en lugar de transmitir árboles de accesibilidad completos al contexto del modelo en cada interacción.
¿Puede Playwright CLI mantenerme autenticado entre ejecuciones?
Sí — usa chromium.launchPersistentContext(userDataDir, { headless: false }) para el primer login manual, luego ejecuta automatizaciones posteriores con el mismo userDataDir en modo headless. Las cookies, localStorage y tokens de sesión persisten en disco.
¿Cuándo debería usar Chrome DevTools MCP en su lugar?
Recurre a Chrome DevTools MCP cuando la tarea es perfilado de rendimiento, análisis de waterfall de red o cualquier debugging profundo a nivel CDP. También es más eficiente en tokens que Playwright MCP para esas cargas de trabajo específicas. Para automatización directa y bucles de QA, Playwright CLI gana.
¿Puede Claude Code escribir scripts de Playwright desde cero?
Sí — y es confiable. Usa npx playwright codegen <url> para grabar una sesión inicial si quieres sembrar el script, luego deja que Claude lo refine. Para la mayoría de las automatizaciones describo el objetivo en 2-3 oraciones y el script funcional está escrito antes de que termine mi café.
Los Once Minutos Que Cambiaron Cómo Construyo
Volviendo al formulario de onboarding. Once minutos, tres bugs reales encontrados y corregidos, cero pulsaciones de tecla de mi parte. En lo que sigo pensando no es en la velocidad — es en que el bucle se cerró solo. El agente no escribió código y se detuvo. Escribió código, ejecutó el test, vio el fallo, rastreó la causa, aplicó la corrección y re-ejecutó el test hasta que el resultado coincidió con el objetivo.
Ese bucle cerrado es lo que hace que la automatización del navegador finalmente se sienta como infraestructura en lugar de teatro. Playwright CLI no inventó el bucle. Lo hizo lo suficientemente barato para ejecutar.
Elige uno de los tres patrones de este post esta noche. El bucle de QA es el más fácil para empezar — apunta a Claude a cualquier aplicación con muchos formularios que mantengas, dale la instrucción de cuatro oraciones de antes, aléjate por diez minutos. Vuelve. Mira qué encontró. La primera vez que el bucle atrape un bug real que no sabías que existía, entenderás por qué cambié cómo construyo.
Trabajemos Juntos
¿Buscas construir sistemas de IA, automatizar flujos de trabajo o escalar tu infraestructura tecnológica? Me encantaría ayudar.
- Fiverr (builds personalizados e integraciones): fiverr.com/s/EgxYmWD
- Portfolio: mejba.me
- Ramlit Limited (soluciones empresariales): ramlit.com
- ColorPark (diseño y branding): colorpark.io
- xCyberSecurity (servicios de seguridad): xcybersecurity.io