Saltar al contenido

Los agentes de pruebas de Playwright en Claude Code, sin perder el control del código

En la práctica/ 12 min de lectura

Planner, generator y healer: qué escribe cada uno, cómo configurarlos en Claude Code y los tres momentos en que una persona tiene que aprobar.

  • Playwright
  • Claude Code
  • QA
  • IA
En este artículo (6)
  1. 1. Tres agentes, tres tipos de archivo
  2. 2. Instalar los agentes en Claude Code
  3. 3. Un ejemplo completo: el carrito de una tienda
  4. 4. Los tres riesgos, por su nombre
  5. 5. CI: la suite se ejecuta, el healer no
  6. Dónde encaja esto

Pídele a un agente de IA que "escriba pruebas para el carrito" y obtendrás archivos que pasan. Esa es la parte fácil. La difícil es saber si prueban algo, y si la siguiente corrección automática convertirá en silencio un bug real en un build verde.

Desde la versión 1.56, publicada en octubre de 2025, Playwright incluye tres definiciones de agentes para este trabajo: un planner, un generator y un healer. Se ejecutan dentro de Claude Code como subagentes. Aquí explicamos qué produce cada uno, cómo instalarlos, un ejemplo completo con el carrito de una tienda y en qué puntos una persona tiene que revisar antes de fusionar nada. Los comandos y nombres de archivo se verificaron contra la documentación de Playwright y el código de la versión 1.64.1, la más reciente al momento de escribir.

1. Tres agentes, tres tipos de archivo

Los agentes se pueden usar uno a la vez o en secuencia. En secuencia forman un ciclo, y cada paso deja un archivo que puedes leer.

  • Planner. Recibe una petición ("planea el carrito"), una prueba semilla y, si quieres, un documento de requisitos. Ejecuta la prueba semilla para dejar la app en un estado conocido, recorre las páginas con un navegador real y guarda un plan en Markdown en specs/: escenarios, pasos numerados y resultados esperados.
  • Generator. Lee un plan y ejecuta cada paso en vivo en el navegador, comprobando los localizadores y las aserciones sobre la marcha. Después escribe una prueba por escenario en tests/, con un comentario // spec: y otro // seed: al inicio, y el texto de cada paso como comentario antes de cada acción.
  • Healer. Ejecuta la suite, se detiene en una prueba que falla, toma una instantánea de la página y edita la prueba hasta que pasa. Si concluye que la prueba está bien y la app está mal, la marca con test.fixme(), que la omite, y deja un comentario con el motivo.

Una línea de las propias instrucciones del healer explica por qué este artículo dedica casi todo su espacio a la revisión. La definición le dice: "Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test". Es decir: no preguntes, haz lo más razonable para que la prueba pase. Que pase es el objetivo que se le da. Decidir si debe pasar sigue siendo tu trabajo.

Desliza la figura para verla completa

2. Instalar los agentes en Claude Code

Necesitas un proyecto de Playwright Test, es decir, un playwright.config.ts y @playwright/test 1.56 o posterior. Si el repositorio no tiene ninguno de los dos, empieza por ahí. Después genera las definiciones de los agentes:

npm init playwright@latest              # solo si Playwright aún no está configurado
npx playwright init-agents --loop=claude

El comando escribe estos archivos:

  • .claude/agents/playwright-test-planner.md, playwright-test-generator.md y playwright-test-healer.md: subagentes de Claude Code, cada uno con nombre, descripción, lista de herramientas permitidas y model: sonnet.
  • .mcp.json: un servidor MCP llamado playwright-test que ejecuta npx playwright run-test-mcp-server. En Windows se escribe como cmd /c npx playwright run-test-mcp-server. Este servidor les da a los agentes el navegador y el ejecutor de pruebas.
  • specs/README.md, si la carpeta specs/ todavía no existe.
  • Una prueba semilla, seed.spec.ts en tu carpeta de pruebas, pero solo si no existe ya un archivo de prueba con "seed" en el nombre.

