首页
/ Puppeteer Browser.browserContexts() 方法详解:枚举浏览器上下文的 API 与 CDP/BiDi 实现剖析

Puppeteer Browser.browserContexts() 方法详解:枚举浏览器上下文的 API 与 CDP/BiDi 实现剖析

2026-09-07 16:15:00作者:谭伦延

本文以 Puppeteer 官方 API 文档中的 Browser.browserContexts() 方法为主体,完整讲解该方法的签名、返回值语义、新建浏览器时的默认行为,并结合 puppeteer-core 源码剖析 CDP 与 WebDriver BiDi 两套协议下的具体实现,以及测试套件对其行为的验证方式。读完后你将能够正确地在自动化脚本中枚举、统计和管理一个 Browser 实例下的所有 BrowserContext,并理解默认上下文与自建上下文在内部存储结构上的差异。

一、方法定义与返回值语义

browserContexts()Browser 类上用于获取所有已打开浏览器上下文列表的方法。官方 API 文档(docs/api/puppeteer.browser.browsercontexts.md)给出的方法说明为:

Gets a list of open browser contexts.(获取已打开浏览器上下文的列表。)

In a newly-created browser, this will return a single instance of BrowserContext.(在一个新建的浏览器中,该方法将只返回一个 BrowserContext 实例。)

方法签名为:

class Browser {
  abstract browserContexts(): BrowserContext[];
}

返回值: BrowserContext[] —— 一个同步返回的数组,包含当前浏览器实例下所有未关闭的 BrowserContext

几个关键语义点:

  1. 同步方法:与 createBrowserContext()(返回 Promise<BrowserContext>)不同,browserContexts() 是同步方法,直接读取内存中维护的上下文注册表,不发起任何协议请求。
  2. 新建浏览器只含默认上下文:浏览器刚启动时,数组中只有一个元素,即默认浏览器上下文(default browser context)。
  3. 每次 createBrowserContext() 都会使数组长度 +1,每次 context.close() 后又会恢复原长度(详见后文测试验证)。
  4. 返回的列表中默认上下文位于首位——这一实现细节在 CDP 实现中可以清晰看到。

二、BrowserContext 概念回顾

理解 browserContexts() 的前提是理解 BrowserContext 本身。根据 BrowserContext 类文档

  • BrowserContext 表示浏览器内的独立用户上下文。浏览器启动时至少拥有一个默认上下文,其余可通过 Browser.createBrowserContext() 创建;
  • 每个上下文拥有隔离的存储(cookies / localStorage 等),互不共享;
  • 在 Chrome 中,所有非默认上下文都是 incognito(隐私)上下文;如果启动参数中提供了 --incognito,默认上下文也可能处于 incognito 状态;
  • 该类的构造函数被标记为内部(internal),第三方代码不应直接实例化或继承 BrowserContext
  • 页面通过 window.open 打开的弹出窗口会归属于父页面所在的浏览器上下文

典型用法(来自官方文档示例):

// Create a new browser context
const context = await browser.createBrowserContext();
// Create a new page inside context.
const page = await context.newPage();
// ... do stuff with page ...
await page.goto('https://example.com');
// Dispose context once it's no longer needed.
await context.close();

browserContexts() 正是用来观察这一整套"创建—使用—销毁"生命周期的观测入口。

三、源码剖析:抽象声明与两套协议实现

3.1 抽象基类中的声明

packages/puppeteer-core/src/api/Browser.ts 中,browserContexts() 被声明为抽象方法:

/**
 * Gets a list of open {@link BrowserContext | browser contexts}.
 *
 * In a newly-created {@link Browser | browser}, this will return a single
 * instance of {@link BrowserContext}.
 */
abstract browserContexts(): BrowserContext[];

/**
 * Gets the default {@link BrowserContext | browser context}.
 *
 * @remarks The default {@link BrowserContext | browser context} cannot be
 * closed.
 */
abstract defaultBrowserContext(): BrowserContext;

注意它与 defaultBrowserContext() 的分工:browserContexts() 返回全部上下文(含默认上下文),而 defaultBrowserContext() 只返回默认上下文,且默认上下文不可被关闭

3.2 CDP 实现:默认上下文 + Map 注册表

