Files
Cyrene_v2/internal/capability/browser/shot_test.go
T
AskaEth f1fbc249fd
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(browser): B1 接线 + B2 薄层工具面(V5)
- 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 落了第三份同策实现待收口;浏览器会话未挂进程关闭链
2026-10-06 20:25:16 +08:00

89 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("空字节应当报错")
}
}