Puppeteer Browser.setCookie() 完全指南:浏览器级 Cookie 注入与 CDP/BiDi 底层实现解析
本文基于 Puppeteer 官方 API 文档 Browser.setCookie() 展开,系统讲解该方法的签名、参数对象 CookieData 的每个字段、返回值与典型用法,并结合仓库源码还原它从 API 调用到 CDP(Storage.setCookies)和 WebDriver BiDi(storage.setCookie)协议的完整实现链路。读完后,你可以熟练地在浏览器层面预置 Cookie(如登录态注入),并理解 sameSite、partitionKey 等字段在两种协议下的转换细节与限制。
方法定位与签名
Browser.setCookie() 是 Puppeteer 提供的浏览器级 Cookie 写入接口,作用于浏览器的默认 BrowserContext。官方文档对它的定义非常明确:
Sets cookies in the default BrowserContext.
也就是说,它与页面级 page.setCookie()、上下文级 browserContext.setCookie() 相比,区别在于作用域:它写入的是默认浏览器上下文中的 Cookie 集合,适合在打开任何页面之前就完成会话凭据、测试用 Cookie 的预置。
方法签名(见 官方 API 文档):
class Browser {
setCookie(...cookies: CookieData[]): Promise<void>;
}
- 参数
cookies:CookieData对象的可变参数列表,一次调用可以同时设置多个 Cookie; - 返回值:
Promise<void>,没有数据返回,完成后即代表 Cookie 已提交给浏览器; - 备注(Remarks):它是
browser.defaultBrowserContext().setCookie()的快捷方式。
从源码结构看,这一"快捷方式"的描述与实现完全一致。在 Browser.ts 中:
/**
* Sets cookies in the default {@link BrowserContext}.
*
* @remarks
*
* Shortcut for
* {@link BrowserContext.setCookie | browser.defaultBrowserContext().setCookie()}.
*/
async setCookie(...cookies: CookieData[]): Promise<void> {
return await this.defaultBrowserContext().setCookie(...cookies);
}
实现只有一行转发:取出默认上下文,再把所有 CookieData 参数原样透传给其 setCookie()。同一文件中的 cookies() 和 deleteCookie() 也是同样的快捷方式模式,三者构成默认上下文上的 Cookie 读写删完整闭环。
基本用法示例
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
// 浏览器级写入:默认浏览器上下文
await browser.setCookie({
name: 'session_id',
value: 'abc123',
domain: 'example.com', // 注意:domain 在 CookieData 中是必填字段
path: '/',
httpOnly: true,
secure: true,
sameSite: 'Lax',
// 过期时间:UNIX 时间戳(秒);省略则为会话 Cookie
expires: Math.floor(Date.now() / 1000) + 60 * 60,
});
// 可以一次写入多个 Cookie
await browser.setCookie(
{name: 'theme', value: 'dark', domain: 'example.com'},
{name: 'lang', value: 'zh', domain: 'example.com', path: '/app'},
);
// 用配套的 cookies() 快捷方法验证写入结果
const cookies = await browser.cookies();
console.log(cookies.map(c => `${c.name}=${c.value}`));
await browser.close();
典型应用场景包括:在 page.goto() 之前注入登录令牌以绕过登录流程、为测试环境预置功能开关(feature flag)Cookie、跨页面共享同一份凭据而不依赖某个具体页面的 URL。
CookieData 参数对象:逐字段说明
setCookie 的入参类型是 CookieData,其完整定义位于 Cookie.ts。它与页面级 API 使用的 CookieParam(同文件 L93-L146)相比有一个关键差异:
| 差异点 | CookieData(浏览器级) |
CookieParam(页面级) |
|---|---|---|
domain |
必填(string) |
可选(url 存在时可由浏览器推导默认 domain) |
url |
不存在该字段 | 可选,用于关联 request-URI 并影响默认 domain/path |
这是因为浏览器级 API 没有页面上下文可供推导,所以必须显式给出 domain——这也是调用 Browser.setCookie() 时最容易踩的坑:漏掉 domain 会直接触发 TypeScript 类型错误。
各字段含义与取值如下:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name |
string |
是 | Cookie 名称 |
value |
string |
是 | Cookie 值 |
domain |
string |
是 | Cookie 域名 |
path |
string |
否 | Cookie 路径 |
secure |
boolean |
否 | 是否仅限安全(HTTPS)传输 |
httpOnly |
boolean |
否 | 是否禁止页面脚本读取 |
sameSite |
CookieSameSite |
否 | 取值 'Strict' | 'Lax' | 'None' | 'Default' |
expires |
number |
否 | 过期时间(UNIX 时间戳,秒);不设置则为会话 Cookie |
priority |
CookiePriority |
否 | 取值 'Low' | 'Medium' | 'High',仅 Chrome 支持 |
sourceScheme |
CookieSourceScheme |
否 | 取值 'Unset' | 'NonSecure' | 'Secure',仅 Chrome 支持 |
partitionKey |
CookiePartitionKey | string |
否 | 分区 Cookie(CHIPS)的分区键 |
这些类型别名的定义同样集中在 Cookie.ts:
export type CookieSameSite = 'Strict' | 'Lax' | 'None' | 'Default';
export type CookiePriority = 'Low' | 'Medium' | 'High';
export type CookieSourceScheme = 'Unset' | 'NonSecure' | 'Secure';
export interface CookiePartitionKey {
sourceOrigin: string;
hasCrossSiteAncestor?: boolean; // 仅 Chrome 支持
}
几个使用上的要点:
- 会话 Cookie:不传
expires即得到会话 Cookie,浏览器关闭即失效。返回侧的Cookie接口(Cookie.ts L59-L85)会额外给出session: boolean和expires(会话 Cookie 为-1)用于回读确认。 partitionKey双形态:可以传字符串(表示顶级站点)或对象{sourceOrigin, hasCrossSiteAncestor?}。在 Chrome 中它对应 CDP 的topLevelSite分区键;在 Firefox 中对应 WebDriver BiDi 的 source origin。- Chrome 专属字段:
priority和sourceScheme在文档中被明确标注 "Supported only in Chrome",跨浏览器使用时应做能力判断。 - 读回的
Cookie类型还包含size(Cookie 大小)与partitionKeyOpaque(分区键是否不透明,仅 Chrome),便于在断言时核对注入结果。
底层实现一:CDP 协议路径(Chrome / Chromium)
在 CDP 实现中,BrowserContext.setCookie() 位于 cdp/BrowserContext.ts:
override async setCookie(...cookies: CookieData[]): Promise<void> {
return await this.#connection.send('Storage.setCookies', {
browserContextId: this.#id,
cookies: cookies.map(cookie => {
return {
...cookie,
partitionKey: convertCookiesPartitionKeyFromPuppeteerToCdp(
cookie.partitionKey,
),
sameSite: convertSameSiteFromPuppeteerToCdp(cookie.sameSite),
};
}),
});
}
调用链为:browser.setCookie() → defaultBrowserContext().setCookie() → CDP 命令 Storage.setCookies,命令参数中携带 browserContextId 以限定作用域到具体浏览器上下文。这里值得注意两个字段在发送前的"净化"转换:
1. sameSite 的降级处理
转换函数 convertSameSiteFromPuppeteerToCdp 定义在 cdp/Page.ts:
switch (sameSite) {
case 'Strict':
case 'Lax':
case 'None':
return sameSite;
default:
return undefined; // 'Default' 被映射为 undefined,即不发送
}
也就是说,'Default' 在 CDP 路径上会被显式丢弃,交由浏览器自行决定默认 SameSite 策略。
2. partitionKey 的形态归一
convertCookiesPartitionKeyFromPuppeteerToCdp(cdp/Page.ts)把 Puppeteer 的双形态分区键统一转换成 CDP 协议对象:
if (typeof partitionKey === 'string') {
return {topLevelSite: partitionKey, hasCrossSiteAncestor: false};
}
return {
topLevelSite: partitionKey.sourceOrigin,
hasCrossSiteAncestor: partitionKey.hasCrossSiteAncestor ?? false,
};
字符串形式等价于 hasCrossSiteAncestor: false;对象形式缺省时同样默认 false。这与 Cookie.ts 中"在 Chrome 中映射到 CDP 的 topLevelSite 分区键"的注释一致。
同一文件中回读方向也有对应处理:cookies() 方法(cdp/BrowserContext.ts L146-L161)调用 Storage.getCookies,并把 CDP 返回的 partitionKey 反向展开为 Puppeteer 的 {sourceOrigin, hasCrossSiteAncestor} 结构,保证写入/读出两侧的类型对称。
底层实现二:WebDriver BiDi 协议路径(Firefox / BiDi 模式)
当以 BiDi 协议驱动浏览器时,走的是另一条链路。BidiBrowserContext.setCookie() 位于 bidi/BrowserContext.ts:
override async setCookie(...cookies: CookieData[]): Promise<void> {
await Promise.all(
cookies.map(async cookie => {
const bidiCookie: Bidi.Storage.PartialCookie = {
domain: cookie.domain,
name: cookie.name,
value: {type: 'string', value: cookie.value},
...(cookie.path !== undefined ? {path: cookie.path} : {}),
...(cookie.httpOnly !== undefined ? {httpOnly: cookie.httpOnly} : {}),
...(cookie.secure !== undefined ? {secure: cookie.secure} : {}),
...(cookie.sameSite !== undefined
? {sameSite: convertCookiesSameSiteCdpToBiDi(cookie.sameSite)}
: {}),
...{expiry: convertCookiesExpiryCdpToBiDi(cookie.expires)},
// Chrome-specific properties.
...cdpSpecificCookiePropertiesFromPuppeteerToBidi(
cookie, 'sourceScheme', 'priority', 'url',
),
};
return await this.userContext.setCookie(
bidiCookie,
convertCookiesPartitionKeyFromPuppeteerToBiDi(cookie.partitionKey),
);
}),
);
}
与 CDP 路径对比,有三个结构性差异:
- 逐条并发发送:BiDi 侧对每个 Cookie 单独构造
Bidi.Storage.PartialCookie并调用userContext.setCookie(),用Promise.all并发等待,而不是 CDP 那样一次性批量提交; - 字段按需展开:所有可选字段都使用"未定义则不发送"的展开语法,避免把
undefined当作有效值传给协议; - Chrome 专属字段的透传:
sourceScheme、priority等通过cdpSpecificCookiePropertiesFromPuppeteerToBidi处理,注释标明它们是 Chrome-specific properties——即 BiDi 通道下这些字段能否生效同样取决于底层浏览器能力。
UserContext.setCookie() 的终点在 bidi/core/UserContext.ts,发送的是 BiDi 命令 storage.setCookie。因此整个 BiDi 链路为:browser.setCookie() → 默认上下文 → storage.setCookie(按 user context 作用域写入)。
与页面级、上下文级 setCookie 的分工
Puppeteer 中存在三个层级的 Cookie 写入入口,从源码抽象层可以看到它们的职责划分:
- api/BrowserContext.ts:
abstract setCookie(...cookies: CookieData[]): Promise<void>,真正的抽象契约,浏览器级方法就是它的一个转发; - api/Page.ts:
abstract setCookie(...cookies: CookieParam[]): Promise<void>,页面级版本使用CookieParam,允许通过url字段让浏览器推导 domain/path; - 具体协议实现分别由
cdp/Page.ts(Network.setCookies,按页面主 target 发送)与上文 CDP/BiDi 上下文实现承接。
实践选型建议:需要全局/跨页面生效的凭据(如站点登录态、全局配置开关),用 browser.setCookie();需要针对特定页面 URL 自动推导作用域时,用 page.setCookie();需要隔离环境(多账号、独立存储)时,先用 browser.createBrowserContext() 再对具体上下文调用 setCookie()。
测试用例中的验证方式
仓库测试套件对该能力有直接覆盖,可作为断言写法的参考:
- test/src/browsercontext-cookies.test.ts:在指定
BrowserContext上context.setCookie({...})写入后,通过context.cookies()回读并按name/value/domain等字段做断言(如 L49、L84 等多处写入用例); - test/src/cookies.test.ts:覆盖页面级写入、
httpOnly/secure/sameSite等属性回显、以及"两个页面共享浏览器上下文内 Cookie 的可见性"等场景; - test/src/defaultbrowsercontext.test.ts:专门验证默认浏览器上下文的行为——这正是
Browser.setCookie()的目标作用域,包括默认上下文的不可关闭性等约束。
这些用例印证了文档的核心语义:浏览器级写入作用于默认上下文,回读接口 cookies() 会返回符合 Cookie 接口 的完整对象(含 path、expires、size、session 等回填字段)。
关键限制与注意事项
domain必填:CookieData与CookieParam最重要的结构差异。TypeScript 项目中漏写会直接编译报错;JS 项目中则可能导致 Cookie 写入范围不符合预期。- 会话 Cookie:省略
expires即为会话 Cookie,仅在当前浏览器会话内有效;回读时expires表现为-1、session为true。 sameSite: 'Default'在 CDP 下被静默丢弃:从 convertSameSiteFromPuppeteerToCdp 的源码结构看,只有'Strict'/'Lax'/'None'会实际下发;需要精确控制时不要依赖'Default'。priority与sourceScheme仅 Chrome 支持:在 Firefox 或 BiDi 通道下这两个字段的实际效果取决于底层浏览器能力(BiDi 实现中它们被归入 "Chrome-specific properties" 处理)。partitionKey字符串简写:传字符串时等价于{sourceOrigin: 该字符串, hasCrossSiteAncestor: false},需要表达跨站祖先关系时必须使用对象形态。- 作用域是默认上下文:如果脚本此前通过
browser.createBrowserContext()创建了隔离上下文,browser.setCookie()不会写入那个隔离上下文,需显式调用对应上下文的setCookie()。
小结
Browser.setCookie() 是 Puppeteer 中面向自动化登录态注入、测试数据预置的高频 API:签名简单(可变参数 + Promise<void>),但参数对象 CookieData 覆盖了现代 Cookie 的全部维度——安全属性(secure/httpOnly/sameSite)、生命周期(expires)、作用域(domain/path)、以及 Chrome 专属的 priority/sourceScheme 与分区 Cookie(partitionKey)。其底层在 CDP 侧收敛为单条 Storage.setCookies 批量命令,在 BiDi 侧展开为按条并发的 storage.setCookie,两条路径都在发送前对 sameSite 与 partitionKey 做了协议化转换,理解这些转换细节是跨浏览器编写可靠 Cookie 注入逻辑的关键。
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 StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00