chore: initialize browser tool devlog from project root
This commit is contained in:
commit
420c6c85bb
28 changed files with 7891 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
node_modules/
|
||||
dist/
|
||||
artifacts/
|
||||
playwright-report/
|
||||
test-results/
|
||||
.opencode/
|
||||
101
README.md
Normal file
101
README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# OpenCode Browser Tool
|
||||
|
||||
Herramienta browser externa para OpenCode, pensada para navegar e interactuar con aplicaciones web desde un navegador real controlado por `Playwright + Chromium`, con integracion por `MCP`.
|
||||
|
||||
## Estado actual
|
||||
|
||||
Proyecto en preparacion de la v1.
|
||||
|
||||
La prioridad actual es lograr una integracion efectiva entre OpenCode y el browser manteniendo la solucion totalmente desacoplada del core de OpenCode.
|
||||
|
||||
## Onboarding recomendado para agentes nuevos
|
||||
|
||||
Al iniciar una sesion en un equipo nuevo, pide primero:
|
||||
|
||||
- `browser_help` para descubrir capacidades, defaults y ejemplos
|
||||
- `browser_health` para ver estado operativo, actividad y artifacts recientes
|
||||
|
||||
Default relevante en v1:
|
||||
|
||||
- `browser_report` usa `saveToFile=true` por defecto (configurable de forma persistente con `browser_config`)
|
||||
|
||||
## Objetivo de la v1
|
||||
|
||||
- exponer una tool browser externa por `MCP`
|
||||
- abrir `Chromium` en modo visible por defecto
|
||||
- permitir navegacion e interaccion base con apps locales
|
||||
- recoger evidencia minima util para diagnostico
|
||||
|
||||
## Stack fijado para la v1
|
||||
|
||||
- `Node.js 20+`
|
||||
- `TypeScript`
|
||||
- `Playwright`
|
||||
- `Chromium`
|
||||
- `MCP` por `stdio`
|
||||
|
||||
## Modos de navegador en runtime
|
||||
|
||||
- `testing`: navegador gestionado por Playwright (default)
|
||||
- `system`: navegador del sistema (ej. `/usr/bin/google-chrome`)
|
||||
|
||||
Ambos modos pueden abrir con perfil efimero o perfil persistente (`userDataDir`) segun configuracion y parametros de `browser_open`.
|
||||
|
||||
Defaults operativos relevantes:
|
||||
|
||||
- `recordVideo=false` (grabacion bajo demanda)
|
||||
- `verbose=true` y `verboseOverlay=true` en modo visible
|
||||
- delay humano de interaccion `1000-3000ms` para acciones click-like
|
||||
|
||||
## Estructura actual
|
||||
|
||||
```text
|
||||
opencode-browser-tool/
|
||||
artifacts/
|
||||
config/
|
||||
docs/
|
||||
scripts/
|
||||
src/
|
||||
browser/
|
||||
tools/
|
||||
types/
|
||||
check.sh
|
||||
install.sh
|
||||
opencode.mcp.example.json
|
||||
package.json
|
||||
README.md
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
## Documentacion principal
|
||||
|
||||
- `docs/PLAN_DE_DESARROLLO.md`
|
||||
- `docs/TODO.md`
|
||||
- `docs/manual_de_uso_heramienta.md`
|
||||
- `docs/QUICKSTART.md`
|
||||
- `docs/VALIDACION_ENTORNO_LIMPIO.md`
|
||||
- `docs/PLAN_CIERRE_INSTALABLE.md`
|
||||
- `docs/REGISTRO_SITUACIONES.md`
|
||||
- `docs/contexto_workspace/` (snapshot de docs globales clave para continuidad)
|
||||
|
||||
## Instalacion prevista
|
||||
|
||||
La idea es que este proyecto pueda copiarse o descargarse en un PC con OpenCode y dejarse listo mediante:
|
||||
|
||||
- `./install.sh`
|
||||
- una guia de instalacion clara
|
||||
- o instrucciones que un agente de OpenCode pueda ejecutar
|
||||
|
||||
### Que hace `install.sh` con Chromium
|
||||
|
||||
El script instala dependencias y luego ejecuta:
|
||||
|
||||
- `npx playwright install chromium`
|
||||
|
||||
Esto instala el `Chromium managed by Playwright` para garantizar compatibilidad estable en la v1.
|
||||
|
||||
Si el sistema ya tiene un Chromium propio, en esta fase igualmente se usa el gestionado por Playwright.
|
||||
|
||||
## Nota
|
||||
|
||||
Este proyecto debe seguir siendo externo a OpenCode para que las actualizaciones del propio OpenCode no afecten a esta herramienta.
|
||||
33
check.sh
Executable file
33
check.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
printf 'Verificando proyecto en %s\n' "$SCRIPT_DIR"
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
printf 'Node.js no encontrado.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
printf 'npm no encontrado.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf 'Node: '
|
||||
node --version
|
||||
printf 'npm: '
|
||||
npm --version
|
||||
|
||||
if [ -d "$SCRIPT_DIR/node_modules" ]; then
|
||||
printf 'Dependencias instaladas: si\n'
|
||||
else
|
||||
printf 'Dependencias instaladas: no\n'
|
||||
fi
|
||||
|
||||
if [ -d "$SCRIPT_DIR/artifacts" ]; then
|
||||
printf 'Carpeta artifacts: lista\n'
|
||||
else
|
||||
printf 'Carpeta artifacts: ausente\n'
|
||||
fi
|
||||
16
config/browser-tool.config.json
Normal file
16
config/browser-tool.config.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"browser": {
|
||||
"defaultKind": "testing",
|
||||
"systemExecutablePath": "/usr/bin/google-chrome",
|
||||
"defaultPersistentProfile": false,
|
||||
"defaultUserDataDir": null,
|
||||
"defaultVerbose": true,
|
||||
"defaultVerboseOverlay": true,
|
||||
"defaultInteractionDelayMinMs": 1000,
|
||||
"defaultInteractionDelayMaxMs": 3000
|
||||
},
|
||||
"report": {
|
||||
"defaultSaveToFile": true,
|
||||
"defaultFormat": "json"
|
||||
}
|
||||
}
|
||||
59
docs/PLAN_CIERRE_INSTALABLE.md
Normal file
59
docs/PLAN_CIERRE_INSTALABLE.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Plan de cierre - Instalable v1
|
||||
|
||||
Objetivo: dejar `opencode-browser-tool` listo para puesta en marcha facil en otro PC con OpenCode, sin rutas fijas.
|
||||
|
||||
## Orden de ejecucion acordado
|
||||
|
||||
1. Ejecutar una prueba funcional final (smoke de aceptacion).
|
||||
2. Cerrar empaquetado/instalacion portable para Linux.
|
||||
3. Ejecutar validacion en entorno limpio con checklist.
|
||||
4. Documentar estado final y criterios de cierre v1.
|
||||
|
||||
## Alcance de cierre (Linux primero)
|
||||
|
||||
- Instalacion robusta con `install.sh` + verificaciones claras.
|
||||
- Configuracion MCP sin rutas hardcodeadas.
|
||||
- Defaults seguros por maquina (`testing` por defecto, perfil persistente opcional).
|
||||
- Soporte de rutas variables:
|
||||
- directorio del proyecto
|
||||
- binario de navegador del sistema
|
||||
- ruta de perfil persistente
|
||||
- ubicacion de OpenCode
|
||||
- Guia de arranque para operario en equipo nuevo.
|
||||
|
||||
## Checklist tecnico de empaquetado
|
||||
|
||||
- `install.sh`:
|
||||
- valida `node` y `npm`
|
||||
- instala dependencias
|
||||
- compila TypeScript
|
||||
- instala navegador testing de Playwright
|
||||
- emite mensaje final de estado
|
||||
- `check.sh`:
|
||||
- valida build
|
||||
- valida carga de config
|
||||
- valida arranque basico MCP
|
||||
- Plantilla MCP:
|
||||
- sin rutas absolutas fijas
|
||||
- instrucciones para reemplazar path local en cada PC
|
||||
- Configuracion runtime:
|
||||
- perfil persistente configurable por ruta local
|
||||
- navegador `system` opcional por ruta local
|
||||
|
||||
## Validacion en entorno limpio
|
||||
|
||||
Usar como referencia operativa:
|
||||
|
||||
- `docs/VALIDACION_ENTORNO_LIMPIO.md`
|
||||
|
||||
Criterio de aceptacion:
|
||||
|
||||
- instalacion completa sin error
|
||||
- MCP conectado en OpenCode
|
||||
- prueba minima de apertura/navegacion/snapshot
|
||||
- prueba Google objetivo completable
|
||||
|
||||
## Nota sobre Windows (evaluacion pendiente)
|
||||
|
||||
La v1 se cierra primero en Linux.
|
||||
Despues se evaluara soporte Windows con enfoque cross-platform (scripts npm + Node).
|
||||
368
docs/PLAN_DE_DESARROLLO.md
Normal file
368
docs/PLAN_DE_DESARROLLO.md
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
# Plan de desarrollo - Browser Tool para OpenCode
|
||||
|
||||
## Proyecto
|
||||
|
||||
Nombre del proyecto: `opencode-browser-tool`
|
||||
|
||||
Objetivo general:
|
||||
|
||||
Construir una herramienta browser externa a OpenCode que permita a un agente navegar, inspeccionar e interactuar con aplicaciones web de forma visible por defecto, con soporte posterior para ejecucion headless y capacidades avanzadas de diagnostico.
|
||||
|
||||
La herramienta debe quedar desacoplada del nucleo de OpenCode para que futuras actualizaciones de OpenCode no rompan ni obliguen a rehacer la solucion.
|
||||
|
||||
---
|
||||
|
||||
## Resultado esperado
|
||||
|
||||
Al terminar el proyecto, OpenCode debe poder usar la herramienta como una tool externa para:
|
||||
|
||||
- abrir un navegador controlado por la herramienta
|
||||
- entrar en aplicaciones locales o webs externas autorizadas
|
||||
- interactuar con la interfaz como un usuario real
|
||||
- inspeccionar DOM, consola y red
|
||||
- recoger evidencia de ejecucion
|
||||
- devolver resultados estructurados al agente
|
||||
|
||||
---
|
||||
|
||||
## Criterios estructurales fijados
|
||||
|
||||
- La solucion debe ser externa a OpenCode.
|
||||
- El browser y su logica no deben vivir dentro del core de OpenCode.
|
||||
- La integracion con OpenCode debe hacerse por `MCP`.
|
||||
- La primera version debe priorizar `visible` como modo por defecto.
|
||||
- `headless` quedara disponible para escenarios tecnicos donde la UI visible no sea el foco principal.
|
||||
- La base tecnologica inicial sera `Playwright + Chromium`.
|
||||
- `CDP` se considerara como capacidad complementaria y progresiva, no como dependencia central de la v1.
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura objetivo por fases
|
||||
|
||||
### Fase 1 - Arquitectura simple funcional
|
||||
|
||||
Flujo:
|
||||
|
||||
`OpenCode -> MCP server externo -> Playwright -> Chromium`
|
||||
|
||||
Caracteristicas:
|
||||
|
||||
- MCP server externo ejecutado fuera del core de OpenCode
|
||||
- Playwright como motor principal de automatizacion
|
||||
- Chromium como navegador base
|
||||
- gestion simple de sesiones de browser
|
||||
- artefactos locales: screenshots, logs, traces y video cuando aplique
|
||||
|
||||
Ventaja principal:
|
||||
|
||||
Permite llegar rapido a una version util sin comprometer la evolucion futura.
|
||||
|
||||
### Fase 2 - Arquitectura modular
|
||||
|
||||
Flujo previsto:
|
||||
|
||||
`OpenCode -> MCP server externo -> backend/orquestador interno -> runner browser`
|
||||
|
||||
Caracteristicas previstas:
|
||||
|
||||
- separacion entre capa MCP y capa operativa del navegador
|
||||
- gestion mas fuerte de sesiones, colas, perfiles y artefactos
|
||||
- posibilidad de varios runners o modos de ejecucion
|
||||
- mejor punto de extension para seguridad, politicas y control de uso
|
||||
|
||||
### Fase 3 - Diagnostico avanzado
|
||||
|
||||
Capacidades previstas:
|
||||
|
||||
- ganchos `CDP` para inspeccion avanzada de Chromium
|
||||
- mas telemetria y diagnostico de red, runtime y rendimiento
|
||||
- capacidades de analisis fino de estados del navegador durante pruebas complejas
|
||||
|
||||
---
|
||||
|
||||
## Alcance funcional de la v1
|
||||
|
||||
La v1 debe resolver correctamente la interaccion efectiva entre OpenCode y el browser.
|
||||
|
||||
### Lo que debe incluir
|
||||
|
||||
- integracion por `MCP`
|
||||
- ejecucion visible por defecto
|
||||
- soporte para abrir navegador e ir a una URL
|
||||
- acciones basicas: `click`, `type`, `press`, `select`, `scroll`, `hover`
|
||||
- esperas utiles: selector, texto, URL, carga de pagina
|
||||
- lectura de informacion de pantalla y DOM
|
||||
- ejecucion de JavaScript en pagina cuando haga falta
|
||||
- logica nativa de estabilizacion tras acciones de navegacion/interaccion para reducir esperas manuales
|
||||
- captura de screenshot
|
||||
- captura de consola y errores del runtime de la pagina
|
||||
- soporte para video o tracing si el coste operativo es razonable desde la v1
|
||||
- respuesta estructurada al agente con estado, error y evidencia
|
||||
|
||||
### Lo que puede quedar fuera de la v1 si retrasa demasiado
|
||||
|
||||
- backend intermedio separado
|
||||
- politicas completas para webs externas
|
||||
- soporte fuerte de perfiles persistentes complejos
|
||||
- capacidades avanzadas de `CDP`
|
||||
- compatibilidad cross-browser real con `WebKit` o `Firefox`
|
||||
|
||||
---
|
||||
|
||||
## Herramientas y componentes previstos
|
||||
|
||||
### Base principal
|
||||
|
||||
- `Playwright`
|
||||
- `Chromium`
|
||||
- `Node.js 20+`
|
||||
- `TypeScript`
|
||||
|
||||
### Integracion
|
||||
|
||||
- `MCP server` externo como interfaz con OpenCode
|
||||
|
||||
### Diagnostico progresivo
|
||||
|
||||
- `CDP` en puntos concretos cuando `Playwright` no cubra suficientemente el caso
|
||||
|
||||
### Artefactos
|
||||
|
||||
- screenshots
|
||||
- logs de consola
|
||||
- resultados estructurados
|
||||
- video o trace cuando convenga
|
||||
|
||||
---
|
||||
|
||||
## Contratos que deben quedar bien definidos desde el inicio
|
||||
|
||||
Aunque la v1 use arquitectura simple, el diseno debe dejar estables estos contratos:
|
||||
|
||||
- contrato de sesion de browser
|
||||
- contrato de acciones del navegador
|
||||
- contrato de artefactos generados
|
||||
- contrato de respuesta al agente
|
||||
|
||||
Esto permitira pasar a una arquitectura con backend/orquestador sin rehacer el modelo base.
|
||||
|
||||
---
|
||||
|
||||
## Stack fijado para la v1
|
||||
|
||||
Queda fijado para la v1:
|
||||
|
||||
- `Node.js 20+`
|
||||
- `TypeScript`
|
||||
- `Playwright`
|
||||
- `Chromium`
|
||||
- `@modelcontextprotocol/sdk`
|
||||
- `MCP` por `stdio`
|
||||
|
||||
Decision de navegador para v1:
|
||||
|
||||
- usar `Chromium managed by Playwright` como modo por defecto y recomendado
|
||||
- ejecutar instalacion de Chromium durante `install.sh` mediante `npx playwright install chromium`
|
||||
- no depender del `Chromium` del sistema en la v1 para evitar variaciones de compatibilidad
|
||||
|
||||
Evolucion prevista:
|
||||
|
||||
- habilitar en fase posterior un modo opcional `system browser` por `executablePath` para quienes prefieran reutilizar un navegador ya instalado
|
||||
|
||||
Queda descartado por ahora:
|
||||
|
||||
- usar `Puppeteer` en paralelo a `Playwright`
|
||||
- introducir `WebKit` en la v1 como motor principal
|
||||
- construir desde el inicio un backend intermedio obligatorio
|
||||
|
||||
---
|
||||
|
||||
## Tools MCP minimas de la v1
|
||||
|
||||
La v1 se implementara con un conjunto pequeno y suficiente de tools MCP.
|
||||
|
||||
Lista inicial fijada:
|
||||
|
||||
- `browser_open`
|
||||
- `browser_close`
|
||||
- `browser_navigate`
|
||||
- `browser_click`
|
||||
- `browser_type`
|
||||
- `browser_press`
|
||||
- `browser_scroll`
|
||||
- `browser_wait`
|
||||
- `browser_snapshot`
|
||||
- `browser_evaluate`
|
||||
|
||||
Capacidad cubierta por este conjunto:
|
||||
|
||||
- apertura y cierre de sesion de browser
|
||||
- navegacion a URL objetivo
|
||||
- interaccion base con la interfaz
|
||||
- esperas para estabilizar ejecuciones
|
||||
- captura de evidencia visual inicial
|
||||
- lectura o evaluacion de estado en pagina mediante JavaScript
|
||||
|
||||
Capacidades previstas para ampliacion posterior sin romper estos contratos:
|
||||
|
||||
- logs de consola como tool separada o adjunta a respuestas
|
||||
- tracing o video
|
||||
- lectura estructurada de red
|
||||
- gestion avanzada de perfiles y sesiones
|
||||
|
||||
---
|
||||
|
||||
## Estructura inicial fijada del proyecto
|
||||
|
||||
La estructura inicial del proyecto queda fijada asi:
|
||||
|
||||
```text
|
||||
opencode-browser-tool/
|
||||
artifacts/
|
||||
docs/
|
||||
PLAN_DE_DESARROLLO.md
|
||||
TODO.md
|
||||
scripts/
|
||||
src/
|
||||
browser/
|
||||
tools/
|
||||
types/
|
||||
server.ts
|
||||
.gitignore
|
||||
check.sh
|
||||
install.sh
|
||||
opencode.mcp.example.json
|
||||
package.json
|
||||
README.md
|
||||
tsconfig.json
|
||||
```
|
||||
|
||||
Objetivo de esta estructura:
|
||||
|
||||
- separar claramente documentacion, codigo, scripts y artefactos
|
||||
- dejar lista la base para evolucionar a una arquitectura modular
|
||||
- mantener el paquete autocontenido para instalarlo en otros equipos con OpenCode
|
||||
|
||||
---
|
||||
|
||||
## Comunicacion interna prevista
|
||||
|
||||
### En la v1
|
||||
|
||||
`OpenCode` actuara como cliente `MCP`.
|
||||
|
||||
La herramienta expondra un `MCP server` externo que OpenCode podra arrancar y usar.
|
||||
|
||||
Ese servidor traducira las tools pedidas por OpenCode a operaciones sobre `Playwright`.
|
||||
|
||||
### En la v2
|
||||
|
||||
El `MCP server` podra seguir siendo la cara publica, pero delegando ya en un backend intermedio propio.
|
||||
|
||||
Esto permite mantener estable la integracion con OpenCode mientras evoluciona el interior del sistema.
|
||||
|
||||
---
|
||||
|
||||
## Instalacion y distribucion esperadas
|
||||
|
||||
La herramienta debe poder distribuirse como carpeta autocontenida del proyecto.
|
||||
|
||||
Estructura objetivo aproximada:
|
||||
|
||||
```text
|
||||
opencode-browser-tool/
|
||||
docs/
|
||||
src/
|
||||
scripts/
|
||||
artifacts/
|
||||
package.json
|
||||
README.md
|
||||
install.sh
|
||||
check.sh
|
||||
opencode.mcp.example.json
|
||||
```
|
||||
|
||||
### Objetivo de instalacion
|
||||
|
||||
En un PC con OpenCode, debe ser posible dejar la herramienta lista con uno de estos caminos:
|
||||
|
||||
- ejecutar un script de instalacion rapida
|
||||
- seguir instrucciones claras en un `.md`
|
||||
- pedir a un agente de OpenCode que ejecute esas instrucciones y deje todo listo
|
||||
|
||||
### Lo que debe dejar listo la instalacion
|
||||
|
||||
- dependencias del proyecto instaladas
|
||||
- Playwright y Chromium preparados
|
||||
- scripts de verificacion funcional
|
||||
- plantilla de configuracion de OpenCode para conectar el `MCP server`
|
||||
- rutas de artefactos creadas
|
||||
- instrucciones claras para primer uso
|
||||
|
||||
---
|
||||
|
||||
## Funcionamiento operativo esperado de la v1
|
||||
|
||||
### Configuracion inicial
|
||||
|
||||
- el usuario instala el proyecto en una carpeta del sistema
|
||||
- el usuario ejecuta el script o sigue el `.md` de instalacion
|
||||
- OpenCode queda configurado para conocer el `MCP server` del browser tool
|
||||
|
||||
### Uso normal
|
||||
|
||||
- el agente decide usar la herramienta o el usuario se lo ordena
|
||||
- OpenCode lanza el `MCP server` externo si la integracion se hace por `stdio`
|
||||
- el `MCP server` llama a `Playwright`
|
||||
- `Playwright` controla `Chromium`
|
||||
- la herramienta devuelve al agente resultados y evidencia
|
||||
|
||||
### Modos de arranque del MCP server
|
||||
|
||||
Se prioriza para la v1:
|
||||
|
||||
- `stdio` autolanzado por OpenCode
|
||||
|
||||
Queda prevista mas adelante la opcion:
|
||||
|
||||
- servicio persistente separado si el crecimiento del sistema lo requiere
|
||||
|
||||
---
|
||||
|
||||
## V2 prevista
|
||||
|
||||
La v2 debe apoyarse en una v1 ya funcional y estable.
|
||||
|
||||
Capacidades previstas:
|
||||
|
||||
- backend/orquestador intermedio
|
||||
- gestion mas rica de sesiones y perfiles
|
||||
- configuracion de seguridad y permisos de uso
|
||||
- control mas fino de navegacion externa
|
||||
- artefactos y observabilidad mas avanzados
|
||||
- puntos de extension para diagnostico con `CDP`
|
||||
- posible evaluacion futura de `WebKit` para compatibilidad, sin desplazar a Chromium como base inicial
|
||||
|
||||
---
|
||||
|
||||
## Por definir mas adelante
|
||||
|
||||
- politica exacta de uso sobre webs externas
|
||||
- allowlist de dominios
|
||||
- confirmacion para acciones sensibles
|
||||
- gestion de `CAPTCHA` y `2FA`
|
||||
- necesidad real de soporte `WebKit`
|
||||
- nivel de persistencia de sesiones entre ejecuciones
|
||||
- politica final de seleccion entre `managed chromium` y `system browser`
|
||||
|
||||
---
|
||||
|
||||
## Criterio de exito de la v1
|
||||
|
||||
La v1 se considerara lograda cuando:
|
||||
|
||||
- OpenCode pueda usar la herramienta browser como tool externa por `MCP`
|
||||
- el browser se abra en modo visible y responda a acciones reales
|
||||
- el agente pueda navegar e interactuar con una app local
|
||||
- la herramienta pueda devolver evidencia suficiente para diagnosticar fallos
|
||||
- la estructura creada permita evolucionar a v2 sin rehacer la integracion con OpenCode
|
||||
152
docs/QUICKSTART.md
Normal file
152
docs/QUICKSTART.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Quickstart - Browser Tool
|
||||
|
||||
Guia corta para dejar la herramienta lista y ejecutar una prueba minima.
|
||||
|
||||
## 1) Instalar
|
||||
|
||||
Desde `opencode-browser-tool/`:
|
||||
|
||||
```bash
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Esto instala dependencias y `Chromium managed by Playwright`.
|
||||
|
||||
## 2) Compilar
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## 3) Configurar MCP en el proyecto
|
||||
|
||||
Crear `opencode-browser-tool/.opencode/opencode.json` con:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"browser-tool": {
|
||||
"type": "local",
|
||||
"command": [
|
||||
"node",
|
||||
"/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/dist/server.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Nota: ajusta la ruta absoluta de `dist/server.js` a tu equipo.
|
||||
|
||||
## 4) Verificar conexion MCP
|
||||
|
||||
```bash
|
||||
opencode mcp list
|
||||
```
|
||||
|
||||
Debe aparecer `browser-tool` como `connected`.
|
||||
|
||||
## 5) Primera prueba minima
|
||||
|
||||
Pide a OpenCode una secuencia simple:
|
||||
|
||||
- abrir navegador
|
||||
- navegar a una URL
|
||||
- tomar snapshot
|
||||
- cerrar navegador
|
||||
|
||||
Ejemplo de instruccion:
|
||||
|
||||
```text
|
||||
Usa solo browser-tool: abre navegador visible, navega a https://example.com, toma snapshot con label quickstart y cierra navegador.
|
||||
```
|
||||
|
||||
## 5.1) Auto-descubrimiento recomendado
|
||||
|
||||
Antes de pruebas complejas, pide:
|
||||
|
||||
- `browser_help`
|
||||
- `browser_health`
|
||||
- `browser_diagnostics_clear` (opcional, para empezar limpio)
|
||||
|
||||
Con eso el agente detecta capacidades disponibles, defaults y estado operativo actual.
|
||||
|
||||
## 6) Dónde quedan las capturas
|
||||
|
||||
Se guardan en:
|
||||
|
||||
- `opencode-browser-tool/artifacts/`
|
||||
|
||||
## 7) Descubrir capacidades automaticamente
|
||||
|
||||
Usa las tools `browser_help` y `browser_health` al inicio de una sesion para que el agente reciba capacidades, defaults y estado de ejecucion.
|
||||
|
||||
Para incidencias durante pruebas, usa `browser_diagnostics` para extraer consola, errores de pagina y requests fallidas.
|
||||
Si un flujo no avanza, usa `browser_observe` para capturar estado intermedio y `browser_handle_consent` para resolver banners.
|
||||
|
||||
## 8) Grabacion bajo demanda (video/trace)
|
||||
|
||||
Para sesiones complejas, abre con grabacion:
|
||||
|
||||
- `recordVideo: true`
|
||||
- `recordTrace: true`
|
||||
- `recordLabel: "caso-xyz"`
|
||||
|
||||
Los artifacts se cierran y quedan disponibles al ejecutar `browser_close`.
|
||||
|
||||
## 9) Reporte consolidado de ejecucion
|
||||
|
||||
Al finalizar un flujo, puedes pedir:
|
||||
|
||||
- `browser_report` con `format: both`
|
||||
|
||||
Nota: por defecto la herramienta ya deja `saveToFile=true` al instalar.
|
||||
|
||||
Esto deja resumen tecnico en JSON y lectura humana en Markdown dentro de `artifacts/`.
|
||||
|
||||
Si quieres cambiar ese default de forma persistente, usa `browser_config` con `action: set`.
|
||||
|
||||
## 10) Elegir navegador: testing o system
|
||||
|
||||
Por defecto, `browser_open` usa `browserKind: testing` (Playwright managed).
|
||||
|
||||
Si necesitas perfil real de usuario, puedes usar navegador del sistema:
|
||||
|
||||
```json
|
||||
{
|
||||
"browserKind": "system",
|
||||
"persistentProfile": true,
|
||||
"userDataDir": "/home/pancho/.chrome-perfil-google-real",
|
||||
"startUrl": "https://www.google.com"
|
||||
}
|
||||
```
|
||||
|
||||
Tambien puedes fijarlo de forma persistente con `browser_config` (`browserDefaultKind`, `browserDefaultPersistentProfile`, `browserDefaultUserDataDir`).
|
||||
|
||||
## 11) Verbose y delay humano (default activo)
|
||||
|
||||
Por defecto, `browser_open` aplica:
|
||||
|
||||
- `verbose: true`
|
||||
- `verboseOverlay: true` (en modo visible)
|
||||
- `interactionDelayMinMs: 1000`
|
||||
- `interactionDelayMaxMs: 3000`
|
||||
|
||||
Puedes desactivar verbose por ejecucion:
|
||||
|
||||
```json
|
||||
{
|
||||
"verbose": false,
|
||||
"verboseOverlay": false
|
||||
}
|
||||
```
|
||||
|
||||
Y puedes ajustar delays por ejecucion:
|
||||
|
||||
```json
|
||||
{
|
||||
"interactionDelayMinMs": 800,
|
||||
"interactionDelayMaxMs": 1800
|
||||
}
|
||||
```
|
||||
218
docs/REGISTRO_SITUACIONES.md
Normal file
218
docs/REGISTRO_SITUACIONES.md
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
# Registro de situaciones detectadas
|
||||
|
||||
## Instrucciones para el agente
|
||||
|
||||
Cuando el usuario pida documentar un error, incidencia o comportamiento inesperado en pruebas con `browser-tool`, debes:
|
||||
|
||||
1. Crear una nueva entrada usando la plantilla de este archivo.
|
||||
2. Escribir informacion concreta y verificable, sin suposiciones vagas.
|
||||
3. Incluir siempre evidencia trazable (`artifact`, log, salida de tool o reporte).
|
||||
4. Indicar estado actual (`pendiente`, `en analisis`, `resuelto`, `descartado`).
|
||||
5. Si aplicaste una correccion, anotar que se cambio y como se valido.
|
||||
6. No borrar entradas anteriores; agregar nuevas y, si aplica, actualizar el estado de la entrada ya existente.
|
||||
7. Usar fecha y hora local en formato `YYYY-MM-DD HH:mm`.
|
||||
|
||||
Objetivo: mantener un historial operativo claro para depurar, priorizar y cerrar hallazgos durante desarrollo y pruebas.
|
||||
|
||||
---
|
||||
|
||||
## Plantilla de entrada
|
||||
|
||||
### Entrada (YYYY-MM-DD HH:mm)
|
||||
|
||||
- Contexto:
|
||||
- Flujo probado:
|
||||
- Situacion observada:
|
||||
- Resultado esperado:
|
||||
- Resultado real:
|
||||
- Evidencia (artifact/log/report):
|
||||
- Hipotesis de causa:
|
||||
- Accion aplicada:
|
||||
- Validacion posterior:
|
||||
- Estado: pendiente | en analisis | resuelto | descartado
|
||||
- Notas:
|
||||
|
||||
---
|
||||
|
||||
## Entradas
|
||||
|
||||
### Entrada (2026-04-23 00:00)
|
||||
|
||||
- Contexto: Inicializacion del registro.
|
||||
- Flujo probado: N/A.
|
||||
- Situacion observada: Se crea el archivo para consolidar hallazgos futuros.
|
||||
- Resultado esperado: Disponer de un formato unico para registrar incidencias.
|
||||
- Resultado real: Archivo creado correctamente.
|
||||
- Evidencia (artifact/log/report): `opencode-browser-tool/docs/REGISTRO_SITUACIONES.md`
|
||||
- Hipotesis de causa: N/A.
|
||||
- Accion aplicada: Creacion de plantilla e instrucciones de uso.
|
||||
- Validacion posterior: Pendiente de primeras entradas reales.
|
||||
- Estado: resuelto
|
||||
- Notas: Punto de arranque para seguimiento de situaciones durante pruebas.
|
||||
|
||||
### Entrada (2026-04-23 19:42)
|
||||
|
||||
- Contexto: Prueba funcional de buscador externo para validar paginacion y localizacion de dominio en resultados.
|
||||
- Flujo probado: Abrir Google, buscar `barranquismo granada`, navegar resultados y avanzar con `Siguiente` hasta encontrar `barranquismogranada.com`.
|
||||
- Situacion observada: La sesion fue redirigida repetidamente a `https://www.google.com/sorry/index` (bloqueo anti-bot), impidiendo una paginacion normal y fiable.
|
||||
- Resultado esperado: Avanzar por paginas de resultados y devolver la posicion real del dominio objetivo.
|
||||
- Resultado real: `NO_ENCONTRADO max_paginas=8`, condicionado por bloqueo anti-bot antes de completar el flujo de paginacion real.
|
||||
- Evidencia (artifact/log/report): `/tmp/opencode_google_paginacion.jsonl` (tool outputs con `currentUrl` en `/sorry/index` y timeout en espera de URL de resultados).
|
||||
- Hipotesis de causa: Deteccion de automatizacion por Google para esta sesion/IP y bloqueo por challenge.
|
||||
- Accion aplicada: Reintento en modo visible y ajuste de flujo; se mantuvo bloqueo. Ademas se implemento en la tool captura automatica en error (`autoSnapshot`) y tool de observacion intermedia (`browser_observe`) para no cortar sin evidencia.
|
||||
- Validacion posterior: Validado en fixture local que ante error se adjunta `autoSnapshot` + `observation`; validado `browser_observe` con screenshot de mitad de flujo.
|
||||
- Estado: en analisis
|
||||
- Notas: Caso util para definir politica de handoff humano/CAPTCHA y estrategia de pruebas en buscadores con anti-bot.
|
||||
|
||||
### Entrada (2026-04-23 21:15)
|
||||
|
||||
- Contexto: Reintento de la misma prueba en Google con flujo mejorado (consentimiento + observacion intermedia + captura automatica en error).
|
||||
- Flujo probado: Buscar `barranquismo granada`, avanzar por resultados y localizar `barranquismogranada.com`.
|
||||
- Situacion observada: Se gestiono consentimiento (`Rechazar todo`), pero tras enviar busqueda Google redirigio a `sorry/index` (verificacion humana).
|
||||
- Resultado esperado: Continuar paginacion y devolver posicion real del dominio.
|
||||
- Resultado real: `BLOQUEADO_HUMANO`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- screenshot: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-14-38-965Z-error-browser_wait.png`
|
||||
- log de ejecucion: `/tmp/opencode_google_retest.jsonl`
|
||||
- Hipotesis de causa: Mecanismo anti-bot de Google (challenge humano) en esta sesion/IP.
|
||||
- Accion aplicada: Rechazo de cookies + reintento visible + observacion automatica.
|
||||
- Validacion posterior: Pendiente de handoff humano para superar challenge y continuar la misma prueba.
|
||||
- Estado: en analisis
|
||||
- Notas: El nuevo mecanismo de `autoSnapshot` y `browser_observe` funciono correctamente y aporta evidencia util en mitad del flujo.
|
||||
|
||||
### Entrada (2026-04-23 21:23)
|
||||
|
||||
- Contexto: Reintento forzado de la prueba "si o si" con clic explicito de verificacion humana.
|
||||
- Flujo probado: Google -> consentimiento -> busqueda -> detectar `sorry/index` -> `browser_observe` -> `browser_handle_human_check` -> reintento de salida a resultados.
|
||||
- Situacion observada: La herramienta intento clic en controles de human-check, incluido intento de checkbox reCAPTCHA en iframe, pero no encontro un control clicable en esta sesion.
|
||||
- Resultado esperado: Resolver challenge humano y continuar paginacion.
|
||||
- Resultado real: `BLOQUEADO_HUMANO`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- screenshot antes: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-21-49-250Z-human-check-before.png`
|
||||
- screenshot despues: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-22-16-015Z-human-check-after.png`
|
||||
- autosnapshot en error: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-22-11-037Z-error-browser_wait.png`
|
||||
- log: `/tmp/opencode_google_humanclick_retest.jsonl`
|
||||
- Hipotesis de causa: Challenge anti-bot no expone checkbox clicable directo en este contexto/IP/sesion.
|
||||
- Accion aplicada: Añadida tool `browser_handle_human_check` y reintento en modo visible.
|
||||
- Validacion posterior: Pendiente de handoff manual humano en la misma sesion para continuar.
|
||||
- Estado: en analisis
|
||||
- Notas: El sistema ya no corta sin intentar y ahora deja trazabilidad completa del intento de human-check.
|
||||
|
||||
### Entrada (2026-04-23 21:48)
|
||||
|
||||
- Contexto: Verificacion visual solicitada por usuario para confirmar si la tool realmente intenta el clic de "soy humano".
|
||||
- Flujo probado: Ejecucion visible con video, captura antes/despues del human-check e intento extendido en iframes/selectores de checkbox.
|
||||
- Situacion observada: Se ejecuto `browser_handle_human_check` con intentos en multiples selectores y iframes, pero no encontro control clicable (`clicked=false`).
|
||||
- Resultado esperado: Clic efectivo sobre control de verificacion humana y salida de `sorry/index`.
|
||||
- Resultado real: Sin clic efectivo; permanencia en `sorry/index`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- `before`: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-47-40-040Z-before-human-check-v2.png`
|
||||
- `after`: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T21-48-04-615Z-after-human-check-v2.png`
|
||||
- video: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@980084a2685562a3f2fba478b8730d98.webm`
|
||||
- log: `/tmp/opencode_google_watch_click_v2.jsonl`
|
||||
- Hipotesis de causa: El challenge mostrado en esta sesion no expone checkbox clicable automatizable con los selectores actuales.
|
||||
- Accion aplicada: Ampliacion de estrategia de clic (iframes + selectors checkbox + fallback por texto).
|
||||
- Validacion posterior: Confirmado por salida de tool (`clicked=false`) y evidencia visual.
|
||||
- Estado: en analisis
|
||||
- Notas: Para completar esta prueba en este entorno, se requiere handoff manual sobre el challenge actual.
|
||||
|
||||
### Entrada (2026-04-24 00:18)
|
||||
|
||||
- Contexto: Validacion pedida por usuario para comprobar movimiento visible de cursor y scroll real con contenido suficiente.
|
||||
- Flujo probado: Abrir fixture largo local -> verificar cursor overlay -> hover -> scroll de pagina -> scroll de contenedor -> snapshot/video.
|
||||
- Situacion observada: Con fixture anterior no habia altura suficiente para probar scroll; se creo fixture nuevo con contenido largo y contenedor interno desplazable.
|
||||
- Resultado esperado: Evidenciar que el cursor virtual existe y que el desplazamiento cambia posiciones reales de scroll.
|
||||
- Resultado real: Cursor overlay presente (`#__browser_tool_cursor`) y scroll efectivo (`movedY=1200` en pagina, `movedY=420` en `#scroll-box`).
|
||||
- Evidencia (artifact/log/report):
|
||||
- fixture: `opencode-browser-tool/scripts/fixture_scroll_long.html`
|
||||
- screenshot: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-18-49-353Z-cursor-scroll-smoke.png`
|
||||
- video: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@b0a5c648845bfba94a0843fd81d664d9.webm`
|
||||
- Hipotesis de causa: La ausencia de scroll en pruebas previas se debia al contenido insuficiente de la pagina de test, no a fallo de `browser_scroll`.
|
||||
- Accion aplicada: Se anadio fixture largo y se enriquecio salida de `browser_scroll` con metricas (`startY/endY/movedY/maxY`).
|
||||
- Validacion posterior: Build OK y smoke visual OK.
|
||||
- Estado: resuelto
|
||||
- Notas: Queda base estable para validar scroll en nuevas regresiones.
|
||||
|
||||
### Entrada (2026-04-24 00:19)
|
||||
|
||||
- Contexto: Retest de Google tras ampliar estrategia de `browser_handle_human_check`.
|
||||
- Flujo probado: Google -> consentimiento reject -> busqueda `barranquismo granada` -> `sorry/index` -> intento human-check -> observacion antes/despues.
|
||||
- Situacion observada: La tool reporto clic efectivo en ancla reCAPTCHA dentro de iframe, pero la URL continuo en `sorry/index` en este intento.
|
||||
- Resultado esperado: Superar challenge y volver a `/search` para continuar paginacion.
|
||||
- Resultado real: `clicked=true` con metodo `frame:iframe[title*='reCAPTCHA']:#recaptcha-anchor`, sin salida inmediata de `sorry/index`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- before: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-19-11-410Z-google-before-human-v3.png`
|
||||
- after: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-19-13-806Z-google-after-human-v3.png`
|
||||
- video: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@02542037c2003982eb2da9641c77f00e.webm`
|
||||
- Hipotesis de causa: El challenge requiere pasos adicionales (puzzle/espera/validacion servidor) tras el clic inicial, o decision por reputacion IP/sesion.
|
||||
- Accion aplicada: Se amplio `handleHumanCheck` con mas selectores y fallback por coordenadas; se confirmo clic efectivo al menos en una variante.
|
||||
- Validacion posterior: Pendiente retest con handoff manual para completar salida a `/search` y continuar paginacion.
|
||||
- Estado: en analisis
|
||||
- Notas: Se cierra duda previa de "si la tool clickea o no": ahora hay evidencia de clic efectivo.
|
||||
|
||||
### Entrada (2026-04-24 00:22)
|
||||
|
||||
- Contexto: Reintento de la prueba objetivo completa (Google + paginacion) usando script de regresion v4.
|
||||
- Flujo probado: Google -> reject consent -> buscar `barranquismo granada` -> human-check -> intentar paginacion hasta 8 paginas.
|
||||
- Situacion observada: `handleHumanCheck` volvio a reportar clic efectivo en reCAPTCHA, pero la sesion se mantuvo en `sorry/index`, impidiendo entrar en `/search` y por tanto sin paginacion real.
|
||||
- Resultado esperado: Salir a resultados de busqueda y avanzar paginas hasta encontrar `barranquismogranada.com`.
|
||||
- Resultado real: `found=false` con bloqueo persistente en `sorry/index`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- screenshot final: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-22-24-694Z-google-pagination-v4-final.png`
|
||||
- video: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@4d214e591bd81c6d279e36fa186d5e0b.webm`
|
||||
- script usado: `opencode-browser-tool/scripts/google_pagination_v4.mjs`
|
||||
- Hipotesis de causa: El challenge requiere validacion adicional no resuelta por el primer clic (puzzle/espera/verificacion servidor) o mantiene bloqueo por reputacion de sesion/IP.
|
||||
- Accion aplicada: Ejecutado retest v4 completo tras mejoras de clic en human-check.
|
||||
- Validacion posterior: Confirmado que el clic se intenta y ocurre (`clicked=true`), pero no desbloquea en este entorno.
|
||||
- Estado: en analisis
|
||||
- Notas: Para completar esta prueba concreta en este entorno sigue siendo necesario handoff manual directo al challenge.
|
||||
|
||||
### Entrada (2026-04-24 21:10)
|
||||
|
||||
- Contexto: Repeticion de prueba de ranking organico tras habilitar modo de perfil persistente y tras login manual previo del usuario.
|
||||
- Flujo probado: Google -> aceptar cookies -> buscar `barranquismo granada` -> resolver posible `sorry/index` -> paginar resultados organicos.
|
||||
- Situacion observada: Persistio redireccion inicial a `sorry/index`, pero el flujo continuo a `/search` y se pudo completar paginacion organica.
|
||||
- Resultado esperado: Obtener posicion del dominio objetivo en resultados organicos.
|
||||
- Resultado real: Dominio encontrado en pagina 2, posicion 6 organica (rank global 16 en conteo del script).
|
||||
- Evidencia (artifact/log/report):
|
||||
- before manual: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-10-05-886Z-google-v7-before-manual.png`
|
||||
- after manual: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-10-31-757Z-google-v7-after-manual.png`
|
||||
- final: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-10-39-450Z-google-v7-final.png`
|
||||
- Hipotesis de causa: El historial/perfil ayuda, pero no elimina completamente el challenge anti-bot en este entorno.
|
||||
- Accion aplicada: Ejecucion con `browserKind=testing` + `persistentProfile=true` + `userDataDir=/home/pancho/.chromium-perfil-google`.
|
||||
- Validacion posterior: Flujo de ranking organico completado y posicion devuelta.
|
||||
- Estado: resuelto
|
||||
- Notas: Siguiente comparativa recomendada en este mismo proyecto: repetir con `browserKind=system` + perfil persistente equivalente.
|
||||
|
||||
### Entrada (2026-04-24 21:28)
|
||||
|
||||
- Contexto: Prueba solicitada en `google.es` con foco en resultados organicos, clic al resultado encontrado y scroll suave en la web destino.
|
||||
- Flujo probado: abrir Google -> buscar `barranquismo granada` -> localizar primer organico con dominio `barranquismogranada.com` -> clic -> scroll progresivo hasta abajo.
|
||||
- Situacion observada: La sesion entro directamente a resultados (`/search`) sin bloqueo `sorry/index` en esta corrida.
|
||||
- Resultado esperado: Obtener posicion organica, abrir el resultado y completar scroll hasta el final.
|
||||
- Resultado real: Dominio encontrado en pagina 2, posicion 7 organica (rank global 17); clic exitoso y scroll completo (`loops=9`, `lastMoved=0`).
|
||||
- Evidencia (artifact/log/report):
|
||||
- destino bottom: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-28-57-330Z-google-v8d-destination-bottom.png`
|
||||
- final: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-28-58-023Z-google-v8d-final.png`
|
||||
- script: `opencode-browser-tool/scripts/google_es_v8d.mjs`
|
||||
- Hipotesis de causa: El perfil persistente aporta estabilidad de sesion para este caso concreto, aunque la posicion organica puede variar por momento/indice localizacion.
|
||||
- Accion aplicada: Se limitaron esperas extra a maximo 3s en tramo de challenge y se ejecuto scroll por pasos suaves.
|
||||
- Validacion posterior: Flujo completo terminado correctamente.
|
||||
- Estado: resuelto
|
||||
- Notas: Se confirma comportamiento solicitado (clic visible + scroll visible + conteo organico).
|
||||
|
||||
### Entrada (2026-04-24 22:10)
|
||||
|
||||
- Contexto: Prueba solicitada con nueva query en Google: `puertas cortafuegos de madera`.
|
||||
- Flujo probado: abrir `google.es` -> buscar query -> paginar resultados -> localizar dominio `puertastecnicasbcn.com` -> navegar al resultado -> snapshot.
|
||||
- Situacion observada: La navegacion inicial en `google.es` acaba sirviendo resultados en host `google.com` en esta corrida, manteniendo idioma/contexto ES.
|
||||
- Resultado esperado: obtener posicion del dominio y capturar la pagina destino.
|
||||
- Resultado real: dominio encontrado en pagina 3, posicion 4 organica (rank global 23), URL `https://www.puertastecnicasbcn.com/puertas-cortafuegos/`.
|
||||
- Evidencia (artifact/log/report):
|
||||
- snapshot destino: `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T20-10-49-705Z-google-puertas-destination.png`
|
||||
- script: `opencode-browser-tool/scripts/google_es_puertas_rank.mjs`
|
||||
- Hipotesis de causa: Variacion normal de SERP por datacenter/sesion/ubicacion.
|
||||
- Accion aplicada: paginacion automatica + click al resultado encontrado + snapshot final.
|
||||
- Validacion posterior: prueba completada sin bloqueo de challenge en esta corrida.
|
||||
- Estado: resuelto
|
||||
- Notas: Playwright `page.screenshot` captura viewport de la pagina, no la barra de URL del navegador.
|
||||
114
docs/TODO.md
Normal file
114
docs/TODO.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# TODO - opencode-browser-tool
|
||||
|
||||
## Objetivo del TODO
|
||||
|
||||
Recoger el trabajo completo necesario desde el estado actual hasta un producto terminado y utilizable al 100 por 100.
|
||||
|
||||
---
|
||||
|
||||
## Fase 0 - Base del proyecto
|
||||
|
||||
- [x] Confirmar y congelar alcance exacto de la v1
|
||||
- [x] Confirmar stack tecnico definitivo para la v1 (`Node.js/TypeScript`, `Playwright`, `Chromium`, `MCP`)
|
||||
- [x] Definir estructura inicial de carpetas del proyecto
|
||||
- [x] Crear `README.md` principal del proyecto
|
||||
- [x] Crear scripts base de instalacion y verificacion
|
||||
|
||||
---
|
||||
|
||||
## Fase 1 - Integracion minima funcional con OpenCode
|
||||
|
||||
- [x] Crear el `MCP server` externo del proyecto
|
||||
- [x] Definir herramientas MCP minimas de la v1
|
||||
- [x] Conectar el `MCP server` con `Playwright`
|
||||
- [x] Lanzar `Chromium` en modo visible por defecto
|
||||
- [x] Validar que OpenCode detecta y usa la herramienta
|
||||
- [x] Probar primera navegacion real contra una URL local
|
||||
|
||||
---
|
||||
|
||||
## Fase 2 - Interaccion efectiva con la UI
|
||||
|
||||
- [x] Implementar acciones base: abrir URL, click, type, press, select, hover y scroll
|
||||
- [x] Implementar esperas utiles para estabilidad de pruebas
|
||||
- [x] Implementar lectura de DOM, texto visible y estado de elementos
|
||||
- [x] Permitir ejecucion de JavaScript controlado en pagina
|
||||
- [x] Definir respuesta estructurada para cada accion
|
||||
- [x] Probar flujos reales sobre una app de desarrollo local
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 - Evidencia y diagnostico inicial
|
||||
|
||||
- [x] Implementar screenshots
|
||||
- [x] Capturar logs de consola
|
||||
- [x] Capturar errores de pagina
|
||||
- [x] Definir almacenamiento de artefactos por ejecucion
|
||||
- [x] Evaluar inclusion de video o trace en la v1
|
||||
- [x] Diseñar formato de reporte util para el agente
|
||||
|
||||
---
|
||||
|
||||
## Fase 4 - Instalacion y empaquetado
|
||||
|
||||
- [x] Crear `install.sh`
|
||||
- [x] Crear `check.sh`
|
||||
- [x] Crear plantilla de configuracion MCP para OpenCode
|
||||
- [x] Documentar instalacion paso a paso en un `.md` (`docs/QUICKSTART.md`)
|
||||
- [ ] Verificar instalacion en un entorno limpio con OpenCode (checklist en `docs/VALIDACION_ENTORNO_LIMPIO.md`)
|
||||
- [x] Asegurar que todo el proyecto se despliega desde una unica carpeta
|
||||
- [ ] Cerrar instalable Linux sin rutas hardcodeadas (proyecto, perfil, OpenCode)
|
||||
- [ ] Alinear `install.sh` y `check.sh` con puesta en marcha en PC nuevo
|
||||
- [ ] Cerrar plantilla MCP portable para rutas locales variables
|
||||
|
||||
---
|
||||
|
||||
## Fase 5 - Robustez de la v1
|
||||
|
||||
- [ ] Mejorar manejo de errores y mensajes devueltos al agente
|
||||
- [ ] Definir gestion minima de sesiones de navegador
|
||||
- [ ] Definir rutas y limpieza de artefactos
|
||||
- [ ] Probar estabilidad en varias ejecuciones consecutivas
|
||||
- [ ] Documentar limitaciones conocidas de la v1
|
||||
- [ ] Cerrar criterios de aceptacion de la v1
|
||||
|
||||
---
|
||||
|
||||
## Fase 6 - V2 preparada para continuidad
|
||||
|
||||
- [ ] Diseñar backend/orquestador intermedio
|
||||
- [ ] Diseñar contratos estables entre MCP y runner interno
|
||||
- [ ] Diseñar gestion mas rica de perfiles y sesiones
|
||||
- [ ] Añadir puntos de extension para ganchos `CDP`
|
||||
- [ ] Preparar base para politicas de seguridad en webs externas
|
||||
- [ ] Evaluar soporte futuro de `WebKit`
|
||||
|
||||
---
|
||||
|
||||
## Fase 7 - Producto terminado al 100 por 100
|
||||
|
||||
- [ ] Cerrar instalacion reproducible en una maquina nueva con OpenCode
|
||||
- [ ] Cerrar experiencia de uso completa para agente y usuario
|
||||
- [ ] Cerrar documentacion final de uso, instalacion y mantenimiento
|
||||
- [ ] Probar apps locales reales con flujos completos
|
||||
- [ ] Probar casos externos autorizados cuando llegue el momento
|
||||
- [ ] Dejar lista la estrategia de evolucion y mantenimiento del proyecto
|
||||
|
||||
---
|
||||
|
||||
## Pendientes inmediatos acordados
|
||||
|
||||
- [ ] Ejecutar prueba funcional final acordada (smoke de aceptacion)
|
||||
- [ ] Ejecutar plan de cierre de instalable segun `docs/PLAN_CIERRE_INSTALABLE.md`
|
||||
- [ ] Evaluar complejidad de soporte Windows y viabilidad de flujo neutro por `npm`/Node
|
||||
|
||||
---
|
||||
|
||||
## Registro de decisiones pendientes
|
||||
|
||||
- [ ] Politica exacta para webs externas
|
||||
- [ ] Allowlist de dominios
|
||||
- [ ] Confirmacion previa para acciones sensibles
|
||||
- [ ] Gestion de `CAPTCHA` y `2FA`
|
||||
- [ ] Persistencia o limpieza de sesion entre ejecuciones
|
||||
- [ ] Estrategia final `managed chromium` vs `system browser`
|
||||
93
docs/VALIDACION_ENTORNO_LIMPIO.md
Normal file
93
docs/VALIDACION_ENTORNO_LIMPIO.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Validacion en entorno limpio (OpenCode)
|
||||
|
||||
Objetivo: verificar que `opencode-browser-tool` puede instalarse y usarse desde cero en una maquina con OpenCode.
|
||||
|
||||
## Alcance de la validacion
|
||||
|
||||
- Instalacion de dependencias y Chromium gestionado por Playwright.
|
||||
- Compilacion del servidor MCP.
|
||||
- Conexion MCP visible para OpenCode.
|
||||
- Ejecucion de una prueba minima end-to-end.
|
||||
|
||||
## Prerrequisitos
|
||||
|
||||
- Node.js 20+ y npm.
|
||||
- OpenCode CLI instalado y funcional.
|
||||
- Acceso a internet para instalar dependencias y Chromium.
|
||||
|
||||
## Pasos
|
||||
|
||||
1) Copiar el proyecto completo en una carpeta local, por ejemplo:
|
||||
|
||||
```bash
|
||||
cp -R opencode-browser-tool "$HOME/opencode-browser-tool"
|
||||
```
|
||||
|
||||
2) Instalar:
|
||||
|
||||
```bash
|
||||
cd "$HOME/opencode-browser-tool"
|
||||
./install.sh
|
||||
```
|
||||
|
||||
3) Verificacion basica:
|
||||
|
||||
```bash
|
||||
npm run check
|
||||
npm run build
|
||||
```
|
||||
|
||||
4) Configurar MCP para OpenCode con la ruta local real de `dist/server.js`:
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"mcp": {
|
||||
"browser-tool": {
|
||||
"type": "local",
|
||||
"command": [
|
||||
"node",
|
||||
"/ABSOLUTE/PATH/opencode-browser-tool/dist/server.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
5) Comprobar que OpenCode detecta el MCP:
|
||||
|
||||
```bash
|
||||
opencode mcp list
|
||||
```
|
||||
|
||||
Resultado esperado: `browser-tool` aparece como `connected`.
|
||||
|
||||
6) Prueba minima desde OpenCode:
|
||||
|
||||
- abrir navegador visible
|
||||
- navegar a `https://example.com`
|
||||
- tomar snapshot con label `clean-env-smoke`
|
||||
- cerrar navegador
|
||||
|
||||
7) Verificar artifacts:
|
||||
|
||||
- Debe existir una captura en `artifacts/`.
|
||||
|
||||
## Criterios de aceptacion
|
||||
|
||||
- `install.sh` termina sin error.
|
||||
- `npm run build` termina sin error.
|
||||
- `opencode mcp list` muestra `browser-tool` conectado.
|
||||
- La prueba minima produce screenshot en `artifacts/`.
|
||||
|
||||
## Resultado de la ejecucion
|
||||
|
||||
Rellenar al completar la validacion real:
|
||||
|
||||
- Fecha:
|
||||
- Equipo/SO:
|
||||
- Version Node:
|
||||
- Version OpenCode:
|
||||
- Resultado: pendiente | ok | fallo
|
||||
- Evidencia (capturas/logs):
|
||||
- Notas:
|
||||
68
docs/contexto_workspace/CONTEXTO_ACTIVO_BROWSER.md
Normal file
68
docs/contexto_workspace/CONTEXTO_ACTIVO_BROWSER.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Contexto activo - Browser Tool
|
||||
|
||||
Este archivo guarda el estado operativo que NO debe perderse por compactacion.
|
||||
|
||||
## Objetivo activo
|
||||
|
||||
Completar la prueba concreta en Google para el flujo:
|
||||
|
||||
- buscar `barranquismo granada`
|
||||
- superar `sorry/index` cuando aparezca
|
||||
- continuar paginacion
|
||||
- localizar `barranquismogranada.com` y devolver posicion real
|
||||
|
||||
## Estado actual real
|
||||
|
||||
- Se mejoro `browser_handle_human_check` con estrategia ampliada (iframes, selectores extra y clic por coordenadas como fallback).
|
||||
- En un retest reciente, la tool reporto clic efectivo:
|
||||
- `clicked: true`
|
||||
- `method: frame:iframe[title*='reCAPTCHA']:#recaptcha-anchor`
|
||||
- Aun con clic detectado, la URL siguio en `sorry/index` en los retests v3 y v4.
|
||||
- En retest v7 con perfil persistente (`/home/pancho/.chromium-perfil-google`) el challenge inicial aparecio, pero se logro continuar a `/search` y completar ranking organico.
|
||||
- Se implemento verbose por defecto (`verbose=true`, `verboseOverlay=true`) y delay humano configurable 1-3s en interacciones.
|
||||
- Se implemento guarda de estabilidad previa a pasos sensibles para reducir errores tipo `Execution context was destroyed`.
|
||||
|
||||
## Evidencia clave
|
||||
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-19-11-410Z-google-before-human-v3.png`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-19-13-806Z-google-after-human-v3.png`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@02542037c2003982eb2da9641c77f00e.webm`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-22-24-694Z-google-pagination-v4-final.png`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@4d214e591bd81c6d279e36fa186d5e0b.webm`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-24T19-10-39-450Z-google-v7-final.png`
|
||||
|
||||
## Cursor y scroll (peticion explicita del usuario)
|
||||
|
||||
- Se creo fixture largo para validar scroll real:
|
||||
- `opencode-browser-tool/scripts/fixture_scroll_long.html`
|
||||
- Retest de smoke (visible) confirmado:
|
||||
- cursor virtual presente (`#__browser_tool_cursor` existe)
|
||||
- scroll de pagina: `movedY=1200`
|
||||
- scroll de contenedor `#scroll-box`: `movedY=420`
|
||||
|
||||
Evidencia:
|
||||
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/2026-04-23T22-18-49-353Z-cursor-scroll-smoke.png`
|
||||
- `/home/pancho/Documentos/Empresa/IA/opencode-browser-tool/artifacts/page@b0a5c648845bfba94a0843fd81d664d9.webm`
|
||||
|
||||
## Siguiente paso exacto (sin desviar foco)
|
||||
|
||||
1. Ejecutar una prueba funcional final acordada (smoke de aceptacion).
|
||||
2. Cerrar instalable Linux portable (sin rutas hardcodeadas) usando `opencode-browser-tool/docs/PLAN_CIERRE_INSTALABLE.md`.
|
||||
3. Validar instalacion en entorno limpio con OpenCode.
|
||||
4. Evaluar complejidad de soporte Windows y opcion cross-platform via scripts npm/Node.
|
||||
|
||||
## Plan de cierre preservado
|
||||
|
||||
- Documento fuente: `opencode-browser-tool/docs/PLAN_CIERRE_INSTALABLE.md`
|
||||
- Este plan debe conservarse y ejecutarse despues de la prueba funcional final.
|
||||
|
||||
## Regla de continuidad
|
||||
|
||||
Antes de iniciar cualquier trabajo nuevo relacionado con browser-tool, leer:
|
||||
|
||||
- `docs/CONTEXTO_ACTIVO_BROWSER.md`
|
||||
- `docs/HISTORIAL_SESIONES.md`
|
||||
- `opencode-browser-tool/docs/REGISTRO_SITUACIONES.md`
|
||||
|
||||
No cerrar ni reemplazar este archivo: actualizarlo incrementalmente.
|
||||
1187
docs/contexto_workspace/HISTORIAL_SESIONES.md
Normal file
1187
docs/contexto_workspace/HISTORIAL_SESIONES.md
Normal file
File diff suppressed because it is too large
Load diff
28
docs/contexto_workspace/SKILL_CONTINUIDAD_BROWSER.md
Normal file
28
docs/contexto_workspace/SKILL_CONTINUIDAD_BROWSER.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Skill local de continuidad (manual)
|
||||
|
||||
Nota: en este entorno no hay skills cargables del sistema.
|
||||
|
||||
Este documento funciona como skill persistente manual para que cualquier agente retome exactamente donde se quedo el trabajo.
|
||||
|
||||
## Prompt recomendado para arranque de agente
|
||||
|
||||
Usar este texto al iniciar una sesion nueva:
|
||||
|
||||
```text
|
||||
Actua como Agente modulo Browser Opencode.
|
||||
Antes de responder, lee y usa como fuente de verdad:
|
||||
- docs/CONTEXTO_ACTIVO_BROWSER.md
|
||||
- docs/HISTORIAL_SESIONES.md
|
||||
- opencode-browser-tool/docs/REGISTRO_SITUACIONES.md
|
||||
|
||||
Objetivo: continuar exactamente el trabajo activo, sin desviarte a tareas nuevas.
|
||||
Si hay un bloqueo, primero deja evidencia (observe/snapshot/video), luego propone o aplica el siguiente paso minimo.
|
||||
Cada accion relevante debe quedar registrada en docs/HISTORIAL_SESIONES.md.
|
||||
```
|
||||
|
||||
## Checklist minima de continuidad
|
||||
|
||||
- Confirmar `session_id` actual.
|
||||
- Revisar ultimo estado en `docs/CONTEXTO_ACTIVO_BROWSER.md`.
|
||||
- Ejecutar solo el siguiente paso exacto definido en ese archivo.
|
||||
- Registrar cambios y evidencia.
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
# Herramienta Browser para OpenCode: idea y funcionamiento
|
||||
|
||||
## Proposito
|
||||
|
||||
Este documento recoge ideas, acuerdos y criterios sobre la futura herramienta browser para OpenCode.
|
||||
|
||||
Su objetivo es servir como espacio vivo de trabajo mientras seguimos conversando sobre posibilidades, tecnologias, formas de implementacion y decisiones funcionales, antes de pasar a fases de diseno tecnico o construccion detallada.
|
||||
|
||||
---
|
||||
|
||||
## Idea general de la herramienta
|
||||
|
||||
La herramienta permitira que OpenCode pueda usar un navegador para probar proyectos del workspace, moviendose por la aplicacion e interactuando con ella de forma similar a como lo haria un usuario real.
|
||||
|
||||
La referencia funcional buscada es un comportamiento parecido al de Antigravity: navegar por la app, ejecutar flujos, interactuar con la interfaz y comprobar si la funcionalidad desarrollada responde como se espera.
|
||||
|
||||
---
|
||||
|
||||
## Vision funcional inicial
|
||||
|
||||
Cuando la herramienta este hecha, se espera que pueda:
|
||||
|
||||
- abrir la aplicacion objetivo en un navegador controlado
|
||||
- recorrer pantallas y flujos reales
|
||||
- hacer acciones de usuario como clic, escritura, seleccion, scroll y navegacion
|
||||
- comprobar resultados esperados en cada paso
|
||||
- recoger evidencia util cuando algo falle o cuando una comprobacion deba quedar registrada
|
||||
- devolver al agente informacion util sobre lo ocurrido durante la prueba
|
||||
|
||||
---
|
||||
|
||||
## Modos de ejecucion
|
||||
|
||||
### Visible como modo por defecto
|
||||
|
||||
El modo visible sera el modo principal de uso de la herramienta.
|
||||
|
||||
Se considera el modo mas adecuado cuando lo que se quiere comprobar esta ligado a la interaccion real con la aplicacion y a la experiencia de uso:
|
||||
|
||||
- comportamiento de la interfaz
|
||||
- navegacion entre pantallas
|
||||
- interacciones propias de usuario
|
||||
- validacion funcional de flujos completos
|
||||
- observacion directa de lo que ocurre en pantalla
|
||||
|
||||
### Headless como modo no principal
|
||||
|
||||
El modo headless no sera el comportamiento por defecto.
|
||||
|
||||
Se utilizara cuando lo que se quiera comprobar no dependa principalmente de la interaccion visible de usuario, sino del comportamiento interno o tecnico del sistema, por ejemplo:
|
||||
|
||||
- comunicacion con backend
|
||||
- entradas y salidas de informacion
|
||||
- interacciones con disco, almacenamiento o estado interno
|
||||
- validaciones tecnicas no centradas en UX
|
||||
|
||||
---
|
||||
|
||||
## Forma de uso esperada
|
||||
|
||||
- El agente podra usar esta herramienta igual que usa el resto de herramientas, cuando la necesite para resolver la tarea que este realizando.
|
||||
- El usuario tambien podra pedir expresamente al agente que use la herramienta browser para una tarea concreta.
|
||||
- Debe existir una forma de activar o desactivar su uso segun prefiera el usuario.
|
||||
- El agente debera respetar esa configuracion para no usar la herramienta cuando el usuario no quiera que intervenga.
|
||||
|
||||
---
|
||||
|
||||
## Criterio estructural principal
|
||||
|
||||
La herramienta debe construirse como una solucion externa a OpenCode.
|
||||
|
||||
Esto implica que tanto el browser como su forma de instalacion, ejecucion e instrucciones de uso para el agente deben quedar fuera del nucleo interno de OpenCode, para que una actualizacion de OpenCode no afecte al funcionamiento de esta herramienta.
|
||||
|
||||
El objetivo es que la integracion sea estable, desacoplada y mantenible, evitando depender de modificaciones internas del proyecto OpenCode.
|
||||
|
||||
---
|
||||
|
||||
## Telemetria en tiempo real (fase posterior)
|
||||
|
||||
En la version actual, la herramienta funciona con un modelo request/response por accion:
|
||||
|
||||
- el agente invoca una tool
|
||||
- la tool ejecuta
|
||||
- la tool devuelve resultado estructurado
|
||||
|
||||
Con lo ya implementado (diagnostico, artifacts, reportes), este modelo cubre bien el uso principal en desarrollo.
|
||||
|
||||
### Que aportaria incluir telemetria en tiempo real mas adelante
|
||||
|
||||
- stream en vivo de eventos de ejecucion (consola, red, errores, cambios de estado)
|
||||
- observacion continua sin esperar al fin de cada accion
|
||||
- mejor soporte para casos intermitentes o muy complejos
|
||||
- mejor operacion para soporte de clientes cuando haya que mirar una sesion en directo
|
||||
|
||||
### Que haria falta para incluirla
|
||||
|
||||
- un servicio paralelo local (sidecar) separado del MCP principal
|
||||
- un canal de eventos push (`WebSocket` o `SSE`)
|
||||
- un esquema de eventos estandar para pasos, diagnostico y estado
|
||||
|
||||
### Decision actual
|
||||
|
||||
- no incluir telemetria en tiempo real en esta version
|
||||
- mantenerla documentada como mejora de continuidad para fases posteriores
|
||||
- priorizar primero estabilidad funcional, diagnostico estructurado y reporte util para desarrollo
|
||||
|
||||
---
|
||||
|
||||
## Por definir
|
||||
|
||||
- si `external_web` debe estar habilitado o deshabilitado por defecto
|
||||
- si desde la v1 debe existir allowlist de dominios permitidos
|
||||
- si las acciones sensibles deben requerir confirmacion explicita del usuario
|
||||
- como se gestionaran `CAPTCHA`, `2FA` o pasos que requieran intervencion manual
|
||||
|
||||
---
|
||||
|
||||
## Estado del documento
|
||||
|
||||
Documento inicial abierto para seguir recogiendo ideas y acuerdos antes de entrar en diseno tecnico, arquitectura o implementacion.
|
||||
686
docs/manual_de_uso_heramienta.md
Normal file
686
docs/manual_de_uso_heramienta.md
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
# Manual de uso de la herramienta Browser
|
||||
|
||||
Este manual explica que tools expone la herramienta browser, como usarlas y que puedes pedirle a OpenCode en la practica.
|
||||
|
||||
## Alcance actual
|
||||
|
||||
- Browser: `Chromium` controlado por `Playwright`
|
||||
- Integracion: `MCP` por `stdio`
|
||||
- Modo por defecto: visible (`headless: false`)
|
||||
- Artefactos: capturas en `opencode-browser-tool/artifacts/`
|
||||
|
||||
## Esquema de respuesta estandar
|
||||
|
||||
Todas las tools devuelven ahora un formato unificado.
|
||||
|
||||
Respuesta OK:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"tool": "browser_<tool>",
|
||||
"data": {},
|
||||
"state": {
|
||||
"isOpen": true,
|
||||
"currentUrl": "https://..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Respuesta con error:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"tool": "browser_<tool>",
|
||||
"error": {
|
||||
"code": "TOOL_EXECUTION_ERROR",
|
||||
"message": "detalle del error"
|
||||
},
|
||||
"state": {
|
||||
"isOpen": false,
|
||||
"currentUrl": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Flujo recomendado de uso
|
||||
|
||||
1. Abrir sesion de navegador con `browser_open`
|
||||
2. Navegar con `browser_navigate`
|
||||
3. Interactuar con `browser_click`, `browser_type`, `browser_press`, `browser_scroll`
|
||||
4. Esperar estados estables con `browser_wait`
|
||||
5. Capturar evidencia con `browser_snapshot`
|
||||
6. Inspeccionar estado dinamico con `browser_evaluate`
|
||||
7. Cerrar sesion con `browser_close`
|
||||
|
||||
Importante: todas las tools excepto `browser_open` y `browser_close` requieren sesion abierta. Si no hay sesion, devuelve error.
|
||||
|
||||
## Espera automatica nativa (comportamiento actual)
|
||||
|
||||
La herramienta ya incorpora estabilizacion automatica despues de acciones clave.
|
||||
|
||||
Que significa en la practica:
|
||||
|
||||
- despues de `browser_navigate`, `browser_click`, `browser_type`, `browser_press`, `browser_scroll` y `browser_evaluate`, la herramienta intenta esperar estado estable por si sola
|
||||
- combina `domcontentloaded`, intento de `networkidle` y una ventana de "quietud" de actividad de red
|
||||
- si no logra estado estable, aplica salida segura por tiempo maximo para evitar bucles de espera infinita
|
||||
|
||||
Resultado:
|
||||
|
||||
- normalmente no necesitas pedir `wait` manual para casos comunes
|
||||
- `browser_wait` sigue disponible cuando quieras una condicion explicita (selector/texto/url/timeout)
|
||||
|
||||
---
|
||||
|
||||
## Tools disponibles
|
||||
|
||||
### `browser_help`
|
||||
|
||||
Devuelve un resumen operativo de la herramienta: capacidades, defaults, notas y ejemplos.
|
||||
|
||||
Cuando usarla:
|
||||
|
||||
- al empezar una sesion nueva con un agente que no conoce la tool
|
||||
- para recordar comportamiento por defecto (modo visible, timeouts, auto-wait)
|
||||
|
||||
Parametros: ninguno.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_health`
|
||||
|
||||
Devuelve estado operativo actual del runtime browser y artifacts recientes.
|
||||
|
||||
Incluye:
|
||||
|
||||
- estado de sesion (`isOpen`, `currentUrl`)
|
||||
- actividad de red interna (`inflight`, `lastActivityAgoMs`)
|
||||
- comportamiento activo (`verbose`, overlay, delay humano)
|
||||
- pasos recientes (`recentSteps`) para diagnostico rapido en vivo
|
||||
- lista de artifacts recientes (ruta, fecha, tamano)
|
||||
|
||||
Parametros:
|
||||
|
||||
- `limit` (number, opcional): cantidad maxima de artifacts a devolver (default `5`, max `20`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_config`
|
||||
|
||||
Permite leer o cambiar configuracion persistente de la herramienta (guardada en `config/browser-tool.config.json`).
|
||||
|
||||
Parametros:
|
||||
|
||||
- `action` (string, opcional): `get` o `set` (default `get`)
|
||||
- `browserDefaultKind` (string, opcional): `testing` o `system`
|
||||
- `browserSystemExecutablePath` (string, opcional): ruta del navegador del sistema (ej. `/usr/bin/google-chrome`)
|
||||
- `browserDefaultPersistentProfile` (boolean, opcional): si `true`, `browser_open` usa perfil persistente por defecto
|
||||
- `browserDefaultUserDataDir` (string, opcional): ruta de perfil persistente por defecto
|
||||
- `browserDefaultVerbose` (boolean, opcional): modo verbose por defecto (`true` recomendado para operacion)
|
||||
- `browserDefaultVerboseOverlay` (boolean, opcional): overlay visual de estado en navegador visible
|
||||
- `browserDefaultInteractionDelayMinMs` (number, opcional): minimo de delay humano en acciones interactivas
|
||||
- `browserDefaultInteractionDelayMaxMs` (number, opcional): maximo de delay humano en acciones interactivas
|
||||
- `reportDefaultSaveToFile` (boolean, opcional): default persistente de `browser_report.saveToFile`
|
||||
- `reportDefaultFormat` (string, opcional): `json`, `markdown`, `both`
|
||||
|
||||
Ejemplo lectura:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "get"
|
||||
}
|
||||
```
|
||||
|
||||
Ejemplo cambio persistente:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "set",
|
||||
"browserDefaultKind": "system",
|
||||
"browserSystemExecutablePath": "/usr/bin/google-chrome",
|
||||
"browserDefaultPersistentProfile": true,
|
||||
"browserDefaultUserDataDir": "/home/pancho/.chrome-perfil-google-real",
|
||||
"browserDefaultVerbose": true,
|
||||
"browserDefaultVerboseOverlay": true,
|
||||
"browserDefaultInteractionDelayMinMs": 1000,
|
||||
"browserDefaultInteractionDelayMaxMs": 3000,
|
||||
"reportDefaultSaveToFile": false,
|
||||
"reportDefaultFormat": "json"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_diagnostics`
|
||||
|
||||
Devuelve eventos recientes de diagnostico capturados durante la sesion:
|
||||
|
||||
- `console` (log, warn, error, etc.)
|
||||
- `pageerror` (errores JS no capturados)
|
||||
- `requestfailed` (peticiones de red fallidas)
|
||||
|
||||
Parametros:
|
||||
|
||||
- `limit` (number, opcional): maximo de eventos devueltos (default `20`, max `200`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"limit": 20
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_diagnostics_clear`
|
||||
|
||||
Limpia el buffer interno de diagnostico.
|
||||
|
||||
Uso recomendado: antes de arrancar una prueba nueva para leer solo eventos de ese flujo.
|
||||
|
||||
Parametros: ninguno.
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_observe`
|
||||
|
||||
Toma una "foto de estado" de la pagina durante el flujo y, opcionalmente, una captura.
|
||||
|
||||
Uso recomendado:
|
||||
|
||||
- cuando el flujo parece bloqueado
|
||||
- cuando no avanza tras una accion
|
||||
- antes de abortar una prueba para entender que esta viendo el navegador
|
||||
|
||||
Parametros:
|
||||
|
||||
- `screenshot` (boolean, opcional, default `true`)
|
||||
- `label` (string, opcional)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"screenshot": true,
|
||||
"label": "mid-flow-check"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_handle_consent`
|
||||
|
||||
Intenta pulsar botones comunes de consentimiento.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `action` (string, opcional): `reject` (default) o `accept`
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "reject"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_handle_human_check`
|
||||
|
||||
Intenta resolver controles comunes de verificacion humana (incluyendo checkbox de reCAPTCHA cuando es clicable).
|
||||
|
||||
Estrategia actual:
|
||||
|
||||
- intenta clic en checkboxs dentro de iframes comunes de reCAPTCHA
|
||||
- intenta selectores de captcha tambien en pagina principal
|
||||
- usa fallback por coordenadas para controles detectados por texto
|
||||
|
||||
Uso recomendado:
|
||||
|
||||
- cuando aparece bloqueo tipo `sorry/index`
|
||||
- cuando el flujo queda detenido en "comprueba que eres humano"
|
||||
|
||||
Parametros: ninguno.
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_open`
|
||||
|
||||
Abre una sesion de `Chromium`.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `headless` (boolean, opcional): por defecto `false`
|
||||
- `startUrl` (string, opcional)
|
||||
- `width` (number, opcional, default `1440`)
|
||||
- `height` (number, opcional, default `900`)
|
||||
- `browserKind` (string, opcional): `testing` (Playwright managed) o `system` (browser del sistema)
|
||||
- `executablePath` (string, opcional): ruta binaria explicita del navegador
|
||||
- `persistentProfile` (boolean, opcional): activar perfil persistente
|
||||
- `userDataDir` (string, opcional): ruta del perfil persistente (requerido si `persistentProfile=true`)
|
||||
- `verbose` (boolean, opcional, default `true`): muestra estado paso a paso en respuestas
|
||||
- `verboseOverlay` (boolean, opcional, default `true`): muestra estado en overlay visual en navegador visible
|
||||
- `interactionDelayMinMs` (number, opcional, default `1000`)
|
||||
- `interactionDelayMaxMs` (number, opcional, default `3000`)
|
||||
- `recordVideo` (boolean, opcional, default `false`)
|
||||
- `recordTrace` (boolean, opcional, default `false`)
|
||||
- `recordLabel` (string, opcional, default `session`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"headless": false,
|
||||
"startUrl": "http://localhost:3000",
|
||||
"width": 1440,
|
||||
"height": 900,
|
||||
"browserKind": "system",
|
||||
"persistentProfile": true,
|
||||
"userDataDir": "/home/pancho/.chrome-perfil-google-real",
|
||||
"verbose": true,
|
||||
"verboseOverlay": true,
|
||||
"interactionDelayMinMs": 1000,
|
||||
"interactionDelayMaxMs": 3000,
|
||||
"recordVideo": true,
|
||||
"recordTrace": true,
|
||||
"recordLabel": "case-123"
|
||||
}
|
||||
```
|
||||
|
||||
Notas:
|
||||
|
||||
- `recordVideo` sigue desactivado por defecto para evitar consumo innecesario.
|
||||
- si activas `recordVideo` o `recordTrace`, los artifacts se generan al cerrar sesion con `browser_close`.
|
||||
- con `verbose=true`, la tool muestra estado actual y aplica guardas de estabilidad antes de pasos sensibles.
|
||||
|
||||
---
|
||||
|
||||
### `browser_close`
|
||||
|
||||
Cierra la sesion activa de navegador.
|
||||
|
||||
Parametros: ninguno.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_navigate`
|
||||
|
||||
Navega la pagina activa a una URL.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `url` (string, requerido)
|
||||
- `waitUntil` (string, opcional): `load`, `domcontentloaded`, `networkidle` (default `domcontentloaded`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"url": "http://localhost:3000/login",
|
||||
"waitUntil": "domcontentloaded"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_click`
|
||||
|
||||
Hace click sobre un selector CSS.
|
||||
|
||||
Nota: antes del click intenta mover el puntero al centro del elemento para hacer la interaccion mas natural.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `selector` (string, requerido)
|
||||
- `timeoutMs` (number, opcional, default `10000`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": "button[type='submit']",
|
||||
"timeoutMs": 10000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_hover`
|
||||
|
||||
Hace hover sobre un elemento por selector.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `selector` (string, requerido)
|
||||
- `timeoutMs` (number, opcional, default `10000`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": ".menu-item-settings",
|
||||
"timeoutMs": 10000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_type`
|
||||
|
||||
Escribe texto en un elemento por selector.
|
||||
|
||||
Nota: antes de escribir intenta mover puntero y enfocar el campo con click.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `selector` (string, requerido)
|
||||
- `text` (string, requerido)
|
||||
- `clear` (boolean, opcional, default `true`)
|
||||
- `timeoutMs` (number, opcional, default `10000`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": "input[name='email']",
|
||||
"text": "usuario@dominio.com",
|
||||
"clear": true,
|
||||
"timeoutMs": 10000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_select`
|
||||
|
||||
Selecciona una opcion en un elemento `<select>` por valor.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `selector` (string, requerido)
|
||||
- `value` (string, requerido)
|
||||
- `timeoutMs` (number, opcional, default `10000`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": "#tipoConsentimiento",
|
||||
"value": "quirurgico",
|
||||
"timeoutMs": 10000
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_press`
|
||||
|
||||
Envia una tecla al contexto de teclado de la pagina.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `key` (string, requerido). Ejemplos: `Enter`, `Escape`, `Tab`, `Control+A`.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"key": "Enter"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_scroll`
|
||||
|
||||
Hace scroll en pagina completa o dentro de un elemento.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `x` (number, opcional, default `0`)
|
||||
- `y` (number, opcional, default `400`)
|
||||
- `selector` (string, opcional): si se envia, el scroll se aplica al elemento
|
||||
|
||||
Nota: cuando el scroll es de pagina, se aplica en varios pasos cortos para que el desplazamiento sea visible y progresivo.
|
||||
Si no hay desplazamiento por rueda, aplica fallback `window.scrollBy`.
|
||||
|
||||
La salida incluye metricas utiles:
|
||||
|
||||
- `startY`, `endY`, `maxY`, `movedY` para scroll de pagina
|
||||
- `movedX`, `movedY` para scroll de elemento
|
||||
|
||||
Ejemplo pagina:
|
||||
|
||||
```json
|
||||
{
|
||||
"y": 700
|
||||
}
|
||||
```
|
||||
|
||||
Ejemplo elemento:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": ".lista-scroll",
|
||||
"y": 500
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_wait`
|
||||
|
||||
Espera a que se cumpla una condicion.
|
||||
|
||||
Nota: en esta version es opcional para muchos flujos, porque la herramienta ya aplica espera automatica nativa tras acciones comunes.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `for` (string, requerido): `selector`, `text`, `url`, `timeout`
|
||||
- `value` (string, requerido para `selector`, `text`, `url`)
|
||||
- `timeoutMs` (number, opcional, default `10000`)
|
||||
|
||||
Nota para `for: timeout`:
|
||||
|
||||
- si envias `value`, se interpreta como milisegundos de espera
|
||||
- si no envias `value`, usa `timeoutMs`
|
||||
|
||||
Ejemplo por selector:
|
||||
|
||||
```json
|
||||
{
|
||||
"for": "selector",
|
||||
"value": ".toast-success",
|
||||
"timeoutMs": 10000
|
||||
}
|
||||
```
|
||||
|
||||
Ejemplo por tiempo:
|
||||
|
||||
```json
|
||||
{
|
||||
"for": "timeout",
|
||||
"timeoutMs": 1500
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_snapshot`
|
||||
|
||||
Toma screenshot y lo guarda en `artifacts/`.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `label` (string, opcional)
|
||||
- `fullPage` (boolean, opcional, default `true`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"label": "post-login",
|
||||
"fullPage": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_evaluate`
|
||||
|
||||
Ejecuta JavaScript en la pagina. Puede recibir una funcion en texto (con `arg`) o una expresion directa.
|
||||
|
||||
Importante: si, puede inyectar y ejecutar logica JavaScript en runtime. Eso incluye leer variables del DOM y tambien modificarlas temporalmente dentro de la sesion de prueba.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `script` (string, requerido): funcion JS serializada
|
||||
- `arg` (object, opcional): dato de entrada a la funcion
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"script": "(arg) => ({ title: document.title, arg })",
|
||||
"arg": { "source": "qa" }
|
||||
}
|
||||
```
|
||||
|
||||
Ejemplo con expresion directa:
|
||||
|
||||
```json
|
||||
{
|
||||
"script": "document.title"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_query`
|
||||
|
||||
Lee estado DOM/UI por selector sin tener que escribir JavaScript para casos comunes.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `selector` (string, requerido)
|
||||
- `mode` (string, opcional): `text`, `html`, `value`, `exists`, `visible`, `enabled`, `count`, `attributes`
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": "#email",
|
||||
"mode": "value"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"selector": ".toast-success",
|
||||
"mode": "exists"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `browser_report`
|
||||
|
||||
Genera un reporte consolidado de ejecucion a partir de pasos, diagnostico y artifacts.
|
||||
|
||||
Parametros:
|
||||
|
||||
- `format` (string, opcional): `json`, `markdown`, `both` (default `both`)
|
||||
- `includeSteps` (boolean, opcional, default `true`)
|
||||
- `includeDiagnostics` (boolean, opcional, default `true`)
|
||||
- `includeArtifacts` (boolean, opcional, default `true`)
|
||||
- `saveToFile` (boolean, opcional): por defecto usa configuracion persistente (`true` al instalar)
|
||||
- `label` (string, opcional, default `report`)
|
||||
- `limitSteps` (number, opcional, default `200`)
|
||||
- `limitDiagnostics` (number, opcional, default `50`)
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```json
|
||||
{
|
||||
"format": "both",
|
||||
"saveToFile": true,
|
||||
"label": "consent-flow",
|
||||
"includeSteps": true,
|
||||
"includeDiagnostics": true,
|
||||
"includeArtifacts": true
|
||||
}
|
||||
```
|
||||
|
||||
Resultado esperado:
|
||||
|
||||
- reporte estructurado para uso del agente
|
||||
- opcionalmente archivos `.json` y `.md` en `artifacts/`
|
||||
|
||||
Comportamiento recomendado:
|
||||
|
||||
- por defecto queda guardando a archivo (`saveToFile=true`) para no perder evidencia
|
||||
- puedes desactivarlo temporalmente en una ejecucion concreta (`saveToFile=false`)
|
||||
- para cambiar el default de forma persistente usa `browser_config`
|
||||
|
||||
---
|
||||
|
||||
## Que puede pedirle el usuario a OpenCode
|
||||
|
||||
Ejemplos utiles:
|
||||
|
||||
- "Abre el browser, entra a `http://localhost:3000` y valida que aparece el boton `Iniciar sesion`."
|
||||
- "Rellena email y password, envia el formulario y espera una URL que contenga `/dashboard`."
|
||||
- "Haz scroll hasta el final, toma captura y guardala con etiqueta `dashboard-final`."
|
||||
- "Ejecuta un `browser_evaluate` para devolver cantidad de filas de la tabla `.orders-table`."
|
||||
- "Usa `browser_query` para comprobar si `.btn-guardar` esta visible y habilitado."
|
||||
- "Ejecuta `browser_health` y dime si hay sesion abierta y cuales son los ultimos artifacts."
|
||||
- "Al terminar la prueba, genera `browser_report` en `both` y guardalo a archivo para revisar resultado y evidencia."
|
||||
|
||||
## Limites actuales de esta version
|
||||
|
||||
- El diagnostico actual es en memoria de sesion (no persiste historico entre reinicios del proceso).
|
||||
- Video/trace se activan manualmente por ejecucion; todavia no hay politica automatica "solo en fallo".
|
||||
- No hay politica de seguridad final para navegacion externa (pendiente de fases posteriores).
|
||||
- El modo recomendado de v1 es `Chromium managed by Playwright`.
|
||||
|
||||
## Errores tipicos y recuperacion
|
||||
|
||||
- `Browser session is not open`: ejecuta `browser_open` antes de usar otras tools.
|
||||
- `Timeout` en `browser_wait`: valida si ya cambio URL/estado con `browser_evaluate`; si ya estas dentro, continua.
|
||||
- Si la pagina parece bloqueada: ejecuta `browser_observe` para capturar estado/screenshot antes de cortar.
|
||||
- Si aparece banner de consentimiento: ejecuta `browser_handle_consent` (`reject` o `accept`) y continua.
|
||||
- Si aparece verificacion humana: ejecuta `browser_handle_human_check`; si no desbloquea, dejar evidencia y hacer handoff manual.
|
||||
- Selector no encontrado en `browser_click` o `browser_type`: confirma selector con `browser_evaluate` y reintenta con uno mas estable.
|
||||
- Flujo lento de red: deja que actue la espera nativa; si no alcanza, usa `browser_wait` explicito por selector de estado final.
|
||||
- Error al evaluar JS en `browser_evaluate`: envia una funcion valida en texto, por ejemplo `(arg) => ({ title: document.title, arg })`.
|
||||
16
install.sh
Executable file
16
install.sh
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
printf 'npm no esta disponible en el sistema.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$SCRIPT_DIR/artifacts"
|
||||
npm install --prefix "$SCRIPT_DIR"
|
||||
npx --yes playwright install chromium
|
||||
|
||||
printf 'Instalacion base completada.\n'
|
||||
printf 'Siguiente paso sugerido: revisar opencode.mcp.example.json\n'
|
||||
10
opencode.mcp.example.json
Normal file
10
opencode.mcp.example.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"browser-tool": {
|
||||
"command": "node",
|
||||
"args": [
|
||||
"/ruta/a/opencode-browser-tool/dist/server.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
1747
package-lock.json
generated
Normal file
1747
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
25
package.json
Normal file
25
package.json
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "opencode-browser-tool",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"description": "Browser MCP tool external to OpenCode",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json",
|
||||
"check": "bash ./check.sh",
|
||||
"dev": "tsx src/server.ts",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.12.0",
|
||||
"playwright": "^1.52.0",
|
||||
"zod": "^3.24.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
19
scripts/fixture_diagnostics.html
Normal file
19
scripts/fixture_diagnostics.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Fixture Diagnostics</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Diagnostics Fixture</h1>
|
||||
<script>
|
||||
console.log("fixture:console-log")
|
||||
console.warn("fixture:console-warn")
|
||||
fetch("/missing-endpoint").catch(() => {})
|
||||
setTimeout(() => {
|
||||
throw new Error("fixture:page-error")
|
||||
}, 50)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
109
scripts/fixture_scroll_long.html
Normal file
109
scripts/fixture_scroll_long.html
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Scroll Fixture Long Page</title>
|
||||
<style>
|
||||
:root {
|
||||
color-scheme: light;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Courier New", Courier, monospace;
|
||||
background: linear-gradient(180deg, #f8f9fb 0%, #e9eef7 100%);
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 20;
|
||||
padding: 10px 14px;
|
||||
background: #1f2a44;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 16px;
|
||||
}
|
||||
.card {
|
||||
min-height: 220px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d7deea;
|
||||
box-shadow: 0 6px 18px rgba(31, 42, 68, 0.12);
|
||||
padding: 14px;
|
||||
}
|
||||
.section {
|
||||
margin: 24px 16px;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #d7deea;
|
||||
}
|
||||
.scroll-box {
|
||||
height: 240px;
|
||||
overflow: auto;
|
||||
border: 1px solid #9aa7bf;
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
background: #f4f7fc;
|
||||
}
|
||||
.line {
|
||||
padding: 8px 4px;
|
||||
border-bottom: 1px dashed #bcc8dc;
|
||||
}
|
||||
.tail {
|
||||
height: 1200px;
|
||||
margin: 16px;
|
||||
border-radius: 12px;
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#dce6f6,
|
||||
#dce6f6 16px,
|
||||
#edf3fd 16px,
|
||||
#edf3fd 32px
|
||||
);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">scrollY: <strong id="scroll-y">0</strong></div>
|
||||
|
||||
<div class="grid" id="cards"></div>
|
||||
|
||||
<section class="section">
|
||||
<h2>Scrollable container</h2>
|
||||
<div class="scroll-box" id="scroll-box"></div>
|
||||
</section>
|
||||
|
||||
<div class="tail"></div>
|
||||
|
||||
<script>
|
||||
const cards = document.getElementById("cards")
|
||||
for (let i = 1; i <= 18; i += 1) {
|
||||
const card = document.createElement("article")
|
||||
card.className = "card"
|
||||
card.innerHTML = `<h3>Card ${i}</h3><p>Long page fixture content block to force vertical scroll.</p>`
|
||||
cards.appendChild(card)
|
||||
}
|
||||
|
||||
const box = document.getElementById("scroll-box")
|
||||
for (let i = 1; i <= 60; i += 1) {
|
||||
const row = document.createElement("div")
|
||||
row.className = "line"
|
||||
row.textContent = `Container row ${i}`
|
||||
box.appendChild(row)
|
||||
}
|
||||
|
||||
const value = document.getElementById("scroll-y")
|
||||
const update = () => {
|
||||
value.textContent = String(Math.round(window.scrollY))
|
||||
}
|
||||
update()
|
||||
window.addEventListener("scroll", update, { passive: true })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
41
scripts/fixture_select_hover.html
Normal file
41
scripts/fixture_select_hover.html
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Fixture Select Hover</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 24px;
|
||||
}
|
||||
#menu {
|
||||
display: inline-block;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
#status {
|
||||
margin-top: 12px;
|
||||
color: #0a5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Browser Tool Fixture</h1>
|
||||
<div id="menu" onmouseover="window.hovered = true">Hover target</div>
|
||||
<p id="status"></p>
|
||||
|
||||
<label for="kind">Kind:</label>
|
||||
<select id="kind" onchange="window.kind = this.value">
|
||||
<option value="">Choose</option>
|
||||
<option value="general">General</option>
|
||||
<option value="quirurgico">Quirurgico</option>
|
||||
<option value="anestesia">Anestesia</option>
|
||||
</select>
|
||||
|
||||
<script>
|
||||
window.hovered = false
|
||||
window.kind = ""
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
163
scripts/google_es_puertas_rank.mjs
Normal file
163
scripts/google_es_puertas_rank.mjs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import path from "node:path"
|
||||
import { BrowserManager } from "../dist/browser/manager.js"
|
||||
|
||||
const browser = new BrowserManager(path.resolve("./artifacts"))
|
||||
|
||||
const query = "puertas cortafuegos de madera"
|
||||
const targetDomain = "puertastecnicasbcn.com"
|
||||
const maxPages = 10
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
const organicScript = `() => {
|
||||
const root = document.querySelector("#search") || document
|
||||
const anchors = Array.from(root.querySelectorAll("a[href]"))
|
||||
const results = []
|
||||
for (const a of anchors) {
|
||||
const href = a.getAttribute("href") || ""
|
||||
if (!href.startsWith("http")) continue
|
||||
if (href.includes("google.") || href.includes("webcache.googleusercontent.com")) continue
|
||||
const h3 = a.querySelector("h3")
|
||||
if (!h3) continue
|
||||
const title = (h3.textContent || "").trim()
|
||||
if (!title) continue
|
||||
if (!results.some((x) => x.href === href)) {
|
||||
results.push({ href, title })
|
||||
}
|
||||
}
|
||||
return results
|
||||
}`
|
||||
|
||||
const run = async () => {
|
||||
const out = { pages: [] }
|
||||
|
||||
out.open = await browser.open({
|
||||
headless: false,
|
||||
width: 1366,
|
||||
height: 900,
|
||||
browserKind: "testing",
|
||||
persistentProfile: true,
|
||||
userDataDir: "/home/pancho/.chromium-perfil-google",
|
||||
startUrl: "https://www.google.es",
|
||||
recordVideo: false,
|
||||
})
|
||||
|
||||
out.cookies = await browser.handleConsent("accept").catch((e) => ({ error: e.message }))
|
||||
await browser.type('textarea[name="q"]', query)
|
||||
await browser.press("Enter")
|
||||
await browser.waitFor({ for: "timeout", value: "1200" })
|
||||
|
||||
out.afterSearch = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
if ((out.afterSearch.url || "").includes("/sorry/index")) {
|
||||
out.humanAttempt = await browser.handleHumanCheck().catch((e) => ({ error: e.message }))
|
||||
await browser.waitFor({ for: "timeout", value: "3000" })
|
||||
out.afterChallenge = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
}
|
||||
|
||||
let found = null
|
||||
let globalRank = 0
|
||||
|
||||
for (let pageNo = 1; pageNo <= maxPages; pageNo += 1) {
|
||||
await browser.waitFor({ for: "timeout", value: "800" })
|
||||
const currentUrl = browser.getState().currentUrl || ""
|
||||
if (!currentUrl.includes("/search")) {
|
||||
out.pages.push({ page: pageNo, status: "not_on_search", url: currentUrl })
|
||||
break
|
||||
}
|
||||
|
||||
const extracted = await browser.evaluate(organicScript, {}).catch(() => ({ result: [] }))
|
||||
const organic = Array.isArray(extracted.result) ? extracted.result : []
|
||||
const idx = organic.findIndex((x) => {
|
||||
try {
|
||||
return new URL(String(x.href)).hostname.includes(targetDomain)
|
||||
} catch {
|
||||
return String(x.href).includes(targetDomain)
|
||||
}
|
||||
})
|
||||
|
||||
out.pages.push({
|
||||
page: pageNo,
|
||||
url: currentUrl,
|
||||
organicCount: organic.length,
|
||||
found: idx >= 0,
|
||||
positionOnPage: idx >= 0 ? idx + 1 : null,
|
||||
})
|
||||
|
||||
if (idx >= 0) {
|
||||
globalRank += idx + 1
|
||||
found = {
|
||||
page: pageNo,
|
||||
positionOnPage: idx + 1,
|
||||
globalRank,
|
||||
href: organic[idx].href,
|
||||
title: organic[idx].title,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
globalRank += organic.length
|
||||
|
||||
let moved = false
|
||||
for (const sel of ["#pnnext", "a#pnnext", "a[aria-label*='siguiente' i]", "a[aria-label*='next' i]"]) {
|
||||
const ex = await browser.query({ selector: sel, mode: "exists" }).catch(() => ({ result: false }))
|
||||
if (ex.result === true) {
|
||||
await browser.click(sel).catch(() => null)
|
||||
moved = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!moved) {
|
||||
const tried = await browser
|
||||
.evaluate(
|
||||
`() => {
|
||||
const nodes = Array.from(document.querySelectorAll("a,button,[role=button]"))
|
||||
for (const n of nodes) {
|
||||
const t = (n.innerText || "").trim()
|
||||
if (!t) continue
|
||||
if (t === "Siguiente" || t.includes("Siguiente") || t === "Next" || t.includes("Next")) {
|
||||
n.click()
|
||||
return { clicked: true, text: t }
|
||||
}
|
||||
}
|
||||
return { clicked: false, text: null }
|
||||
}`,
|
||||
{},
|
||||
)
|
||||
.catch(() => ({ result: { clicked: false, text: null } }))
|
||||
out.pages[out.pages.length - 1].nextFallback = tried.result
|
||||
if (!tried.result?.clicked) {
|
||||
break
|
||||
}
|
||||
}
|
||||
await sleep(1400)
|
||||
}
|
||||
|
||||
out.result = found || { found: false }
|
||||
|
||||
if (found?.href) {
|
||||
await browser.navigate(found.href, "domcontentloaded")
|
||||
await browser.waitFor({ for: "timeout", value: "1000" })
|
||||
out.destinationObserve = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
out.destinationSnapshot = await browser.snapshot({ label: "google-puertas-destination", fullPage: false })
|
||||
}
|
||||
|
||||
out.finalObserve = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
out.close = await browser.close()
|
||||
console.log(JSON.stringify(out, null, 2))
|
||||
}
|
||||
|
||||
run().catch(async (err) => {
|
||||
console.error("GOOGLE_PUERTAS_ERROR", err?.message || err)
|
||||
try {
|
||||
const snap = await browser.snapshot({ label: "google-puertas-error", fullPage: true })
|
||||
console.error("ERROR_SNAPSHOT", snap.filePath)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await browser.close()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
246
scripts/google_es_v8d.mjs
Normal file
246
scripts/google_es_v8d.mjs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
import path from "node:path"
|
||||
import { BrowserManager } from "../dist/browser/manager.js"
|
||||
|
||||
const browser = new BrowserManager(path.resolve("./artifacts"))
|
||||
const targetDomain = "barranquismogranada.com"
|
||||
const maxPages = 10
|
||||
const out = { pages: [], debug: [] }
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
const organicScript = `() => {
|
||||
const root = document.querySelector("#search") || document
|
||||
const anchors = Array.from(root.querySelectorAll("a[href]"))
|
||||
const results = []
|
||||
for (const a of anchors) {
|
||||
const href = a.getAttribute("href") || ""
|
||||
if (!href.startsWith("http")) continue
|
||||
if (href.includes("google.") || href.includes("webcache.googleusercontent.com")) continue
|
||||
const h3 = a.querySelector("h3")
|
||||
if (!h3) continue
|
||||
const title = (h3.textContent || "").trim()
|
||||
if (!title) continue
|
||||
if (!results.some((x) => x.href === href)) results.push({ href, title })
|
||||
}
|
||||
return results
|
||||
}`
|
||||
|
||||
const nextFallbackScript = `() => {
|
||||
const nodes = Array.from(document.querySelectorAll("a,button,[role=button]"))
|
||||
for (const n of nodes) {
|
||||
const t = (n.innerText || "").trim()
|
||||
if (!t) continue
|
||||
if (t === "Siguiente" || t.includes("Siguiente") || t === "Next" || t.includes("Next")) {
|
||||
n.click()
|
||||
return { clicked: true, text: t }
|
||||
}
|
||||
}
|
||||
return { clicked: false, text: null }
|
||||
}`
|
||||
|
||||
const safeEval = async (script, arg, tag) => {
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
try {
|
||||
return await browser.evaluate(script, arg)
|
||||
} catch {
|
||||
out.debug.push(`${tag}:eval-retry-${i + 1}`)
|
||||
await sleep(450)
|
||||
}
|
||||
}
|
||||
return { result: null }
|
||||
}
|
||||
|
||||
const safeObserve = async (tag) => {
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
try {
|
||||
return await browser.observe()
|
||||
} catch {
|
||||
out.debug.push(`${tag}:observe-retry-${i + 1}`)
|
||||
await sleep(450)
|
||||
}
|
||||
}
|
||||
return { url: browser.getState().currentUrl, failed: true }
|
||||
}
|
||||
|
||||
const safeScroll = async (y) => {
|
||||
try {
|
||||
return await browser.scroll({ y })
|
||||
} catch {
|
||||
return { movedY: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
out.open = await browser.open({
|
||||
headless: false,
|
||||
width: 1366,
|
||||
height: 900,
|
||||
browserKind: "testing",
|
||||
persistentProfile: true,
|
||||
userDataDir: "/home/pancho/.chromium-perfil-google",
|
||||
startUrl: "https://www.google.es",
|
||||
recordVideo: false,
|
||||
recordLabel: "google-es-organic-click-scroll-v8d",
|
||||
})
|
||||
|
||||
out.cookies = await browser.handleConsent("accept").catch((e) => ({ error: e.message }))
|
||||
await browser.type('textarea[name="q"]', "barranquismo granada")
|
||||
await browser.press("Enter")
|
||||
await browser.waitFor({ for: "timeout", value: "1200" })
|
||||
|
||||
out.afterSearch = await safeObserve("afterSearch")
|
||||
|
||||
if ((out.afterSearch.url || "").includes("/sorry/index")) {
|
||||
out.humanAttempt = await browser.handleHumanCheck().catch((e) => ({ error: e.message }))
|
||||
out.before3s = await browser.snapshot({ label: "google-v8d-before-3s", fullPage: true }).catch((e) => ({
|
||||
error: e.message,
|
||||
}))
|
||||
await browser.waitFor({ for: "timeout", value: "3000" })
|
||||
out.after3sObserve = await safeObserve("after3s")
|
||||
out.after3s = await browser.snapshot({ label: "google-v8d-after-3s", fullPage: true }).catch((e) => ({
|
||||
error: e.message,
|
||||
}))
|
||||
}
|
||||
|
||||
let found = null
|
||||
let globalRank = 0
|
||||
|
||||
for (let pageNo = 1; pageNo <= maxPages; pageNo += 1) {
|
||||
await browser.waitFor({ for: "timeout", value: "700" })
|
||||
const currentUrl = browser.getState().currentUrl || ""
|
||||
if (!currentUrl.includes("/search")) {
|
||||
out.pages.push({ page: pageNo, status: "not_on_search", url: currentUrl })
|
||||
break
|
||||
}
|
||||
|
||||
const extracted = await safeEval(organicScript, {}, `organic-p${pageNo}`)
|
||||
const organic = Array.isArray(extracted.result) ? extracted.result : []
|
||||
const idx = organic.findIndex((x) => {
|
||||
try {
|
||||
return new URL(String(x.href)).hostname.includes(targetDomain)
|
||||
} catch {
|
||||
return String(x.href).includes(targetDomain)
|
||||
}
|
||||
})
|
||||
|
||||
out.pages.push({
|
||||
page: pageNo,
|
||||
url: currentUrl,
|
||||
organicCount: organic.length,
|
||||
found: idx >= 0,
|
||||
positionOnPage: idx >= 0 ? idx + 1 : null,
|
||||
})
|
||||
|
||||
if (idx >= 0) {
|
||||
globalRank += idx + 1
|
||||
found = {
|
||||
page: pageNo,
|
||||
positionOnPage: idx + 1,
|
||||
globalRank,
|
||||
href: organic[idx].href,
|
||||
title: organic[idx].title,
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
globalRank += organic.length
|
||||
await safeScroll(680)
|
||||
await sleep(180)
|
||||
await safeScroll(-240)
|
||||
|
||||
let moved = false
|
||||
for (const sel of ["#pnnext", "a#pnnext", "a[aria-label*='siguiente' i]", "a[aria-label*='next' i]"]) {
|
||||
const ex = await browser.query({ selector: sel, mode: "exists" }).catch(() => ({ result: false }))
|
||||
if (ex.result === true) {
|
||||
await browser.click(sel).catch(() => null)
|
||||
moved = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!moved) {
|
||||
const tried = await safeEval(nextFallbackScript, {}, `next-p${pageNo}`)
|
||||
out.pages[out.pages.length - 1].nextFallback = tried.result
|
||||
if (!tried.result?.clicked) break
|
||||
}
|
||||
|
||||
await sleep(1200)
|
||||
}
|
||||
|
||||
out.result = found || { found: false }
|
||||
|
||||
if (found?.href) {
|
||||
let clicked = false
|
||||
const byHrefSelector = `a[href="${String(found.href).replace(/"/g, '\\"')}"]`
|
||||
const exists = await browser.query({ selector: byHrefSelector, mode: "exists" }).catch(() => ({ result: false }))
|
||||
if (exists.result === true) {
|
||||
await browser
|
||||
.click(byHrefSelector)
|
||||
.then(() => {
|
||||
clicked = true
|
||||
})
|
||||
.catch(() => null)
|
||||
}
|
||||
|
||||
if (!clicked) {
|
||||
const clickRes = await safeEval(
|
||||
`(arg) => {
|
||||
const links = Array.from(document.querySelectorAll("#search a[href], a[href]"))
|
||||
const t = links.find((a) => (a.getAttribute("href") || "") === arg.href)
|
||||
if (!t) return { clicked: false }
|
||||
t.click()
|
||||
return { clicked: true }
|
||||
}`,
|
||||
{ href: found.href },
|
||||
"click-target",
|
||||
)
|
||||
clicked = Boolean(clickRes.result?.clicked)
|
||||
}
|
||||
|
||||
out.clickTarget = { clicked }
|
||||
if (!clicked) {
|
||||
await browser.navigate(found.href, "domcontentloaded")
|
||||
out.clickFallbackNavigate = true
|
||||
}
|
||||
|
||||
await browser.waitFor({ for: "timeout", value: "1000" })
|
||||
out.destinationObserve = await safeObserve("destination")
|
||||
|
||||
let loops = 0
|
||||
let lastMoved = 1
|
||||
while (loops < 50 && lastMoved !== 0) {
|
||||
const s = await safeScroll(420)
|
||||
lastMoved = Number(s.movedY ?? 0)
|
||||
await sleep(220)
|
||||
loops += 1
|
||||
}
|
||||
|
||||
out.scrollRun = { loops, lastMoved }
|
||||
out.destinationSnapshot = await browser.snapshot({ label: "google-v8d-destination-bottom", fullPage: true }).catch((e) => ({
|
||||
error: e.message,
|
||||
}))
|
||||
}
|
||||
|
||||
out.finalObserve = await safeObserve("final")
|
||||
out.finalSnapshot = await browser.snapshot({ label: "google-v8d-final", fullPage: true }).catch((e) => ({
|
||||
error: e.message,
|
||||
}))
|
||||
out.close = await browser.close()
|
||||
console.log(JSON.stringify(out, null, 2))
|
||||
}
|
||||
|
||||
run().catch(async (err) => {
|
||||
console.error("GOOGLE_V8D_ERROR", err?.stack || err?.message || err)
|
||||
try {
|
||||
const snap = await browser.snapshot({ label: "google-v8d-error", fullPage: true })
|
||||
console.error("ERROR_SNAPSHOT", snap.filePath)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await browser.close()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
113
scripts/google_pagination_v4.mjs
Normal file
113
scripts/google_pagination_v4.mjs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import path from "node:path"
|
||||
import { BrowserManager } from "../dist/browser/manager.js"
|
||||
|
||||
const browser = new BrowserManager(path.resolve("./artifacts"))
|
||||
const maxPages = 8
|
||||
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
const linksScript = `() =>
|
||||
Array.from(document.querySelectorAll('a[href]'))
|
||||
.map((a) => a.getAttribute('href') || '')
|
||||
.filter(Boolean)`
|
||||
|
||||
const nextScript = `() => {
|
||||
const labels = ['Siguiente', 'Next']
|
||||
const nodes = Array.from(document.querySelectorAll('a,button,[role="button"]'))
|
||||
for (const n of nodes) {
|
||||
const t = (n.innerText || '').trim()
|
||||
if (!t) continue
|
||||
if (labels.some((k) => t === k || t.includes(k))) {
|
||||
n.click()
|
||||
return { clicked: true, text: t }
|
||||
}
|
||||
}
|
||||
const byId = document.querySelector('#pnnext')
|
||||
if (byId) {
|
||||
byId.click()
|
||||
return { clicked: true, text: '#pnnext' }
|
||||
}
|
||||
return { clicked: false, text: null }
|
||||
}`
|
||||
|
||||
const run = async () => {
|
||||
const out = { pages: [] }
|
||||
out.open = await browser.open({
|
||||
headless: false,
|
||||
width: 1366,
|
||||
height: 900,
|
||||
startUrl: "https://www.google.com",
|
||||
recordVideo: true,
|
||||
recordLabel: "google-pagination-v4",
|
||||
})
|
||||
|
||||
out.consent = await browser.handleConsent("reject").catch((e) => ({ error: e.message }))
|
||||
await browser.type('textarea[name="q"]', "barranquismo granada")
|
||||
await browser.press("Enter")
|
||||
|
||||
out.observeStart = await browser.observe()
|
||||
if ((out.observeStart.url || "").includes("/sorry/index")) {
|
||||
out.human = await browser.handleHumanCheck().catch((e) => ({ error: e.message }))
|
||||
await browser.waitFor({ for: "timeout", value: "4000" })
|
||||
out.observeAfterHuman = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
}
|
||||
|
||||
let found = null
|
||||
|
||||
for (let p = 1; p <= maxPages; p += 1) {
|
||||
const url = browser.getState().currentUrl || ""
|
||||
if (!url.includes("/search")) {
|
||||
out.pages.push({ page: p, status: "not-on-search", url })
|
||||
break
|
||||
}
|
||||
|
||||
const links = await browser.evaluate(linksScript, undefined)
|
||||
const normalized = Array.isArray(links.result) ? links.result.map(String) : []
|
||||
const matchIndex = normalized.findIndex((href) => href.includes("barranquismogranada.com"))
|
||||
|
||||
out.pages.push({
|
||||
page: p,
|
||||
url,
|
||||
links: normalized.length,
|
||||
found: matchIndex >= 0,
|
||||
idx: matchIndex >= 0 ? matchIndex + 1 : null,
|
||||
})
|
||||
|
||||
if (matchIndex >= 0) {
|
||||
found = { page: p, positionOnPage: matchIndex + 1, url }
|
||||
break
|
||||
}
|
||||
|
||||
const nextTried = await browser.evaluate(nextScript, undefined)
|
||||
out.pages[out.pages.length - 1].next = nextTried.result
|
||||
if (!nextTried.result?.clicked) {
|
||||
break
|
||||
}
|
||||
await wait(1400)
|
||||
}
|
||||
|
||||
out.result = found || { found: false }
|
||||
out.finalObserve = await browser.observe().catch((e) => ({ error: e.message }))
|
||||
out.snapshot = await browser.snapshot({ label: "google-pagination-v4-final", fullPage: true }).catch((e) => ({
|
||||
error: e.message,
|
||||
}))
|
||||
out.close = await browser.close()
|
||||
|
||||
console.log(JSON.stringify(out, null, 2))
|
||||
}
|
||||
|
||||
run().catch(async (err) => {
|
||||
console.error("GOOGLE_PAGINATION_V4_ERROR", err?.message || err)
|
||||
try {
|
||||
const snap = await browser.snapshot({ label: "google-pagination-v4-error", fullPage: true })
|
||||
console.error("ERROR_SNAPSHOT", snap.filePath)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await browser.close()
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
process.exit(1)
|
||||
})
|
||||
1089
src/browser/manager.ts
Normal file
1089
src/browser/manager.ts
Normal file
File diff suppressed because it is too large
Load diff
1049
src/server.ts
Normal file
1049
src/server.ts
Normal file
File diff suppressed because it is too large
Load diff
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue