首页
/ Puppeteer Browser.pages() API 详解:枚举浏览器中所有打开页面的正确姿势

Puppeteer Browser.pages() API 详解:枚举浏览器中所有打开页面的正确姿势

2026-09-04 13:17:22作者:范垣楠Rhoda

Browser.pages() 是 Puppeteer 浏览器实例上的一个基础但关键的 API:它一次性返回当前浏览器中所有打开的 Page 对象。本篇基于 API 文档 展开,结合 packages/puppeteer-core 的源码实现,讲清楚它的签名、参数、跨浏览器上下文(Browser Context)的行为、includeAll 的过滤语义,以及 CDP 与 WebDriver BiDi 两套协议实现下的差异,帮助你在自动化脚本和测试中可靠地枚举、遍历并管理页面。

API 签名与参数说明

Browser.pages() 的官方签名如下:

class Browser {
  pages(includeAll?: boolean): Promise<Page[]>;
}

行为要点(与 文档 一致):

  • 返回该 Browser 内部所有打开的 Page 列表;
  • 如果浏览器中存在多个浏览器上下文BrowserContext),则返回所有上下文中的页面,而不是仅限默认上下文。

参数

参数 类型 说明
includeAll boolean (可选)实验性参数。设为 true 时会包含各种类型的页面(见下文过滤逻辑解析)。

返回值

Promise<Page[]> —— 一个包含所有 Page 实例的数组。

重要备注(Remarks)

不可见的页面(non-visible pages),例如 "background_page" 类型的页面,默认不会出现在返回值中。这类页面可以通过 Target.page() 来查找。这一点是理解该 API 行为边界的关键,下面会结合源码解释为什么。

跨上下文聚合: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);
  }, []);
}

调用链非常清晰:

  1. 通过 this.browserContexts() 拿到当前浏览器下的全部浏览器上下文(包括默认上下文和通过 createBrowserContext() 创建的隔离上下文);
  2. 对每个上下文并发调用 context.pages(includeAll),将 includeAll 参数原样透传;
  3. Promise.all 等待所有结果后,用 reduce 将二维数组展平为一维 Page[]

也就是说,Browser.pages() 本身不直接与底层协议交互,真正的"哪些目标(Target)算页面"的判断下沉到了各协议实现(CDP 或 BiDi)的 BrowserContext.pages() 中。对应的抽象声明见 api/BrowserContext.tsabstract pages(includeAll?: boolean): Promise<Page[]>

CDP 实现:includeAll 到底过滤了什么

在 CDP 协议下,BrowserContext.pages() 的实现位于 cdp/BrowserContext.ts

override async pages(includeAll = false): Promise<Page[]> {
  const pages = await Promise.all(
    this.targets()
      .filter(target => {
        return (
          target.type() === 'page' ||
          ((target.type() === 'other' || includeAll) &&
            this.#browser._getIsPageTargetCallback()?.(target))
        );
      })
      .map(target => {
        return target.page();
      }),
  );
  return pages.filter(page => {
    return !!page;
  });
}

拆解这段过滤逻辑:

  • 第一步:取该上下文内的所有 targets()(通过 target.browserContext() === this 过滤,见 L55-L59);
  • 第二步:一个目标被纳入,当且仅当
    • 它的 target.type() 就是 'page'(普通可见页面,默认即被包含);或者
    • 目标类型是 'other',或者 includeAlltrue(实验性参数会放宽类型门槛),并且通过 isPageTarget 回调的判断;
  • 第三步:对命中的目标调用 target.page() 转换为 Page 对象;
  • 第四步:过滤掉 null(即某些目标无法创建出 Page 实例的情况),保证返回数组中只有可用的 Page

默认的"页面目标"判定回调

isPageTarget 回调的默认定义在 cdp/Browser.ts

#setIsPageTargetCallback(isPageTargetCallback?: IsPageTargetCallback): void {
  this.#isPageTargetCallback =
    isPageTargetCallback ||
    ((target: Target): boolean => {
      return (
        target.type() === 'page' ||
        target.type() === 'background_page' ||
        target.type() === 'webview' ||
        (this.#handleDevToolsAsPage &&
          target.type() === 'other' &&
          isDevToolsPageTarget(target.url()))
      );
    });
}