Chrome 走 CDP 协议,对应实现在 packages/puppeteer-core/src/cdp/Browser.ts

override browserContexts(): CdpBrowserContext[] {
  return [this.#defaultContext, ...Array.from(this.#contexts.values())];
}

override defaultBrowserContext(): CdpBrowserContext {
  return this.#defaultContext;
}

其中 #defaultContext#contextsMap<string, CdpBrowserContext>,以 browserContextId 为键)在 构造函数中初始化

this.#defaultContext = new CdpBrowserContext(
  this.#connection,
  this,
  undefined,   // 默认上下文没有 contextId
  logger,
);
for (const contextId of contextIds) {
  this.#contexts.set(
    contextId,
    new CdpBrowserContext(this.#connection, this, contextId, logger),
  );
}

从源码结构看,CDP 实现有三个要点:

  1. 返回顺序固定:默认上下文始终排在数组第一位,其后是按插入顺序排列的自建上下文;
  2. 默认上下文无 contextId:构造时第三个参数传 undefined,这也是 BrowserContext.idstring | undefined)允许为 undefined 的原因——自建上下文会携带由 CDP 协议分配的 browserContextId
  3. 连接既有浏览器时可携带初始上下文contextIds 参数用于 puppeteer.connect() 场景,把远端浏览器上已经存在的上下文批量注册进 #contexts,从而保证 browserContexts() 返回的列表是完整的(测试用例"should work across sessions"验证了这一点,见第四节)。

自建上下文的创建路径为 createBrowserContext():向浏览器发送 Target.createBrowserContext CDP 命令拿到 browserContextId,再构造 CdpBrowserContext 并写入 #contexts,因此创建成功后立即调用 browserContexts() 就能看到新上下文。

3.3 BiDi 实现:userContexts 映射

Firefox(以及走 WebDriver BiDi 协议的 Chrome)使用 packages/puppeteer-core/src/bidi/Browser.ts 中的实现:

override browserContexts(): BidiBrowserContext[] {
  return [...this.#browserCore.userContexts].map(context => {
    return this.#browserContexts.get(context)!;
  });
}

override defaultBrowserContext(): BidiBrowserContext {
  return this.#browserContexts.get(this.#browserCore.defaultUserContext)!;
}

可以推断,BiDi 实现维护一个 #browserCore(协议侧浏览器核心),其 userContexts 属性提供所有用户上下文 ID,#browserContexts 是一个 ID 到 BidiBrowserContext 实例的缓存表;browserContexts() 把两者做映射后返回。默认上下文则通过 #browserCore.defaultUserContext 单独定位。BiDi 核心层还会通过 browsingContext.getTree 同步上下文树(见 packages/puppeteer-core/src/bidi/core/Browser.ts),在 getTree 期间若上下文被创建或销毁,会借助 browsingContext.contextCreated 事件检测,保证上下文列表的实时性。

两套实现虽然内部数据结构不同,但对外行为一致:都返回含默认上下文在内的全部上下文列表,均满足"新建浏览器返回单个默认上下文"的文档承诺。

3.4 一个隐藏细节:closed 判定依赖 browserContexts()

browserContexts() 不只是查询接口,它还参与 BrowserContext 的关闭状态判定。在抽象基类 packages/puppeteer-core/src/api/BrowserContext.ts 中,closed 属性的判定逻辑正是通过成员检测实现的:

return !this.browser().browserContexts().includes(this);

即"不在 browserContexts() 返回列表中的上下文即为已关闭"。这说明上下文的关闭必须保证它从注册表中被移除,browserContexts() 因此是上下文生命周期状态的权威来源。

四、测试套件验证的行为契约

test/src/browsercontext.test.ts 针对 browserContexts() 的行为写了多组断言,可作为该方法的"可验证契约":

契约 1:默认浏览器至少有一个上下文第 17-23 行):

it('should have default context', async () => {
  const {browser} = await getTestState({skipContextCreation: true});
  expect(browser.browserContexts().length).toBeGreaterThanOrEqual(1);
});

契约 2:创建/关闭上下文会精确改变列表长度与成员第 38-50 行):

