feat(browser): B1 接线 + B2 薄层工具面(V5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- browsercfg 接进组合根:配置读 $DATA_DIR/config/plugins/browser.json(零 env)→ 探测(本机
  Chromium → 已在跑的 CDP)→ 不可用就不注册(原因码入日志)
- 自研最小出站 CDP 客户端(不 Enabled Runtime、隔离世界求值、不发 --enable-automation)+
  导航 + 截图 0600 落盘 + 指纹自检;工具 browser(open/screenshot/info,owner-only)
- 3 个变异自检(启动恢复 --enable-automation/无视可用性/空闲回收失效)
- 真机对照(两站各 1 次,**如实结论**):两侧都 pass ⇒ 「headless 被拦 / headful 通过」**未被证明**;
  但测到两处硬差异:无头 UA 带 HeadlessChrome、screen 800 vs 2632;两模式 webdriver 均 false
  (把 L6 的推断变成实测)。跑完 0 残留 Chrome、无对外监听端口
⚠️ 未做:MCP 自动接入(口径委派未落地)/自动注入上下文/面板页/搜索降级/docker 配方/Windows 用
  pipe(Go ExtraFiles 不支持);SSRF 落了第三份同策实现待收口;浏览器会话未挂进程关闭链
This commit is contained in:
AskaEth committed 2026-10-06 20:25:16 +08:00
1 parent ea150ceea9
commit f1fbc249fd
21 files changed
+3579

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
package browser
import (
"bytes"
"image"
"image/color"
"image/png"
"os"
"path/filepath"
"runtime"
"testing"
)
// pngBytes 造一张 w×h 的合法 PNG(用标准编码器,保证 imageSize 真能解出来)。
func pngBytes(t *testing.T, w, h int) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, w, h))
img.Set(0, 0, color.RGBA{R: 1, G: 2, B: 3, A: 255})
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
t.Fatalf("编码 PNG: %v", err)
}
return buf.Bytes()
}
// TestSaveShot_Writes0600AndDimensions 钉住截图落盘的**三件事**:0600 权限、
// 名字不含站点信息、宽高来自字节本身(而不是配置里的视口)。
func TestSaveShot_Writes0600AndDimensions(t *testing.T) {
dir := filepath.Join(t.TempDir(), "shots")
data := pngBytes(t, 7, 5)
shot, err := saveShot(dir, data, shotExtPNG)
if err != nil {
t.Fatalf("saveShot: %v", err)
}
if shot.Bytes != len(data) {
t.Errorf("Bytes = %d, want %d", shot.Bytes, len(data))
}
if !shot.DimensionsKnown || shot.Width != 7 || shot.Height != 5 {
t.Errorf("尺寸 = (%d,%d) known=%v, want (7,5) true", shot.Width, shot.Height, shot.DimensionsKnown)
}
if shot.Format != shotExtPNG || shot.Degraded {
t.Errorf("format=%q degraded=%v, want png/false", shot.Format, shot.Degraded)
}
if len(shot.SHA256) != 64 {
t.Errorf("SHA256 长度 = %d, want 64", len(shot.SHA256))
}
if filepath.Ext(shot.Path) != ".png" {
t.Errorf("扩展名 = %q, want .png", filepath.Ext(shot.Path))
}
if fi, err := os.Stat(shot.Path); err != nil {
t.Fatalf("stat %s: %v", shot.Path, err)
} else if runtime.GOOS != "windows" && fi.Mode().Perm() != shotFileMode {
// Windows 不做 POSIX 权限(Go 只映射只读位),因此这一条只在类 Unix 平台上断言;
// Windows 上 0600 是**尽力而为**(对齐 browsercfg.EnsureProfileDir 的既有口径)。
t.Errorf("权限 = %v, want %v", fi.Mode().Perm(), shotFileMode)
}
// 目录权限:非 Windows 上必须是 0700(Windows 不做 POSIX 权限,跳过)。
if runtime.GOOS != "windows" {
if fi, err := os.Stat(dir); err != nil {
t.Fatalf("stat %s: %v", dir, err)
} else if fi.Mode().Perm() != shotDirMode {
t.Errorf("目录权限 = %v, want %v", fi.Mode().Perm(), shotDirMode)
}
}
}
// TestSaveShot_DegradedJPEGFlagged 钉住「降级要说出来」:JPEG 落盘必须置 degraded
// 且扩展名是 .jpeg(模型/面板据此知道这张图不是无损的那张)。
func TestSaveShot_DegradedJPEGFlagged(t *testing.T) {
dir := t.TempDir()
shot, err := saveShot(dir, []byte{0xFF, 0xD8, 0xFF, 0xDB, 0x00, 0x01}, shotExtJPEG)
if err != nil {
t.Fatalf("saveShot: %v", err)
}
if !shot.Degraded || shot.Format != shotExtJPEG {
t.Errorf("degraded=%v format=%q, want true/jpeg", shot.Degraded, shot.Format)
}
if shot.DimensionsKnown {
t.Error("非图像字节不该被判成「尺寸已知」")
}
}
// TestSaveShot_RejectsEmpty 钉住空字节不落盘(不写一个 0 字节的「截图」骗人)。
func TestSaveShot_RejectsEmpty(t *testing.T) {
if _, err := saveShot(t.TempDir(), nil, shotExtPNG); err == nil {
t.Fatal("空字节应当报错")
}
}