ego-browser
ego-browser expone un navegador Chromium real a través de un tiempo de ejecución Node.js accesible por CLI. Sus fachadas precargadas page, page.locator(...), browser y taskSpaces siguen los nombres y formas de llamada de Playwright; taskSpaces, site, fetch y cdp proporcionan capacidades específicas de ego-browser.
Para problemas de configuración, instalación o conexión, lea references/install.md.
Ejecute el trabajo del navegador con la herramienta Bash como ego-browser nodejs <<'EOF' ... EOF. Coloque el JavaScript directamente en el heredoc; no cree un archivo .js, importe Playwright, inicie otro navegador ni invente nombres de ayuda.
Un heredoc es solo el contenedor de JavaScript; la invocación de Bash es la ronda de ejecución. De forma predeterminada, utilice una invocación de Bash para toda la tarea del navegador. Cada await es una operación interna, no un límite de paso. Antes del lanzamiento, codifique cada observación, acción, espera, extracción, verificación y alternativa acotada predecible en el script. Utilice los resultados del navegador inmediatamente en JavaScript y siga adaptándose dentro del proceso hasta que la tarea se complete; no salga solo para inspeccionar la salida intermedia o planificar la siguiente acción. Inicie otro comando de Bash solo para el control de usuario o externo requerido, una inspección visual que no pueda ocurrir dentro del proceso, o una falla a nivel de proceso que el script no pueda recuperar.
Elija la ruta confiable con menos estado antes de inspeccionar los controles de la página. Cuando la tarea especifica un resultado o restricciones pero no una interacción requerida, prefiera un estado ya correcto o una URL estable o ruta del sitio conocida que los codifique directamente; verifique el estado de la meta resultante en lugar de repetir filtros, ordenamientos o navegación equivalentes a través de la UI. Use controles de página cuando el usuario haya solicitado esa interacción, la interacción en sí misma esté bajo prueba, o no se conozca un equivalente confiable. Nunca invente una ruta frágil.
Trate una condición posterior ya satisfecha como trabajo completado. Antes de manipular un control cuyo valor requerido puede ya ser visible, realice solo la lectura más pequeña necesaria para decidir ese estado. Si coincide, no abra su editor, repita la interacción ni lo lea nuevamente; continúe directamente a los resultados insatisfechos restantes. Palabras como "establecer", "seleccionar" o "asegurar" describen el estado final requerido a menos que el usuario requiera explícitamente la transición o la interacción en sí misma esté bajo prueba.
Separe el trabajo del navegador de la finalización del terminal. useOrCreate inicia o reanuda un objetivo de usuario; mantenga su task.id devuelto y reutilice el mismo id o exactamente el mismo nombre hasta que ese objetivo sea terminal. Mantenga cada observación, acción, espera, extracción y verificación predecible compacta, pero no llame a taskSpaces.complete(...) en una invocación de Bash que aún está determinando si el objetivo está satisfecho. Primero termine el trabajo del navegador e imprima evidencia de que cada resultado solicitado y cualquier límite de alcance o cobertura requerido ha sido probado. Solo después de revisar esa salida anterior, una invocación de Bash final dedicada puede completar el espacio de tarea original; no realiza trabajo de page o browser. Esta única confirmación de ciclo de vida es la excepción a la regla de una invocación predeterminada, no un paso o ronda del navegador. Los resultados parciales no vacíos o plausibles, una página estancada, reintentos agotados o un intento alternativo no son evidencia de finalización. keep: true conserva un resultado terminal para el usuario; no mantiene una tarea inacabada con vida.
Congele la ventana de tiempo para el trabajo con fecha actual o relativa. Establezca "hoy/actual/más reciente" una vez desde el usuario/entorno de tarea o el estado de página actual explícitamente verificado antes de recopilar registros. Trate las marcas de tiempo de contenido como datos, no como el reloj. Los registros más antiguos revelados por desplazamiento, virtualización, recarga, caché o un lote de resultados cambiado no deben reemplazar ese anclaje. Continúe evaluando los registros contra la ventana original; no reubique silenciosamente la tarea a la fecha de contenido más nueva observada.
Inicio rápido
Cada ejemplo en esta habilidad es deliberadamente compuesto. Adapte su URL, selectores y datos a la tarea del usuario.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('inspect example page')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 20000 })
const heading = await page.getByRole('heading').first().innerText()
const info = await page.info()
if (!heading || !('url' in info)) throw new Error('Example page was not ready')
const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
Mantenga todo el trabajo predecible dentro del script hasta que la tarea esté completa. Emita los resultados finales con console.log(...).
Patrones compuestos
Extraer, elegir, navegar, verificar
En una página de lista o búsqueda, extraiga candidatos estructurados antes de elegir. Mantenga la extracción, elección, acción, espera, verificación y limpieza en una invocación de Bash.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('compare search results')
await browser.openOrReuseTab('https://example.com/search?q=browser+automation', {
wait: true,
timeout: 20000,
})
const cards = page.locator('article')
const items = await cards.evaluateAll((nodes) =>
nodes.map((node) => ({
title: node.querySelector('h2')?.textContent?.trim(),
href: node.querySelector('a')?.href,
})),
)
const chosenIndex = items.findIndex((item) => item.title && item.href)
if (chosenIndex < 0) throw new Error('No usable result: ' + JSON.stringify(items))
const before = await page.url()
const navigation = page.waitForURL((url) => url.href !== before, { timeout: 15000 })
await cards.nth(chosenIndex).getByRole('link').first().click()
if (!(await navigation)) throw new Error('Chosen result did not navigate')
const info = await page.info()
if (!('url' in info) || info.url === before) throw new Error('Navigation was not verified')
const result = { chosen: items[chosenIndex], opened: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
Llenar, activar, esperar, leer de vuelta
Registre las esperas de solicitud/respuesta antes de la acción que las activa, luego verifique el estado de la página resultante en lugar de tratar el clic como éxito.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('search orders')
await browser.openOrReuseTab('https://example.com/orders', { wait: true, timeout: 20000 })
const responsePromise = page.waitForResponse(
(response) => response.url().includes('/api/orders') && response.ok(),
{ timeout: 15000 },
)
await page.getByLabel('Search orders').fill('pending')
await page.getByRole('button', { name: /search/i }).click()
const response = await responsePromise
const rows = await page.locator('table tbody tr').allInnerTexts()
if (!rows.length) throw new Error('Search completed but returned no visible rows')
const result = { status: response.status(), rows }
console.log(JSON.stringify(result, null, 2))
EOF
Actualizar identificadores de pestañas, cambiar, inspeccionar
Trate targetId como un identificador de corta duración. Descúbralo, valídelo y utilícelo en la misma invocación de Bash.
ego-browser nodejs <<'EOF'
const task = await taskSpaces.useOrCreate('review generated report')
const tabs = await browser.listTabs({ includeChrome: false })
const reportTab = tabs.find((tab) => tab.url.includes('/reports/'))
if (!reportTab?.targetId) throw new Error('Report tab not found: ' + JSON.stringify(tabs))
await browser.switchTab(reportTab.targetId)
const info = await page.info()
const heading = await page.getByRole('heading').first().innerText()
if (!('url' in info) || !info.url.includes('/reports/')) throw new Error('Wrong tab selected')
const result = { taskSpaceId: task.id, heading, url: info.url }
console.log(JSON.stringify(result, null, 2))
EOF
Mapa de tiempo de ejecución
page: navegación y estado (goto,reload,url,title,info), localizadores semánticos, esperas,snapshot,screenshot,screencast,evaluate,keyboard,mouse, descargas y drenaje de eventos.page.locator(selector): encadenamiento y filtrado;first/nth/last; métodos de clic, hover,dragTo,scrollIntoViewIfNeeded, formulario, teclado, carga, lectura de estado, colección, evaluación de elementos, captura de pantalla y espera.browser:listTabs,currentTab,switchTab,openOrReuseTab,closeTab,ensureRealTab,iframeTarget.taskSpaces:list,switch,new,useOrCreate,claim,complete,handOff,takeOver,waitForAgentControl.fetch.serverrealiza solicitudes del lado de Node;fetch.browserrealiza solicitudes en el origen de la página actual. Usecdpsolo como una escotilla de escape.console.loges el canal de salida. Useconsole.log(help('page')),console.log(help('locator'))u otra llamadahelp(name)cuando una firma exacta no esté clara.
Reglas de ejecución
page.url()es asíncrono en ego-browser; siempre useawait page.url(). Un predicadopage.waitForURL(...)recibe un objetoURL, así que inspeccioneurl.href,url.pathnameourl.searchParams. Esperaloadde forma predeterminada; usewaitUntil: 'commit'solo cuando intencionalmente proceda antes de la carga.page.waitForURL,page.waitForLoadState,page.waitForSelector, elwaitFordel localizador ypage.waitForFunctiondevuelven un valor falso en caso de tiempo de espera. Verifique el resultado o verifique inmediatamente el estado requerido antes de continuar.- Registre las esperas de solicitud, respuesta o navegación antes de la acción que las activa. Prefiera las esperas basadas en estado; use
page.waitForTimeout(...)solo para un breve asentamiento visual y manténgalo en o por debajo de 2000 ms. - Prefiera localizadores semánticos estables. Cuando la estructura de la página sea desconocida, recolecte los controles o candidatos relevantes una vez con
evaluateAll,allInnerTextsu otra lectura acotada, derive las siguientes acciones en JavaScript y continúe en el mismo heredoc en lugar de enumerar conjeturas de selectores entre comandos. - Las acciones de un solo elemento y las lecturas requeridas, incluidos los localizadores CSS sin procesar y
xpath=sin procesar, son estrictos y tienen autoespera. Para cero coincidencias, confirme el estado de carga, la pestaña activa y el estado de modal/superposición antes de corregir el localizador. Para múltiples coincidencias, inspeccionecount()/allInnerTexts(), reduzca semánticamente o confilter(...), y usefirst()/nth()solo después de confirmar que los duplicados son legítimos. Deje que una acción exitosa lleve el script hacia adelante; lea el estado cuando determine una rama y una vez para las condiciones posteriores finales requeridas de la tarea, no después de cada acción. Un estado requerido ya satisfecho no necesita repetición. - En caso de falla, use una observación dirigida para cambiar la estrategia materialmente. No repita localizadores o comandos casi idénticos; cambie a una ruta semántica, DOM o visual estable basada en la evidencia.
- Preserve las transiciones visibles para el usuario solicitadas explícitamente y los límites de parada. Cuando un clic requerido pueda navegar la pestaña actual o abrir otra, haga clic una vez y resuelva el resultado a partir de
await page.url()más unbrowser.listTabs()actualizado en el mismo script; no reemplace el clic con navegación directa solo porque su destino es conocido. No trague fallas de acciones requeridas.
Espacios de tarea
Un espacio de tarea es un contexto de navegación aislado con sus propias pestañas que hereda el estado de inicio de sesión del usuario. Selecciónelo una vez cerca del inicio del primer script de Bash con taskSpaces.useOrCreate(nameOrId). Si una dependencia externa hace que un comando posterior sea inevitable, seleccione el mismo task.id numérico devuelto o el mismo nombre corto de objetivo exacto antes de continuar; cree un nuevo espacio solo para un objetivo de usuario separado. Preserve los hechos ya verificados entre comandos en lugar de reiniciar la configuración.
useOrCreate reutiliza o crea espacios propiedad del agente. Si el espacio coincidente es propiedad del usuario, selecciona el espacio sin reclamarlo, por lo que el trabajo del navegador llega a una parada brusca de control de usuario. Después de la confirmación explícita del usuario para trabajar allí, use taskSpaces.list() → taskSpaces.claim(id) → browser.listTabs() → browser.switchTab(targetId).
Cada espacio tiene ownership: 'agent' | 'agentDelegatedToUser' | 'user':
| Operación en un espacio propiedad del usuario | Comportamiento |
|---|---|
taskSpaces.switch | Lanza; solo cambia espacios propiedad del agente |
taskSpaces.claim | Transfiere la propiedad al agente y selecciona el espacio |
taskSpaces.handOff / complete(..., { keep: true }) | Omite con { done: false, skipped: 'user-owned' } |
taskSpaces.complete(..., { keep: false }) | Reclama, luego cierra el espacio |
taskSpaces.takeOver / waitForAgentControl | No realiza verificación de propiedad |
Verifique el resultado done de handOff y complete antes de reclamar éxito.
Trate la finalización como una confirmación terminal separada de la ejecución del navegador. Termine la invocación de Bash en curso sin finalización después de capturar e imprimir la URL final, los valores y otra evidencia. Revise esa salida: cada condición posterior solicitada y cualquier límite de alcance o cobertura requerido debe estar probado, no solo ser probable. Si están probados, ejecute una invocación de Bash final dedicada que llame a taskSpaces.complete(nameOrId, { keep }) como máximo una vez para el id original o nombre exacto, verifique done y no realice trabajo de page o browser. Si algo no se cumple o no se prueba, continúe en ese mismo espacio de tarea original; una corrección, reintento o fase posterior no es un nuevo objetivo. keep es obligatorio. El valor predeterminado es false; use true solo cuando el usuario haya solicitado mantener la página terminada, deba actuar manualmente en ella, o el resultado no se pueda entregar como URL, archivo, artefacto o resumen. Cierre las pestañas de borrador a medida que avanza y conserve solo las pestañas que el usuario necesita.
Nunca codifique, copie manualmente ni cambie el nombre de un targetId a id. Obténgalo y utilícelo dentro de la invocación de Bash actual. Si otro comando es genuinamente necesario, actualice browser.listTabs() y valide los resultados de find(...) antes de cambiar o cerrar. browser.iframeTarget(...) devuelve una cadena de id de destino o null, no un objeto.
Transferencia de control
Un error de "el usuario está controlando", "inactivo" o "no asignado" es una parada brusca para toda la tarea. No reintente, lo evite ni llame a taskSpaces.takeOver automáticamente. Pregunte al usuario y espere.
Para inicio de sesión, captcha u otro paso manual, termine
...
[truncado para traducción]