默认情况下,该回调认为以下目标"算页面":'page''background_page''webview',以及在开启 handleDevToolsAsPage 时对 'other' 类型的 DevTools 页面。

把两段代码合起来看,就能得到文档 Remarks 的完整解释:

  • 默认(includeAll = false:只有 target.type() === 'page' 的目标直接通过过滤;'background_page' 等目标不满足该条件,因此不会出现在 Browser.pages() 的结果中;
  • includeAll = true:类型门槛放宽,'background_page''webview' 等目标会经过 isPageTarget 回调判断并可能被包含进来(这也是该参数被标注为"experimental"的原因——它会改变返回集合的组成,且不同浏览器版本的目标类型集合可能存在差异)。

如果需要拿到 background_page 这类非可见页面,更稳妥的方式是直接遍历 browser.targets(),对每个 Target 调用 Target.page() 尝试获取 Page 实例,而不是依赖 includeAll

BiDi 实现:includeAll 当前被忽略

除了 CDP,Puppeteer 还支持 WebDriver BiDi 协议。其上下文实现位于 bidi/BrowserContext.ts

override async pages(_includeAll = false): Promise<BidiPage[]> {
  return [...this.userContext.browsingContexts].map(context => {
    return this.#pages.get(context)!;
  });
}

与 CDP 实现相比有两个差异值得注意:

  1. 它直接遍历 BiDi 的 userContext.browsingContexts(用户上下文中的浏览上下文列表),再从内部映射表 this.#pages 取出对应的 BidiPage 对象;
  2. 参数名以下划线开头(_includeAll),意味着 BiDi 路径当前不使用 includeAll 参数。因此,如果你的脚本依赖 includeAll = true 的语义,请确认自己运行在 CDP 协议(protocol: 'cdp')下,这是该参数的适用前提。

实战用法

1. 枚举并遍历所有页面

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();

// 打开几个页面(含隔离上下文)
const context = await browser.createBrowserContext();
const pageA = await browser.newPage();           // 默认上下文
const pageB = await context.newPage();           // 隔离上下文
await pageA.goto('https://example.com/');
await pageB.setContent('<h1>isolated</h1>');

// 一次性拿到所有上下文中的全部页面
const allPages = await browser.pages();
for (const page of allPages) {
  console.log(page.url(), await page.title());
}

2. 关闭全部页面而保持浏览器连接

这是 Browser.pages() 最典型的清理场景。Puppeteer 自身的测试 test/src/browser.test.ts 验证了"关闭所有页面后浏览器仍保持连接、且可继续 newPage()"的行为:

it('should keep connected after the last page is closed', async () => {
  const {browser, close} = await launch({});
  try {
    const pages = await browser.pages();
    await Promise.all(
      pages.map(page => {
        return page.close();
      }),
    );
    // Verify the browser is still connected.
    expect(browser.connected).toBe(true);
    // Verify the browser can open a new page.
    await browser.newPage();
  } finally {
    await close();
  }
});

对应到日常脚本中,即可实现"会话级清理":

const pages = await browser.pages();
await Promise.all(pages.map(page => page.close()));
// 此时 browser 仍处于 connected 状态,可继续 newPage(),最后再 browser.close()

3. 只清理某个上下文内的页面

由于 Browser.pages() 返回的是跨上下文聚合的结果,如果你只想清理某个隔离上下文,应使用粒度更细的 BrowserContext.pages()

const context = await browser.createBrowserContext();
// ... 使用 context ...
const contextPages = await context.pages();
await Promise.all(contextPages.map(p => p.close()));
await context.close(); // 默认上下文不能 close,隔离上下文可以

这一模式在测试 test/src/browsercontext.test.ts 中也有广泛使用,可参考其上下文隔离的断言方式。

小结与参考

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

项目优选

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