Hay dos opciones que conviene conocer. --project <nombre> elige a qué proyecto de tu configuración pertenece la semilla, y --prompts escribe además plantillas de prompts listas en .claude/prompts.

Cuatro detalles nos tomaron por sorpresa, o lo habrían hecho:

  1. Reemplaza .mcp.json. En la versión 1.64.1 el archivo se escribe completo, solo con el servidor playwright-test. Si tu proyecto ya declaraba otros servidores MCP ahí, desaparecen. Haz commit antes de ejecutar el comando, después corre git diff .mcp.json y vuelve a agregar tus servidores.
  2. Hay que volver a ejecutarlo después de cada actualización de Playwright. La documentación lo indica, porque las herramientas y las instrucciones cambian entre versiones. Al volver a ejecutarlo se sobrescriben los archivos de los agentes, así que no los edites. Pon tus reglas en CLAUDE.md o en el prompt.
  3. Claude Code pregunta antes de arrancar un servidor MCP del proyecto. La primera vez que abres el proyecto apruebas playwright-test, y /mcp muestra si está conectado. Si lo rechazas por error, claude mcp reset-project-choices vuelve a hacer la pregunta.
  4. Reinicia Claude Code después de la primera instalación. Claude Code detecta cambios en los subagentes sin reiniciar, pero solo en carpetas que ya existían al iniciar la sesión. Una carpeta .claude/agents/ nueva necesita una sesión nueva.

Una nota sobre la documentación: la sección "Agent Definitions" de la página de Playwright muestra --loop=vscode bajo el título de Claude Code y menciona .github/ como carpeta de los agentes. Eso describe la configuración de VS Code. Para Claude Code el comando es --loop=claude y los archivos van a .claude/agents/.

3. Un ejemplo completo: el carrito de una tienda

La tienda es ilustrativa: un solo producto llamado "Ceramic mug" a $18.00 USD, con el carrito habitual del tema en un diálogo. No es un cliente, y los textos de los botones y los precios son inventados para el ejemplo. Ejecuta los agentes contra un servidor local o una tienda de desarrollo, nunca contra producción. Hacen clic en botones de verdad.

La prueba semilla

La semilla les dice a los agentes dónde empieza la app y sirve de modelo para cada archivo generado. Mantenla corta:

// tests/seed.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Cart', () => {
  test('seed', async ({ page }) => {
    await page.goto('/products/ceramic-mug');
    await expect(page.getByRole('heading', { level: 1, name: 'Ceramic mug' })).toBeVisible();
  });
});

El planner, y la primera revisión

En Claude Code, pide el subagente por su nombre y ponle límites:

Usa el subagente playwright-test-planner para planear el carrito: agregar el producto,
cambiar la cantidad, quitarlo. Semilla: tests/seed.spec.ts. No cubras el checkout.

Un fragmento de lo que guarda como specs/cart.md:

# Ceramic mug store - Cart Test Plan

## Test Scenarios

### 1. Cart

**Seed:** `tests/seed.spec.ts`

#### 1.1 Add a product to the cart

**Steps:**
1. On the "Ceramic mug" product page, click the "Add to cart" button

**Expected Results:**
- A cart dialog opens and lists "Ceramic mug" with quantity 1
- The cart link in the header reports 1 item
- The subtotal shows $18.00

#### 1.3 Remove the only item

**Expected Results:**
- The cart shows "Your cart is empty"

Punto de control 1: aprobar el plan. El plan es el contrato de todo lo que viene después, así que léelo antes de generar nada. Revisa que cada resultado esperado sea algo que una prueba pueda observar y que sea concreto. "El subtotal muestra $18.00" puede fallar; "el carrito se actualiza" no. Revisa que estén las reglas del negocio, como una cantidad máxima o una variante agotada, porque el planner solo sabe lo que vio en pantalla. Borra los escenarios que prueban el contenido de demostración del tema. Después edita el Markdown a mano y haz commit. Un plan que nadie aprobó es una descripción de la app tal como está hoy, con sus bugs incluidos.

