llm(三协议 Provider)/ session(JSONL 持久化)/ tools(Registry、围栏、 builtin、computer)/ cdp(browser)/ skills / mcp / agent(循环、两级压缩、 编排、审批门控)+ 底座 jsonl/textx/execx/diag/scratch/spill/imagecache。 产品装配代码(config 桥接、schedule 工具、memory/plan/retro/prompt) 留在 AutoGo,宿主接入方式见 MIGRATION.md。
138 lines
3.7 KiB
Go
138 lines
3.7 KiB
Go
package cdp
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"os"
|
||
"strings"
|
||
"sync"
|
||
"testing"
|
||
)
|
||
|
||
// fakeBrowserEnv 构造完整的假调试端点:/json/list、/json/new(PUT)、
|
||
// 每个 target 的 ws 端点(CDP 协议)。
|
||
func fakeBrowserEnv(t *testing.T) (*Browser, *httptest.Server) {
|
||
t.Helper()
|
||
var mu sync.Mutex
|
||
targets := []Target{{
|
||
ID: "t1", Title: "Blank", URL: "about:blank", Type: "page",
|
||
WebSocketDebuggerURL: "", // srv.URL 已知后再填
|
||
}}
|
||
var srv *httptest.Server
|
||
|
||
wsHandler := fakeCDPHandler(t, "Fake Page")
|
||
mux := http.NewServeMux()
|
||
mux.HandleFunc("/json/list", func(w http.ResponseWriter, r *http.Request) {
|
||
mu.Lock()
|
||
defer mu.Unlock()
|
||
_ = json.NewEncoder(w).Encode(targets)
|
||
})
|
||
mux.HandleFunc("/json/new", func(w http.ResponseWriter, r *http.Request) {
|
||
if r.Method != http.MethodPut {
|
||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||
return
|
||
}
|
||
mu.Lock()
|
||
targets = append(targets, Target{ID: "t2", Title: "New", URL: r.URL.Query().Get("url"), Type: "page"})
|
||
nt := targets[len(targets)-1]
|
||
mu.Unlock()
|
||
_ = json.NewEncoder(w).Encode(nt)
|
||
})
|
||
mux.HandleFunc("/devtools/page/t1", wsHandler)
|
||
mux.HandleFunc("/devtools/page/t2", wsHandler)
|
||
|
||
srv = httptest.NewServer(mux)
|
||
mu.Lock()
|
||
targets[0].WebSocketDebuggerURL = "ws" + strings.TrimPrefix(srv.URL, "http") + "/devtools/page/t1"
|
||
mu.Unlock()
|
||
|
||
br := &Browser{DebugBase: srv.URL}
|
||
t.Cleanup(func() { srv.Close(); br.Close() })
|
||
return br, srv
|
||
}
|
||
|
||
func TestBrowserToolEndToEnd(t *testing.T) {
|
||
br, _ := fakeBrowserEnv(t)
|
||
tool := browserToolWith(br)
|
||
ctx := context.Background()
|
||
|
||
// tabs
|
||
res, err := tool.Run(ctx, `{"action":"tabs"}`)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.Contains(res.Text, "t1 | Blank | about:blank") {
|
||
t.Fatalf("tabs: %q", res.Text)
|
||
}
|
||
|
||
// open:navigate + loadEventFired + 读取标题
|
||
res, err = tool.Run(ctx, `{"action":"open","url":"https://example.com/x"}`)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if !strings.Contains(res.Text, "opened https://example.com/x") || !strings.Contains(res.Text, "Fake Page") {
|
||
t.Fatalf("open: %q", res.Text)
|
||
}
|
||
|
||
// js:Runtime.evaluate 结果 + 错误路径
|
||
res, err = tool.Run(ctx, `{"action":"js","javascript":"document.title"}`)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if res.Text != "Fake Page" {
|
||
t.Fatalf("js: %q", res.Text)
|
||
}
|
||
|
||
// screenshot:PNG 落盘并作为 Image 返回
|
||
res, err = tool.Run(ctx, `{"action":"screenshot"}`)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if len(res.Images) != 1 {
|
||
t.Fatalf("screenshot images: %v", res.Images)
|
||
}
|
||
data, err := os.ReadFile(res.Images[0].Path)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if string(data[:4]) != "\x89PNG" {
|
||
t.Fatalf("not a png: %x", data[:4])
|
||
}
|
||
os.Remove(res.Images[0].Path)
|
||
|
||
// 参数校验
|
||
if _, err := tool.Run(ctx, `{"action":"open"}`); err == nil {
|
||
t.Fatal("open without url should fail")
|
||
}
|
||
if _, err := tool.Run(ctx, `{"action":"js"}`); err == nil {
|
||
t.Fatal("js without javascript should fail")
|
||
}
|
||
if _, err := tool.Run(ctx, `{"action":"bogus"}`); err == nil {
|
||
t.Fatal("unknown action should fail")
|
||
}
|
||
}
|
||
|
||
// TestNewTabPUT:无 page target 时经 PUT /json/new 新开 tab。
|
||
func TestNewTabPUT(t *testing.T) {
|
||
br, srv := fakeBrowserEnv(t)
|
||
// 清空 targets:强制走 NewTab
|
||
_ = srv
|
||
ctx := context.Background()
|
||
tg, err := br.NewTab(ctx, "https://example.com/new")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if tg.ID != "t2" || tg.URL != "https://example.com/new" {
|
||
t.Fatalf("new tab: %+v", tg)
|
||
}
|
||
}
|
||
|
||
// TestFindBrowserExplicit:显式指定不存在的二进制给出可操作错误。
|
||
func TestFindBrowserExplicit(t *testing.T) {
|
||
if _, err := findBrowser("/no/such/browser"); err == nil || !strings.Contains(err.Error(), "AUTOGO_BROWSER_BIN") {
|
||
t.Fatalf("expected explicit-bin error, got %v", err)
|
||
}
|
||
}
|