首页
/ Puppeteer BrowserContext.newPage() 详解:在隔离浏览器上下文中创建新页面

Puppeteer BrowserContext.newPage() 详解:在隔离浏览器上下文中创建新页面

2026-09-06 11:34:19作者:何举烈Damon

BrowserContext.newPage() 是 Puppeteer 页面管理链路的起点:它在指定的浏览器上下文中创建一个全新的 Page 并返回其 Promise,是执行导航、交互、截图等一切自动化操作前的第一步。本篇围绕该 API 的签名、CreatePageOptions 参数组合(标签页/独立窗口/后台创建)展开,并结合当前仓库中 CDP 与 BiDi 两套协议实现,说明一次 newPage 调用在底层实际发生了什么,帮助你在多上下文隔离场景(如多账号、无痕环境)下正确创建和管理页面。

BrowserContext.newPage() 的签名与参数

官方文档(docs/api/puppeteer.browsercontext.newpage.md)给出的方法定义为:

class BrowserContext {
  abstract newPage(options?: CreatePageOptions): Promise<Page>;
}

参数说明:

参数 类型 说明
options CreatePageOptions (可选)控制新页面的创建方式(标签页或窗口、是否后台创建)

返回值: Promise<Page>,解析为该上下文内新创建的 Page 实例。

在抽象基类 BrowserContext 中,newPage 被声明为 abstract 方法——具体行为由各协议实现(CDP 或 WebDriver BiDi)分别填充,而上下文管理、事件发射、Cookie 操作等公共逻辑则统一在基类中实现。

理解 newPage 的前提是理解 BrowserContext 本身:浏览器启动时至少带有一个默认上下文,更多上下文可通过 Browser.createBrowserContext() 创建(参见 Browser.createBrowserContext()),每个上下文拥有相互隔离的存储(cookies、localStorage 等)。在 Chrome 中,所有非默认上下文都是无痕(incognito)模式;若启动时传入 --incognito 参数,默认上下文也会成为无痕上下文。

CreatePageOptions:控制“以什么形态”创建页面

CreatePageOptions 定义在 api/Browser.ts,是一个联合类型与公共字段的交叉类型:

export type CreatePageOptions = (
  | {
      type?: 'tab';
    }
  | {
      type: 'window';
      windowBounds?: WindowBounds;
    }
) & {
  /**
   * Whether to create the page in the background.
   *
   * @defaultValue `false`
   */
  background?: boolean;
};

