23f9aa90e8
- .mcp.json - CAPABILITIES_TODO.md - demo_e2e/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
129 lines
8.1 KiB
Markdown
129 lines
8.1 KiB
Markdown
# Ejercicio e2e — validación de capacidades del browser_mcp
|
|
|
|
Fecha: 06/06/2026. Objetivo: comprobar, ejecutando de verdad contra sitios reales, que el servidor
|
|
`browser_mcp` (control de navegador vía CDP) puede hacer tareas distintas de recopilación de datos —
|
|
de simples a complejas. No "compila", sino "funciona".
|
|
|
|
## Montaje
|
|
|
|
- **Servidor**: `projects/web_scraping/apps/browser_mcp/browser_mcp` (36 tools, pool de conexiones).
|
|
- **Navegador**: Chrome/Chromium 148 aislado en el puerto CDP **9333** (no el 9222 del navegador diario),
|
|
`user-data-dir` dedicado. Lanzado headless para la batería y luego **sin headless** (ventana visible)
|
|
para inspección humana — ambos con 5/5.
|
|
- **Cliente**: `mcp_client.py` — cliente JSON-RPC stdio **secuencial** (espera la respuesta de cada tool
|
|
antes de mandar la siguiente, como hace un cliente MCP real). Un primer intento mandando todos los
|
|
mensajes de golpe falló por una race: el servidor procesa requests de forma concurrente.
|
|
- **Runner**: `run_demo.py` — ejecuta las 5 pruebas, guarda pasos/respuestas/veredicto en `results/`.
|
|
|
|
## Resultado: 8/8 PASS (headless y con ventana visible)
|
|
|
|
Las pruebas 1-5 son la batería inicial; 6-8 se añadieron para validar las tandas de fixes (A/D/E y B).
|
|
|
|
| # | Prueba | Sitio sandbox | Capacidades ejercitadas | Resultado |
|
|
|---|---|---|---|---|
|
|
| 1 | Extraer citas estructuradas | quotes.toscrape.com | navigate, wait_load, **eval → JSON real** | PASS — 10 citas `{text,author,tags}` |
|
|
| 2 | Percibir página como agente | the-internet.herokuapp.com | **page_perceive** (AX outline) | PASS — outline 4021 chars con `#ref` accionables |
|
|
| 3 | Submit de formulario con teclado | the-internet.herokuapp.com/login | dom_click, dom_type, **press_key Enter**, wait_element | PASS — "You logged into a secure area!" |
|
|
| 4 | Login + sesión persistente | the-internet.herokuapp.com | form, **storage_save/load**, cookie_clear | PASS — sesión restaurada sin re-login |
|
|
| 5 | Scraping paginado + dedup | books.toscrape.com | navegación multi-página, eval, composición | PASS — 60 libros únicos (3 páginas) |
|
|
| 6 | sessionStorage en storage_state | the-internet.herokuapp.com | set→save→clear→load→get | PASS — `clear=null`, `restore=demo_v` (fix D) |
|
|
| 7 | `find_by_text` honesto | quotes.toscrape.com | dom_find_by_text presente vs inexistente | PASS — texto inexistente devuelve error, no vacío (fix E) |
|
|
| 8 | Verificación post-acción | quotes.toscrape.com | dom_click sobre oculto / dom_type sin foco | PASS — ambos devuelven error en vez de actuar al vacío (fix B) |
|
|
|
|
Cobertura conjunta: lanzar/atar navegador, navegar, esperar carga, evaluar JS con JSON real, percibir
|
|
(AX outline), leer texto, teclado, formularios, cookies, estado de sesión, y composición multi-página.
|
|
|
|
## Valor del ejercicio: 3 bugs reales encontrados y arreglados
|
|
|
|
Ejecutar de verdad reveló defectos que "compila" jamás habría detectado:
|
|
|
|
1. **`page_perceive` roto** (bug de integración del MCP). Invocaba `fn run cdp_perceive_outline
|
|
--debug-port 9333 ...` con flags, pero `fn run` pasa los argumentos **posicionalmente** a la función
|
|
del pipeline → `int('--debug-port')` reventaba. Toda la percepción AX caía. Fix: argumentos
|
|
posicionales en el orden de la firma. (`tools_read.go`)
|
|
|
|
2. **`cdp_save_storage_state` guardaba cookies de todos los dominios.** Usaba `Network.getAllCookies`
|
|
(global), así que el `storage_state` arrastraba cookies de sitios visitados antes en la misma sesión
|
|
(en la prueba, cookies de Wikipedia contaminaban la sesión de the-internet). Fix: filtrar por el host
|
|
actual (`location.hostname`). (`cdp_save_storage_state.go`)
|
|
|
|
3. **`cdp_load_storage_state` no restauraba la sesión httpOnly.** `Network.setCookies` no aplicaba de
|
|
forma fiable la cookie de sesión (`rack.session`, httpOnly) porque a cada cookie le faltaba el campo
|
|
`url`. Fix: sintetizar `url` por cookie a partir de `domain`/`secure`/`path`. Con esto el login
|
|
persistente (la pieza estrella) funciona de verdad. (`cdp_load_storage_state.go`)
|
|
|
|
## Segunda tanda de fixes (A + D + E) — a partir del análisis de deuda
|
|
|
|
Tras la primera batería se atacaron tres deudas, cada una validada:
|
|
|
|
- **A — Aislamiento robusto del navegador del agente** (`chrome_launch.go`). El wrapper del sistema
|
|
`/etc/chromium.d/cdp` inyecta `--user-data-dir`/`--remote-debugging-port` globales a todo chromium;
|
|
el aislamiento dependía de que nuestros flags fueran al final (Chrome usa el último duplicado). Fix:
|
|
`findChrome` prefiere el **binario real** (`/usr/lib/chromium/chromium`), que al ejecutarse directo no
|
|
pasa por el wrapper y por tanto no hereda esos flags. Validado por construcción (el binario existe y va
|
|
primero; `browser_launch` devuelve PID correcto); la inspección observable del cmdline choca con el
|
|
exit-144 del entorno de pruebas al lanzar chromium desde el harness, no con el fix.
|
|
- **D — `sessionStorage` en `storage_state`** (`cdp_save_storage_state.go`, `cdp_load_storage_state.go`).
|
|
Antes solo cookies + localStorage. Ahora también el "cajón temporal". Validado por la prueba 6.
|
|
- **E — `cdp_find_by_text` honesto** (`cdp_find_by_text.go`). Antes devolvía `("", nil)` cuando no
|
|
encontraba (el caller creía que había encontrado algo). Ahora devuelve error explícito. Validado por la
|
|
prueba 7.
|
|
|
|
## Tercera tanda de fix (B) — verificación post-acción
|
|
|
|
- **B — fin del "fire-and-forget"** (`cdp_click.go`, `cdp_type_text.go`). `cdp_click` ahora verifica que
|
|
el elemento es **visible** antes de clicar (display:none / tamaño 0 / opacity 0 → error, en vez de clicar
|
|
en (0,0) sin efecto). `cdp_type_text` verifica que hay un **campo editable enfocado** (input/textarea/
|
|
select/contenteditable) antes de escribir (sin foco → error claro "usa CdpClick primero", en vez de
|
|
escribir a la nada). Validado por la prueba 8. Pendiente de esta familia: `cdp_scroll` con target
|
|
explícito (P1.5) y el puente percepción→acción por nodeId (P1.3).
|
|
|
|
## Hallazgos secundarios
|
|
|
|
- **`press_key Enter` no dispara widgets JS complejos.** Contra el buscador de Wikipedia (typeahead Vue
|
|
del skin Vector) el keyevent sintético se ejecutó sin error pero el widget no reaccionó. La prueba 3 se
|
|
reorientó a un formulario HTML normal (login de the-internet), donde Enter sí envía el form. Deuda: para
|
|
widgets JS-driven puede hacer falta disparar el evento del framework o submit explícito.
|
|
- **Las pruebas comparten un mismo Chrome**, por lo que el estado (cookies) se acumula entre ellas. Se
|
|
añadió `cookie_clear` al inicio de las pruebas con login para aislarlas. Un harness más estricto usaría
|
|
un contexto/perfil por prueba.
|
|
|
|
## Deuda pendiente (no bloqueó el ejercicio)
|
|
|
|
- `storage_state` aún no captura `sessionStorage` (sí cookies + localStorage). Suficiente para sesiones
|
|
basadas en cookie como the-internet; insuficiente para sitios que guardan el token en sessionStorage.
|
|
- Verificación post-acción (P1 del análisis LLM-readiness): `dom_click`/`dom_type` siguen siendo
|
|
fire-and-forget. En estas pruebas se compensó con `dom_wait_element` y checks por `eval`, pero las tools
|
|
no confirman su efecto por sí mismas.
|
|
|
|
## Cómo reproducir
|
|
|
|
```bash
|
|
# 1. Chrome aislado en 9333 (headless)
|
|
systemd-run --user -q --unit=browser_demo \
|
|
chromium --headless=new --remote-debugging-port=9333 \
|
|
--user-data-dir=/tmp/browser_mcp_userdata about:blank
|
|
|
|
# 1-bis. O con ventana visible (Linux con sesión gráfica):
|
|
systemd-run --user -q --unit=browser_demo \
|
|
--setenv=DISPLAY=:0 --setenv=XAUTHORITY=$HOME/.Xauthority \
|
|
chromium --remote-debugging-port=9333 \
|
|
--user-data-dir=/tmp/browser_mcp_visible --start-maximized about:blank
|
|
|
|
# 2. Compilar el MCP y ejecutar la batería
|
|
cd projects/web_scraping/apps/browser_mcp && go build -o browser_mcp .
|
|
cd ../../demo_e2e && python3 run_demo.py
|
|
|
|
# 3. Parar el navegador
|
|
systemctl --user stop browser_demo.service
|
|
```
|
|
|
|
## Archivos
|
|
|
|
- `mcp_client.py` — cliente MCP stdio secuencial (reutilizable).
|
|
- `run_demo.py` — las 5 pruebas.
|
|
- `results/prueba_N_*.json` — pasos, respuestas y datos extraídos por prueba.
|
|
- `results/run.log` — log de la corrida.
|
|
- `results/summary.json` — veredicto agregado.
|
|
- `results/mcp_stderr.log` — stderr del servidor MCP.
|