Files
fn_registry/functions/browser/cdp_set_file_input.go
T
Egutierrez 9798aed2cf feat(browser): cdp_collect_console + cdp_print_pdf + cdp_select_option + cdp_set_file_input
Cuatro primitivas CDP nuevas para el dominio browser, base de nuevas tools del
browser_mcp:
- cdp_collect_console: snapshot temporal de console + exceptions + log entries
- cdp_print_pdf: Page.printToPDF -> []byte
- cdp_select_option: selecciona <option> en un <select> y dispara input/change
- cdp_set_file_input: sube archivos a un <input type=file> via DOM.setFileInputFiles

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

83 lines
2.7 KiB
Go

package browser
import (
"fmt"
"os"
)
// CdpSetFileInput sube archivos a un <input type="file"> identificado por el
// selector CSS. Resuelve el nodo via DOM.getDocument + DOM.querySelector y luego
// asigna los archivos con DOM.setFileInputFiles. Util para automatizar formularios
// de subida sin simular el dialogo nativo de seleccion de archivos.
//
// Cada path de paths se valida con os.Stat ANTES de enviar el comando: si alguno
// no existe (o no es accesible) se devuelve error inmediato sin tocar el DOM. Los
// paths deben ser absolutos y accesibles por el proceso de Chrome (ver Gotchas en
// el .md): Chrome lee los archivos desde su propio contexto, no desde el de este
// programa.
func CdpSetFileInput(c *CDPConn, selector string, paths []string) error {
if c == nil {
return fmt.Errorf("cdp set file input: conexion nula")
}
if selector == "" {
return fmt.Errorf("cdp set file input: selector vacio")
}
if len(paths) == 0 {
return fmt.Errorf("cdp set file input: lista de paths vacia")
}
// Validar que cada path exista en disco antes de mandar nada a Chrome.
for _, p := range paths {
if p == "" {
return fmt.Errorf("cdp set file input: path vacio en la lista")
}
if _, err := os.Stat(p); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("cdp set file input: el archivo no existe: %q", p)
}
return fmt.Errorf("cdp set file input: no se puede acceder al archivo %q: %w", p, err)
}
}
// Obtener el nodo raiz del documento.
docRes, err := c.sendCDP("DOM.getDocument", map[string]any{"depth": 0})
if err != nil {
return fmt.Errorf("cdp set file input: DOM.getDocument: %w", err)
}
root, ok := docRes["root"].(map[string]any)
if !ok {
return fmt.Errorf("cdp set file input: respuesta de DOM.getDocument sin root")
}
rootNodeID, ok := root["nodeId"].(float64)
if !ok {
return fmt.Errorf("cdp set file input: DOM.getDocument sin nodeId raiz")
}
// Resolver el input por selector.
qsRes, err := c.sendCDP("DOM.querySelector", map[string]any{
"nodeId": int(rootNodeID),
"selector": selector,
})
if err != nil {
return fmt.Errorf("cdp set file input: DOM.querySelector %q: %w", selector, err)
}
nodeIDVal, ok := qsRes["nodeId"].(float64)
if !ok || int(nodeIDVal) == 0 {
return fmt.Errorf("cdp set file input: el selector %q no coincide con ningun elemento", selector)
}
// Asignar los archivos al input.
files := make([]any, len(paths))
for i, p := range paths {
files[i] = p
}
if _, err := c.sendCDP("DOM.setFileInputFiles", map[string]any{
"files": files,
"nodeId": int(nodeIDVal),
}); err != nil {
return fmt.Errorf("cdp set file input: DOM.setFileInputFiles en %q: %w", selector, err)
}
return nil
}