bb38eedfd1
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>
144 lines
3.5 KiB
Go
144 lines
3.5 KiB
Go
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)
|
|
}
|
|
}
|