fix: map Secure field in cookie conversions and add per-cookie error handling
All checks were successful
CI / vet (pull_request) Successful in 1m7s
CI / build (pull_request) Successful in 1m7s
CI / test (pull_request) Successful in 1m10s

The Secure field was dropped in both Playwright<->internal cookie
conversion functions, causing cookies with __Secure-/__Host- prefixes
to be rejected by Chromium. Additionally, batch AddCookies meant one
invalid cookie would fail browser creation entirely.

Changes:
- Map Secure field in cookieToPlaywrightOptionalCookie and
  playwrightCookieToCookie
- Add cookies one-by-one with slog.Warn on failure instead of
  failing the entire batch
- Add unit tests for both conversion functions

Closes #75

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 02:13:19 +00:00
parent 01aea52533
commit debf0ee2ed
3 changed files with 145 additions and 6 deletions

View File

@@ -152,12 +152,11 @@ func initBrowser(opt BrowserOptions) (*browserInitResult, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting cookies from cookie jar: %w", err) return nil, fmt.Errorf("error getting cookies from cookie jar: %w", err)
} }
pwCookies := make([]playwright.OptionalCookie, len(cookies)) for _, c := range cookies {
for i, c := range cookies { oc := cookieToPlaywrightOptionalCookie(c)
pwCookies[i] = cookieToPlaywrightOptionalCookie(c) if err := bctx.AddCookies([]playwright.OptionalCookie{oc}); err != nil {
} slog.Warn("skipping invalid cookie", "name", c.Name, "host", c.Host, "error", err)
if err := bctx.AddCookies(pwCookies); err != nil { }
return nil, fmt.Errorf("error adding cookies to browser: %w", err)
} }
} }

View File

@@ -128,6 +128,7 @@ func cookieToPlaywrightOptionalCookie(cookie Cookie) playwright.OptionalCookie {
Domain: playwright.String(cookie.Host), Domain: playwright.String(cookie.Host),
Path: playwright.String(cookie.Path), Path: playwright.String(cookie.Path),
Expires: playwright.Float(float64(cookie.Expires.Unix())), Expires: playwright.Float(float64(cookie.Expires.Unix())),
Secure: playwright.Bool(cookie.Secure),
HttpOnly: playwright.Bool(cookie.HttpOnly), HttpOnly: playwright.Bool(cookie.HttpOnly),
} }
if cookie.SameSite != "" { if cookie.SameSite != "" {
@@ -143,6 +144,7 @@ func playwrightCookieToCookie(cookie playwright.Cookie) Cookie {
Host: cookie.Domain, Host: cookie.Domain,
Path: cookie.Path, Path: cookie.Path,
Expires: time.Unix(int64(cookie.Expires), 0), Expires: time.Unix(int64(cookie.Expires), 0),
Secure: cookie.Secure,
HttpOnly: cookie.HttpOnly, HttpOnly: cookie.HttpOnly,
SameSite: playwrightSameSiteToSameSite(cookie.SameSite), SameSite: playwrightSameSiteToSameSite(cookie.SameSite),
} }

138
playwright_test.go Normal file
View File

@@ -0,0 +1,138 @@
package extractor
import (
"testing"
"time"
"github.com/playwright-community/playwright-go"
)
func TestPlaywrightCookieToCookie_AllFields(t *testing.T) {
pwCookie := playwright.Cookie{
Name: "session",
Value: "abc123",
Domain: ".example.com",
Path: "/app",
Expires: 1700000000,
Secure: true,
HttpOnly: true,
SameSite: playwright.SameSiteAttributeStrict,
}
c := playwrightCookieToCookie(pwCookie)
if c.Name != "session" {
t.Errorf("Name = %q, want %q", c.Name, "session")
}
if c.Value != "abc123" {
t.Errorf("Value = %q, want %q", c.Value, "abc123")
}
if c.Host != ".example.com" {
t.Errorf("Host = %q, want %q", c.Host, ".example.com")
}
if c.Path != "/app" {
t.Errorf("Path = %q, want %q", c.Path, "/app")
}
if c.Expires != time.Unix(1700000000, 0) {
t.Errorf("Expires = %v, want %v", c.Expires, time.Unix(1700000000, 0))
}
if !c.Secure {
t.Error("Secure = false, want true")
}
if !c.HttpOnly {
t.Error("HttpOnly = false, want true")
}
if c.SameSite != SameSiteStrict {
t.Errorf("SameSite = %q, want %q", c.SameSite, SameSiteStrict)
}
}
func TestPlaywrightCookieToCookie_SecureFalse(t *testing.T) {
pwCookie := playwright.Cookie{
Name: "tracking",
Value: "xyz",
Domain: "example.com",
Path: "/",
Secure: false,
}
c := playwrightCookieToCookie(pwCookie)
if c.Secure {
t.Error("Secure = true, want false")
}
}
func TestCookieToPlaywrightOptionalCookie_AllFields(t *testing.T) {
c := Cookie{
Name: "__Secure-ID",
Value: "token123",
Host: ".example.com",
Path: "/secure",
Expires: time.Unix(1700000000, 0),
Secure: true,
HttpOnly: true,
SameSite: SameSiteLax,
}
oc := cookieToPlaywrightOptionalCookie(c)
if oc.Name != "__Secure-ID" {
t.Errorf("Name = %q, want %q", oc.Name, "__Secure-ID")
}
if oc.Value != "token123" {
t.Errorf("Value = %q, want %q", oc.Value, "token123")
}
if oc.Domain == nil || *oc.Domain != ".example.com" {
t.Errorf("Domain = %v, want %q", oc.Domain, ".example.com")
}
if oc.Path == nil || *oc.Path != "/secure" {
t.Errorf("Path = %v, want %q", oc.Path, "/secure")
}
if oc.Expires == nil || *oc.Expires != 1700000000 {
t.Errorf("Expires = %v, want %v", oc.Expires, 1700000000)
}
if oc.Secure == nil || !*oc.Secure {
t.Error("Secure = nil or false, want *true")
}
if oc.HttpOnly == nil || !*oc.HttpOnly {
t.Error("HttpOnly = nil or false, want *true")
}
if oc.SameSite == nil || *oc.SameSite != *playwright.SameSiteAttributeLax {
t.Errorf("SameSite = %v, want Lax", oc.SameSite)
}
}
func TestCookieToPlaywrightOptionalCookie_SecureFalse(t *testing.T) {
c := Cookie{
Name: "tracker",
Value: "v",
Host: "example.com",
Path: "/",
Secure: false,
}
oc := cookieToPlaywrightOptionalCookie(c)
if oc.Secure == nil {
t.Fatal("Secure = nil, want *false")
}
if *oc.Secure {
t.Error("Secure = *true, want *false")
}
}
func TestCookieToPlaywrightOptionalCookie_NoSameSite(t *testing.T) {
c := Cookie{
Name: "basic",
Value: "val",
Host: "example.com",
Path: "/",
}
oc := cookieToPlaywrightOptionalCookie(c)
if oc.SameSite != nil {
t.Errorf("SameSite = %v, want nil", oc.SameSite)
}
}