feat: app script_navegador y dashboard Metabase
App Go para ejecutar scripts de navegación automatizada usando las funciones CDP del registry. Incluye script de creación de dashboard en Metabase para monitoreo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"fn-registry/functions/infra"
|
||||
)
|
||||
|
||||
// StepResult es el resultado de ejecutar un paso.
|
||||
type StepResult struct {
|
||||
Index int
|
||||
Action string
|
||||
Output string // resultado de evaluate/get_html, path de screenshot, etc.
|
||||
Err error
|
||||
Elapsed time.Duration
|
||||
}
|
||||
|
||||
// RunnerOpts configura la ejecucion del runner.
|
||||
type RunnerOpts struct {
|
||||
AbortOnError bool
|
||||
}
|
||||
|
||||
// Runner ejecuta los pasos de un Script sobre una conexion CDP activa.
|
||||
type Runner struct {
|
||||
conn *infra.CDPConn
|
||||
opts RunnerOpts
|
||||
}
|
||||
|
||||
// NewRunner crea un Runner con la conexion CDP dada.
|
||||
func NewRunner(conn *infra.CDPConn, opts RunnerOpts) *Runner {
|
||||
return &Runner{conn: conn, opts: opts}
|
||||
}
|
||||
|
||||
// Run ejecuta todos los pasos del script y retorna los resultados de cada paso.
|
||||
// Siempre retorna todos los resultados procesados hasta el momento, incluso si aborta.
|
||||
func (r *Runner) Run(script *Script) ([]StepResult, error) {
|
||||
results := make([]StepResult, 0, len(script.Steps))
|
||||
|
||||
for i, step := range script.Steps {
|
||||
start := time.Now()
|
||||
output, err := r.runStep(step)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
res := StepResult{
|
||||
Index: i,
|
||||
Action: step.Action,
|
||||
Output: output,
|
||||
Err: err,
|
||||
Elapsed: elapsed,
|
||||
}
|
||||
results = append(results, res)
|
||||
|
||||
if err != nil {
|
||||
if step.ContinueOnError {
|
||||
// Continuar con el siguiente paso aunque este fallo
|
||||
continue
|
||||
}
|
||||
if r.opts.AbortOnError {
|
||||
return results, fmt.Errorf("step[%d] %s: %w", i, step.Action, err)
|
||||
}
|
||||
// Por defecto: abortar si el paso fallo y no tiene continue_on_error
|
||||
return results, fmt.Errorf("step[%d] %s: %w", i, step.Action, err)
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// runStep ejecuta un paso individual y retorna su output y error.
|
||||
func (r *Runner) runStep(step Step) (string, error) {
|
||||
switch step.Action {
|
||||
case "navigate":
|
||||
if err := infra.CdpNavigate(r.conn, step.URL); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Esperar a que la página cargue completamente
|
||||
timeout := time.Duration(step.TimeoutMs) * time.Millisecond
|
||||
if timeout <= 0 {
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
return "", infra.CdpWaitLoad(r.conn, timeout)
|
||||
|
||||
case "wait_load":
|
||||
timeout := time.Duration(step.TimeoutMs) * time.Millisecond
|
||||
if timeout <= 0 {
|
||||
timeout = 15 * time.Second
|
||||
}
|
||||
return "", infra.CdpWaitLoad(r.conn, timeout)
|
||||
|
||||
case "wait":
|
||||
timeout := time.Duration(step.TimeoutMs) * time.Millisecond
|
||||
if timeout <= 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
return "", infra.CdpWaitElement(r.conn, step.Selector, timeout)
|
||||
|
||||
case "click":
|
||||
return "", infra.CdpClick(r.conn, step.Selector)
|
||||
|
||||
case "type":
|
||||
// Hacer click primero para enfocar el elemento
|
||||
if err := infra.CdpClick(r.conn, step.Selector); err != nil {
|
||||
return "", fmt.Errorf("enfocar elemento para type: %w", err)
|
||||
}
|
||||
return "", infra.CdpTypeText(r.conn, step.Text)
|
||||
|
||||
case "screenshot":
|
||||
opts := infra.CdpScreenshotOpts{
|
||||
FullPage: step.FullPage,
|
||||
Format: "png",
|
||||
}
|
||||
if err := infra.CdpScreenshot(r.conn, step.Path, opts); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return step.Path, nil
|
||||
|
||||
case "evaluate":
|
||||
result, err := infra.CdpEvaluate(r.conn, step.Expr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result, nil
|
||||
|
||||
case "get_html":
|
||||
html, err := infra.CdpGetHTML(r.conn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Truncar para el log (el HTML puede ser muy largo)
|
||||
if len(html) > 200 {
|
||||
return html[:200] + "...", nil
|
||||
}
|
||||
return html, nil
|
||||
|
||||
case "sleep":
|
||||
time.Sleep(time.Duration(step.Ms) * time.Millisecond)
|
||||
return fmt.Sprintf("slept %dms", step.Ms), nil
|
||||
|
||||
default:
|
||||
return "", fmt.Errorf("accion desconocida: %q", step.Action)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user