4187f9b6b1
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>
99 lines
5.6 KiB
Markdown
99 lines
5.6 KiB
Markdown
---
|
|
name: cdp_select_dropdown
|
|
kind: function
|
|
lang: go
|
|
domain: browser
|
|
version: "1.0.0"
|
|
purity: impure
|
|
signature: "func CdpSelectDropdown(c *CDPConn, triggerSelector string, optionText string, opts CdpDropdownOpts) error"
|
|
description: "Selecciona una opcion en un DESPLEGABLE CUSTOM (combobox/listbox ARIA, react-select, MUI Select, headlessui, select2) — esos donde un <select> nativo NO aplica. Replica el patron de Playwright (que no tiene API para custom dropdowns): click REAL en el trigger (mousedown, no element.click JS), espera la apertura por polling (aria-expanded=true O [role=listbox]/[role=menu] visible O opciones con rect>0), localiza la opcion por texto normalizado (substring o exacto, case-insensitive) y hace click REAL en su centro, con verificacion suave (aria-expanded vuelve a false o Enter como fallback). Reusa CdpEvaluate, CdpClickXYHuman y CdpPressKey."
|
|
tags: [browser, chrome, cdp, automation, dropdown, combobox, listbox, aria, select, react-select, mui, headlessui, devtools]
|
|
uses_functions: [cdp_evaluate_go_browser, cdp_click_xy_human_go_browser, cdp_press_key_go_browser]
|
|
uses_types: []
|
|
returns: []
|
|
returns_optional: false
|
|
error_type: "error_go_core"
|
|
imports: [fmt, strings, time]
|
|
params:
|
|
- name: c
|
|
desc: "conexion CDP activa (*CDPConn)"
|
|
- name: triggerSelector
|
|
desc: "selector CSS del elemento que abre el desplegable (el boton/combobox sobre el que se hace click real)"
|
|
- name: optionText
|
|
desc: "texto visible de la opcion a elegir; se normaliza (trim + colapsar espacios) y se compara case-insensitive, por substring si opts.Exact=false o por igualdad si opts.Exact=true"
|
|
- name: opts
|
|
desc: "CdpDropdownOpts{Exact bool (igualdad vs substring, default substring); TimeoutMs int (espera apertura+opcion, default 3000); OptionRole string (rol ARIA de las opciones, default 'option' — usar 'menuitem' para menus, 'treeitem' para arboles)}"
|
|
output: "error si el trigger no existe, si el dropdown no abre dentro del timeout (\"el dropdown no abrio\"), o si la opcion no aparece (\"option %q not found in dropdown\"); nil si el click sobre la opcion se realizo (la verificacion de cierre es suave y no falla duro si queda ambigua)"
|
|
tested: false
|
|
tests: []
|
|
test_file_path: ""
|
|
file_path: "functions/browser/cdp_select_dropdown.go"
|
|
---
|
|
|
|
## Ejemplo
|
|
|
|
```go
|
|
conn, _ := CdpConnect(9222)
|
|
CdpNavigate(conn, "https://mui.com/material-ui/react-select/")
|
|
|
|
// Combobox MUI: el trigger es el div con role=combobox; el listbox monta y
|
|
// anima al abrir. CdpSelectDropdown clica el trigger, espera a que el listbox
|
|
// este visible y entonces clica la opcion "Twenty".
|
|
err := CdpSelectDropdown(conn, "[role=combobox]", "Twenty", CdpDropdownOpts{})
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// react-select / headlessui: trigger por clase + match exacto + timeout amplio
|
|
// para listas que tardan en montar.
|
|
err = CdpSelectDropdown(conn, ".select__control", "España", CdpDropdownOpts{
|
|
Exact: true,
|
|
TimeoutMs: 6000,
|
|
})
|
|
|
|
// Menu tipo dropdown-menu (no listbox): las opciones son role=menuitem.
|
|
err = CdpSelectDropdown(conn, "#user-menu-btn", "Cerrar sesion", CdpDropdownOpts{
|
|
OptionRole: "menuitem",
|
|
})
|
|
```
|
|
|
|
## Cuando usarla
|
|
|
|
Usala cuando el desplegable NO es un `<select>` nativo: comboboxes/listboxes ARIA,
|
|
react-select, MUI Select, headlessui, select2, Ant Design, o cualquier menu hecho
|
|
con `<div>`/`<li>` + JS donde elegir = clicar el trigger y luego clicar la opcion
|
|
del menu desplegado. Es el equivalente al patron de Playwright
|
|
`click(trigger) -> getByRole('option', {name}) -> click(option)`, con la espera de
|
|
apertura ya resuelta. Para un `<select>` nativo de HTML usa `CdpSelectOption` (setea
|
|
`select.value` + dispara `input`/`change`), que es mas robusto y directo para ese
|
|
caso.
|
|
|
|
## Gotchas
|
|
|
|
- **Click real, no element.click()**: muchos dropdowns custom escuchan `mousedown`
|
|
(no `click`), por eso esta funcion despacha eventos de raton reales sobre el
|
|
centro del bbox. Solo cae a `element.click()` JS si el nodo no tiene geometria.
|
|
- **Animaciones de apertura**: el fallo nº1 reportado en Playwright es clicar la
|
|
opcion ANTES de que el listbox monte/anime. Por eso hay polling de apertura
|
|
(`dropdownWaitOpen`) que no avanza hasta que hay opciones visibles. Si tu
|
|
dropdown anima muy lento, sube `TimeoutMs`.
|
|
- **Listas virtualizadas** (react-window, virtuoso): solo renderizan las opciones
|
|
en viewport. Si la opcion buscada esta fuera del scroll inicial, puede que nunca
|
|
se monte y la funcion devuelva "not found" aunque exista. Mitigacion: escribe en
|
|
el combobox para filtrar (`CdpTypeText`) antes de llamar a esta funcion, o haz
|
|
scroll dentro del listbox primero.
|
|
- **Trigger vs contenedor**: `triggerSelector` debe apuntar al elemento que ABRE el
|
|
menu (el boton/combobox), no al `[role=listbox]` (que no existe hasta abrir).
|
|
- **Match de texto**: normaliza espacios y es case-insensitive; por defecto es
|
|
substring (`Exact=false`). Si varias opciones comparten substring, elige la
|
|
primera visible en orden de documento — usa `Exact=true` para desambiguar.
|
|
- **OptionRole**: por defecto `option` (`[role=option]`). Para menus de acciones usa
|
|
`menuitem`; para arboles `treeitem`. La deteccion de apertura tambien considera
|
|
`[role=menu]` y `li[role]` para cubrir patrones comunes.
|
|
- **Verificacion suave**: tras clicar, si el dropdown sigue abierto la funcion pulsa
|
|
`Enter` como fallback y devuelve `nil`. No falla duro si la seleccion no se puede
|
|
confirmar inequivocamente pero el click se hizo — comprueba el estado resultante
|
|
(texto del trigger, valor del formulario) si necesitas certeza.
|
|
- **iframes**: opera en el documento principal (via `CdpEvaluate`). Para un dropdown
|
|
dentro de un iframe necesitarias el contexto del frame (no cubierto aqui).
|