diff --git a/functions/browser/cdp_click_ref.go b/functions/browser/cdp_click_ref.go index 2597bafb..2e100618 100644 --- a/functions/browser/cdp_click_ref.go +++ b/functions/browser/cdp_click_ref.go @@ -1,6 +1,15 @@ package browser -import "fmt" +import ( + "fmt" + "time" +) + +// refActionableTimeout es cuánto espera CdpClickRef/CdpHoverRef a que el elemento +// sea accionable (visible+stable+hit-test) antes de caer al cálculo de centro +// previo. Lo bastante para tragar animaciones/overlays transitorios sin penalizar +// el caso común (que converge en ~1 frame). +const refActionableTimeout = 2 * time.Second // refBoxCenter resuelve el centro (x,y) en coords de página de un nodo DOM por su // backendDOMNodeId, vía DOM.getBoxModel. El content quad son 8 floats (4 esquinas). @@ -37,6 +46,13 @@ func CdpClickRef(c *CDPConn, backendNodeID int, opts MouseHumanOpts) error { if opts.Mode == "instant" { return clickRefViaJS(c, backendNodeID) } + // Preferir el punto validado por actionability (visible + stable + hit-test): + // evita clicks tragados por overlays/banners y elementos aún montándose o + // animándose. Si no converge dentro del timeout, se cae al cálculo de centro + // previo (sin regresión). + if x, y, err := CdpWaitActionable(c, backendNodeID, false, refActionableTimeout); err == nil { + return CdpClickXYHuman(c, x, y, opts) + } // scroll al elemento si no está visible; ignorar error (no fatal) _, _ = c.sendCDP("DOM.scrollIntoViewIfNeeded", map[string]any{"backendNodeId": backendNodeID}) cx, cy, err := refBoxCenter(c, backendNodeID) diff --git a/functions/browser/cdp_click_ref.md b/functions/browser/cdp_click_ref.md index 3f1da0ed..bc6e314f 100644 --- a/functions/browser/cdp_click_ref.md +++ b/functions/browser/cdp_click_ref.md @@ -8,7 +8,7 @@ purity: impure signature: "func CdpClickRef(c *CDPConn, backendNodeID int, opts MouseHumanOpts) error" description: "Click humanizado (Bézier + jitter) sobre el elemento identificado por su #ref del AX outline. El #ref es el backendDOMNodeId estable del nodo DOM. Hace scroll al elemento si no está en viewport antes de calcular las coordenadas vía DOM.getBoxModel." tags: [cdp, browser, action, ref, humanized, navegator] -uses_functions: [cdp_click_xy_human_go_browser] +uses_functions: [cdp_click_xy_human_go_browser, cdp_wait_actionable_go_browser] uses_types: [] returns: [] returns_optional: false diff --git a/functions/browser/cdp_hover_ref.go b/functions/browser/cdp_hover_ref.go index 2e28dcd0..ccfd82a1 100644 --- a/functions/browser/cdp_hover_ref.go +++ b/functions/browser/cdp_hover_ref.go @@ -9,6 +9,10 @@ func CdpHoverRef(c *CDPConn, backendNodeID int, opts MouseHumanOpts) error { if c == nil { return fmt.Errorf("cdp hover ref: conexión nil") } + // Preferir el punto validado por actionability; si no converge, caer al centro. + if x, y, err := CdpWaitActionable(c, backendNodeID, false, refActionableTimeout); err == nil { + return CdpMoveMouseHuman(c, x, y, opts) + } // scroll al elemento si no está visible; ignorar error (no fatal) _, _ = c.sendCDP("DOM.scrollIntoViewIfNeeded", map[string]any{"backendNodeId": backendNodeID}) cx, cy, err := refBoxCenter(c, backendNodeID) diff --git a/functions/browser/cdp_hover_ref.md b/functions/browser/cdp_hover_ref.md index e57f74d9..65f33584 100644 --- a/functions/browser/cdp_hover_ref.md +++ b/functions/browser/cdp_hover_ref.md @@ -8,7 +8,7 @@ purity: impure signature: "func CdpHoverRef(c *CDPConn, backendNodeID int, opts MouseHumanOpts) error" description: "Mueve el ratón con trayectoria humanizada (Bézier) sobre el elemento identificado por su #ref del AX outline. Útil para activar menús desplegables, tooltips y cualquier interacción que dependa de hover. El #ref es el backendDOMNodeId estable del nodo DOM." tags: [cdp, browser, action, ref, humanized, navegator] -uses_functions: [cdp_move_mouse_human_go_browser] +uses_functions: [cdp_move_mouse_human_go_browser, cdp_wait_actionable_go_browser] uses_types: [] returns: [] returns_optional: false