9798aed2cf
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>
88 lines
2.8 KiB
Go
88 lines
2.8 KiB
Go
package browser
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// CdpSelectOption selecciona la <option> de un <select> (localizado por selector
|
|
// CSS) cuyo value coincide con value; si ningun value coincide, busca por texto
|
|
// visible de la option. Tras setear select.value despacha los eventos 'input' y
|
|
// 'change' con bubbles:true para que frameworks (React/Vue) reaccionen al cambio.
|
|
//
|
|
// Devuelve error si el select no existe ("select not found") o si ninguna option
|
|
// coincide por value ni por texto ("option not found").
|
|
func CdpSelectOption(c *CDPConn, selector string, value string) error {
|
|
if c == nil {
|
|
return fmt.Errorf("cdp select option: conexion nula")
|
|
}
|
|
|
|
// Script JS: localiza el select, busca la option por value y, como fallback,
|
|
// por textContent (trim). Si encuentra, setea value, dispara input+change y
|
|
// devuelve "__OK__". Si no, devuelve un centinela de error claro. Usamos
|
|
// JSON.stringify de los inputs para inyectarlos de forma segura.
|
|
js := fmt.Sprintf(`(function() {
|
|
var sel = document.querySelector(%s);
|
|
if (!sel) return '__NO_SELECT__';
|
|
var want = %s;
|
|
var opts = Array.prototype.slice.call(sel.options);
|
|
var match = null;
|
|
for (var i = 0; i < opts.length; i++) {
|
|
if (opts[i].value === want) { match = opts[i]; break; }
|
|
}
|
|
if (!match) {
|
|
for (var j = 0; j < opts.length; j++) {
|
|
if ((opts[j].textContent || '').trim() === want) { match = opts[j]; break; }
|
|
}
|
|
}
|
|
if (!match) return '__NO_OPTION__';
|
|
sel.value = match.value;
|
|
sel.dispatchEvent(new Event('input', {bubbles: true}));
|
|
sel.dispatchEvent(new Event('change', {bubbles: true}));
|
|
return '__OK__';
|
|
})()`, jsString(selector), jsString(value))
|
|
|
|
res, err := CdpEvaluate(c, js)
|
|
if err != nil {
|
|
return fmt.Errorf("cdp select option: evaluar selector %q: %w", selector, err)
|
|
}
|
|
|
|
res = strings.Trim(res, `"`)
|
|
switch res {
|
|
case "__OK__":
|
|
return nil
|
|
case "__NO_SELECT__":
|
|
return fmt.Errorf("cdp select option: select not found para selector %q", selector)
|
|
case "__NO_OPTION__":
|
|
return fmt.Errorf("cdp select option: option not found para value %q en select %q", value, selector)
|
|
default:
|
|
return fmt.Errorf("cdp select option: resultado inesperado %q para selector %q", res, selector)
|
|
}
|
|
}
|
|
|
|
// jsString convierte un string Go en un literal JS seguro (entre comillas dobles,
|
|
// con escapes para comillas, backslashes y saltos de linea). Evita la inyeccion
|
|
// de codigo al interpolar selectores/valores arbitrarios en el script JS.
|
|
func jsString(s string) string {
|
|
var b strings.Builder
|
|
b.WriteByte('"')
|
|
for _, r := range s {
|
|
switch r {
|
|
case '"':
|
|
b.WriteString(`\"`)
|
|
case '\\':
|
|
b.WriteString(`\\`)
|
|
case '\n':
|
|
b.WriteString(`\n`)
|
|
case '\r':
|
|
b.WriteString(`\r`)
|
|
case '\t':
|
|
b.WriteString(`\t`)
|
|
default:
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
b.WriteByte('"')
|
|
return b.String()
|
|
}
|