mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
266 lines
7.3 KiB
Go
266 lines
7.3 KiB
Go
package backend
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
goruntime "runtime"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"ant-chrome/backend/internal/browser"
|
|
"ant-chrome/backend/internal/config"
|
|
)
|
|
|
|
func TestBrowserInstanceOpenURLUsesCDPCreateTarget(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := newRecordedCDPServer(t)
|
|
defer server.Close()
|
|
|
|
app := newBrowserOpenURLTestApp(t)
|
|
app.browserMgr.Profiles = map[string]*BrowserProfile{
|
|
"profile-ready": {
|
|
ProfileId: "profile-ready",
|
|
ProfileName: "Ready Browser",
|
|
Running: true,
|
|
DebugReady: true,
|
|
DebugPort: server.Port(),
|
|
Pid: 12345,
|
|
},
|
|
}
|
|
app.browserMgr.BrowserProcesses = make(map[string]*exec.Cmd)
|
|
|
|
ok, err := app.BrowserInstanceOpenUrl("profile-ready", "https://open.example/")
|
|
if err != nil {
|
|
t.Fatalf("BrowserInstanceOpenUrl returned error: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("expected BrowserInstanceOpenUrl to succeed")
|
|
}
|
|
|
|
want := []recordedCDPCommand{
|
|
{Scope: "browser", Method: "Target.createTarget", URL: "https://open.example/"},
|
|
}
|
|
if got := server.Commands(); !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("unexpected CDP command sequence:\n got=%v\nwant=%v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestBrowserInstanceOpenURLFallsBackToWindowWhenDebugPending(t *testing.T) {
|
|
app, exePath := newBrowserOpenURLTestAppWithCore(t)
|
|
|
|
cmd := longLivedCommand(2 * time.Second)
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("启动长生命周期测试进程失败: %v", err)
|
|
}
|
|
defer func() {
|
|
if cmd.Process != nil {
|
|
_ = cmd.Process.Kill()
|
|
_, _ = cmd.Process.Wait()
|
|
}
|
|
}()
|
|
|
|
profile := &BrowserProfile{
|
|
ProfileId: "profile-pending",
|
|
ProfileName: "Pending Browser",
|
|
UserDataDir: "profile-pending",
|
|
Running: true,
|
|
DebugReady: false,
|
|
DebugPort: 0,
|
|
Pid: cmd.Process.Pid,
|
|
}
|
|
app.browserMgr.Profiles = map[string]*BrowserProfile{profile.ProfileId: profile}
|
|
app.browserMgr.BrowserProcesses = map[string]*exec.Cmd{profile.ProfileId: cmd}
|
|
|
|
expectedUserDataDir := app.browserMgr.ResolveUserDataDir(profile)
|
|
var gotPath string
|
|
var gotArgs []string
|
|
|
|
originalStart := startBrowserWindowProcess
|
|
startBrowserWindowProcess = func(chromeBinaryPath string, args []string) (*exec.Cmd, error) {
|
|
gotPath = chromeBinaryPath
|
|
gotArgs = append([]string{}, args...)
|
|
return nil, nil
|
|
}
|
|
defer func() {
|
|
startBrowserWindowProcess = originalStart
|
|
}()
|
|
|
|
ok, err := app.BrowserInstanceOpenUrl(profile.ProfileId, "https://pending.example/")
|
|
if err != nil {
|
|
t.Fatalf("BrowserInstanceOpenUrl returned error: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("expected BrowserInstanceOpenUrl to succeed")
|
|
}
|
|
if gotPath != exePath {
|
|
t.Fatalf("unexpected browser path: got=%q want=%q", gotPath, exePath)
|
|
}
|
|
|
|
wantArgs := []string{
|
|
fmt.Sprintf("--user-data-dir=%s", expectedUserDataDir),
|
|
"https://pending.example/",
|
|
}
|
|
if !reflect.DeepEqual(gotArgs, wantArgs) {
|
|
t.Fatalf("unexpected browser args:\n got=%v\nwant=%v", gotArgs, wantArgs)
|
|
}
|
|
}
|
|
|
|
func TestBrowserInstanceOpenURLFallsBackToWindowWhenCDPOpenFails(t *testing.T) {
|
|
app, exePath := newBrowserOpenURLTestAppWithCore(t)
|
|
|
|
cmd := longLivedCommand(2 * time.Second)
|
|
if err := cmd.Start(); err != nil {
|
|
t.Fatalf("启动长生命周期测试进程失败: %v", err)
|
|
}
|
|
defer func() {
|
|
if cmd.Process != nil {
|
|
_ = cmd.Process.Kill()
|
|
_, _ = cmd.Process.Wait()
|
|
}
|
|
}()
|
|
|
|
profile := &BrowserProfile{
|
|
ProfileId: "profile-ready-fallback",
|
|
ProfileName: "Fallback Browser",
|
|
UserDataDir: "profile-ready-fallback",
|
|
Running: true,
|
|
DebugReady: true,
|
|
DebugPort: freeLoopbackPort(t),
|
|
Pid: cmd.Process.Pid,
|
|
}
|
|
app.browserMgr.Profiles = map[string]*BrowserProfile{profile.ProfileId: profile}
|
|
app.browserMgr.BrowserProcesses = map[string]*exec.Cmd{profile.ProfileId: cmd}
|
|
|
|
expectedUserDataDir := app.browserMgr.ResolveUserDataDir(profile)
|
|
var gotPath string
|
|
var gotArgs []string
|
|
|
|
originalStart := startBrowserWindowProcess
|
|
startBrowserWindowProcess = func(chromeBinaryPath string, args []string) (*exec.Cmd, error) {
|
|
gotPath = chromeBinaryPath
|
|
gotArgs = append([]string{}, args...)
|
|
return nil, nil
|
|
}
|
|
defer func() {
|
|
startBrowserWindowProcess = originalStart
|
|
}()
|
|
|
|
ok, err := app.BrowserInstanceOpenUrl(profile.ProfileId, "https://fallback.example/")
|
|
if err != nil {
|
|
t.Fatalf("BrowserInstanceOpenUrl returned error: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("expected BrowserInstanceOpenUrl to succeed")
|
|
}
|
|
if gotPath != exePath {
|
|
t.Fatalf("unexpected browser path: got=%q want=%q", gotPath, exePath)
|
|
}
|
|
|
|
wantArgs := []string{
|
|
fmt.Sprintf("--user-data-dir=%s", expectedUserDataDir),
|
|
"https://fallback.example/",
|
|
}
|
|
if !reflect.DeepEqual(gotArgs, wantArgs) {
|
|
t.Fatalf("unexpected browser args:\n got=%v\nwant=%v", gotArgs, wantArgs)
|
|
}
|
|
}
|
|
|
|
func TestBrowserInstanceOpenURLMarksStaleProfileStopped(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
app := newBrowserOpenURLTestApp(t)
|
|
profile := &BrowserProfile{
|
|
ProfileId: "profile-stale",
|
|
ProfileName: "Stale Browser",
|
|
Running: true,
|
|
DebugReady: false,
|
|
}
|
|
app.browserMgr.Profiles = map[string]*BrowserProfile{profile.ProfileId: profile}
|
|
app.browserMgr.BrowserProcesses = make(map[string]*exec.Cmd)
|
|
|
|
ok, err := app.BrowserInstanceOpenUrl(profile.ProfileId, "https://stale.example/")
|
|
if err == nil {
|
|
t.Fatal("expected BrowserInstanceOpenUrl to fail for stale runtime state")
|
|
}
|
|
if ok {
|
|
t.Fatal("expected BrowserInstanceOpenUrl to return false for stale runtime state")
|
|
}
|
|
if !strings.Contains(err.Error(), "运行状态已失效") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if profile.Running {
|
|
t.Fatal("expected stale profile to be marked stopped")
|
|
}
|
|
if profile.DebugReady {
|
|
t.Fatal("expected stale profile debug state to be cleared")
|
|
}
|
|
if profile.DebugPort != 0 || profile.Pid != 0 {
|
|
t.Fatalf("expected runtime identifiers to be cleared, got debugPort=%d pid=%d", profile.DebugPort, profile.Pid)
|
|
}
|
|
}
|
|
|
|
func newBrowserOpenURLTestApp(t *testing.T) *App {
|
|
t.Helper()
|
|
|
|
cfg := config.DefaultConfig()
|
|
app := NewApp("")
|
|
app.config = cfg
|
|
app.browserMgr = browser.NewManager(cfg, t.TempDir())
|
|
app.browserMgr.Profiles = make(map[string]*BrowserProfile)
|
|
app.browserMgr.BrowserProcesses = make(map[string]*exec.Cmd)
|
|
return app
|
|
}
|
|
|
|
func newBrowserOpenURLTestAppWithCore(t *testing.T) (*App, string) {
|
|
t.Helper()
|
|
|
|
cfg := config.DefaultConfig()
|
|
exePath := createFakeBrowserExecutable(t)
|
|
cfg.Browser.Cores = []config.BrowserCore{
|
|
{
|
|
CoreId: "core-open-url-test",
|
|
CoreName: "Open URL Test Core",
|
|
CorePath: exePath,
|
|
IsDefault: true,
|
|
},
|
|
}
|
|
|
|
app := NewApp("")
|
|
app.config = cfg
|
|
app.browserMgr = browser.NewManager(cfg, t.TempDir())
|
|
app.browserMgr.Profiles = make(map[string]*BrowserProfile)
|
|
app.browserMgr.BrowserProcesses = make(map[string]*exec.Cmd)
|
|
return app, exePath
|
|
}
|
|
|
|
func createFakeBrowserExecutable(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
candidates := browser.CoreExecutableCandidates()
|
|
if len(candidates) == 0 {
|
|
t.Fatal("no core executable candidates available")
|
|
}
|
|
|
|
baseDir := t.TempDir()
|
|
exePath := filepath.Join(baseDir, filepath.FromSlash(candidates[0]))
|
|
if err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {
|
|
t.Fatalf("创建测试内核目录失败: %v", err)
|
|
}
|
|
|
|
mode := os.FileMode(0o644)
|
|
content := []byte("test-browser")
|
|
if goruntime.GOOS != "windows" {
|
|
mode = 0o755
|
|
content = []byte("#!/bin/sh\nexit 0\n")
|
|
}
|
|
if err := os.WriteFile(exePath, content, mode); err != nil {
|
|
t.Fatalf("写入测试内核可执行文件失败: %v", err)
|
|
}
|
|
return exePath
|
|
}
|