puppeteer Browser 类详解:从启动、窗口与屏幕管理到断开重连的浏览器实例控制
本篇技术文章基于 puppeteer 官方 API 文档 Browser 类 展开,系统讲解 Browser 抽象类的完整 API 面:页面与浏览器上下文的创建、Cookie 与权限的快捷方法、窗口/屏幕管理、扩展与 PWA 管理,以及 close / disconnect / wsEndpoint 构成的连接生命周期体系。读完后你将能够基于 Browser 实例完成多上下文隔离、远程重连、多屏仿真等典型场景,并理解每个方法在 packages/puppeteer-core/src/api/Browser.ts 与 CDP 实现层 packages/puppeteer-core/src/cdp/Browser.ts 中的真实调用链。
类的定位:抽象基类与两种获取途径
Browser 表示一个浏览器实例,该实例要么通过 Puppeteer.connect() 连接而来,要么由 PuppeteerNode.launch() 启动。官方类签名如下:
export declare abstract class Browser extends EventEmitter<BrowserEvents>
它继承自 EventEmitter<BrowserEvents>,是一个抽象类。文档中明确要求:该类的构造函数被标记为内部(internal),第三方代码不应直接调用其构造函数,也不应创建继承自 Browser 的子类。
在源码中可以印证这一约束。packages/puppeteer-core/src/api/Browser.ts#L478 中声明了抽象类,构造函数同样标注为 @internal:
// packages/puppeteer-core/src/api/Browser.ts
export abstract class Browser extends EventEmitter<BrowserEvents> {
#logger: Logger;
/** @internal */
constructor(logger: Logger) {
super(undefined, logger);
this.#logger = logger;
}
...
}
从源码结构看,仓库中存在多个具体实现:CDP 协议的 CdpBrowser(packages/puppeteer-core/src/cdp/Browser.ts)、WebDriver BiDi 协议的 packages/puppeteer-core/src/bidi/Browser.ts 与 packages/puppeteer-core/src/bidi/core/Browser.ts。用户通过 launch() / connect() 得到的 Browser 实例由对应协议的实现类填充。
官方示例一:用 Browser 创建 Page
文档给出的第一个核心用法,是启动浏览器后创建页面并关闭:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await browser.close();
在 CDP 实现中,newPage 本身只是一层转发——它把调用委托给默认浏览器上下文:
// packages/puppeteer-core/src/cdp/Browser.ts
override async newPage(options?: CreatePageOptions): Promise<Page> {
return await this.#defaultContext.newPage(options);
}
真正的建页逻辑在 _createPageInContext(L414-L455):先发送 CDP 命令 Target.createTarget(url: 'about:blank'),再调用 waitForTarget 等待目标完成初始化,最后经 target.page() 得到 Page 实例。注意 CreatePageOptions 支持三种形态(见 api/Browser.ts#L255-L270):
- 省略
type或type: 'tab':在当前窗口新建标签页; type: 'window'并可附带windowBounds(left/top/width/height/windowState):新建独立窗口;background?: boolean:在后台创建页面,默认false。
测试文件 test/src/browser.test.ts#L168-L196 演示了窗口形态:通过 context.newPage({type: 'window', windowBounds}) 建窗后,用 page.windowId() 拿到窗口 id,再配合 browser.getWindowBounds 回读校验。
官方示例二:断开连接与重连
第二个官方示例展示 wsEndpoint()、disconnect() 与 Puppeteer.connect() 的协作——先保存 WebSocket 端点,断开后再凭端点重建连接:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
// Store the endpoint to be able to reconnect to the browser.
const browserWSEndpoint = browser.wsEndpoint();
// Disconnect puppeteer from the browser.
await browser.disconnect();
// Use the endpoint to reestablish a connection
const browser2 = await puppeteer.connect({browserWSEndpoint});
// Close the browser.
await browser2.close();
文档同时说明:wsEndpoint() 返回用于连接本浏览器的 WebSocket URL,通常配合 Puppeteer.connect() 使用;你也可以从 http://HOST:PORT/json/version 中的 webSocketDebuggerUrl 字段找到调试器地址,且该地址格式固定为 ws://HOST:PORT/devtools/browser/<id>。在 CDP 实现里它直接返回连接 URL(cdp/Browser.ts#L406-L408):
override wsEndpoint(): string {
return this.#connection.url();
}
有一个重要的适用前提:pipe 连接下没有 WebSocket 端点。启动时设置 pipe: true 后,wsEndpoint() 返回空字符串,这一点在测试 test/src/cdp/pipe.test.ts#L17 中被断言:expect(browser.wsEndpoint()).toBe('')。
重连场景在 test/src/launcher.test.ts#L774-L809 中有完整验证:先 browser.disconnect(),再用 puppeteer.connect({browserWSEndpoint, protocol}) 重连,随后通过 remoteBrowser.pages() 找回断开前已导航到 nested-frames.html 的页面,并确认其 frame 树与可执行 evaluate(返回 7 * 8 === 56)——证明浏览器进程与页面状态在断开期间被完整保留。
实例属性:connected 与 debugInfo
文档中 Browser 暴露两个只读属性:
| 属性 | 修饰符 | 类型 | 说明 |
|---|---|---|---|
connected |
readonly |
boolean |
Puppeteer 是否已连接到该浏览器 |
debugInfo |
readonly |
DebugInfo |
(实验性) 获取 Puppeteer 的调试信息,目前包含未完成的协议调用(pending protocol calls) |
connected 在 CDP 实现中就是对底层连接关闭状态的取反(cdp/Browser.ts#L709-L711):
override get connected(): boolean {
return !this.#connection._closed;
}
debugInfo 的返回结构由接口 DebugInfo 定义,仅含 pendingProtocolErrors: Error[];CDP 实现直接取自连接层:{pendingProtocolErrors: this.#connection.getPendingProtocolErrors()}(cdp/Browser.ts#L727-L731)。
测试 test/src/browser.test.ts#L77-L93 验证了 connected 的一个关键行为:关闭所有页面后浏览器连接依然保持(expect(browser.connected).toBe(true)),且此时仍可继续 newPage()——即“页面全部关闭”不会导致浏览器断开。
页面与目标(Target)管理
Browser 上围绕页面/目标的常用方法如下(均引自文档的 Methods 表):
newPage(options?):在默认浏览器上下文中创建新页面;pages(includeAll?):获取本浏览器内所有打开的页面;存在多个浏览器上下文时,返回所有上下文中的页面。注意:不可见页面(如"background_page"类型)不会列出,可通过Target.page()找到它们;target():获取与默认浏览器上下文关联的目标;targets():获取所有活动目标,多个上下文时返回全部上下文中的目标;waitForTarget(predicate, options):等待匹配predicate的目标出现并返回它,会遍历所有打开的浏览器上下文。
从源码结构看,pages() 并非查询浏览器,而是对每个上下文逐页聚合(api/Browser.ts#L637-L647):
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) => acc.concat(x), []);
}
waitForTarget 则是基类中用 RxJS 组合的事件流实现(api/Browser.ts#L608-L623):对已存在目标做一次快照,再 merge targetcreated 与 targetchanged 事件流,经 predicate 异步过滤后,与 AbortSignal 和 timeout(ms) 竞速:
async waitForTarget(
predicate: (x: Target) => boolean | Promise<boolean>,
options: WaitForTargetOptions = {},
): Promise<Target> {
const {timeout: ms = 30000, signal} = options;
return await firstValueFrom(
merge(
fromEmitterEvent(this, BrowserEvent.TargetCreated),
fromEmitterEvent(this, BrowserEvent.TargetChanged),
from(this.targets()),
).pipe(
filterAsync(predicate),
raceWith(fromAbortSignal(signal), timeout(ms)),
),
);
}
对应的 WaitForTargetOptions(api/Browser.ts#L148-L160)中,timeout 默认 30000 毫秒、传 0 可禁用,signal 支持用 AbortSignal 取消等待。文档中给出的典型用法——捕获 window.open 打开的新窗口:
await page.evaluate(() => window.open('https://www.example.com/'));
const newWindowTarget = await browser.waitForTarget(
target => target.url() === 'https://www.example.com/',
);
targets() 在 CDP 实现中返回的是“已暴露且初始化成功”的目标(cdp/Browser.ts#L659-L668),而 target() 从中查找 type() === 'browser' 的那个;若找不到会抛出 Browser target is not found。
浏览器上下文(BrowserContext)与 Cookie 快捷方法
上下文管理:
createBrowserContext(options?):创建一个不与其他上下文共享 Cookie/缓存的新浏览器上下文;browserContexts():获取所有打开的上下文列表,新建的浏览器中只有一个(默认上下文);defaultBrowserContext():获取默认上下文,且默认上下文无法被关闭。
createBrowserContext 的选项 BrowserContextOptions 定义在 api/Browser.ts#L41-L58:proxyServer(可选端口的代理服务器,账密通过 Page.authenticate 设置)、proxyBypassList(绕过代理的主机列表)、downloadBehavior(下载行为定义,未设置则用默认)。CDP 实现将前两者直接传给 Target.createBrowserContext(cdp/Browser.ts#L267-L290),downloadBehavior 则在上下文创建后单独调用 setDownloadBehavior。文档中的示例:
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
// Create a new browser context.
const context = await browser.createBrowserContext();
// Create a new page in a pristine context.
const page = await context.newPage();
// Do stuff
await page.goto('https://example.com');
Cookie 与权限快捷方法。文档中明确这四个方法都是对默认上下文的快捷方式:
cookies():返回默认BrowserContext中的所有 Cookie(等价browser.defaultBrowserContext().cookies());setCookie(cookies):在默认上下文设置 Cookie(等价browser.defaultBrowserContext().setCookie());deleteCookie(cookies):从默认上下文删除 Cookie(等价browser.defaultBrowserContext().deleteCookie());deleteMatchingCookies(filters):按过滤条件从默认上下文删除 Cookie(等价browser.defaultBrowserContext().deleteMatchingCookies());setPermission(origin, permissions):为指定源设置默认上下文中的权限(等价browser.defaultBrowserContext().setPermission())。
源码印证了这种“转发”关系,例如 api/Browser.ts#L691-L705:
async cookies(): Promise<Cookie[]> {
return await this.defaultBrowserContext().cookies();
}
async setCookie(...cookies: CookieData[]): Promise<void> {
return await this.defaultBrowserContext().setCookie(...cookies);
}
权限值支持 PermissionDescriptor(name、userVisibleOnly、sysex、panTiltZoom、allowWithoutSanitization)与状态 'granted' | 'denied' | 'prompt'(api/Browser.ts#L132-L143);旧的字符串联合类型 Permission('camera'、'geolocation'、'notifications' 等 18 种)已被标记为 @deprecated,建议改用 PermissionDescriptor。
连接生命周期:process、close、disconnect 与资源释放
process():获取关联的 Node ChildProcess。若该实例是通过 Puppeteer.connect 连接而来,则返回 null。测试 test/src/browser.test.ts#L58-L76 双向验证了这一点:本地 launch 的浏览器 process().pid > 0;而通过 wsEndpoint 远程重连得到的 remoteBrowser.process() 为 null。
close():关闭该浏览器及所有关联页面。CDP 实现中它是“关闭回调 + 断开连接”的组合(cdp/Browser.ts#L690-L700):
override async close(): Promise<void> {
await this.#closeCallback.call(null);
await this.disconnect();
}
override disconnect(): Promise<void> {
this.#targetManager.dispose();
this.#connection.dispose();
this._detach();
return Promise.resolve();
}
disconnect():把 Puppeteer 从浏览器上断开,但进程继续运行——这正是前面重连示例的基础。
此外,Browser 实现了显式资源管理(Symbol.dispose / Symbol.asyncDispose),这解释了为什么文档方法表中列出这两个方法,也是测试代码中 using remoteBrowser = await puppeteer.connect(...) 语法的底层支撑。基类实现(api/Browser.ts#L858-L871)的决策逻辑非常简洁:
override [disposeSymbol](): void {
return void this[asyncDisposeSymbol]().catch(error => {
this.#logger?.(DEBUG_PREFIXES.error)?.(error);
});
}
override async [asyncDisposeSymbol](): Promise<void> {
if (this.process()) {
await this.close();
} else {
await this.disconnect();
}
await super[asyncDisposeSymbol]();
}
即:自己启动的浏览器(有进程)执行 close(),仅连接的浏览器执行 disconnect()——using 声明可以按获取方式自动选择正确的清理策略。
浏览器窗口与屏幕管理
这一组方法主要用于窗口级控制与 headless 多屏仿真:
getWindowBounds(windowId)/setWindowBounds(windowId, windowBounds):获取/设置指定窗口的 bounds;screens():获取屏幕信息对象列表;addScreen(params):新增一块屏幕并返回其ScreenInfo,仅 headless 模式支持;removeScreen(screenId):移除一块屏幕,仅 headless 模式支持,且不能移除主屏幕(指定主屏幕 id 会失败)。
类型定义方面,WindowBounds 含 left/top/width/height/windowState,WindowState 为 'normal' | 'minimized' | 'maximized' | 'fullscreen'(api/Browser.ts#L234-L250);ScreenInfo 包含 id、label、isPrimary、isExtended、devicePixelRatio、orientation、workArea 相关尺寸等完整字段(api/Browser.ts#L283-L326),AddScreenParams 支持 left/top/width/height 必填项及 workAreaInsets、devicePixelRatio、rotation、colorDepth、label、isInternal 可选项。
CDP 层对应关系为:Browser.getWindowBounds / Browser.setWindowBounds(注意 windowId 会被转成 Number 发送,cdp/Browser.ts#L642-L657),以及 Emulation.getScreenInfos / Emulation.addScreen / Emulation.removeScreen(cdp/Browser.ts#L623-L640)。
测试给出了可直接参考的断言样例(test/src/browser.test.ts#L96-L166):headless 下默认单块 800×600 主屏;addScreen({left: 800, top: 0, width: 1600, height: 1200, colorDepth: 32, workAreaInsets: {bottom: 80}, label: 'secondary'}) 后 screens() 返回 2 块,且新屏 availHeight 因底部 80 像素内缩变为 1120;removeScreen(screenInfo.id) 后回到 1 块。窗口最大化场景(L198-L231)则展示了先 addScreen 建副屏、在该屏上开窗、再 setWindowBounds(windowId, {windowState: 'maximized'}) 的完整流程。
扩展与 PWA 管理
扩展(Extension):
installExtension(path, options?):安装扩展并返回扩展 ID;uninstallExtension(id):卸载指定扩展;extensions():获取当前已安装扩展的 Map,键为扩展 ID,值为Extension实例。
CDP 实现中,安装走 Extensions.loadUnpacked(enableInIncognito 默认 false),返回 id(cdp/Browser.ts#L500-L510);卸载走 Extensions.uninstall,并有一段针对 service worker 目标销毁事件的补偿逻辑(L512-L540)——当前 CDP 的 Extensions.uninstall 不会触发对应 service worker 的 Target.targetDestroyed 事件,实现中通过手动补发事件避免测试抖动,注释中标记为待上游修复后移除。extensions() 则通过 Extensions.getExtensions 拉取清单并与本地缓存的 CdpExtension 实例合并。
PWA(渐进式 Web 应用),共 4 个方法,且有统一的前置限制:
installPWA(options):安装 PWA,返回其 manifest id。参数InstallPWAOptions含必填的manifestId(Web App 清单中的 id,通常为站点 URL)、必填的installUrlOrBundleUrl(因为浏览器级 CDP 会话没有可推导安装 URL 的页面)、可选displayMode: 'standalone' | 'browser';launchPWA(options):启动已安装的 PWA,解析为承载该应用窗口的Page。参数含manifestId、可选url(应用作用域内要打开的 URL)、可选timeout(默认 30 秒,0禁用);getPWAState(options):返回已安装 PWA 的操作系统集成状态(如角标计数与已注册的文件处理器);uninstallPWA(options):卸载之前安装的 PWA。
文档对该组方法的限定必须牢记:仅通过 pipe 连接可用——需在 puppeteer.launch 中设置 pipe: true(该启动选项默认 false),底层的 PWA CDP 域不会通过 WebSocket 连接暴露。此外:installPWA 返回的 manifest id 就是传入的 InstallPWAOptions.manifestId 的回显,可直接传给 launchPWA / getPWAState / uninstallPWA;launchPWA 在 Chromium 聚焦已有应用窗口时返回该窗口的既有 page;查询未安装应用的 getPWAState 会 reject。
从源码结构看,四个实现都先做网络限制检查,配置了 blocklist/allowlist 时直接抛错(PWA APIs are not supported when network restrictions are configured.,cdp/Browser.ts#L542-L621);launchPWA 的实现值得注意:PWA.launch 解析出的是 tab 目标的 id,而 tab 目标位于 page 目标之上的层级、不会暴露在 browser.targets() 中,因此实现会 waitForTarget 等待该 tab 目标的子 page 目标出现,再取 target.page() 返回(L572-L608)。getPWAState 则调用 PWA.getOsAppState 返回 {badgeCount, fileHandlers}。
版本、User-Agent 与事件
version():返回表示浏览器名称与版本的字符串。headless 浏览器形如"HeadlessChrome/61.0.3153.0",非 headless / new-headless 形如"Chrome/61.0.3153.0",Firefox 形如"Firefox/116.0a1";文档提醒该格式可能随浏览器版本变化。CDP 实现通过一次Browser.getVersion拿到并缓存(Deferred保证只发一次协议请求,cdp/Browser.ts#L680-L725);userAgent():返回该浏览器的原始 User-Agent;各Page可用Page.setUserAgent()覆盖。
测试对两者均有覆盖(test/src/browser.test.ts#L14-L46):version() 非空且包含 chrome 或 firefox;userAgent() 在 Chrome 下包含 WebKit、Firefox 下包含 Gecko。
事件。Browser 会发出文档 BrowserEvent 枚举所列的事件,源码定义在 api/Browser.ts#L167-L207:
| 事件 | 值 | 触发时机 |
|---|---|---|
Disconnected |
'disconnected' |
Puppeteer 与浏览器断开,可能是浏览器关闭/崩溃,或调用了 Browser.disconnect |
TargetChanged |
'targetchanged' |
目标 URL 变化,负载为 Target 实例(含所有上下文) |
TargetCreated |
'targetcreated' |
目标被创建,如 window.open 或 browser.newPage 打开新页面 |
TargetDestroyed |
'targetdestroyed' |
目标被销毁,如页面关闭 |
TargetDiscovered |
'targetdiscovered'(internal) |
内部事件 |
CDP 实现中,这些事件由 TargetManager 驱动:TargetAvailable → emit(BrowserEvent.TargetCreated, target),TargetGone → TargetDestroyed,TargetChanged → TargetChanged,且会同时向所属 BrowserContext 再发一次同名字事件(cdp/Browser.ts#L373-L404);连接层的 CDPSessionEvent.Disconnected 触发 Disconnected。这也解释了 waitForTarget 为何要监听 TargetCreated 与 TargetChanged 两个事件——新建目标与 URL 变化都可能让 predicate 首次成立。
小结与延伸阅读
Browser是 puppeteer 的浏览器级入口抽象:页面/上下文/窗口/屏幕/扩展/PWA 的资源管理都汇聚于此,具体行为由 CDP 或 BiDi 实现类填充;- 连接模型是理解它的钥匙:
process()区分“自启动”与“纯连接”,close与disconnect语义不同,wsEndpoint()是跨进程重连的凭证,[Symbol.asyncDispose]则按前者自动选择清理方式; - 注意适用前提:屏幕管理仅限 headless,PWA API 仅限
pipe: true的 pipe 连接,wsEndpoint()在 pipe 连接下为空字符串。
可进一步深入的材料:api/Browser.ts 抽象基类、cdp/Browser.ts CDP 实现、test/src/browser.test.ts、test/src/launcher.test.ts、test/src/cdp/pipe.test.ts,以及 BrowserContext、Target、Page 等关联 API 文档。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00