diff --git a/internal/camoufoxnative/page.go b/internal/camoufoxnative/page.go index 37f2411..a50ec4b 100644 --- a/internal/camoufoxnative/page.go +++ b/internal/camoufoxnative/page.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" ) // pageDOMHelpers 定义官网页面共用的可见目标与按钮状态判断 @@ -25,6 +26,11 @@ const promptReadyExpression = `(() => {` + pageDOMHelpers + ` // workerPageReadyExpression 等待输入框出现或页面跳转到登录入口 const workerPageReadyExpression = `(location.hostname === 'accounts.google.com' || ` + promptReadyExpression + `)` +// normalizePromptNewlines 把 CRLF/CR 统一成 LF,官网 textarea 会把写入值规范化为 LF +func normalizePromptNewlines(value string) string { + return strings.ReplaceAll(strings.ReplaceAll(value, "\r\n", "\n"), "\r", "\n") +} + // fillPromptExpression 向当前可见提示框写入文本并通知页面表单 func fillPromptExpression(prompt string) string { encoded, _ := json.Marshal(prompt) diff --git a/internal/camoufoxnative/page_test.go b/internal/camoufoxnative/page_test.go new file mode 100644 index 0000000..e8e2911 --- /dev/null +++ b/internal/camoufoxnative/page_test.go @@ -0,0 +1,38 @@ +package camoufoxnative + +import "testing" + +// TestNormalizePromptNewlines 覆盖官网 textarea 把写入值规范化为 LF 的比较场景 +func TestNormalizePromptNewlines(t *testing.T) { + cases := []struct { + name string + value string + want string + }{ + {name: "lf", value: "a\nb", want: "a\nb"}, + {name: "crlf", value: "a\r\nb", want: "a\nb"}, + {name: "cr", value: "a\rb", want: "a\nb"}, + {name: "mixed", value: "a\r\nb\nc\rd", want: "a\nb\nc\nd"}, + {name: "empty", value: "", want: ""}, + {name: "no newline", value: "abc", want: "abc"}, + } + for _, item := range cases { + t.Run(item.name, func(t *testing.T) { + if got := normalizePromptNewlines(item.value); got != item.want { + t.Fatalf("normalizePromptNewlines(%q) = %q, want %q", item.value, got, item.want) + } + }) + } +} + +// TestNormalizePromptNewlinesMatchesTextareaValue 确认 CRLF 提示词与官网返回的 LF 值判定为同步 +func TestNormalizePromptNewlinesMatchesTextareaValue(t *testing.T) { + prompt := "line1\r\nline2\r\nline3" + textarea := "line1\nline2\nline3" + if prompt == textarea { + t.Fatal("用例前提失效: CRLF 提示词不应与 textarea 值直接相等") + } + if normalizePromptNewlines(prompt) != normalizePromptNewlines(textarea) { + t.Fatal("CRLF 提示词与 textarea LF 值应判定为已同步") + } +} diff --git a/internal/camoufoxnative/worker.go b/internal/camoufoxnative/worker.go index 769cb79..db16e33 100644 --- a/internal/camoufoxnative/worker.go +++ b/internal/camoufoxnative/worker.go @@ -130,7 +130,7 @@ func (worker *Worker) Proof(ctx context.Context, digest string, prompt string) ( if err != nil { return "", fmt.Errorf("同步官网 prompt: %w", err) } - if value == prompt { + if normalizePromptNewlines(value) == normalizePromptNewlines(prompt) { break } if time.Now().After(deadline) { @@ -249,7 +249,7 @@ func (worker *Worker) bootstrap(ctx context.Context, options Options, storage st } options.reportStartup(StartupBootstrappingWAA) filled, err := client.evaluateString(ctx, contextID, fillPromptExpression(options.BootstrapPrompt)) - if err != nil || filled != options.BootstrapPrompt { + if err != nil || normalizePromptNewlines(filled) != normalizePromptNewlines(options.BootstrapPrompt) { return fmt.Errorf("填写 bootstrap 提示词失败 value=%q err=%v", filled, err) } if _, err := client.command(ctx, "session.subscribe", map[string]any{