Habilidad de Navegador Dev
Automatización del navegador que mantiene el estado de la página entre ejecuciones de scripts. Escribe scripts pequeños y enfocados para completar tareas de forma incremental. Una vez que hayas probado parte de un flujo de trabajo y haya trabajo repetido por hacer, puedes escribir un script para realizar ese trabajo repetido en una sola ejecución.
Elegir tu Enfoque
- Sitios locales/con código fuente disponible: Lee el código fuente primero para escribir selectores directamente
- Diseños de página desconocidos: Usa
getAISnapshot()para descubrir elementos yselectSnapshotRef()para interactuar con ellos - Retroalimentación visual: Toma capturas de pantalla para ver lo que el usuario ve
Configuración
Dos modos disponibles. Pregunta al usuario si no está claro cuál usar.
Modo Independiente (Predeterminado)
Inicia un nuevo navegador Chromium para sesiones de automatización frescas.
./skills/dev-browser/server.sh &
Añade la bandera --headless si el usuario lo solicita. Espera el mensaje Ready antes de ejecutar scripts.
Modo Extensión
Se conecta al navegador Chrome existente del usuario. Úsalo cuando:
- El usuario ya ha iniciado sesión en sitios y quiere que hagas cosas detrás de una experiencia autenticada que no sea de desarrollo local.
- El usuario te pide que uses la extensión
Importante: El flujo principal sigue siendo el mismo. Creas páginas con nombre dentro de su navegador.
Inicia el servidor relay:
cd skills/dev-browser && npm i && npm run start-extension &
Espera Waiting for extension to connect... seguido de Extension connected en la consola. Para saber que un cliente se ha conectado y el navegador está listo para ser controlado.
Flujo de trabajo:
- Los scripts llaman a
client.page("nombre")igual que en el modo normal para crear nuevas páginas / conectarse a las existentes. - La automatización se ejecuta en la sesión real del navegador del usuario
Si la extensión aún no se ha conectado, dile al usuario que la lance y active. Enlace de descarga: https://github.com/SawyerHood/dev-browser/releases
Escribir Scripts
Ejecuta todos los scripts desde el directorio
skills/dev-browser/. El alias de importación@/requiere la configuración de este directorio.
Ejecuta scripts en línea usando heredocs:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect, waitForPageLoad } from "@/client.js";
const client = await connect();
// Crea una página con tamaño de viewport personalizado (opcional)
const page = await client.page("ejemplo", { viewport: { width: 1920, height: 1080 } });
await page.goto("https://example.com");
await waitForPageLoad(page);
console.log({ title: await page.title(), url: page.url() });
await client.disconnect();
EOF
Escribe en archivos tmp/ solo cuando el script necesite reutilizarse, sea complejo o el usuario lo solicite explícitamente.
Principios Clave
- Scripts pequeños: Cada script hace UNA cosa (navegar, hacer clic, rellenar, comprobar)
- Evaluar estado: Registra/retorna el estado al final para decidir los siguientes pasos
- Nombres de página descriptivos: Usa
"checkout","login", no"principal" - Desconectar para salir:
await client.disconnect()- las páginas persisten en el servidor - JS plano en evaluate:
page.evaluate()se ejecuta en el navegador - sin sintaxis de TypeScript
Bucle de Flujo de Trabajo
Sigue este patrón para tareas complejas:
- Escribe un script para realizar una acción
- Ejecútalo y observa la salida
- Evalúa - ¿Funcionó? ¿Cuál es el estado actual?
- Decide - ¿La tarea está completa o necesitamos otro script?
- Repite hasta que la tarea esté terminada
Sin TypeScript en el Contexto del Navegador
El código pasado a page.evaluate() se ejecuta en el navegador, que no entiende TypeScript:
// ✅ Correcto: JavaScript plano
const text = await page.evaluate(() => {
return document.body.innerText;
});
// ❌ Incorrecto: La sintaxis de TypeScript fallará en tiempo de ejecución
const text = await page.evaluate(() => {
const el: HTMLElement = document.body; // ¡La anotación de tipo falla en el navegador!
return el.innerText;
});
Extracción de Datos
Para extraer grandes conjuntos de datos, intercepta y reproduce solicitudes de red en lugar de desplazarte por el DOM. Consulta references/scraping.md para la guía completa que cubre captura de solicitudes, descubrimiento de esquemas y reproducción de API paginada.
API del Cliente
const client = await connect();
// Obtener o crear una página con nombre (viewport solo aplica a páginas nuevas)
const page = await client.page("nombre");
const pageWithSize = await client.page("nombre", { viewport: { width: 1920, height: 1080 } });
const pages = await client.list(); // Listar todos los nombres de páginas
await client.close("nombre"); // Cerrar una página
await client.disconnect(); // Desconectar (las páginas persisten)
// Métodos de instantánea ARIA
const snapshot = await client.getAISnapshot("nombre"); // Obtener árbol de accesibilidad
const element = await client.selectSnapshotRef("nombre", "e5"); // Obtener elemento por ref
El objeto page es una página estándar de Playwright.
Espera
import { waitForPageLoad } from "@/client.js";
await waitForPageLoad(page); // Después de la navegación
await page.waitForSelector(".resultados"); // Para elementos específicos
await page.waitForURL("**/exito"); // Para URL específica
Inspeccionar el Estado de la Página
Capturas de Pantalla
await page.screenshot({ path: "tmp/captura.png" });
await page.screenshot({ path: "tmp/completa.png", fullPage: true });
Instantánea ARIA (Descubrimiento de Elementos)
Usa getAISnapshot() para descubrir elementos de la página. Retorna un árbol de accesibilidad en formato YAML:
- banner:
- enlace "Hacker News" [ref=e1]
- navegación:
- enlace "new" [ref=e2]
- main:
- lista:
- elemento de lista:
- enlace "Título del Artículo" [ref=e8]
- enlace "328 comentarios" [ref=e9]
- contentinfo:
- cuadro de texto [ref=e10]
- /placeholder: "Buscar"
Interpretación de refs:
[ref=eN]- Referencia de elemento para interacción (solo elementos visibles y cliqueables)[checked],[disabled],[expanded]- Estados del elemento[level=N]- Nivel de encabezado/url:,/placeholder:- Propiedades del elemento
Interactuando con refs:
const snapshot = await client.getAISnapshot("hackernews");
console.log(snapshot); // Encuentra la ref que necesitas
const element = await client.selectSnapshotRef("hackernews", "e2");
await element.click();
Recuperación de Errores
El estado de la página persiste después de fallos. Depura con:
cd skills/dev-browser && npx tsx <<'EOF'
import { connect } from "@/client.js";
const client = await connect();
const page = await client.page("hackernews");
await page.screenshot({ path: "tmp/debug.png" });
console.log({
url: page.url(),
title: await page.title(),
bodyText: await page.textContent("body").then((t) => t?.slice(0, 200)),
});
await client.disconnect();
EOF