Files
fn_registry/functions/browser/cdp_wait_actionable.md
T
Egutierrez 4187f9b6b1 feat(browser): actionability + dropdowns + fill + role locator (estilo Playwright)
Tras estudiar el código de Playwright (sources/playwright), 4 primitivas nuevas y
1 endurecida para que la interacción web sea fiable:

- cdp_wait_actionable: visible + stable (2 rAF) + enabled + hit-test (elementFromPoint
  cruzando shadow DOM) + retry backoff + scroll cycling. Devuelve el punto validado.
  Réplica de _retryAction/_checkElementIsStable/expectHitTarget de Playwright.
- cdp_select_dropdown: desplegables custom (combobox/MUI/select2/headlessui): click real
  en trigger -> espera apertura (aria-expanded/[role=option] visible) -> click real en
  la opción. Resuelve el fallo nº1: clicar antes de que monte el listbox.
- cdp_select_option (endurecida v1.1.0): valida <select> real, match value/label
  normalizado/índice, option.selected para multiple, eventos input{composed}+change.
- cdp_fill: escribir fiable en inputs React/Vue: focus -> select-all -> Input.insertText
  (sin native value setter, como Playwright); native setter solo para inputs especiales.
- cdp_find_by_role: localizar por rol ARIA + accessible name (estilo getByRole),
  reutilizando el AX tree de cdp_get_ax_outline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:49:37 +02:00

5.2 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, params, output, file_path
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports tested tests test_file_path params output file_path
cdp_wait_actionable function go browser 1.0.0 impure func CdpWaitActionable(c *CDPConn, backendNodeID int, needEnabled bool, timeout time.Duration) (x float64, y float64, err error) Bloquea hasta que el elemento del #ref sea accionable (listo para un click/hover fiable) o expire timeout. Reproduce el modelo de actionability de Playwright: en bucle con backoff [0,20,100,100,500]ms comprueba visible (client rects + computed style), stable (mismo getBoundingClientRect en dos requestAnimationFrame seguidos), enabled opcional (disabled / aria-disabled / fieldset disabled subiendo la jerarquía), scroll into view rotando alineación block (center/start/end), y hit-test (elementFromPoint subiendo por shadow DOM apunta al target o descendiente). Devuelve el punto central (x,y) en coords de viewport listo para Input.dispatchMouseEvent. Al expirar, el error indica qué estado falló (not visible / not stable / disabled / outside viewport / intercepted by other element).
cdp
browser
action
ref
actionability
browser-actionability
navegator
false error_go_core
false
name desc
c Conexión CDP activa al tab objetivo.
name desc
backendNodeID El #ref del AX outline = backendDOMNodeId estable del nodo DOM. Se obtiene de page_perceive / render_ax_outline.
name desc
needEnabled Si true, exige también el estado enabled (no disabled, no aria-disabled=true, no dentro de <fieldset disabled>). Pasar false para elementos no interactivos (texto, contenedores) donde enabled no aplica.
name desc
timeout Tiempo máximo de espera antes de rendirse. <=0 usa 5s por defecto. El bucle de reintento nunca duerme más allá de este deadline.
(x, y) punto central del elemento en coordenadas de viewport (CSS px), listo para despachar el pointer, cuando todos los chequeos pasan; error si la conexión es nil, el nodo no resuelve a objectId, se desconecta del DOM, o expira el timeout (con el estado que falló al final). functions/browser/cdp_wait_actionable.go

Ejemplo

// Tras un page_perceive que devuelve outline con #ref=1234, esperar a que el
// elemento sea accionable y luego clicar el punto exacto que devuelve:
conn, _ := CdpConnect(9222)
x, y, err := CdpWaitActionable(conn, 1234, true, 5*time.Second)
if err != nil {
    log.Fatalf("no accionable: %v", err) // ej: "intercepted by other element: div#cookie-banner"
}
// x,y ya están en viewport, estables y sin overlay encima: click fiable.
_ = CdpClickXYHuman(conn, x, y, MouseHumanOpts{})

Cuando usarla

Antes de CUALQUIER click/hover/type que deba ser fiable sobre un #ref del outline. Llamarla justo después de page_perceive y antes de cdp_click_ref / cdp_click_xy_human / dom_*_ref para evitar los fallos clásicos del navegador: clicar un botón que aún se está animando hacia su posición, un elemento tapado por un banner de cookies / modal / spinner, o un control todavía disabled. Es la puerta de actionability que separa "el nodo existe en el DOM" de "el nodo está listo para recibir el evento ahí donde lo voy a despachar". Usar needEnabled=true para botones/inputs/enlaces; needEnabled=false para hover sobre texto o medir un contenedor.

Gotchas

  • Coste de polling. Es síncrona y bloqueante: hace un Runtime.callFunctionOn por iteración + 2 requestAnimationFrame por chequeo de estabilidad. En el peor caso poll-ea hasta timeout con backoff creciente (0,20,100,100,500ms → 500ms). No la metas en un bucle apretado sobre N elementos sin necesidad; una sola llamada por acción es lo correcto. Timeouts altos sobre elementos que nunca llegan (genuinamente ocultos) cuestan el timeout entero.
  • Shadow DOM. El hit-test sube por shadow roots (assignedSlot / parentNode.host) y por eso funciona con web components con shadow root abierto. Con shadow roots cerrados elementFromPoint no expone el interior y el hit-test puede reportar intercepted erróneamente; en ese caso usar el click vía element.click() (modo instant de cdp_click_ref), que no depende del hit-test geométrico.
  • iframes. Opera sobre el contexto de la página/frame al que apunta el *CDPConn. Un backendNodeID de otro frame no resuelve aquí: hay que tener la conexión/contexto del frame correcto (ver cdp_eval_in_frame). Las coordenadas devueltas son relativas al viewport de ESE documento, no compuestas con el offset del iframe en la página padre.
  • Estabilidad vs animaciones infinitas. Un elemento con una animación CSS perpetua que mueve su rect (spinner que se desplaza, marquee) nunca pasará el chequeo stable y agotará el timeout con "not stable". Es comportamiento correcto (no es accionable de forma fiable), pero conviene saberlo.
  • El punto devuelto es (x,y) de viewport, no de página. Es lo que Input.dispatchMouseEvent espera. Si necesitas coords de página (con scroll), el JS interno ya las calcula (pageX/pageY) pero la firma pública expone solo las de viewport para encajar con el dispatch de pointer.