El generator, y la segunda revisión

Usa el subagente playwright-test-generator para generar el escenario 1.1 de specs/cart.md.

Un archivo que el generator podría escribir, tests/cart/add-a-product-to-the-cart.spec.ts:

// spec: specs/cart.md
// seed: tests/seed.spec.ts

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

test.describe('Cart', () => {
  test('Add a product to the cart', async ({ page }) => {
    await page.goto('/products/ceramic-mug');

    // 1. On the "Ceramic mug" product page, click the "Add to cart" button
    await page.getByRole('button', { name: 'Add to cart' }).click();

    // - A cart dialog opens and lists "Ceramic mug" with quantity 1
    const cart = page.getByRole('dialog', { name: 'Your cart' });
    await expect(cart.getByRole('link', { name: 'Ceramic mug' })).toBeVisible();
    await expect(cart.getByRole('spinbutton', { name: 'Quantity for Ceramic mug' })).toHaveValue('1');

    // - The cart link in the header reports 1 item
    await expect(page.getByRole('link', { name: /cart/i })).toContainText('1');

    // - The subtotal shows $18.00
    await expect(cart.getByText('Subtotal')).toBeVisible();
  });
});

Punto de control 2: leer cada localizador y cada aserción. Los tres primeros localizadores están bien: un rol más el nombre accesible que un cliente escucha en un lector de pantalla. Las dos últimas líneas son donde falla, y son justo el tipo de cosa que se aprueba con una mirada rápida.

  • getByText('Subtotal') comprueba que existe una etiqueta. Pasa con un subtotal de $0.00, o de $36.00. Cuando un valor no tiene un rol accesible, un test id es la opción honesta: await expect(cart.getByTestId('cart-subtotal')).toHaveText('$18.00');
  • { name: /cart/i } con toContainText('1') coincide con cualquier enlace cuyo nombre contenga "cart", y pasa con 1, 10 o 12 artículos. Fija el nombre accesible exacto que expone tu tema, por ejemplo getByRole('link', { name: 'Cart (1 item)' }).

Busca también lo que no debería estar: clases de CSS, nth-child, ids generados, esperas fijas con waitForTimeout. Después haz que la prueba falle una vez a propósito. Cambia '$18.00' por '$19.00', corre npx playwright test tests/cart y confirma que se pone en rojo. Una prueba que nunca viste fallar no ha demostrado nada.

El healer, y la tercera revisión

Un mes después, el texto del botón cambia de "Add to cart" a "Add to bag". CI se pone en rojo con este error:

Error: locator.click: Test timeout of 30000ms exceeded.
Call log:
  - waiting for getByRole('button', { name: 'Add to cart' })

En local, en una rama:

Usa el subagente playwright-test-healer para arreglar "Add a product to the cart".

El healer ejecuta la prueba, se detiene en el fallo, lee la instantánea de la página, encuentra button "Add to bag" y propone este cambio, y vuelve a ejecutar hasta que pasa:

-    await page.getByRole('button', { name: 'Add to cart' }).click();
+    await page.getByRole('button', { name: 'Add to bag' }).click();

Desliza la figura para verla completa

Punto de control 3: validar cada corrección propuesta. El parche es correcto solo si el cambio de nombre fue intencional. Busca el cambio que lo provocó, como el archivo de idioma del tema o un pull request del equipo de contenido. Si fue intencional, acéptalo y actualiza también specs/cart.md, para que el plan y la prueba digan lo mismo. Si el botón ahora dijera "Sold out" porque falló una sincronización de inventario, el mismo movimiento apuntaría la prueba a una tienda rota, o la marcaría con test.fixme() y la omitiría. En los dos casos el build se pone en verde mientras los clientes no pueden comprar. Solo una persona sabe cuál de los dos casos es. Revisa cuatro cosas en cada diff del healer:

  • ¿Cambió un valor esperado, como un precio, una cantidad o un mensaje? Eso cambia lo que la tienda debe hacer, no cómo la prueba lo encuentra.
  • ¿Amplió un localizador a una expresión regular, como /add to (cart|bag)/i? Acéptalo solo si los dos textos son legítimos, por ejemplo durante una prueba A/B.
  • ¿Agregó test.fixme()? Eso es un reporte de bug. Abre un issue en lugar de fusionar una prueba omitida.
  • ¿Tocó algo fuera de tests/? Corre git status.