const contextCount = browser.browserContexts().length;
expect(contextCount).toBeGreaterThanOrEqual(1);
const context = await browser.createBrowserContext();
expect(browser.browserContexts()).toHaveLength(contextCount + 1);
expect(browser.browserContexts().indexOf(context) !== -1).toBe(true);
await context.close();
expect(browser.browserContexts()).toHaveLength(contextCount);

契约 3:跨连接会话共享同一份上下文列表第 218-236 行):

expect(browser.browserContexts()).toHaveLength(1);
const context = await browser.createBrowserContext();
try {
  expect(browser.browserContexts()).toHaveLength(2);
  using remoteBrowser = await puppeteer.connect({
    browserWSEndpoint: browser.wsEndpoint(),
    protocol: browser.protocol,
  });
  const contexts = remoteBrowser.browserContexts();
  expect(contexts).toHaveLength(2);
} finally {
  await context.close();
}

最后这个用例特别有价值:它证明 puppeteer.connect() 建立的新 Browser 实例(即第二节的 contextIds 注册路径)能枚举出与本地实例完全一致的上下文数量,因此 browserContexts() 在多进程/分布式控制场景下同样可靠。

契约 4:自建上下文携带 id第 238-251 行):createBrowserContext() 之后 context.id 被断言为已定义(toBeDefined()),与 BrowserContext.id: string | undefined 的类型声明(见 BrowserContext 文档 的属性表)互相印证。

五、实战用法

5.1 统计与枚举浏览器中的所有上下文

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

// 新建浏览器:仅默认上下文
console.log(browser.browserContexts().length); // 1

// 创建两个隔离上下文(例如:匿名会话 + 已登录会话)
const anonymous = await browser.createBrowserContext();
const logged = await browser.createBrowserContext();

for (const context of browser.browserContexts()) {
  console.log('context id:', context.id); // 默认上下文为 undefined,自建上下文为字符串
  console.log('pages:', context.pages().length);
}

// 用完即关:列表长度恢复为 1
await anonymous.close();
await logged.close();
await browser.close();

注意默认上下文的 idundefined,如需识别默认上下文,应使用 browser.defaultBrowserContext() 做引用比较,而不是依赖 id

5.2 多账号并行采集的隔离模型

browserContexts() 常与 createBrowserContext() 配合,实现"一个浏览器进程、多个隔离会话"的架构:每个账号分配一个独立上下文,各自的 cookies / localStorage 互不可见。定期调用 browserContexts() 可以核对实际存活上下文数量是否符合预期(防止上下文泄漏——例如任务结束忘记 close())。

六、与相关 API 的关系

API 文档 browserContexts() 的关系
Browser.createBrowserContext(options) docs/api/puppeteer.browser.createbrowsercontext.md 创建新上下文,会使列表长度 +1;支持 proxyServerproxyBypassListdownloadBehaviorBrowserContextOptions
Browser.defaultBrowserContext() docs/api/puppeteer.browser.defaultbrowsercontext.md 只取默认上下文,不遍历列表;默认上下文不可关闭
BrowserContext.close() docs/api/puppeteer.browsercontext.close.md 关闭上下文及其全部页面,关闭后该上下文从 browserContexts() 中消失
BrowserContext.pages(includeAll) docs/api/puppeteer.browsercontext.pages.md 获取单个上下文内页面;Browser.pages() 则是对其遍历所有上下文后取并集(见 api/Browser.ts 中对 browserContexts() 的 flatMap 式调用)
BrowserContext.targets() docs/api/puppeteer.browsercontext.targets.md 获取单个上下文内活跃 target

七、结论与参考路径

browserContexts() 是 Puppeteer 中管理多上下文(多隔离会话)的观测与核对入口:同步返回、含默认上下文、CDP 实现下默认上下文恒居首位。其正确性由 puppeteer-core 的 CDP/BiDi 双实现保证,并由 test/src/browsercontext.test.ts 的长度变化、成员包含、跨会话一致性三组断言固化。

本文涉及的关键仓库路径:

适用前提:以上行号与实现细节基于当前仓库版本(puppeteer-core 源码,browserContexts() 为同步方法的版本);在不同 Puppeteer 大版本间,BiDi 上下文同步等内部实现可能调整,公共 API 语义以官方文档为准。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388