Files
fn_registry/functions/infra/chrome_launch.go
T
egutierrez 73bf619191 feat: funciones Chrome CDP para automatización de navegador
10 funciones Go en infra/ para controlar Chrome via Chrome DevTools Protocol:
chrome_launch, cdp_connect, cdp_navigate, cdp_evaluate, cdp_screenshot,
cdp_click, cdp_type_text, cdp_wait_element, cdp_get_html, cdp_close.
WebSocket RFC 6455 implementado sin dependencias externas.
Incluye tests de integración con Chrome real.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:30:56 +02:00

122 lines
3.3 KiB
Go

package infra
import (
"fmt"
"net"
"os"
"os/exec"
"time"
)
// ChromeLaunchOpts configura el lanzamiento de Chrome con CDP.
type ChromeLaunchOpts struct {
// Port es el puerto de remote debugging. Por defecto 9222.
Port int
// UserDataDir es el directorio de perfil de Chrome. Por defecto /tmp/chrome-cdp-profile.
UserDataDir string
// Headless activa el modo headless (--headless=new). Por defecto false.
Headless bool
// ExtraArgs permite pasar flags adicionales a Chrome.
ExtraArgs []string
}
// chromePaths lista los ejecutables de Chrome conocidos en WSL2/Linux.
var chromePaths = []string{
"chrome.exe",
"google-chrome",
"chromium-browser",
"chromium",
"/mnt/c/Program Files/Google/Chrome/Application/chrome.exe",
"/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
"/mnt/c/Users/Public/Desktop/chrome.exe",
}
// findChrome localiza el ejecutable de Chrome en el sistema.
func findChrome() (string, error) {
for _, p := range chromePaths {
if path, err := exec.LookPath(p); err == nil {
return path, nil
}
if _, err := os.Stat(p); err == nil {
return p, nil
}
}
return "", fmt.Errorf("chrome: ejecutable no encontrado en PATH ni en rutas conocidas de Windows")
}
// waitCDPReady espera hasta que el puerto CDP responda conexiones TCP.
func waitCDPReady(port int, timeout time.Duration) error {
deadline := time.Now().Add(timeout)
addr := fmt.Sprintf("localhost:%d", port)
for time.Now().Before(deadline) {
conn, err := net.DialTimeout("tcp", addr, 200*time.Millisecond)
if err == nil {
conn.Close()
return nil
}
time.Sleep(200 * time.Millisecond)
}
return fmt.Errorf("chrome: puerto CDP %d no disponible despues de %s", port, timeout)
}
// ChromeLaunch lanza Google Chrome con remote debugging habilitado en el puerto indicado.
// Retorna el PID del proceso Chrome. Espera hasta 15s a que el puerto CDP este listo.
// Busca chrome.exe en PATH (WSL2) o en rutas conocidas de Windows.
func ChromeLaunch(opts ChromeLaunchOpts) (int, error) {
if opts.Port == 0 {
opts.Port = 9222
}
if opts.UserDataDir == "" {
opts.UserDataDir = "/tmp/chrome-cdp-profile"
}
chromePath, err := findChrome()
if err != nil {
return 0, err
}
args := []string{
fmt.Sprintf("--remote-debugging-port=%d", opts.Port),
fmt.Sprintf("--user-data-dir=%s", opts.UserDataDir),
"--no-first-run",
"--no-default-browser-check",
"--disable-background-networking",
"--disable-client-side-phishing-detection",
"--disable-default-apps",
"--disable-extensions",
"--disable-hang-monitor",
"--disable-popup-blocking",
"--disable-prompt-on-repost",
"--disable-sync",
"--disable-translate",
"--metrics-recording-only",
"--safebrowsing-disable-auto-update",
}
if opts.Headless {
args = append(args, "--headless=new", "--disable-gpu")
}
args = append(args, opts.ExtraArgs...)
cmd := exec.Command(chromePath, args...)
// Chrome necesita que no haya stderr/stdout bloqueados
cmd.Stdout = nil
cmd.Stderr = nil
if err := cmd.Start(); err != nil {
return 0, fmt.Errorf("chrome: arrancar proceso: %w", err)
}
pid := cmd.Process.Pid
// Esperar a que el puerto CDP este listo
if err := waitCDPReady(opts.Port, 15*time.Second); err != nil {
// Matar proceso si no arranco correctamente
cmd.Process.Kill()
return 0, err
}
return pid, nil
}