Puppeteer Browser.pages() 深度解析:枚举浏览器中所有打开的页面
2026/9/8 20:00:38 网站建设 项目流程

Puppeteer Browser.pages() 深度解析:枚举浏览器中所有打开的页面

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

本篇围绕 Puppeteer 官方 API 文档中Browser.pages()方法展开,覆盖其方法签名、可选参数includeAll的语义、返回值以及"后台页面不可见"这一关键行为限制。结合packages/puppeteer-core中的源码实现,你将理解该方法如何聚合所有 BrowserContext 的页面、CDP 协议下 target 类型的过滤规则,以及如何在 WebDriver BiDi 模式下呈现差异,从而在自动化脚本中可靠地枚举与管理页面对象。

方法签名与基本行为

Browser.pages()用于获取当前Browser实例中所有已打开的页面(Page)。当浏览器中存在多个 BrowserContext(浏览器上下文)时,该方法会返回所有BrowserContext 中页面的合并结果。

方法签名(TypeScript):

class Browser { pages(includeAll?: boolean): Promise<Page[]>; }
参数类型说明
includeAllboolean(可选)实验性参数。设置为true时,包含所有类型的页面(all kinds of pages)。

返回值:Promise<Page[]>—— 解析为Page对象数组的 Promise。

一个最典型的使用场景是启动浏览器后检查初始页面、或在打开多个标签页后统一处理:

const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const pages = await browser.pages(); for (const page of pages) { console.log(page.url()); } await browser.close(); })();

从 API 文档侧看,puppeteer.browser.pages.md 对行为的一句话概括是"获取此浏览器内所有打开的页面列表",而页面与上下文的概念分别由 Page 类文档、Browser 类文档 和 BrowserContext 文档 定义。

跨 BrowserContext 聚合的实现

pages()的文档声明了"跨所有 BrowserContext 返回页面"这一行为,其具体实现位于puppeteer-core的抽象基类中。在 Browser.ts 中,pages方法对每个 BrowserContext 分别调用context.pages(includeAll),并用Promise.all并发等待,最后将各上下文的数组扁平化(flatten)合并:

// packages/puppeteer-core/src/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. includeAll参数会被透传给每一个 BrowserContext 的pages()实现,因此"包含所有类型的页面"这一语义在所有上下文上是一致的;
  2. 该聚合逻辑定义在抽象层 BrowserContext.ts 之上——abstract pages(includeAll?: boolean): Promise<Page[]>是抽象方法,具体过滤行为由 CDP 与 BiDi 两套实现分别给出。

CDP 模式下的 target 过滤规则

在 CDP(Chrome DevTools Protocol)实现中,页面列表并不是直接枚举Page对象,而是从target(目标)集合中筛选出来的。核心实现位于 cdp/BrowserContext.ts:

// packages/puppeteer-core/src/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; }); }

这段源码把includeAll参数落地成了清晰的过滤表达式:

  • target.type() === 'page':标准网页标签页无条件纳入,与includeAll无关;
  • target.type() === 'other':类型被 CDP 归为other的 target(例如扩展后台页、Service Worker 宿主、DevTools 页面等),只有当浏览器实例判定其为"页面目标"时才会被纳入;
  • includeAlltrue时,放宽了类型限制——原本仅限other类型参与的判定路径扩展为所有类型的 target 都可以进入isPageTarget回调的判定,因此能够捕获background_pagewebview等常规调用中被排除的类型。

这里的"是否算页面"由Browser实例上的_getIsPageTargetCallback()提供,定义于 cdp/Browser.ts。默认的判定回调覆盖如下 target 类型:

// packages/puppeteer-core/src/cdp/Browser.ts(默认 isPageTargetCallback) return ( target.type() === 'page' || target.type() === 'background_page' || target.type() === 'webview' || (this.#handleDevToolsAsPage && target.type() === 'other' && isDevToolsPageTarget(target.url())) );

