首页
/ Puppeteer Browser.browserContexts() 详解:枚举、跟踪与管理所有浏览器上下文的官方 API

Puppeteer Browser.browserContexts() 详解:枚举、跟踪与管理所有浏览器上下文的官方 API

2026-09-05 20:38:53作者:龚格成

本文围绕 Puppeteer 官方 API 文档 docs/api/puppeteer.browser.browsercontexts.md 中定义的 Browser.browserContexts() 方法展开,讲清它的签名语义、在 CDP 与 BiDi 两种协议下的具体实现,以及它在 BrowserContext.closed 判定、Browser.pages() 聚合等内部调用链中的核心作用。读完本文,你既能正确地在自动化脚本中枚举和管理多个浏览器上下文(isolated context / incognito),也能从源码层面理解"新浏览器只有一个上下文"这一行为背后的实现机制。

一、方法签名与基本语义

Browser.browserContexts() 定义在抽象基类 Browser 上,用于获取当前浏览器实例中所有打开的浏览器上下文列表。官方文档(docs/api/puppeteer.browser.browsercontexts.md)给出的完整签名如下:

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

返回值BrowserContext[]BrowserContext 数组)

关键语义有三点:

  1. 同步方法,无参数:它不返回 Promise,读取的是 Puppeteer 侧内存中维护的上下文注册表,因此调用开销极低,可以在任意时机(包括事件回调中)调用。
  2. 新浏览器只有 1 个上下文:文档明确指出 "In a newly-created browser, this will return a single instance of BrowserContext"。这唯一的实例就是默认浏览器上下文(default browser context),可通过 browser.defaultBrowserContext() 单独获取。
  3. 上下文意味着存储隔离:每个 BrowserContext 拥有相互隔离的 cookies、localStorage 等存储。在 Chrome 中,所有非默认上下文都是 incognito(无痕)模式;默认上下文是否无痕取决于启动时是否传入 --incognito 参数(见 docs/api/puppeteer.browsercontext.md 的 Remarks 部分)。

抽象声明位于 api/Browser.ts

