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) } }