三个可配置维度如下:

  1. type?: 'tab'(默认):创建一个常规标签页。这是最常用的形态,不传 options 时即为此行为。
  2. type: 'window':创建一个拥有独立浏览器窗口的页面。此时可搭配:
    • windowBounds?: WindowBounds:指定新窗口的初始位置、尺寸与状态,结构为(见 WindowBounds 定义):

      export interface WindowBounds {
        left?: number;
        top?: number;
        width?: number;
        height?: number;
        windowState?: 'normal' | 'minimized' | 'maximized' | 'fullscreen';
      }
      

      例如 context.newPage({type: 'window', windowBounds: {left: 0, top: 0, width: 1024, height: 768, windowState: 'maximized'}}) 会以最大化窗口形式打开新页面。窗口边界的后续调整可参照 Browser.setWindowBounds()WindowBounds 文档。

  3. background?: boolean(默认 false:是否把新页面创建在后台。设为 true 时页面不立即抢占前台焦点,适合批量开页或后台采集场景。

典型用法:从创建上下文到创建页面

官方文档给出的标准流程如下(来自 BrowserContext 类文档):

// 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();

几个关键点值得注意:

  • context.newPage()browser.newPage() 的区别Browser.newPage() 实际上是在默认上下文中创建页面。从 CDP 实现源码可以确认这一点——cdp/Browser.tsBrowser.newPage() 直接转发为 this.#defaultContext.newPage(options);BiDi 侧 bidi/Browser.ts 同样是委托给 defaultBrowserContext().newPage(options)。也就是说,只有 context.newPage() 才能让页面落入你指定上下文(含其隔离的 Cookie/存储)
  • 页面生命周期随上下文收敛context.close() 会关闭该上下文及其关联的所有页面(参见 BrowserContext.close()),因此上下文级清理比逐个 page.close() 更彻底。注意默认上下文不能被关闭——CDP 实现的 close() 中带有 assert(this.#id, 'Default BrowserContext cannot be closed!')(见 cdp/BrowserContext.ts)。
  • 窗口打开的弹窗归属:如果某个页面通过 window.open 打开了另一个页面,新页面属于父页面所在的浏览器上下文,这保证了弹窗与发起方的存储隔离策略一致。

源码视角:一次 newPage 调用在 CDP 实现中的执行路径

CDP 实现位于 cdp/BrowserContext.ts

override async newPage(options?: CreatePageOptions): Promise<Page> {
  using _guard = await this.waitForScreenshotOperations();
  return await this.#browser._createPageInContext(this.#id, options);
}

从源码结构看,这里有两层值得注意的机制:

  1. 截图互斥锁newPage 会先通过基类的 waitForScreenshotOperations()api/BrowserContext.ts)尝试获取 #pageScreenshotMutex 的守卫。基类用 #screenshotOperationsCount 跟踪上下文内是否有进行中的页面截图操作;若正在截图,创建新页面会先等待截图完成。这是为了避免创建页面(可能触发目标枚举、页面列表变化)与截图过程相互干扰,属于上下文级的一致性保护。
  2. 协议调用下沉到 Browser 层:真正的“建页”工作交由 CdpBrowser._createPageInContext(this.#id, options)(定义于 cdp/Browser.ts)完成,其中 this.#id 是该上下文对应的 CDP browserContextIdtype: 'window'windowBounds 的组合决定了底层是用 Target.createTarget(标签页)还是 Browser.createBrowserWindow 之类的窗口 API 建页,background 参数则映射到 CDP 的 newWindow 参数语义。

BiDi 实现:通过 WebDriver BiDi 创建 Browsing Context

在 WebDriver BiDi 协议下,bidi/BrowserContext.ts 的实现展示了同一抽象在另一协议栈上的落地方式:

override async newPage(options?: CreatePageOptions): Promise<Page> {
  using _guard = await this.waitForScreenshotOperations();

  const type =
    options?.type === 'window'
      ? Bidi.BrowsingContext.CreateType.Window
      : Bidi.BrowsingContext.CreateType.Tab;

  const context = await this.userContext.createBrowsingContext(type, {
    background: options?.background,
  });
  const page = this.#pages.get(context)!;
  if (!page) {
    throw new Error('Page is not found');
  }
  if (this.#defaultViewport) {
    try {
      await page.setViewport(this.#defaultViewport);
    } catch (error) {
      // Tolerate not supporting `browsingContext.setViewport`. Only log it.
      this.#logger?.(DEBUG_PREFIXES.error)?.(error);
    }
  }
  if (options?.type === 'window' && options?.windowBounds !== undefined) {
    try {
      await this.browser().setWindowBounds(
        context.windowId,
        options.windowBounds,
      );
    } catch (error) {
      // Tolerate not supporting `browser.setClientWindowState`. Only log it.
      this.#logger?.(DEBUG_PREFIXES.error)?.(error);
    }
  }

  return page;
}

可以归纳出 BiDi 路径的三个步骤:

  1. CreatePageOptions.type 映射为 browsingContext.create 的创建类型'window' 对应 CreateType.Window,否则为 CreateType.Tabbackground 原样透传给 BiDi 命令。
  2. 默认视口继承:如果上下文设置了 #defaultViewport,会尝试对新页面调用 setViewport,且对不支持该能力的浏览器仅记录日志而不会抛出异常——这是 BiDi 协议兼容性的容错设计。
  3. 窗口边界后置应用windowBounds 在新窗口创建之后通过 browser().setWindowBounds(context.windowId, ...) 应用,不支持时同样只记日志。

两套实现共同印证了 CreatePageOptions 各字段的语义:type 决定创建形态、windowBounds 只与 window 形态搭配、background 控制前台可见性,并且不同协议实现都对“能力缺失”采取了容忍策略,而不是硬失败。

页面出现后的可观测性:事件与查询 API

newPage 返回后,新页面随即成为上下文内的一个 Target。基类 api/BrowserContext.ts 定义了上下文级的三个事件(BrowserContextEvent):

  • TargetCreated:上下文内创建了 Target 时发射,既包括 window.open 打开的新页面,也包括 browserContext.newPage() 创建的页面,事件携带 Target 实例;
  • TargetChanged:上下文内 Target 的 URL 等状态变化时发射;
  • TargetDestroyed:页面关闭时发射。

配合这些事件,你可以区分“自己主动 newPage()”与“页面内代码自行弹窗”两种来源。常用查询/等待手段包括:

  • BrowserContext.pages(includeAll?):列出上下文内所有已打开的可见页面;注意 background_page 等非可见页面不会出现在这里,需通过 Target.page() 查找;
  • BrowserContext.waitForTarget(predicate, options):等待匹配谓词的 Target 出现(默认超时 30000ms,实现基于 RxJS 的 merge/raceWith,见 api/BrowserContext.ts),适合捕获 window.open 产生的弹窗;
  • closed(只读):该上下文是否已关闭,基类实现为检查 browser.browserContexts() 是否仍包含自己(api/BrowserContext.ts);
  • id(只读):上下文的协议层标识符,默认上下文通常没有 id(基类直接返回 undefined),这与“默认上下文不可关闭”的约束相呼应。

此外,BrowserContext 实现了 Symbol.asyncDispose,支持在 await using 语义下自动调用 close() 释放上下文(见 api/BrowserContext.ts),适合在长生命周期脚本中管理多个隔离上下文。

实践建议与适用边界

  • 隔离需求优先用上下文:需要多账号、A/B 环境或无痕测试时,为每个环境 createBrowserContext()context.newPage(),避免默认上下文的 Cookie/存储污染;用完 context.close() 一次性回收。
  • 批量开页用 background:需要并行打开多个标签页且不希望焦点频繁切换时,传 {background: true} 可减少前台抖动。
  • 独立窗口用于可视化调试/多屏布局{type: 'window', windowBounds} 组合可精确控制窗口位置与尺寸,配合 windowState: 'maximized' | 'fullscreen' 满足截图比对类任务的固定布局需求。
  • 能力差异要心里有数:从 BiDi 实现可见,setViewport 与窗口状态设置对不支持的浏览器会被静默容忍;跨浏览器(Chrome/Firefox)运行时,window 形态与 windowBounds 的实际效果可能因浏览器实现而异,建议以当前仓库 test/ 目录下的上下文相关测试用例作为行为基准。
  • 适用前提CreatePageOptions 的窗口/后台语义依赖底层浏览器支持窗口级 API(主要为 Chromium CDP 与 BiDi 的窗口能力),在纯 headless 模式下窗口形态的视觉效果可能受限;参数以当前仓库源码中的类型定义为准确认。

相关文档

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

项目优选

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