Puppeteer CookieSameSite 类型全解析:Strict / Lax / None / Default 的语义、源码映射与实战用法
Puppeteer(本仓库:puppeteer1/puppeteer,提供 Chrome 与 Firefox 的 JavaScript API)使用 CookieSameSite 联合类型统一描述 Cookie 的 SameSite 状态。本篇以 docs/api/puppeteer.cookiesamesite_2.md 为主线,结合 puppeteer-core 中 CDP 与 WebDriver BiDi 两套协议适配层的源码实现,讲解四个取值 'Strict' | 'Lax' | 'None' | 'Default' 的真实语义、在 setCookie/cookies 各 API 中的使用位置,以及跨协议时发生的底层映射,帮助你写出可正确落盘、可跨浏览器验证的 Cookie 操作代码。
一、类型定义:文档说了什么
CookieSameSite 在 API 文档中以“Type alias”(类型别名)形式定义,说明文字为:
Represents the cookie's 'SameSite' status.
即:它表示 Cookie 的 SameSite 状态,并且文档明确将规范来源指向 https://tools.ietf.org/html/draft-west-first-party-cookies(First-Party Cookies 草案,也就是我们常说的 SameSite Cookie 规范草案)。其完整签名如下:
export type CookieSameSite = 'Strict' | 'Lax' | 'None' | 'Default';
这是一个纯字面量联合类型(string literal union),而不是枚举(enum)。也就是说在 TypeScript 中,只有当字段被显式声明为 CookieSameSite 时,这四个字符串字面量才可赋值;你无法通过索引枚举遍历它。
在类型汇总文档 docs/api/index.md 中,它被收录在 Type Aliases 列表(锚点 #cookiesamesite_2),并链接到本文档。而实际源码定义位于 packages/puppeteer-core/src/common/Cookie.ts:
/**
* Represents the cookie's 'SameSite' status:
* https://tools.ietf.org/html/draft-west-first-party-cookies
*
* @public
*/
export type CookieSameSite = 'Strict' | 'Lax' | 'None' | 'Default';
文件为何命名为 cookiesamesite_2
细心的读者会发现 API 文档目录中该文件名为 puppeteer.cookiesamesite_2.md。从文档生成工具 docgen 的命名规律与类型汇总索引 #cookiesamesite_2 的锚点可以推断:文档生成器为同名标识符去重时添加了 _2 后缀(历史版本中曾存在一个同名 CookieSameSite 枚举)。因此在实际代码中引用的是这一份 Type alias,使用 _2 仅仅是文档产物层面的命名差异,不影响 API 使用。
二、四个取值逐个拆解
| 取值 | 语义要点 | 说明 |
|---|---|---|
'Strict' |
严格模式 | Cookie 只在“第一方上下文”中随请求发送;任何跨站请求都不会携带该 Cookie。安全性最高,但对跨站跳转后的首次访问(如从邮件/外链进入)不友好,常导致登录态在跳转落地页暂时丢失。 |
'Lax' |
宽松模式 | 现代浏览器事实上的默认行为:顶层导航发起的跨站 GET 请求(如用户点击链接跳转)会携带 Cookie,而跨站的图片、脚本、XHR/fetch、iframe 等子资源请求不携带。在“安全”与“可用”之间取得平衡。 |
'None' |
显式允许跨站携带 | Cookie 在跨站上下文中也可以被发送。需要与 Secure 属性配合(见下文第六节),否则主流浏览器会拒绝。对应 Chrome 为支持第三方场景(如 SSO、支付回调)开放的选项。 |
'Default' |
交给浏览器自行决定 | 在 Puppeteer 的类型层面占位,用于“不强制指定 SameSite”的意图。当它被传给 Chrome/CDP 时会被转换成“不发送该字段”(详见第四节),浏览器按自身的默认策略处理(现代 Chrome 将无 SameSite 属性的 Cookie 视同 Lax 处理)。 |
需要特别留意:四个取值是字符串字面量,首字母大写、其余小写,例如必须是 'None' 而不是 'none'。这一大小写约定贯穿 CookieParam、CookieData 以及 page.cookies() 的返回值。而下文会看到,在 Firefox 走 WebDriver BiDi 时,协议内层使用小写 'none',由 Puppeteer 适配层负责转换,对使用方无感。
三、类型用在哪里:从 CookieParam 到 CookieData
CookieSameSite 是 Puppeteer Cookie 体系的基础类型之一,出现在“写入参数”与“读取结果”两侧,均在 packages/puppeteer-core/src/common/Cookie.ts 中定义:
-
CookieParam.sameSite(页面级 API 写入参数):CookieParam是 page-level cookies API 使用的参数对象(文档见 puppeteer.cookieparam.md),其中字段声明为sameSite?: CookieSameSite。它用于Page.setCookie()/Page.deleteCookie()这类以页面为作用域的调用。 -
CookieData.sameSite(浏览器级 API 写入/读取参数):CookieData是 browser-level cookies API 使用的参数对象(文档见 puppeteer.cookiedata.md),同样声明sameSite?: CookieSameSite。它用于browser.setCookie()/browser.cookies()/browser.deleteCookie()/BrowserContext.setCookie()/browserContext.cookies()等以整个浏览器/浏览器上下文为作用域的调用。 -
Cookie接口(读取结果):puppeteer-core内部还有Cookie接口(继承CookieData,补充path、expires、size、secure、session等只读字段)。因此通过page.cookies()拿到的每个 Cookie 对象上,也会带一个sameSite: CookieSameSite属性,供你校验设置是否生效、或分析站点当前的 Cookie 策略。
注意页面级与浏览器级两套 API 面向的对象不同:页面级 API 通常要求提供 url 或结合当前页面 URL 推断 domain/path,而浏览器级 API 需要显式提供 domain 等完整定位信息。二者对 sameSite 的处理完全一致,都收束到同一个类型。
四、底层原理(一):Chrome/CDP 路径下的真实映射
当 Puppeteer 连接 Chrome(通过 Chrome DevTools Protocol,即 CDP)时,sameSite 并不会被“原样透传”,而是经过一次转换。转换函数定义在 packages/puppeteer-core/src/cdp/Page.ts:
/**
* @internal
*/
export function convertSameSiteFromPuppeteerToCdp(
sameSite: CookieSameSite | undefined,
): Protocol.Network.CookieSameSite | undefined {
switch (sameSite) {
case 'Strict':
case 'Lax':
case 'None':
return sameSite;
default:
return undefined;
}
}
从中可以读出三条关键事实:
'Strict'、'Lax'、'None'三个值与 CDP 协议层Protocol.Network.CookieSameSite中的字面量一一对应、直接透传,无需改写;'Default'(以及undefined)会落到default分支,返回undefined,即:该字段在发送给 CDP 时被省略,浏览器按自身默认 SameSite 策略处理;- 该函数被
CdpPage在写入 Cookie 的调用链(如page.setCookie()处理 CookieParam、以及 cdp 层BrowserContext.setCookie())中调用,例如 packages/puppeteer-core/src/cdp/Page.ts 处将cookieParam.sameSite转换后放入协议参数。
这也解释了 'Default' 的设计意图:它不是 CDP 协议里的真实取值(协议里还存在独立的 'Unspecified' 状态,表示“未指定”),而是 Puppeteer 面向用户提供的一个“语义化缺省值”。
五、底层原理(二):Firefox/WebDriver BiDi 路径下的双向转换
当 Puppeteer 连接 Firefox(或启用了 WebDriver BiDi 的 Chrome)时,Cookie 通过 WebDriver BiDi 的 storage 域读写。BiDi 协议内部对 SameSite 采用小写枚举(strict / lax / none),且带有一个 default 状态,与 Puppeteer 的大写字符串不同。因此 puppeteer-core 在 packages/puppeteer-core/src/bidi/Page.ts 中实现了两个方向的对偶转换函数:
读取方向(BiDi → Puppeteer),见 packages/puppeteer-core/src/bidi/Page.ts:
function convertCookiesSameSiteBiDiToCdp(
sameSite: Bidi.Network.SameSite | undefined,
): CookieSameSite {
switch (sameSite) {
case 'strict':
return 'Strict';
case 'lax':
return 'Lax';
case 'none':
return 'None';
default:
return 'Default';
}
}
写入方向(Puppeteer → BiDi),见 packages/puppeteer-core/src/bidi/Page.ts:
export function convertCookiesSameSiteCdpToBiDi(
sameSite: CookieSameSite | undefined,
): Bidi.Network.SameSite {
switch (sameSite) {
case 'Strict':
return Bidi.Network.SameSite.Strict;
case 'Lax':
return Bidi.Network.SameSite.Lax;
case 'None':
return Bidi.Network.SameSite.None;
default:
return Bidi.Network.SameSite.Default;
}
}
由此可见两条非常重要的设计结论:
- “Default”在 BiDi 侧是真实状态:读取一个未显式声明 SameSite 的 Cookie 时,BiDi 返回
default,Puppeteer 会把它归一化为字符串'Default'暴露给你——这是它与 CDP 路径的关键差异(CDP 下未指定的 Cookie 通常表现为没有该字段或'Unspecified')。因此用page.cookies()在 Firefox 与 Chrome 上拿到同样的sameSite: 'Default'或缺失,属于协议差异,不代表 Cookie 本身不同。 - 该转换函数是
export的,因为它还被bidi/BrowserContext.ts等模块复用(如 packages/puppeteer-core/src/bidi/BrowserContext.ts 处写入 Cookie 时同样先做convertCookiesSameSiteCdpToBiDi转换)。
六、实战:setCookie 中如何写、cookies 中如何验
1. 页面级设置 Cookie(CDP 路径)
使用 Page.setCookie()(API 见 puppeteer.page.setcookie.md),在 CookieParam 中传入 sameSite:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setCookie(
{
name: 'pref',
value: 'dark-theme',
domain: 'example.com',
sameSite: 'Lax', // CookieSameSite 取值
},
{
name: 'session_key',
value: 'abc123',
url: 'https://example.com/login',
httpOnly: true,
secure: true,
sameSite: 'Strict', // 严格模式
},
);
2. 跨站场景设置 None 必须配合 Secure
要让 Cookie 允许跨站携带(如 SSO 回调、第三方登录态),应使用 sameSite: 'None',同时务必带上 secure: true 并基于 HTTPS 访问——因为现代浏览器会拒绝未带 Secure 属性的 SameSite=None Cookie。仓库中的集成测试印证了这一行为:在 test/src/cookies.test.ts 的用例 “should set secure same-site cookies from a frame” 中,测试用 httpsServer 启动页面,在跨进程 iframe 场景下通过 page.setCookie({..., sameSite: 'None'}) 写入,随后断言读取结果中 sameSite: 'None' 且 secure: true。也就是说,即使代码里没有显式写 secure: true,Chrome 也会把 SameSite=None 的 Cookie 强制按 Secure 处理;反过来,在 http 页面里尝试写 sameSite: 'None' 大概率会被浏览器拒绝。
3. 读取校验(Firefox 与 Chrome 通用)
无论走哪条协议路径,读取 API 都返回统一大写形式的 CookieSameSite:
const cookies = await page.cookies('https://example.com');
for (const c of cookies) {
console.log(c.name, c.sameSite); // 例如 'Lax' / 'Strict' / 'None' / 'Default'
}
利用返回值可以快速断言“Cookie 是否真的按预期策略落盘”,例如校验三方登录态确实是 None:
const sso = (await page.cookies('https://idp.example.com')).find(
c => c.name === 'sso_session',
);
if (sso?.sameSite !== 'None') {
throw new Error(`sso_session SameSite 异常: ${sso?.sameSite}`);
}
4. 浏览器级 API 同样支持
如果需要在创建 BrowserContext 之后、尚未导航任何页面之前就预置 Cookie,可用浏览器级 API browser.setCookie() 或 browserContext.setCookie()(API 见 puppeteer.browser.setcookie.md、puppeteer.browsercontext.setcookie.md),它们使用 CookieData,sameSite 字段类型完全一致:
const context = await browser.createBrowserContext();
await context.setCookie({
name: 'geoloc-consent',
value: '1',
domain: 'example.com',
secure: true,
sameSite: 'Lax',
});
const page = await context.newPage();
await page.goto('https://example.com');
注意浏览器级 API 需要提供完整的 domain(甚至 path)来唯一定位 Cookie;若只想按当前页面域名收窄操作,页面级 API(提供 url)会更方便。
七、选择建议与注意事项小结
- 绝大多数内部自动化/无痕场景用
'Lax'或直接省略即可:省略(或显式'Default')时,CDP 路径不发送该字段,浏览器按默认策略处理;BiDi 路径读取时会归一化为'Default'。 - 登录态加固可选
'Strict':适合同站应用、期望最高防护等级、且能接受外链落地页暂不带登录态的场景。 - 只有确需跨站携带时才用
'None',且必须走 HTTPS + Secure,例如第三方登录(SSO)、支付回调、跨子域数据上报。 - 字符串大小写敏感:传
'NONE'、'none'、'Lax '(带空格)都无法通过 TypeScript 类型检查或会被底层当作非法值处理;写入用'Strict' | 'Lax' | 'None' | 'Default',读取返回同一组值。 - 跨浏览器断言时留意协议差异:Chrome 与 Firefox 在读写同一枚“未声明 SameSite”的 Cookie 时,字段呈现方式可能不同(CDP 下可能缺省,BiDi 下返回
Default),Puppeteer 通过第四节/第五节的转换层在应用层尽量抹平差异,但断言时仍建议同时检查字段是否存在两种分支。
深入阅读指引:类型定义与 JSDoc 见 packages/puppeteer-core/src/common/Cookie.ts;CDP 映射逻辑见 packages/puppeteer-core/src/cdp/Page.ts;WebDriver BiDi 双向映射见 packages/puppeteer-core/src/bidi/Page.ts;配套参数接口文档见 puppeteer.cookieparam.md 与 puppeteer.cookiedata.md;端到端行为测试见 test/src/cookies.test.ts。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00