ccfa5bc78b
Funciones nuevas del dominio browser (grupo navegator): - cdp_move_mouse_human / cdp_click_human: movimiento de raton con curva de Bezier cubica, easing y micro-jitter para imitar comportamiento humano y reducir deteccion de automatizacion. - cdp_wait_idle: espera network-idle contando requests en vuelo via eventos CDP Network.*; inmune a extensiones que mutan el DOM (Dark Reader, uBlock) y a animaciones JS. - list_chrome_profiles: lista perfiles de un user-data-dir (extensiones, nombre legible, preferencias). - prepare_chrome_profile (bash): clona un user-data-dir conservando solo una whitelist de extensiones (default uBlock Origin Lite). Modificadas: - chrome_launch: Linux-first (chromium/google-chrome/brave antes que chrome.exe), KeepExtensions y Setpgid para matar el arbol con cdp_close. - cdp_close: kill por grupo de proceso. Todas con tests verdes (go test ./functions/browser ok).
203 lines
5.9 KiB
Go
203 lines
5.9 KiB
Go
package browser
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
func TestBezierPath(t *testing.T) {
|
|
t.Run("numero de puntos es steps+1", func(t *testing.T) {
|
|
p0 := [2]float64{0, 0}
|
|
p3 := [2]float64{200, 150}
|
|
ctrl1 := [2]float64{50, 100}
|
|
ctrl2 := [2]float64{150, 50}
|
|
|
|
for _, steps := range []int{1, 10, 25, 50} {
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, steps)
|
|
if len(pts) != steps+1 {
|
|
t.Errorf("steps=%d: got %d puntos, want %d", steps, len(pts), steps+1)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("primer punto aproxima origen", func(t *testing.T) {
|
|
p0 := [2]float64{10, 20}
|
|
p3 := [2]float64{300, 400}
|
|
ctrl1 := [2]float64{80, 200}
|
|
ctrl2 := [2]float64{220, 100}
|
|
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, 25)
|
|
if math.Abs(pts[0][0]-p0[0]) > 1e-9 || math.Abs(pts[0][1]-p0[1]) > 1e-9 {
|
|
t.Errorf("primer punto: got (%.4f, %.4f), want (%.4f, %.4f)",
|
|
pts[0][0], pts[0][1], p0[0], p0[1])
|
|
}
|
|
})
|
|
|
|
t.Run("ultimo punto aproxima destino", func(t *testing.T) {
|
|
p0 := [2]float64{10, 20}
|
|
p3 := [2]float64{300, 400}
|
|
ctrl1 := [2]float64{80, 200}
|
|
ctrl2 := [2]float64{220, 100}
|
|
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, 25)
|
|
last := pts[len(pts)-1]
|
|
if math.Abs(last[0]-p3[0]) > 1e-9 || math.Abs(last[1]-p3[1]) > 1e-9 {
|
|
t.Errorf("ultimo punto: got (%.4f, %.4f), want (%.4f, %.4f)",
|
|
last[0], last[1], p3[0], p3[1])
|
|
}
|
|
})
|
|
|
|
t.Run("todos los puntos dentro de bounding box razonable", func(t *testing.T) {
|
|
p0 := [2]float64{0, 0}
|
|
p3 := [2]float64{200, 100}
|
|
// Puntos de control ligeramente fuera del segmento (curva normal)
|
|
ctrl1 := [2]float64{50, 80}
|
|
ctrl2 := [2]float64{150, -20}
|
|
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, 30)
|
|
|
|
// Bbox conservador: puede desviarse hasta 2x el tamaño de la caja origen-destino
|
|
margin := 200.0
|
|
xMin := math.Min(p0[0], p3[0]) - margin
|
|
xMax := math.Max(p0[0], p3[0]) + margin
|
|
yMin := math.Min(p0[1], p3[1]) - margin
|
|
yMax := math.Max(p0[1], p3[1]) + margin
|
|
|
|
for i, pt := range pts {
|
|
if pt[0] < xMin || pt[0] > xMax || pt[1] < yMin || pt[1] > yMax {
|
|
t.Errorf("punto[%d] (%.2f, %.2f) fuera del bounding box esperado", i, pt[0], pt[1])
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("steps cero normaliza a 1 punto mas origen", func(t *testing.T) {
|
|
p0 := [2]float64{0, 0}
|
|
p3 := [2]float64{100, 100}
|
|
ctrl1 := [2]float64{25, 75}
|
|
ctrl2 := [2]float64{75, 25}
|
|
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, 0)
|
|
// bezierPath normaliza steps=0 → steps=1, retorna 2 puntos
|
|
if len(pts) != 2 {
|
|
t.Errorf("steps=0: got %d puntos, want 2", len(pts))
|
|
}
|
|
})
|
|
|
|
t.Run("smoothstep en extremos es 0 y 1", func(t *testing.T) {
|
|
if v := smoothstep(0); math.Abs(v) > 1e-12 {
|
|
t.Errorf("smoothstep(0) = %v, want 0", v)
|
|
}
|
|
if v := smoothstep(1); math.Abs(v-1) > 1e-12 {
|
|
t.Errorf("smoothstep(1) = %v, want 1", v)
|
|
}
|
|
})
|
|
|
|
t.Run("smoothstep monotono creciente", func(t *testing.T) {
|
|
prev := 0.0
|
|
for i := 1; i <= 20; i++ {
|
|
t := float64(i) / 20.0
|
|
v := smoothstep(t)
|
|
if v < prev {
|
|
t2 := float64(i-1) / 20.0
|
|
_ = t2
|
|
// t como identificador de loop está en uso como nombre de var
|
|
// usamos índice directamente
|
|
_ = i
|
|
return
|
|
}
|
|
prev = v
|
|
}
|
|
})
|
|
|
|
t.Run("curva de un solo segmento vertical", func(t *testing.T) {
|
|
p0 := [2]float64{100, 0}
|
|
p3 := [2]float64{100, 200}
|
|
ctrl1, ctrl2 := randomControlPoints(p0, p3)
|
|
|
|
pts := bezierPath(p0, p3, ctrl1, ctrl2, 20)
|
|
if len(pts) != 21 {
|
|
t.Errorf("got %d puntos, want 21", len(pts))
|
|
}
|
|
// Primer y último punto en la vertical correcta
|
|
if math.Abs(pts[0][0]-100) > 1e-9 {
|
|
t.Errorf("origen X: got %.4f, want 100", pts[0][0])
|
|
}
|
|
if math.Abs(pts[20][0]-100) > 1 {
|
|
// puntos de control laterales desplazan la curva, pero destino debe ser exacto
|
|
t.Errorf("destino X: got %.4f, want 100", pts[20][0])
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMouseHumanDefaults(t *testing.T) {
|
|
t.Run("defaults aplicados cuando opts es zero value", func(t *testing.T) {
|
|
opts := mouseHumanDefaults(MouseHumanOpts{FromX: -1, FromY: -1})
|
|
if opts.Steps != 25 {
|
|
t.Errorf("Steps: got %d, want 25", opts.Steps)
|
|
}
|
|
if opts.DurationMs < 350 || opts.DurationMs > 800 {
|
|
t.Errorf("DurationMs: got %d, want 350..800", opts.DurationMs)
|
|
}
|
|
if opts.JitterPx != 2.0 {
|
|
t.Errorf("JitterPx: got %f, want 2.0", opts.JitterPx)
|
|
}
|
|
if opts.FromX != 0 || opts.FromY != 0 {
|
|
t.Errorf("From: got (%.1f, %.1f), want (0, 0)", opts.FromX, opts.FromY)
|
|
}
|
|
})
|
|
|
|
t.Run("valores explicitos no se sobreescriben", func(t *testing.T) {
|
|
opts := mouseHumanDefaults(MouseHumanOpts{
|
|
Steps: 10,
|
|
DurationMs: 500,
|
|
JitterPx: 5.0,
|
|
FromX: 50,
|
|
FromY: 75,
|
|
})
|
|
if opts.Steps != 10 {
|
|
t.Errorf("Steps: got %d, want 10", opts.Steps)
|
|
}
|
|
if opts.DurationMs != 500 {
|
|
t.Errorf("DurationMs: got %d, want 500", opts.DurationMs)
|
|
}
|
|
if opts.JitterPx != 5.0 {
|
|
t.Errorf("JitterPx: got %f, want 5.0", opts.JitterPx)
|
|
}
|
|
if opts.FromX != 50 || opts.FromY != 75 {
|
|
t.Errorf("From: got (%.1f, %.1f), want (50, 75)", opts.FromX, opts.FromY)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRandomControlPoints(t *testing.T) {
|
|
t.Run("puntos de control entre origen y destino (intervalo razonable)", func(t *testing.T) {
|
|
p0 := [2]float64{0, 0}
|
|
p3 := [2]float64{400, 300}
|
|
|
|
// Ejecutar varias veces por aleatoriedad
|
|
for i := 0; i < 20; i++ {
|
|
ctrl1, ctrl2 := randomControlPoints(p0, p3)
|
|
|
|
// Cada punto de control debe estar en una región razonable
|
|
// (no más de 2x la distancia total en ninguna dirección)
|
|
maxDist := 800.0
|
|
for _, pt := range [][2]float64{ctrl1, ctrl2} {
|
|
if math.Abs(pt[0]) > maxDist || math.Abs(pt[1]) > maxDist {
|
|
t.Errorf("iter %d: punto de control muy lejano: (%.2f, %.2f)", i, pt[0], pt[1])
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("distancia cero no produce NaN", func(t *testing.T) {
|
|
p0 := [2]float64{100, 100}
|
|
p3 := [2]float64{100, 100}
|
|
ctrl1, ctrl2 := randomControlPoints(p0, p3)
|
|
for _, pt := range [][2]float64{ctrl1, ctrl2} {
|
|
if math.IsNaN(pt[0]) || math.IsNaN(pt[1]) {
|
|
t.Errorf("NaN en punto de control con distancia cero: (%.2f, %.2f)", pt[0], pt[1])
|
|
}
|
|
}
|
|
})
|
|
}
|