也就是说,默认情况下后台页(background_page)、WebView(webview)以及(在handleDevToolsAsPage开启时)DevTools 页面都会被识别为页面目标——这也解释了为何文档 Remarks 中提到:

Non-visible pages, such as"background_page", will not be listed here. You can find them using Target.page().

browser.pages()的常规调用(includeAll = false)不会列出不可见的后台页面,这类 target 需要借助 Target.page() 单独解析。includeAll: true作为实验性开关,允许调用方把更多"类页面"target 也纳入返回列表。

测试用例印证了上述机制:test/src/cdp/devtools.test.ts 验证了browser.pages()handleDevToolsAsPage配置下能否返回 DevTools 页面,并明确覆盖"未提供自定义isPageTarget时,页面不会出现在browser.pages()结果中"的反向场景。

BiDi 模式下的差异

除 CDP 外,Puppeteer 还提供 WebDriver BiDi 协议实现。在 bidi/BrowserContext.ts 中:

// packages/puppeteer-core/src/bidi/BrowserContext.ts override async pages(_includeAll = false): Promise<BidiPage[]> { return [...this.userContext.browsingContexts].map(context => { return this.#pages.get(context)!; }); }

可以观察到:BiDi 实现直接映射用户上下文(user context)下所有browsingContexts,且方法签名中includeAll参数被下划线前缀忽略(_includeAll)。从源码结构看,BiDi 侧的 browsing context 本身即对应"页面",因此不存在 CDP 中"按 target 类型过滤"的问题,includeAll在 BiDi 模式下实际上不产生额外过滤差异。这一差异在编写跨浏览器(Chrome CDP / Firefox BiDi)脚本时需要留意:includeAll主要影响 CDP 模式下的 target 覆盖面。

测试中的验证方式

仓库测试对pages()的行为有多处直接断言,可作为行为基线参考:

  • test/src/browser.test.ts:const pages = await browser.pages();验证浏览器打开后的页面集合;
  • test/src/browsercontext.test.ts:通过browserContext.newPage()动态创建页面后,断言browser.pages()的长度从 1 变为 2、再在关闭页面后回到 1,验证了跨上下文聚合的实时性。

这些测试表明browser.pages()返回的是当前时刻的页面快照,每次调用都会重新枚举 target,而非缓存列表。

注意事项与最佳实践

  1. 结果是一次性快照pages()返回当前打开的页面数组;若脚本后续会动态打开/关闭标签页,需要在操作前后重新调用,或改用browser.waitForTarget()/BrowserContext.waitForTarget()基于事件的方式等待特定 target 出现。
  2. includeAll是实验性参数:文档明确标注 experimental,其覆盖面(background_pagewebview等)依赖底层isPageTarget回调的判定,且在不同协议实现(CDP 与 BiDi)中行为不完全一致,生产脚本中谨慎依赖。
  3. 后台页面走 Target 通道:需要处理background_page等不可见页面时,正确路径是遍历browser.targets(),对满足条件的 target 调用 Target.page() 获取页面对象,而不是依赖pages()的默认列表。
  4. 区分上下文粒度:如果只需管理某一个隔离环境(例如多用户登录态测试),应调用browserContext.pages()而非browser.pages(),避免把其他上下文中的页面混入操作范围。

小结

Browser.pages()是 Puppeteer 中枚举页面集合的入口方法:文档层面它承诺跨所有 BrowserContext 返回Page[]includeAll提供实验性的全类型覆盖;源码层面则由 api/Browser.ts 的上下文聚合、cdp/BrowserContext.ts 的 target 过滤以及 cdp/Browser.ts 的isPageTarget判定共同实现,而 bidi/BrowserContext.ts 给出了 BiDi 协议下的简化映射。理解这条从"target 枚举 → 类型过滤 → Page 对象"的调用链,是正确处理后台页面、DevTools 页面和多上下文场景的基础。

【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询