Files
fn_registry/functions/browser/cdp_click.go
T
egutierrez d7f2c00d7b feat: externalize apps/analysis to Gitea repos, add analysis table
- Migration 007: repo_url on apps table + analysis table with FTS5
- Analysis struct, parser, CRUD, validation, hash computation
- Selective purge: remote-only apps/analysis preserved across fn index
- CLI: fn app list/clone/pull, fn analysis list/clone/pull
- search/show/list now include analysis results
- Apps removed from git tracking (content lives in Gitea repos)
- .gitkeep for apps/ and analysis/ dirs
- Bash functions: jupyter analysis pipeline, shell utilities
- Browser domain: CDP functions moved from infra to browser

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 04:23:51 +02:00

95 lines
2.6 KiB
Go

package browser
import (
"fmt"
"strconv"
"strings"
)
// CdpClick hace click en el primer elemento que coincide con el selector CSS.
// Obtiene las coordenadas del elemento via Runtime.evaluate y luego despacha
// eventos mousedown, mouseup y click via Input.dispatchMouseEvent.
func CdpClick(c *CDPConn, selector string) error {
if c == nil {
return fmt.Errorf("cdp click: conexion nula")
}
// Obtener coordenadas del centro del elemento
js := fmt.Sprintf(`(function() {
var el = document.querySelector(%q);
if (!el) return null;
var r = el.getBoundingClientRect();
return JSON.stringify({x: r.left + r.width/2, y: r.top + r.height/2});
})()`, selector)
coordStr, err := CdpEvaluate(c, js)
if err != nil {
return fmt.Errorf("cdp click: obtener coordenadas de %q: %w", selector, err)
}
if coordStr == "" || coordStr == "null" {
return fmt.Errorf("cdp click: elemento %q no encontrado en el DOM", selector)
}
// Parsear "{x:...,y:...}" — CdpEvaluate ya retorna el JSON como string
coordStr = strings.Trim(coordStr, `"`)
x, y, err := parseCoords(coordStr)
if err != nil {
return fmt.Errorf("cdp click: parsear coordenadas %q: %w", coordStr, err)
}
// Hacer scroll al elemento para que este visible
scrollJS := fmt.Sprintf(`document.querySelector(%q).scrollIntoView({block:'center'})`, selector)
if _, err := CdpEvaluate(c, scrollJS); err != nil {
// No es fatal si el scroll falla
_ = err
}
// Despachar mousedown
mouseParams := map[string]any{
"type": "mousePressed",
"x": x,
"y": y,
"button": "left",
"clickCount": 1,
}
if _, err := c.sendCDP("Input.dispatchMouseEvent", mouseParams); err != nil {
return fmt.Errorf("cdp click: mousedown: %w", err)
}
// Despachar mouseup
mouseParams["type"] = "mouseReleased"
if _, err := c.sendCDP("Input.dispatchMouseEvent", mouseParams); err != nil {
return fmt.Errorf("cdp click: mouseup: %w", err)
}
return nil
}
// parseCoords extrae x e y de un string JSON como {"x":100,"y":200}.
func parseCoords(s string) (float64, float64, error) {
// Buscar valores x e y manualmente para evitar dependencia de encoding/json
s = strings.TrimSpace(s)
s = strings.TrimPrefix(s, "{")
s = strings.TrimSuffix(s, "}")
var x, y float64
for part := range strings.SplitSeq(s, ",") {
kv := strings.SplitN(strings.TrimSpace(part), ":", 2)
if len(kv) != 2 {
continue
}
k := strings.Trim(strings.TrimSpace(kv[0]), `"`)
v, err := strconv.ParseFloat(strings.TrimSpace(kv[1]), 64)
if err != nil {
return 0, 0, fmt.Errorf("parsear valor %q: %w", kv[1], err)
}
switch k {
case "x":
x = v
case "y":
y = v
}
}
return x, y, nil
}