4. Los tres riesgos, por su nombre

Pruebas que pasan sin probar nada. Son aserciones sobre etiquetas, comprobaciones de visibilidad sin ningún valor detrás y acciones sin ninguna aserción después. Hay un caso más sutil. El generator comprueba sus aserciones contra lo que la app hace ahora, así que si la app está mal hoy, la prueba registra el bug como comportamiento esperado. El remedio es el punto de control 1: el plan debe salir de los requisitos, no solo de recorrer la app.

Selectores frágiles. Son clases, posiciones e ids que cambian con cada actualización del tema. El texto también es frágil cuando la tienda vende en varios idiomas: "Add to cart" es "Agregar al carrito" en el mercado en español. Ejecuta la suite por idioma con los nombres correctos, o usa un test id para los controles cuyo texto se traduce.

Correcciones que esconden bugs reales. Valores esperados actualizados, pruebas omitidas y timeouts más largos convierten una señal roja en una verde. Un timeout más largo en agregar al carrito puede esconder un problema de rendimiento que los clientes sí notan, como explicamos en por qué el botón de agregar al carrito se siente lento.

5. CI: la suite se ejecuta, el healer no

La regla que usamos es que los agentes corren en la máquina de quien desarrolla y proponen, y CI solo ejecuta. CI usa el flujo estándar de Playwright, sin Claude Code, sin servidor MCP y sin agentes:

- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
- name: Fail on skipped tests that nobody reviewed
  run: |
    if grep -rn "test.fixme(" tests/; then
      echo "test.fixme found: open an issue and review it"; exit 1
    fi

El paso de fixme es nuestro, no forma parte del flujo oficial. Ajústalo a tu política; algunos equipos permiten un fixme que enlace a un issue. Los cambios del healer llegan a la rama principal igual que cualquier otro código: en una rama, en un pull request, con alguien que lee el diff.

No ejecutes el healer sin interfaz desde CI con claude -p. En ese modo Claude Code carga los servidores MCP del proyecto sin pedir aprobación, y el healer tiene herramientas para editar y escribir archivos. Tendrías un build rojo que se pone verde solo, sin que nadie lo vea.

En local, mantén la sesión en el modo de permisos por defecto, para que Claude Code pregunte antes de cada edición. Los subagentes heredan las reglas de permisos de la sesión, así que también puedes trazar un límite firme alrededor del código de la app mientras corriges pruebas, en .claude/settings.local.json:

{
  "permissions": {
    "deny": ["Edit(/src/**)"]
  }
}

Las reglas de Edit se aplican a todas las herramientas integradas que editan archivos. Cambia la ruta a donde vive el código de tu app; en un tema de Shopify son sections/, snippets/, templates/ y las demás.

Dónde encaja esto

Los agentes ahorran el tecleo: recorrer un flujo, escribir localizadores, perseguir un botón renombrado. No deciden lo que la tienda debe hacer. Esa decisión vive en el plan que apruebas, en las aserciones que lees y en las correcciones que aceptas o rechazas. Si quieres ver la sesión completa alrededor de esto, lee mi flujo de 4 pasos con Claude Code. Para saber qué probar en una tienda antes del lanzamiento, lee un QA previo al lanzamiento que sí encuentra errores.

Escrito por

Luis Morales

Luis Morales

Desarrollador frontend y Shopify, EcomLabs

@luismoralesdev en Instagram

Publicado el 22 de octubre de 2026 · actualizado el 23 de octubre de 2026

← Blog