chore: auto-commit (13 archivos)

- CAPABILITIES_TODO.md
- demo_e2e/RESUMEN.md
- demo_e2e/results/prueba_1_quotes.json
- demo_e2e/results/prueba_2_perceive.json
- demo_e2e/results/prueba_3_search.json
- demo_e2e/results/prueba_4_login_session.json
- demo_e2e/results/prueba_5_books.json
- demo_e2e/results/prueba_6_session_storage.json
- demo_e2e/results/prueba_7_find_honesto.json
- demo_e2e/results/prueba_8_verificacion.json
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 13:20:36 +02:00
parent 23f9aa90e8
commit 618e3b0295
13 changed files with 363 additions and 64 deletions
+122 -1
View File
@@ -2,7 +2,7 @@
title: Capacidades de navegador (CDP) + construcción del MCP full-CDP
artefacto: project · projects/web_scraping
created: 06/06/2026 00:00
updated: 06/06/2026 07:00
updated: 06/06/2026 09:00
status: in_progress
related_issues: []
related_flows: []
@@ -244,6 +244,18 @@ Prioridad BAJA (formularios compuestos, emulación device, performance, drag):
## Hecho (lo que YA tenemos)
- [x] **Gap #1 — bucle percibir→actuar por `#ref` + auto-observe** (06/06/2026, 9/9 e2e)
- `#ref` = `backendDOMNodeId` (estable, no el efímero del AX) → ref→acción + refs estables resueltos
juntos y **stateless** (sin mapa en el MCP). Validado con dump de AXNode real antes de codear.
- Funciones nuevas: `cdp_click_xy_human` (primitivo de click humanizado por coords, compartido por
selector/#ref/visión), `cdp_click_ref`, `cdp_type_ref`, `cdp_hover_ref`. `cdp_click_human`
refactorizado para usar el primitivo (un solo camino de click).
- MCP: tools `dom_click_ref`/`dom_type_ref`/`dom_hover_ref` con **auto-observe** (devuelven el outline
tras la acción). browser_mcp v0.3.0, **39 tools**.
- `render_ax_outline` mejorado (defectos F1): guard de ciclo + profundidad, sin `ljust`, renderiza `value`.
- Validado prueba e2e 9: login en the-internet **solo por `#ref`**, sin un selector CSS.
- Pendiente de la familia: política de humanización por sesión (`human`/`fast`/`instant`).
- enlace: functions/browser/cdp_{click_xy_human,click_ref,type_ref,hover_ref}.go + render_ax_outline.py
- [x] **Tanda de deuda A+D+E+B — 4 fixes + 8/8 e2e** (06/06/2026)
- **A** aislamiento robusto: `chrome_launch` usa el binario real (salta el wrapper que pisaba flags).
- **D** `sessionStorage` añadido a `storage_state` (save+load). Validado por prueba e2e 6.
@@ -433,6 +445,115 @@ funcionó**. Evaluado contra el bucle PERCIBIR → DECIDIR → ACTUAR → VERIFI
---
## Roadmap MCP de clase — benchmark Playwright + Visión (apuntado 06/06/2026)
Objetivo: pasar de "wrapper con ~36 tools" a un MCP de clase. La métrica NO es el número de tools
(Playwright MCP ~60, Chrome DevTools MCP 26) — gran parte de los 60 de Playwright son inflado (storage
desglosado, video/tracing). Lo que sube el nivel real es **cerrar los 4 gaps de clase + dar al agente
tres sentidos de percepción→acción**.
### Los tres sentidos del agente (percepción → acción)
El agente tendrá tres vías para "ver" una página y actuar sobre ella. Cada una necesita su puente a la acción:
1. **DOM / AX tree** (hoy parcial) — `page_perceive` → outline con `#ref`. **Falta el puente `#ref` → acción.**
2. **Texto** (hoy) — `cdp_get_text` (compacto, selector opcional).
3. **Visión** (futuro, el usuario aportará los modelos) — screenshot → **OCR** (texto + bbox) + **YOLO**
(objetos + bbox) → el agente ve la página como imagen. **Puente a la acción = coordinate mouse**
(`click_xy` sobre el bounding box detectado). Por esto coordinate mouse NO es inflado: es el sentido
de la mano para el ojo visual.
### Transversal — humanización SIEMPRE en las acciones (anti-detección)
REGLA DE DISEÑO: todas las acciones de ratón/teclado del agente usan por defecto la variante humanizada
(curva de Bézier + jitter + micro-pausas variables), no el evento sintético directo. Ya existen
`cdp_click_human` y `cdp_move_mouse_human`; el `cdp_type_text` ya escribe char a char con pausa. Aplicar:
- El puente `#ref` → acción (A1) y `click_xy` (C1) deben ir **por defecto** por el camino humanizado
(mover el ratón con Bézier hasta el destino + click con micro-pausa press/release), no por
`Input.dispatchMouseEvent` seco. Exponer un flag `instant=true` solo para tests/velocidad.
- Donde falte versión humanizada (scroll, drag_xy), crear la variante con jitter/easing.
- Objetivo: que el tráfico de input sea indistinguible de un humano — es ventaja nuestra frente a
Playwright/CDP-MCP, que no humanizan. No perderla al añadir tools nuevas.
### A. Gaps de clase (los 4 que de verdad suben el nivel)
- [x] **A1 — Puente `#ref` → acción** ✅ (06/06). `cdp_click_ref`/`cdp_type_ref`/`cdp_hover_ref` + primitivo
`cdp_click_xy_human`. Tools MCP `dom_click_ref`/`type_ref`/`hover_ref`. Validado prueba e2e 9.
Hoy el outline da `#ref=44` pero el LLM no puede usarlo: tiene que volver a selector CSS. Cierra el loop
percibir↔actuar. **Gap #1 ahora.**
- [x] **A2 — Refs estables** ✅ (06/06). Resuelto stateless: el `#ref` ES el `backendDOMNodeId` (estable
mientras el nodo viva), no el `nodeId` efímero del AX. Sin mapa de estado en el MCP. `render_ax_outline` actualizado.
- [x] **A3 — Auto-observe tras acción** ✅ (06/06). Las tools `_ref` del MCP devuelven el outline AX
actualizado tras la acción (settle 400ms + perceiveOutline). Verificación implícita. Validado prueba 9.
- [ ] **A4 — Política de target segura en el MCP** (P0 seguridad). `cdp_connect_target` da la herramienta;
falta la regla: el MCP lanza Chrome propio (perfil + puerto dedicados, ya en 9333) o exige target
explícito; nunca engancha a la tab del 9222 por `match=""`.
- [ ] **A5 — Intercept / mock de red** (`Fetch.enable`) (P0 programático). Mockear APIs, bloquear recursos,
inyectar headers. La tool más valiosa de Playwright (`route`). Hoy 0.
### B. Tools valiosas del benchmark Playwright (delta real, ~12)
- [ ] **B1 verbos de formulario**: `hover`, `select_option` (`<select>`), `file_upload` (`DOM.setFileInputFiles`),
`drag`/`drop`.
- [ ] **B2 network observability**: `network_requests` (lista en vivo) + `network_request` (inspeccionar uno).
Complementa el HAR post-mortem.
- [ ] **B3 `console_messages`**: capturar consola + excepciones JS (`Runtime.consoleAPICalled`). Debug + detección.
- [ ] **B4 assertions (modo programático)**: `verify_element_visible` / `verify_text_visible` / `verify_value`.
Es el EJE VERIFICAR aplicado a recetas YAML (assert por paso).
### C. Coordinate mouse — IMPORTANTE (base del modo visión)
Reclasificado de "inflado" a importante por el usuario: es el puente entre visión (bbox de OCR/YOLO) y acción.
- [ ] **C1** `cdp_click_xy(x, y)` — click por coordenadas (sobre lo que la visión detecta).
- [ ] **C2** `cdp_move_xy` / `cdp_mouse_down` / `cdp_mouse_up` — control fino del ratón por píxeles.
- [ ] **C3** `cdp_drag_xy(x1,y1,x2,y2)` — drag por coordenadas.
- [ ] **C4** `cdp_scroll` ya existe; añadir target por coordenadas/elemento (P1.5).
### D. Misc — IMPORTANTE
- [ ] **D1** `cdp_save_pdf` (`Page.printToPDF`) — exportar página a PDF.
- [ ] **D2** `cdp_get_console` (= B3) — consola + excepciones.
- [ ] **D3** `cdp_resize` / `cdp_emulate_device` (`Emulation.setDeviceMetricsOverride`) — viewport conocido
(clave para visión: coordenadas estables) + mobile/touch.
- [ ] **D4** `cdp_nav_back`/`forward` ya existen; `wait_for` cubierto por wait_load/idle/element.
- [ ] Screenshot como **MCP image content** (hoy `page_screenshot` va a archivo → el LLM no lo ve). Prerequisito
del modo visión.
### E. VISIÓN (OCR + YOLO) — tarea futura, el usuario aporta los modelos
- [ ] **E1** Pipeline de percepción visual: `page_screenshot` (a buffer) → **OCR** (texto + bbox) + **YOLO**
(objetos UI + bbox) → estructura `{label, text, bbox}` que el agente consume como "outline visual".
- [ ] **E2** Función `ocr_page` y `detect_ui_elements` (los modelos los aportará el usuario). Dominio nuevo
(¿`vision`?) o `browser`. Probable Python (modelos ML).
- [ ] **E3** Puente visión → acción vía coordinate mouse (sección C): el agente clica el centro del bbox.
- [ ] **E4** Screenshot como image content en el MCP (D) para que el LLM también vea el píxel directamente.
- Nota: la visión es el tercer sentido — complementa, no sustituye, al AX outline (que es más barato en
tokens). Útil cuando el DOM miente (canvas, iframes cross-origin, apps que pintan en `<canvas>`).
### F. Defectos de calidad a corregir en lo ya construido (del coach)
- [x] **F1** ✅ (06/06) `render_ax_outline`: guard de ciclo (`visited`) + límite de profundidad (`_MAX_DEPTH=60`);
`ljust(60)` eliminado; renderiza el `value` de inputs (`= 'texto'`). Hecho en la misma edición del `#ref`.
- [ ] **F2** `cdp_get_html` sin límite: **documentar** que es "crudo a propósito" (la vía compacta es
get_text + outline).
- [ ] **F3** `cdp_connect_target` con `match=""`: documentar que es inseguro fuera del MCP; la política
segura vive en A4.
- [ ] **F4** Limpieza de recursos: isolated worlds que crea `cdp_eval_in_frame`, conexiones del pool del MCP.
### G. Inflado de Playwright — NO construir salvo necesidad concreta
Storage granular desglosado (cookie/localStorage/sessionStorage × get/set/delete/list/clear = 17 tools;
nosotros lo hacemos con 6), video/tracing/highlight (9 tools de debug visual de testing), `generate_locator`,
`run_code_unsafe` (= nuestro `page_eval_js`). Replicar esto sube el contador a ~45 sin añadir poder.
### Telemetría
`call_monitor` ya existe — cada tool del MCP encaja sin trabajo extra.
---
## Enlaces
- App orquestadora — projects/web_scraping/apps/script_navegador (CLI rápido: open/click/type/eval/html/shot/wait/tabs/launch/close/profiles + runner YAML)
+23 -2
View File
@@ -15,9 +15,9 @@ de simples a complejas. No "compila", sino "funciona".
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)
## Resultado: 9/9 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).
Las pruebas 1-5 son la batería inicial; 6-9 se añadieron para validar las tandas de fixes (A/D/E, B, y gap #1).
| # | Prueba | Sitio sandbox | Capacidades ejercitadas | Resultado |
|---|---|---|---|---|
@@ -29,6 +29,7 @@ Las pruebas 1-5 son la batería inicial; 6-8 se añadieron para validar las tand
| 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) |
| 9 | Bucle percibir→actuar por `#ref` | the-internet.herokuapp.com/login | page_perceive → dom_type_ref/dom_click_ref + auto-observe | PASS — login solo por `#ref` (sin selector); cada acción devuelve el outline nuevo (gap #1) |
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.
@@ -69,6 +70,26 @@ Tras la primera batería se atacaron tres deudas, cada una validada:
encontraba (el caller creía que había encontrado algo). Ahora devuelve error explícito. Validado por la
prueba 7.
## Gap #1 — bucle percibir→actuar por `#ref` (lo que cierra el loop del agente)
El cambio que más sube el nivel del agente. Un solo cambio resolvió tres cosas a la vez:
- **`#ref` = `backendDOMNodeId`** (no el `nodeId` efímero del AX tree). `render_ax_outline` ahora emite el id
del nodo DOM real, estable mientras el nodo viva → el `#ref` que el LLM lee sigue válido cuando actúa un
instante después. Resuelve "ref→acción" y "refs estables" de golpe, **sin mantener mapa de estado** en el MCP.
- **Funciones por ref** (`cdp_click_ref`, `cdp_type_ref`, `cdp_hover_ref`): resuelven `backendDOMNodeId` →
bbox (`DOM.getBoxModel`) → centro → acción humanizada.
- **Primitivo único `cdp_click_xy_human`**: click humanizado por coordenadas, compartido por las tres vías
(selector, `#ref`, y futura visión OCR/YOLO). `cdp_click_human` se refactorizó para usarlo (un solo camino).
- **Auto-observe**: cada tool `_ref` del MCP devuelve el outline AX actualizado tras la acción → el LLM ve el
efecto sin pedir otra lectura = verificación implícita (como Playwright MCP).
- **Defectos de calidad de `render_ax_outline` corregidos** en la misma edición: guard de ciclo + límite de
profundidad (evita `RecursionError`), se quitó el `ljust(60)` (gastaba tokens en relleno), y ahora renderiza
el `value` de los inputs (texto escrito, estado).
Validado por la prueba 9: login completo en the-internet **actuando solo por `#ref`** (sin un solo selector CSS).
Pendiente de esta familia: política de humanización por sesión (`human`/`fast`/`instant`) para scraping masivo.
## 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
+3 -3
View File
@@ -40,7 +40,7 @@
"port": 9333,
"url": "https://quotes.toscrape.com"
},
"ms": 735,
"ms": 113,
"is_error": false,
"response": "navigated to https://quotes.toscrape.com"
},
@@ -50,7 +50,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 437,
"ms": 235,
"is_error": false,
"response": "page loaded"
},
@@ -60,7 +60,7 @@
"port": 9333,
"expression": "[...document.querySelectorAll('.quote')].map(q=>({text:q.querySelector('.text').innerText,author:q.querySelector('.author').innerText,tags:[...q.querySelectorAll('.tag')].map(t=>t.innerText)}))"
},
"ms": 2,
"ms": 6,
"is_error": false,
"response": "[{\"author\":\"Albert Einstein\",\"tags\":[\"change\",\"deep-thoughts\",\"thinking\",\"world\"],\"text\":\"“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”\"},{\"author\":\"J.K. Rowling\",\"tags\":[\"abilities\",\"choices\"],\"text\":\"“It is our choices, Harry, that show what we truly are, far more than our abilities.”\"},{\"author\":\"Albert Einstein\",\"tags\":[\"inspirational\",\"life\",\"live\",\"miracle\",\"miracles\"],\"text\":\"“There are only two ways to live your life. One is as though nothing is a miracle. The other is as though everything is a miracle.”\"},{\"author\":\"Jane Austen\",\"tags\":[\"aliteracy\",\"books\",\"classic\",\"humor\"],\"text\":\"“The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.”\"},{\"author\":\"Marilyn Monroe\",\"tags\":[\"be-yourself\",\"inspirational\"],\"text\":\"“Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.”\"},{\"author\":\"Albert Einstein\",\"tags\":[\"adulthood\",\"success\",\"value\"],\"text\":\"“Try not to become a man of success. Rather become a man of value.”\"},{\"author\":\"André Gide\",\"tags\":[\"life\",\"love\"],\"text\":\"“It is better to be hated for what you are than to be loved for what you are not.”\"},{\"author\":\"Thomas A. Edison\",\"tags\":[\"edison\",\"failure\",\"inspirational\",\"paraphrased\"],\"text\":\"“I have not failed. I've just found 10,000 ways that won't work.”\"},{\"author\":\"Eleanor Roosevelt\",\"tags\":[\"misattributed-eleanor-roosevelt\"],\"text\":\"“A woman is like a tea bag; you never know how strong it is until it's in hot water.”\"},{\"author\":\"Steve Martin\",\"tags\":[\"humor\",\"obvious\",\"simile\"],\"text\":\"“A day without sunshine is like, you know, night.”\"}]"
}
+6 -6
View File
@@ -3,8 +3,8 @@
"verdict": "PASS",
"has_refs": true,
"has_link": true,
"outline_len": 4021,
"outline_preview": "RootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=169\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=72\n image \"Fork me on GitHub\"\ngeneric\n heading \"Welcome to the-internet\"\n StaticText \"Welcome to the-internet\"\n InlineTextBox \"Welcome to the-internet\"\n heading \"Available Examples\"\n StaticText \"Available Examples\"\n InlineTextBox \"Available Examples\"\n list\n listitem\n link \"A/B Testing\" #ref=79\n StaticText \"A/B Testing\"\n InlineTextBox \"A/B Testing\"\n listitem\n link \"Add/Remove Elements\" #ref=81\n StaticText \"Add/Remove Elements\"\n InlineTextBox \"Add/Remove Elements\"\n listitem\n link \"Basic Auth\" #ref=83\n StaticText \"Basic Auth\"\n InlineTextBox \"Basic Auth\"\n StaticText \" (user and pass: admin)\"\n InlineTextBox \" (user and pass: admin)\"\n listitem\n link \"Broken Images\" #ref=85\n StaticText \"Broken Images\"\n InlineTextBox \"Broken Images\"\n listitem\n link \"Challenging DOM\" #ref=87\n StaticText \"Challenging DOM\"\n ",
"outline_len": 4017,
"outline_preview": "RootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=169\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=74\n image \"Fork me on GitHub\"\ngeneric\n heading \"Welcome to the-internet\"\n StaticText \"Welcome to the-internet\"\n InlineTextBox \"Welcome to the-internet\"\n heading \"Available Examples\"\n StaticText \"Available Examples\"\n InlineTextBox \"Available Examples\"\n list\n listitem\n link \"A/B Testing\" #ref=81\n StaticText \"A/B Testing\"\n InlineTextBox \"A/B Testing\"\n listitem\n link \"Add/Remove Elements\" #ref=83\n StaticText \"Add/Remove Elements\"\n InlineTextBox \"Add/Remove Elements\"\n listitem\n link \"Basic Auth\" #ref=84\n StaticText \"Basic Auth\"\n InlineTextBox \"Basic Auth\"\n StaticText \" (user and pass: admin)\"\n InlineTextBox \" (user and pass: admin)\"\n listitem\n link \"Broken Images\" #ref=86\n StaticText \"Broken Images\"\n InlineTextBox \"Broken Images\"\n listitem\n link \"Challenging DOM\" #ref=88\n StaticText \"Challenging DOM\"\n InlineTextBox \"Challenging DOM\"\n listitem\n link \"Checkboxes\" #ref=90\n StaticText \"Checkboxes\"\n InlineTextBox \"Checkboxes\"\n listitem\n link \"Context Menu\" #ref=92\n StaticTex",
"steps": [
{
"tool": "tab_navigate",
@@ -12,7 +12,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com"
},
"ms": 388,
"ms": 404,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com"
},
@@ -22,7 +22,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 833,
"ms": 241,
"is_error": false,
"response": "page loaded"
},
@@ -32,9 +32,9 @@
"port": 9333,
"max_chars": 4000
},
"ms": 125,
"ms": 103,
"is_error": false,
"response": "RootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=169\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=72\n image \"Fork me on GitHub\"\ngeneric\n heading \"Welcome to the-internet\"\n StaticText \"Welcome to the-internet\"\n InlineTextBox \"Welcome to the-internet\"\n heading \"Available Examples\"\n StaticText \"Available Examples\"\n InlineTextBox \"Available Examples\"\n list\n listitem\n link \"A/B Testing\" #ref=79\n StaticText \"A/B Testing\"\n InlineTextBox \"A/B Testing\"\n listitem\n link \"Add/Remove Elements\" #ref=81\n StaticText \"Add/Remove Elements\"\n InlineTextBox \"Add/Remove Elements\"\n listitem\n link \"Basic Auth\" #ref=83\n StaticText \"Basic Auth\"\n InlineTextBox \"Basic Auth\"\n StaticText \" (user and pass: admin)\"\n InlineTextBox \" (user and pass: admin)\"\n listitem\n link \"Broken Images\" #ref=85\n StaticText \"Broken Images\"\n InlineTextBox \"Broken Images\"\n listitem\n link \"Challenging DOM\" #ref=87\n StaticText \"Challenging DOM\"\n InlineTextBox \"Challenging DOM\"\n listitem\n link \"Checkboxes\" #ref=89\n StaticText \"Checkboxes\"\n InlineTextBox \"Checkboxes\"\n listitem\n link \"Context Menu\" #ref=91\n StaticText \"Context Menu\"\n InlineTextBox \"Context Menu\"\n listitem\n link \"Digest Authentication\" #ref=93\n StaticText \"Digest Authentication\"\n InlineTextBox \"Digest Au"
"response": "RootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=169\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=74\n image \"Fork me on GitHub\"\ngeneric\n heading \"Welcome to the-internet\"\n StaticText \"Welcome to the-internet\"\n InlineTextBox \"Welcome to the-internet\"\n heading \"Available Examples\"\n StaticText \"Available Examples\"\n InlineTextBox \"Available Examples\"\n list\n listitem\n link \"A/B Testing\" #ref=81\n StaticText \"A/B Testing\"\n InlineTextBox \"A/B Testing\"\n listitem\n link \"Add/Remove Elements\" #ref=83\n StaticText \"Add/Remove Elements\"\n InlineTextBox \"Add/Remove Elements\"\n listitem\n link \"Basic Auth\" #ref=84\n StaticText \"Basic Auth\"\n InlineTextBox \"Basic Auth\"\n StaticText \" (user and pass: admin)\"\n InlineTextBox \" (user and pass: admin)\"\n listitem\n link \"Broken Images\" #ref=86\n StaticText \"Broken Images\"\n InlineTextBox \"Broken Images\"\n listitem\n link \"Challenging DOM\" #ref=88\n StaticText \"Challenging DOM\"\n InlineTextBox \"Challenging DOM\"\n listitem\n link \"Checkboxes\" #ref=90\n StaticText \"Checkboxes\"\n InlineTextBox \"Checkboxes\"\n listitem\n link \"Context Menu\" #ref=92\n StaticText \"Context Menu\"\n InlineTextBox \"Context Menu\"\n listitem\n link \"Digest Authentication\" #ref=94\n StaticText \"Digest Authentication\"\n InlineTextBox \"Digest Authentication\"\n StaticText \" (user and pass: admin)\"\n InlineTextBox \" (user and pass: admin)\"\n listitem\n link \"Disappearing Elements\" #ref=96\n StaticText \"Disappearing Elements\"\n InlineTextBox \"Disappearing Elements\"\n listitem\n link \"Drag and Drop\" #ref=97\n "
}
]
}
+11 -11
View File
@@ -8,7 +8,7 @@
"args": {
"port": 9333
},
"ms": 1,
"ms": 7,
"is_error": false,
"response": "cookies cleared"
},
@@ -18,7 +18,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com/login"
},
"ms": 91,
"ms": 94,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com/login"
},
@@ -28,7 +28,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 249,
"ms": 233,
"is_error": false,
"response": "page loaded"
},
@@ -38,7 +38,7 @@
"port": 9333,
"selector": "#username"
},
"ms": 23,
"ms": 17,
"is_error": false,
"response": "clicked #username"
},
@@ -48,7 +48,7 @@
"port": 9333,
"text": "tomsmith"
},
"ms": 115,
"ms": 107,
"is_error": false,
"response": "typed text"
},
@@ -58,7 +58,7 @@
"port": 9333,
"selector": "#password"
},
"ms": 8,
"ms": 6,
"is_error": false,
"response": "clicked #password"
},
@@ -68,7 +68,7 @@
"port": 9333,
"text": "SuperSecretPassword!"
},
"ms": 279,
"ms": 265,
"is_error": false,
"response": "typed text"
},
@@ -78,7 +78,7 @@
"port": 9333,
"key": "Enter"
},
"ms": 4,
"ms": 5,
"is_error": false,
"response": "pressed Enter"
},
@@ -88,7 +88,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 1,
"ms": 2,
"is_error": false,
"response": "page loaded"
},
@@ -99,7 +99,7 @@
"selector": "#flash",
"timeout_ms": 8000
},
"ms": 435,
"ms": 208,
"is_error": false,
"response": "element appeared: #flash"
},
@@ -110,7 +110,7 @@
"selector": "#flash",
"max_bytes": 200
},
"ms": 2,
"ms": 0,
"is_error": false,
"response": " You logged into a secure area!\n×"
}
+20 -20
View File
@@ -13,7 +13,7 @@
"args": {
"port": 9333
},
"ms": 2,
"ms": 1,
"is_error": false,
"response": "cookies cleared"
},
@@ -23,7 +23,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com/login"
},
"ms": 96,
"ms": 86,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com/login"
},
@@ -33,7 +33,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 247,
"ms": 222,
"is_error": false,
"response": "page loaded"
},
@@ -43,7 +43,7 @@
"port": 9333,
"selector": "#username"
},
"ms": 4,
"ms": 13,
"is_error": false,
"response": "clicked #username"
},
@@ -53,7 +53,7 @@
"port": 9333,
"text": "tomsmith"
},
"ms": 93,
"ms": 112,
"is_error": false,
"response": "typed text"
},
@@ -63,7 +63,7 @@
"port": 9333,
"selector": "#password"
},
"ms": 9,
"ms": 5,
"is_error": false,
"response": "clicked #password"
},
@@ -73,7 +73,7 @@
"port": 9333,
"text": "SuperSecretPassword!"
},
"ms": 290,
"ms": 271,
"is_error": false,
"response": "typed text"
},
@@ -83,7 +83,7 @@
"port": 9333,
"selector": "button[type=submit]"
},
"ms": 10,
"ms": 9,
"is_error": false,
"response": "clicked button[type=submit]"
},
@@ -115,7 +115,7 @@
"selector": "#flash",
"max_bytes": 300
},
"ms": 3,
"ms": 1,
"is_error": false,
"response": " You logged into a secure area!\n×"
},
@@ -125,7 +125,7 @@
"port": 9333,
"path": "/tmp/demo_session.json"
},
"ms": 9,
"ms": 4,
"is_error": false,
"response": "storage state saved to /tmp/demo_session.json"
},
@@ -134,7 +134,7 @@
"args": {
"port": 9333
},
"ms": 4,
"ms": 1,
"is_error": false,
"response": "cookies cleared"
},
@@ -144,7 +144,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com/secure"
},
"ms": 195,
"ms": 183,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com/secure"
},
@@ -154,7 +154,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 229,
"ms": 231,
"is_error": false,
"response": "page loaded"
},
@@ -165,7 +165,7 @@
"selector": "#flash",
"max_bytes": 300
},
"ms": 0,
"ms": 1,
"is_error": false,
"response": " You must login to view the secure area!\n×"
},
@@ -175,7 +175,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com"
},
"ms": 92,
"ms": 102,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com"
},
@@ -185,7 +185,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 241,
"ms": 238,
"is_error": false,
"response": "page loaded"
},
@@ -195,7 +195,7 @@
"port": 9333,
"path": "/tmp/demo_session.json"
},
"ms": 5,
"ms": 7,
"is_error": false,
"response": "storage state loaded from /tmp/demo_session.json"
},
@@ -205,7 +205,7 @@
"port": 9333,
"url": "https://the-internet.herokuapp.com/secure"
},
"ms": 102,
"ms": 98,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com/secure"
},
@@ -215,7 +215,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 220,
"ms": 231,
"is_error": false,
"response": "page loaded"
},
@@ -225,7 +225,7 @@
"port": 9333,
"expression": "JSON.stringify({path:location.pathname,secure:document.body.innerText.includes('Secure Area')})"
},
"ms": 2,
"ms": 3,
"is_error": false,
"response": "{\"path\":\"/secure\",\"secure\":true}"
}
+8 -8
View File
@@ -27,7 +27,7 @@
"port": 9333,
"url": "https://books.toscrape.com/catalogue/page-1.html"
},
"ms": 164,
"ms": 26,
"is_error": false,
"response": "navigated to https://books.toscrape.com/catalogue/page-1.html"
},
@@ -37,7 +37,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 659,
"ms": 211,
"is_error": false,
"response": "page loaded"
},
@@ -47,7 +47,7 @@
"port": 9333,
"expression": "[...document.querySelectorAll('.product_pod')].map(b=>({title:b.querySelector('h3 a').getAttribute('title'),price:b.querySelector('.price_color').innerText,stock:b.querySelector('.availability').innerText.trim()}))"
},
"ms": 3,
"ms": 5,
"is_error": false,
"response": "[{\"price\":\"£51.77\",\"stock\":\"In stock\",\"title\":\"A Light in the Attic\"},{\"price\":\"£53.74\",\"stock\":\"In stock\",\"title\":\"Tipping the Velvet\"},{\"price\":\"£50.10\",\"stock\":\"In stock\",\"title\":\"Soumission\"},{\"price\":\"£47.82\",\"stock\":\"In stock\",\"title\":\"Sharp Objects\"},{\"price\":\"£54.23\",\"stock\":\"In stock\",\"title\":\"Sapiens: A Brief History of Humankind\"},{\"price\":\"£22.65\",\"stock\":\"In stock\",\"title\":\"The Requiem Red\"},{\"price\":\"£33.34\",\"stock\":\"In stock\",\"title\":\"The Dirty Little Secrets of Getting Your Dream Job\"},{\"price\":\"£17.93\",\"stock\":\"In stock\",\"title\":\"The Coming Woman: A Novel Based on the Life of the Infamous Feminist, Victoria Woodhull\"},{\"price\":\"£22.60\",\"stock\":\"In stock\",\"title\":\"The Boys in the Boat: Nine Americans and Their Epic Quest for Gold at the 1936 Berlin Olympics\"},{\"price\":\"£52.15\",\"stock\":\"In stock\",\"title\":\"The Black Maria\"},{\"price\":\"£13.99\",\"stock\":\"In stock\",\"title\":\"Starving Hearts (Triangular Trade Trilogy, #1)\"},{\"price\":\"£20.66\",\"stock\":\"In stock\",\"title\":\"Shakespeare's Sonnets\"},{\"price\":\"£17.46\",\"stock\":\"In stock\",\"title\":\"Set Me Free\"},{\"price\":\"£52.29\",\"stock\":\"In stock\",\"title\":\"Scott Pilgrim's Precious Little Life (Scott Pilgrim #1)\"},{\"price\":\"£35.02\",\"stock\":\"In stock\",\"title\":\"Rip it Up and Start Again\"},{\"price\":\"£57.25\",\"stock\":\"In stock\",\"title\":\"Our Band Could Be Your Life: Scenes from the American Indie Underground, 1981-1991\"},{\"price\":\"£23.88\",\"stock\":\"In stock\",\"title\":\"Olio\"},{\"price\":\"£37.59\",\"stock\":\"In stock\",\"title\":\"Mesaerion: The Best Science Fiction Stories 1800-1849\"},{\"price\":\"£51.33\",\"stock\":\"In stock\",\"title\":\"Libertarianism for Beginners\"},{\"price\":\"£45.17\",\"stock\":\"In stock\",\"title\":\"It's Only the Himalayas\"}]"
},
@@ -57,7 +57,7 @@
"port": 9333,
"url": "https://books.toscrape.com/catalogue/page-2.html"
},
"ms": 107,
"ms": 22,
"is_error": false,
"response": "navigated to https://books.toscrape.com/catalogue/page-2.html"
},
@@ -67,7 +67,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 632,
"ms": 216,
"is_error": false,
"response": "page loaded"
},
@@ -87,7 +87,7 @@
"port": 9333,
"url": "https://books.toscrape.com/catalogue/page-3.html"
},
"ms": 110,
"ms": 18,
"is_error": false,
"response": "navigated to https://books.toscrape.com/catalogue/page-3.html"
},
@@ -97,7 +97,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 429,
"ms": 222,
"is_error": false,
"response": "page loaded"
},
@@ -107,7 +107,7 @@
"port": 9333,
"expression": "[...document.querySelectorAll('.product_pod')].map(b=>({title:b.querySelector('h3 a').getAttribute('title'),price:b.querySelector('.price_color').innerText,stock:b.querySelector('.availability').innerText.trim()}))"
},
"ms": 4,
"ms": 2,
"is_error": false,
"response": "[{\"price\":\"£57.31\",\"stock\":\"In stock\",\"title\":\"Slow States of Collapse: Poems\"},{\"price\":\"£26.41\",\"stock\":\"In stock\",\"title\":\"Reasons to Stay Alive\"},{\"price\":\"£47.61\",\"stock\":\"In stock\",\"title\":\"Private Paris (Private #10)\"},{\"price\":\"£23.11\",\"stock\":\"In stock\",\"title\":\"#HigherSelfie: Wake Up Your Life. Free Your Soul. Find Your Tribe.\"},{\"price\":\"£45.07\",\"stock\":\"In stock\",\"title\":\"Without Borders (Wanderlove #1)\"},{\"price\":\"£31.77\",\"stock\":\"In stock\",\"title\":\"When We Collided\"},{\"price\":\"£50.27\",\"stock\":\"In stock\",\"title\":\"We Love You, Charlie Freeman\"},{\"price\":\"£14.27\",\"stock\":\"In stock\",\"title\":\"Untitled Collection: Sabbath Poems 2014\"},{\"price\":\"£44.18\",\"stock\":\"In stock\",\"title\":\"Unseen City: The Majesty of Pigeons, the Discreet Charm of Snails \\u0026 Other Wonders of the Urban Wilderness\"},{\"price\":\"£18.78\",\"stock\":\"In stock\",\"title\":\"Unicorn Tracks\"},{\"price\":\"£25.52\",\"stock\":\"In stock\",\"title\":\"Unbound: How Eight Technologies Made Us Human, Transformed Society, and Brought Our World to the Brink\"},{\"price\":\"£16.28\",\"stock\":\"In stock\",\"title\":\"Tsubasa: WoRLD CHRoNiCLE 2 (Tsubasa WoRLD CHRoNiCLE #2)\"},{\"price\":\"£31.12\",\"stock\":\"In stock\",\"title\":\"Throwing Rocks at the Google Bus: How Growth Became the Enemy of Prosperity\"},{\"price\":\"£19.49\",\"stock\":\"In stock\",\"title\":\"This One Summer\"},{\"price\":\"£17.27\",\"stock\":\"In stock\",\"title\":\"Thirst\"},{\"price\":\"£19.09\",\"stock\":\"In stock\",\"title\":\"The Torch Is Passed: A Harding Family Story\"},{\"price\":\"£56.13\",\"stock\":\"In stock\",\"title\":\"The Secret of Dreadwillow Carse\"},{\"price\":\"£56.41\",\"stock\":\"In stock\",\"title\":\"The Pioneer Woman Cooks: Dinnertime: Comfort Classics, Freezer Food, 16-Minute Meals, and Other Delicious Ways to Solve Supper!\"},{\"price\":\"£56.50\",\"stock\":\"In stock\",\"title\":\"The Past Never Ends\"},{\"price\":\"£45.22\",\"stock\":\"In stock\",\"title\":\"The Natural History of Us (The Fine Art of Pretending #2)\"}]"
}
@@ -21,7 +21,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 224,
"ms": 239,
"is_error": false,
"response": "page loaded"
},
@@ -41,7 +41,7 @@
"port": 9333,
"path": "/tmp/demo_ss.json"
},
"ms": 10,
"ms": 5,
"is_error": false,
"response": "storage state saved to /tmp/demo_ss.json"
},
@@ -51,7 +51,7 @@
"port": 9333,
"expression": "window.sessionStorage.clear(); 'cleared'"
},
"ms": 2,
"ms": 1,
"is_error": false,
"response": "cleared"
},
@@ -71,7 +71,7 @@
"port": 9333,
"path": "/tmp/demo_ss.json"
},
"ms": 6,
"ms": 4,
"is_error": false,
"response": "storage state loaded from /tmp/demo_ss.json"
},
+4 -4
View File
@@ -11,7 +11,7 @@
"port": 9333,
"url": "https://quotes.toscrape.com"
},
"ms": 122,
"ms": 117,
"is_error": false,
"response": "navigated to https://quotes.toscrape.com"
},
@@ -21,7 +21,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 232,
"ms": 228,
"is_error": false,
"response": "page loaded"
},
@@ -31,7 +31,7 @@
"port": 9333,
"text": "Login"
},
"ms": 7,
"ms": 2,
"is_error": false,
"response": "body > div > div:nth-of-type(1) > div:nth-of-type(2) > p > a"
},
@@ -41,7 +41,7 @@
"port": 9333,
"text": "ZZZ_texto_inexistente_42"
},
"ms": 5,
"ms": 1,
"is_error": true,
"response": "cdp find by text: no se encontro elemento con texto \"ZZZ_texto_inexistente_42\""
}
+3 -3
View File
@@ -10,7 +10,7 @@
"port": 9333,
"url": "https://quotes.toscrape.com"
},
"ms": 112,
"ms": 103,
"is_error": false,
"response": "navigated to https://quotes.toscrape.com"
},
@@ -20,7 +20,7 @@
"port": 9333,
"timeout_ms": 12000
},
"ms": 212,
"ms": 223,
"is_error": false,
"response": "page loaded"
},
@@ -30,7 +30,7 @@
"port": 9333,
"expression": "var b=document.createElement('button');b.id='hidden_btn';b.textContent='x';b.style.display='none';document.body.appendChild(b);'injected'"
},
"ms": 2,
"ms": 3,
"is_error": false,
"response": "injected"
},
+107
View File
@@ -0,0 +1,107 @@
{
"name": "9 - Bucle percibir->actuar por #ref + auto-observe (gap #1)",
"verdict": "PASS",
"refs": {
"username": 5,
"password": 6,
"login": 7
},
"logged_in": true,
"auto_observed": true,
"flash": "You logged into a secure area!\n×",
"after_observe_preview": "clicked ref 7\n\nRootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=103\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Sel",
"steps": [
{
"tool": "cookie_clear",
"args": {
"port": 9333
},
"ms": 3,
"is_error": false,
"response": "cookies cleared"
},
{
"tool": "tab_navigate",
"args": {
"port": 9333,
"url": "https://the-internet.herokuapp.com/login"
},
"ms": 109,
"is_error": false,
"response": "navigated to https://the-internet.herokuapp.com/login"
},
{
"tool": "page_wait_load",
"args": {
"port": 9333,
"timeout_ms": 12000
},
"ms": 244,
"is_error": false,
"response": "page loaded"
},
{
"tool": "page_perceive",
"args": {
"port": 9333,
"max_chars": 6000
},
"ms": 107,
"is_error": false,
"response": "RootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=51\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=31\n image \"Fork me on GitHub\"\ngeneric\nheading \"Login Page\"\n StaticText \"Login Page\"\n InlineTextBox \"Login Page\"\nheading \"This is where you can log into the secure area. Enter tomsmith for the username and SuperSecretPassword! for the password. If the information is wrong you should see error messages.\"\n StaticText \"This is where you can log into the secure area. Enter \"\n InlineTextBox \"This is where you can log into the secure area. Enter \"\n emphasis\n StaticText \"tomsmith\"\n InlineTextBox \"tomsmith\"\n StaticText \" for the username and \"\n InlineTextBox \" for the \"\n InlineTextBox \"username and \"\n emphasis\n StaticText \"SuperSecretPassword!\"\n InlineTextBox \"SuperSecretPassword!\"\n StaticText \" for the password. If the information is wrong you should see error messages.\"\n InlineTextBox \" for the password. If the \"\n InlineTextBox \"information is wrong you should see error messages.\"\ngeneric\n button \" Login\" #ref=7\n generic\n StaticText \" Login\"\n InlineTextBox \" Login\"\ngeneric\n LabelText\n StaticText \"Username\"\n InlineTextBox \"Username\"\n textbox \"Username\" #ref=5\ngeneric\n LabelText\n StaticText \"Password\"\n InlineTextBox \"Password\"\n textbox \"Password\" #ref=6\nStaticText \"\"\n InlineTextBox \"\"\n"
},
{
"tool": "dom_type_ref",
"args": {
"port": 9333,
"ref": 5,
"text": "tomsmith"
},
"ms": 618,
"is_error": false,
"response": "typed into ref 5\n\nRootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=51\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=31\n image \"Fork me on GitHub\"\ngeneric\nheading \"Login Page\"\n StaticText \"Login Page\"\n InlineTextBox \"Login Page\"\nheading \"This is where you can log into the secure area. Enter tomsmith for the username and SuperSecretPassword! for the password. If the information is wrong you should see error messages.\"\n StaticText \"This is where you can log into the secure area. Enter \"\n InlineTextBox \"This is where you can log into the secure area. Enter \"\n emphasis\n StaticText \"tomsmith\"\n InlineTextBox \"tomsmith\"\n StaticText \" for the username and \"\n InlineTextBox \" for the \"\n InlineTextBox \"username and \"\n emphasis\n StaticText \"SuperSecretPassword!\"\n InlineTextBox \"SuperSecretPassword!\"\n StaticText \" for the password. If the information is wrong you should see error messages.\"\n InlineTextBox \" for the password. If the \"\n InlineTextBox \"information is wrong you should see error messages.\"\ngeneric\n button \" Login\" #ref=7\n generic\n StaticText \" Login\"\n InlineTextBox \" Login\"\ngeneric\n LabelText\n StaticText \"Username\"\n InlineTextBox \"Username\"\n textbox \"Username\" = 'tomsmith' #ref=5\n generic\n StaticText \"tomsmith\"\n InlineTextBox \"tomsmith\"\ngeneric\n LabelText\n StaticText \"Password\"\n InlineTextBox \"Password\"\n textbox \"Password\" #ref=6\nStaticText \"\"\n InlineTextBox \"\"\n"
},
{
"tool": "dom_type_ref",
"args": {
"port": 9333,
"ref": 6,
"text": "SuperSecretPassword!"
},
"ms": 759,
"is_error": false,
"response": "typed into ref 6\n\nRootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=51\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\nlink \"Fork me on GitHub\" #ref=31\n image \"Fork me on GitHub\"\ngeneric\nheading \"Login Page\"\n StaticText \"Login Page\"\n InlineTextBox \"Login Page\"\nheading \"This is where you can log into the secure area. Enter tomsmith for the username and SuperSecretPassword! for the password. If the information is wrong you should see error messages.\"\n StaticText \"This is where you can log into the secure area. Enter \"\n InlineTextBox \"This is where you can log into the secure area. Enter \"\n emphasis\n StaticText \"tomsmith\"\n InlineTextBox \"tomsmith\"\n StaticText \" for the username and \"\n InlineTextBox \" for the \"\n InlineTextBox \"username and \"\n emphasis\n StaticText \"SuperSecretPassword!\"\n InlineTextBox \"SuperSecretPassword!\"\n StaticText \" for the password. If the information is wrong you should see error messages.\"\n InlineTextBox \" for the password. If the \"\n InlineTextBox \"information is wrong you should see error messages.\"\ngeneric\n button \" Login\" #ref=7\n generic\n StaticText \" Login\"\n InlineTextBox \" Login\"\ngeneric\n LabelText\n StaticText \"Username\"\n InlineTextBox \"Username\"\n textbox \"Username\" = 'tomsmith' #ref=5\n generic\n StaticText \"tomsmith\"\n InlineTextBox \"tomsmith\"\ngeneric\n LabelText\n StaticText \"Password\"\n InlineTextBox \"Password\"\n textbox \"Password\" = '••••••••••••••••••••' #ref=6\n generic\n StaticText \"••••••••••••••••••••\"\n InlineTextBox \"••••••••••••••••••••\"\nStaticText \"\"\n InlineTextBox \"\"\n"
},
{
"tool": "dom_click_ref",
"args": {
"port": 9333,
"ref": 7
},
"ms": 1562,
"is_error": false,
"response": "clicked ref 7\n\nRootWebArea \"The Internet\"\ngeneric\n generic\n separator\n generic\n StaticText \"Powered by \"\n InlineTextBox \"Powered by \"\n link \"Elemental Selenium\" #ref=103\n StaticText \"Elemental Selenium\"\n InlineTextBox \"Elemental \"\n InlineTextBox \"Selenium\"\ngeneric\n StaticText \" You logged into a secure area!\"\n InlineTextBox \" You logged into a secure area!\"\n link \"×\" #ref=89\n StaticText \"×\"\n InlineTextBox \"×\"\nlink \"Fork me on GitHub\" #ref=91\n image \"Fork me on GitHub\"\ngeneric\nheading \"Secure Area\"\n StaticText \"Secure Area\"\n InlineTextBox \"Secure Area\"\nheading \"Welcome to the Secure Area. When you are done click logout below.\"\n StaticText \"Welcome to the Secure Area. When you are done click logout below.\"\n InlineTextBox \"Welcome to the Secure Area. When you are done click logout below.\"\nlink \"Logout\" #ref=97\n StaticText \"Logout\"\n InlineTextBox \"Logout\"\nStaticText \"\"\n InlineTextBox \"\"\n"
},
{
"tool": "page_wait_load",
"args": {
"port": 9333,
"timeout_ms": 12000
},
"ms": 1,
"is_error": false,
"response": "page loaded"
},
{
"tool": "page_get_text",
"args": {
"port": 9333,
"selector": "#flash",
"max_bytes": 200
},
"ms": 0,
"is_error": false,
"response": " You logged into a secure area!\n×"
}
]
}
+6 -1
View File
@@ -7,7 +7,7 @@
{
"prueba": "prueba_2_perceive",
"verdict": "PASS",
"detail": "outline 4021 chars, refs=True"
"detail": "outline 4017 chars, refs=True"
},
{
"prueba": "prueba_3_search",
@@ -38,5 +38,10 @@
"prueba": "prueba_8_verificacion",
"verdict": "PASS",
"detail": "click_hidden_err=True type_nofocus_err=True"
},
{
"prueba": "prueba_9_ref_loop",
"verdict": "PASS",
"detail": "refs=True logged=True auto_observe=True"
}
]
+46 -1
View File
@@ -15,6 +15,7 @@ Requisitos:
import json
import os
import re
import time
from mcp_client import MCPClient
@@ -296,6 +297,50 @@ def prueba_8_verificacion(c, log):
return verdict, f"click_hidden_err={bool(click_hidden_err)} type_nofocus_err={bool(type_nofocus_err)}"
def prueba_9_ref_loop(c, log):
"""Bucle percibir->actuar por #ref: perceive -> type_ref/click_ref (gap #1)."""
r = Recorder(c, log)
base = "https://the-internet.herokuapp.com"
r.step("cookie_clear", {"port": PORT})
r.step("tab_navigate", {"port": PORT, "url": base + "/login"})
r.step("page_wait_load", {"port": PORT, "timeout_ms": 12000})
outline, _ = r.step("page_perceive", {"port": PORT, "max_chars": 6000}, timeout=90)
def ref_for(pattern):
m = re.search(pattern + r'[^\n]*?#ref=(\d+)', outline)
return int(m.group(1)) if m else None
user_ref = ref_for(r'textbox "Username"')
pass_ref = ref_for(r'textbox "Password"')
# El name del botón incluye un icono FontAwesome antes de "Login" ().
login_ref = ref_for(r'button "[^"]*Login"')
refs_ok = all(x is not None for x in (user_ref, pass_ref, login_ref))
after = ""
if refs_ok:
# Actuar SOLO por #ref del outline — sin selector CSS. Cierra el bucle.
r.step("dom_type_ref", {"port": PORT, "ref": user_ref, "text": "tomsmith"})
r.step("dom_type_ref", {"port": PORT, "ref": pass_ref, "text": "SuperSecretPassword!"})
after, _ = r.step("dom_click_ref", {"port": PORT, "ref": login_ref}) # auto-observe → outline nuevo
r.step("page_wait_load", {"port": PORT, "timeout_ms": 12000})
flash, _ = r.step("page_get_text", {"port": PORT, "selector": "#flash", "max_bytes": 200})
logged = "logged into" in flash.lower()
# auto-observe: el dom_click_ref devolvió el outline tras la acción.
auto_observed = ("#ref=" in after) or ("Logout" in after) or (len(after) > 50)
ok = refs_ok and logged and auto_observed
verdict = "PASS" if ok else "FAIL"
save("prueba_9_ref_loop.json", {
"name": "9 - Bucle percibir->actuar por #ref + auto-observe (gap #1)",
"verdict": verdict,
"refs": {"username": user_ref, "password": pass_ref, "login": login_ref},
"logged_in": logged, "auto_observed": auto_observed,
"flash": flash.strip(), "after_observe_preview": after[:300],
"steps": r.steps,
})
return verdict, f"refs={refs_ok} logged={logged} auto_observe={auto_observed}"
def main():
log = open(os.path.join(RESULTS, "run.log"), "w", encoding="utf-8")
log.write(f"=== Demo e2e browser_mcp @ {time.strftime('%d/%m/%Y %H:%M')} ===\n")
@@ -307,7 +352,7 @@ def main():
pruebas = [prueba_1_quotes, prueba_2_perceive, prueba_3_search,
prueba_4_login_session, prueba_5_books,
prueba_6_session_storage, prueba_7_find_honesto,
prueba_8_verificacion]
prueba_8_verificacion, prueba_9_ref_loop]
summary = []
for fn in pruebas:
name = fn.__doc__.split("\n")[0]