/**
 * 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[];

二、上下文从哪里来:createBrowserContext() 与默认上下文

browserContexts() 返回的列表,其元素来源只有两条路径:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
// 创建一个全新的浏览器上下文(与其他上下文不共享 cookies/cache)
const context = await browser.createBrowserContext();
// 在该上下文中创建页面
const page = await context.newPage();
await page.goto('https://example.com');

官方示例(同时收录在 api/Browser.ts 的 JSDoc 中)演示了最典型的"干净上下文"用法:新上下文不继承任何既有登录态与缓存,天然适合做隔离测试或多账号场景。

一个完整的上下文生命周期示例:

// 创建新浏览器上下文
const context = await browser.createBrowserContext();
// 在上下文中创建页面
const page = await context.newPage();
await page.goto('https://example.com');
// ... 使用 page ...
// 上下文不再需要时销毁
await context.close();

createBrowserContextbrowserContexts() 在源码中是"写"与"读"的对应关系:前者把新上下文注册进内部容器,后者负责枚举该容器。理解这一点对后面阅读两种协议实现至关重要。

三、源码级实现:CDP 与 BiDi 两套引擎

Puppeteer 同时支持 CDP(Chrome DevTools Protocol)与 WebDriver BiDi 两种协议,browserContexts() 各有独立实现。

3.1 CDP 实现:默认上下文 + 上下文 Map

CDP 版的 CdpBrowser 用一个 Map<browserContextId, CdpBrowserContext> 保存所有非默认上下文,默认上下文则单独持有。browserContexts() 就是把默认上下文拼在 Map 值之前:

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

cdp/Browser.ts。从源码结构看:

  • 返回值顺序是确定的:默认上下文永远排在数组首位;
  • 新浏览器只有一个上下文的解释:#contexts Map 初始为空,数组里就只有 #defaultContext 一项;
  • createBrowserContext() 的写入路径:该实现先向浏览器发送 Target.createBrowserContext 协议命令拿到 browserContextId,再 new CdpBrowserContext(...) 并存入 #contexts(见 cdp/Browser.ts);上下文关闭时由 _disposeContext() 发送 Target.disposeBrowserContext 并从 Map 中删除(cdp/Browser.ts)。因此 browserContexts() 的列表长度随上下文的创建/销毁实时增减。

3.2 BiDi 实现:基于 UserContext 的 WeakMap

BiDi 版的 BidiBrowser 使用 WeakMap<UserContext, BidiBrowserContext> 把底层协议的 user context 映射到 Puppeteer 的 BrowserContext 对象:

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

bidi/Browser.ts。浏览器初始化时会遍历 browserCore.userContexts 逐个建立映射(#initialize()bidi/Browser.ts),因此 browserContexts() 返回的是当前协议侧全部现存 user context 的映射结果,默认上下文同样包含在内(由 defaultBrowserContext() 依据 browserCore.defaultUserContext 定位)。

3.3 上下文标识 id 的取值差异

browserContexts() 配合使用时常会读取 context.id。两个实现的取值规则不同(从源码结构看):

基类 BrowserContext.id 默认返回 undefinedapi/BrowserContext.ts)。这意味着跨协议编写脚本时,不宜把"id 一定存在"当作前提。

四、内部调用链:browserContexts() 支撑哪些 API

browserContexts() 虽然看起来只是一个"取列表"的方法,但它实际上是 Puppeteer 多处聚合逻辑与状态判定的基础。

4.1 BrowserContext.closed:用"是否在列表里"判定上下文是否已关闭

BrowserContext 基类的 closed 属性直接通过 browserContexts() 反向查询实现(api/BrowserContext.ts):

get closed(): boolean {
  return !this.browser().browserContexts().includes(this);
}

也就是说,一个上下文是否关闭,等价于它是否还出现在 browserContexts() 的返回列表中。上下文被 close() 后从内部容器移除,closed 随即变为 true。这解释了为什么该方法必须是同步且实时读取注册表的。

4.2 Browser.pages():跨上下文聚合所有页面

Browser 基类实现的 pages() 会遍历所有上下文并合并各自的结果(api/Browser.ts):

async pages(includeAll = false): Promise<Page[]> {
  const contextPages = await Promise.all(
    this.browserContexts().map(context => {
      return context.pages(includeAll);
    }),
  );
  // Flatten array.
  return contextPages.reduce((acc, x) => {
    return acc.concat(x);
  }, []);
}

因此 browser.pages() 与逐个上下文调用 context.pages(includeAll?) 的差异正是"是否跨上下文聚合"。注意:不可见的页面(如 "background_page")不会被列出,这类页面可通过 Target.page() 找到(见 docs/api/puppeteer.browsercontext.pages.md 的 Remarks)。

4.3 Browser.targets():BiDi 版同样基于上下文聚合

BiDi 实现的 targets() 也是先枚举上下文再平铺其 targets()bidi/Browser.ts),与文档中"存在多个上下文时返回所有上下文中的全部 targets"(api/Browser.ts)的描述一致。

五、测试用例中的行为验证

仓库集成测试 test/src/browsercontext.test.tsbrowserContexts() 的行为做了系统验证,可作为该 API 契约的直接依据:

  • 新浏览器至少有一个上下文expect(browser.browserContexts().length).toBeGreaterThanOrEqual(1)test/src/browsercontext.test.ts);
  • 创建/关闭使列表长度增减:创建上下文后 expect(browser.browserContexts()).toHaveLength(contextCount + 1),且 indexOf(context) !== -1trueclose() 后长度恢复(test/src/browsercontext.test.ts);
  • 新创建的上下文不共享存储:测试创建两个 incognito 上下文,各自 targets() 为空、cookies 互不可见(test/src/browsercontext.test.ts);
  • 跨会话一致:通过 puppeteer.connect({ browserWSEndpoint }) 重新连接同一浏览器后,remoteBrowser.browserContexts() 仍能正确列出已创建的上下文(test/src/browsercontext.test.ts)——这说明上下文的注册状态来自浏览器端协议状态,而非仅存在于单个 Puppeteer 连接内。

此外,测试还验证了默认上下文不可关闭(defaultContext.close() 会抛错,test/src/browsercontext.test.ts)以及新上下文会带有 idtest/src/browsercontext.test.ts)。

六、实战模式:用 browserContexts() 管理上下文生命周期

以下模式均以 browserContexts() 为观察入口,适用于日常自动化与测试框架开发。

6.1 多账号并行会话

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

const accounts = ['alice@example.com', 'bob@example.com'];
for (const email of accounts) {
  const context = await browser.createBrowserContext(); // 独立 cookies/存储
  const page = await context.newPage();
  // ... 在该 page 中完成 email 对应的登录流程 ...
  await page.goto('https://example.com/login?user=' + encodeURIComponent(email));
}

console.log(browser.browserContexts().length); // 3:默认 + 2 个账号上下文

每个账号会话处于独立上下文,登录态互不污染;关闭某个上下文即彻底清理该账号的会话数据。

6.2 周期性对账:清理"僵尸"上下文

在长驻进程(如浏览器农场、CI 中复用的浏览器)中,可以周期性检查上下文数量是否与预期任务一致:

function reconcileContexts(browser: Awaited<ReturnType<typeof puppeteer.launch>>, expected: number) {
  const contexts = browser.browserContexts();
  if (contexts.length > expected) {
    // 关闭多余的上下文(注意:默认上下文 close() 会抛错,应先排除)
    const defaultContext = browser.defaultBrowserContext();
    return Promise.all(
      contexts.filter(c => c !== defaultContext && c.closed !== true).map(c => c.close()),
    );
  }
  return Promise.resolve();
}

6.3 重连后恢复状态

Puppeteer.connect 重连已有浏览器时,browserContexts() 是恢复管理视图的第一步:

const browser = await puppeteer.connect({ browserWSEndpoint });
const contexts = browser.browserContexts();
for (const context of contexts) {
  const pages = await context.pages();
  console.log(context.id ?? '(default)', pages.length);
}

这与 docs/api/puppeteer.connect.md 描述的连接流程配合使用;测试用例 "should work across sessions" 已验证重连后列表的完整性。

6.4 结合 BrowserContextEvent 事件流

browserContexts() 提供的是快照,配合 BrowserContextEventtargetcreated / targetchanged / targetdestroyed)可构建完整的上下文/目标生命周期监控。例如在上下文内 window.open 产生新 target 时监听 TargetCreated,再结合 context.waitForTarget() 等待特定 URL 的 target 出现(示例见 api/BrowserContext.tswaitForTarget 的 JSDoc)。

七、注意事项与边界

  1. 默认上下文不可关闭defaultBrowserContext() 返回的实例调用 close() 会抛错;批量关闭上下文前先与默认上下文区分(依据 docs/api/puppeteer.browsercontext.close.md 的 Remarks 与上述测试用例)。
  2. 返回值是同步快照,不是订阅:它不监听后续变化;列表在两次调用之间可能变化,需要持续跟踪请结合事件(BrowserContextEventBrowserEvent)。
  3. incognito 语义仅限 Chrome 文档明确描述:"在 Chrome 中所有非默认上下文都是 incognito";BiDi 下上下文对应协议的 user context,其行为以浏览器实现为准,编写跨协议脚本时避免对无痕特性做硬假设。
  4. id 可能为 undefined:基类默认返回 undefined,BiDi 下默认上下文返回 undefined(见第 3.3 节),以 id 作为键存入 Map 时需做兜底。
  5. 页面列表的可见性过滤:基于 browserContexts() 聚合的 pages() 不含 "background_page" 等不可见页面,如需完整目标请使用 targets() + Target.page()

小结

Browser.browserContexts() 是 Puppeteer 多上下文能力的枚举入口:签名简单(同步、无参、返回 BrowserContext[]),但它是上下文注册表的权威读取接口——BrowserContext.closed 的状态判定、Browser.pages() / Browser.targets() 的跨上下文聚合、重连后的状态恢复,全部构建在它之上。CDP 实现以"默认上下文 + Map<id, context>"组织(cdp/Browser.ts),BiDi 实现以 WeakMap<UserContext, BidiBrowserContext> 组织(bidi/Browser.ts),两者共同保证了"新浏览器恰有一个默认上下文、createBrowserContext() 增加一项、close() 移除一项"的一致行为,这一契约由 test/src/browsercontext.test.ts 中的计数断言直接固化。掌握该方法后,配合 createBrowserContext()context.newPage()context.close(),即可在 Puppeteer 中安全地编排任意数量的隔离会话。

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

项目优选

收起
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++
915
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