Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions lib/sp/platforms/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,20 +71,41 @@ async function launchContext(cookies: BrowserCookie[]): Promise<{
}
}

// Playwright's addCookies only accepts sameSite "Strict" | "Lax" | "None".
// Cookie-Editor / Chrome exports use other spellings ("no_restriction",
// "unspecified", lowercase "lax"/"strict") or null, which fail the enum check
// with: cookies[N].sameSite: expected one of (Strict|Lax|None). Map them.
function normalizeSameSite(value: unknown): BrowserCookie["sameSite"] {
switch (String(value ?? "").toLowerCase()) {
case "strict":
return "Strict";
case "none":
case "no_restriction":
return "None";
default:
// "lax", "unspecified", "", null, or anything unexpected.
return "Lax";
}
}

export function parseCookies(raw: string): BrowserCookie[] {
const parsed = JSON.parse(raw);
const arr: unknown[] = Array.isArray(parsed) ? parsed : parsed.cookies ?? parsed;
if (!Array.isArray(arr)) throw new Error("Cookie JSON must be an array.");
return arr.map((c: any) => ({
name: c.name,
value: c.value,
domain: c.domain ?? c.host ?? "",
path: c.path ?? "/",
expires: c.expirationDate ?? c.expires ?? -1,
httpOnly: c.httpOnly ?? false,
secure: c.secure ?? false,
sameSite: (c.sameSite as BrowserCookie["sameSite"]) ?? "Lax",
}));
return arr.map((c: any) => {
const sameSite = normalizeSameSite(c.sameSite);
return {
name: c.name,
value: c.value,
domain: c.domain ?? c.host ?? "",
path: c.path ?? "/",
expires: c.expirationDate ?? c.expires ?? -1,
httpOnly: c.httpOnly ?? false,
// Chromium rejects SameSite=None cookies that aren't Secure.
secure: (c.secure ?? false) || sameSite === "None",
sameSite,
};
});
}

// ---------- Reddit ----------
Expand Down
47 changes: 47 additions & 0 deletions tests/sp/parse-cookies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, it, expect } from "vitest";
import { parseCookies } from "@/lib/sp/platforms/browser";

const ALLOWED = new Set(["Strict", "Lax", "None"]);

describe("parseCookies sameSite normalization", () => {
it("maps Chrome/Cookie-Editor sameSite spellings to Playwright's enum", () => {
const raw = JSON.stringify([
{ name: "a", value: "1", domain: "x.com", sameSite: "no_restriction" },
{ name: "b", value: "2", domain: "x.com", sameSite: "lax" },
{ name: "c", value: "3", domain: "x.com", sameSite: "strict" },
{ name: "d", value: "4", domain: "x.com", sameSite: "unspecified" },
{ name: "e", value: "5", domain: "x.com", sameSite: null },
{ name: "f", value: "6", domain: "x.com" }, // missing entirely
]);
const cookies = parseCookies(raw);
expect(cookies.map((c) => c.sameSite)).toEqual([
"None",
"Lax",
"Strict",
"Lax",
"Lax",
"Lax",
]);
// Every value is one Playwright accepts.
for (const c of cookies) expect(ALLOWED.has(c.sameSite!)).toBe(true);
});

it("forces Secure on SameSite=None cookies (Chromium rejects otherwise)", () => {
const raw = JSON.stringify([
{ name: "n", value: "1", domain: "x.com", sameSite: "no_restriction", secure: false },
{ name: "l", value: "2", domain: "x.com", sameSite: "lax", secure: false },
]);
const [none, lax] = parseCookies(raw);
expect(none.secure).toBe(true);
expect(lax.secure).toBe(false);
});

it("accepts already-correct capitalized values and cookie-wrapper JSON", () => {
const raw = JSON.stringify({
cookies: [{ name: "a", value: "1", domain: "x.com", sameSite: "None", secure: true }],
});
const [c] = parseCookies(raw);
expect(c.sameSite).toBe("None");
expect(c.secure).toBe(true);
});
});
Loading