Puppeteer Browser.addScreen():Headless 模式下虚拟多屏仿真的接口契约与 CDP 实现解析
2026/9/8 22:00:24 网站建设 项目流程

Puppeteer Browser.addScreen():Headless 模式下虚拟多屏仿真的接口契约与 CDP 实现解析

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

Browser.addScreen()是 Puppeteer 中用于在浏览器内动态创建虚拟屏幕的 Browser 级方法:它接收一份 AddScreenParams 屏幕描述,通过 CDP 的Emulation.addScreen命令在 Chromium 内部注册一块新屏幕,并返回 ScreenInfo 屏幕信息对象。本文基于 API 文档与 puppeteer-core 源码 完整拆解该方法的签名、参数语义、返回值结构以及 CDP/WebSocket 两种连接模式下的实现差异,读完后可在 headless 测试中搭建稳定的“双屏”环境,配合窗口定位、跨屏拖拽等多显示器场景。

方法签名与适用范围

按官方 API 文档 puppeteer.browser.addscreen.md,该方法用于“添加一块新屏幕,并返回所添加的 screen information object”。其 TypeScript 签名在抽象基类 Browser 中声明:

class Browser { abstract addScreen(params: AddScreenParams): Promise<ScreenInfo>; }
项目说明
入参paramsAddScreenParams,描述新屏幕的几何位置、尺寸与显示属性
返回值Promise<ScreenInfo>,解析为该屏幕的完整信息对象(含由 Chromium 生成的id
运行限制文档 Remarks 明确:Only supported in headless mode(仅支持 headless 模式)

“仅 headless”这一限制在源码层面有直接印证:headful 模式下Browser.screens()会返回操作系统的真实屏幕信息,而真实屏幕不可被注入,因此addScreen这类“伪造屏幕”的能力只保留给了 headless 仿真环境。仓库的集成测试 test/src/browser.test.ts 中,Browser.screens用例在 headful 环境直接抛出Not testable in headful,也从侧面验证了这一约束。

addScreen并非孤立方法,它与Browser.screens()(查询所有屏幕)、Browser.removeScreen(screenId)(按 id 移除屏幕)构成一组屏幕仿真 API。CHANGELOG 中也有对应记录:add browser.screens, .addScreen and .removeScreen methods (#14445)。从 Browser.ts 的 TSDoc 可以看到,removeScreen额外注明“Fails if the primary screen id is specified”——主屏幕不可被移除,这也约束了addScreen的返回值中isPrimary恒为false(新添加的必然是扩展屏)。

AddScreenParams 参数逐项解析

AddScreenParams 接口定义于 packages/puppeteer-core/src/api/Browser.ts#L315-L326,共有 9 个字段,其中 4 个必填:

export interface AddScreenParams { left: number; // 必填:新屏幕左上角在虚拟桌面坐标系中的 x 坐标 top: number; // 必填:新屏幕左上角的 y 坐标 width: number; // 必填:屏幕物理宽度(CSS 像素) height: number; // 必填:屏幕物理高度(CSS 像素) workAreaInsets?: WorkAreaInsets; // 可选:任务栏等系统 UI 对可用区域的裁剪 devicePixelRatio?: number; // 可选:设备像素比(DPR) rotation?: number; // 可选:旋转角度 colorDepth?: number; // 可选:色彩位深,如 24 / 32 label?: string; // 可选:屏幕标签名(用于 screen.label) isInternal?: boolean; // 可选:是否内建屏(如笔记本内屏) }

其中workAreaInsets的完整定义见 Browser.ts#L305-L310:

export interface WorkAreaInsets { top?: number; left?: number; bottom?: number; right?: number; }

各可选字段的实际效果可由仓库测试用例直接验证。test/src/browser.test.ts#L131-L166 中“should add and remove a screen”用例是最完整的实战参考:

const screenInfo = await browser.addScreen({ left: 800, top: 0, width: 1600, height: 1200, colorDepth: 32, workAreaInsets: {bottom: 80}, label: 'secondary', });

对应断言揭示了每个字段的落地语义:

  • workAreaInsets.bottom: 80→ 返回对象中availHeight: 1120(1200 − 80),availWidth保持 1600;即 insets 只收缩avail*系列字段,不改变width/height物理尺寸;
  • label: 'secondary'screenInfo.label === 'secondary',未设置label的主屏默认为空字符串;
  • colorDepth: 32→ 直接透传到返回对象的colorDepth
  • 未设置devicePixelRatio→ 默认返回 1;未设置rotationorientation{angle: 0, type: 'landscapePrimary'}
  • 自动推导字段isExtended: true(因为已存在主屏)、isPrimary: falseisInternal: falseavailLeft: 800availTop: 0,并由 Chromium 生成字符串id(测试用expect.any(String)匹配)。

left/top的坐标系语义是“整块虚拟桌面”的全局坐标:headless 默认主屏占据(0,0)-(800,600)(默认 viewport 800x600,见 browser.test.ts#L97-L128 的默认屏断言),因此示例中left: 800表示新屏紧贴主屏右侧,模拟常见的双显示器并排布局。

返回值 ScreenInfo 对象结构

addScreen返回的 ScreenInfo 在源码中的完整定义(Browser.ts#L283-L300):

属性类型含义
left/topnumber屏幕在虚拟桌面中的原点坐标
width/heightnumber物理宽高
availLeft/availTopnumber可用区域原点(扣除 insets 后的左上角)
availWidth/availHeightnumber可用区域宽高(width/height减去 insets)
devicePixelRationumber设备像素比
colorDepthnumber色彩位深
orientationScreenOrientationangle: numbertype: string(如landscapePrimary
isExtendedboolean是否为扩展屏(非主屏)
isInternalboolean是否内建屏
isPrimaryboolean是否主屏(addScreen结果恒为false
labelstring屏幕标签
idstringChromium 分配的屏幕 id,供removeScreen(id)使用

id是屏幕仿真的关键句柄:addScreen的返回值是唯一的“注册凭证”,后续browser.removeScreen(screenInfo.id)依赖它注销屏幕。测试用例 browser.test.ts#L161-L164 完整演示了增删闭环:添加后screens()长度从 1 变为 2,removeScreen(id)后再回到 1。

源码实现:CDP 直通 Emulation 域,BiDi 未实现

CDP 路径(Chrome over CDP)addScreen的实现非常薄,位于 packages/puppeteer-core/src/cdp/Browser.ts#L630-L640:

override async addScreen(params: AddScreenParams): Promise<ScreenInfo> { const {screenInfo} = await this.#connection.send( 'Emulation.addScreen', params, ); return screenInfo; }

可以确认:

  1. 参数对象原样透传给 CDP 的Emulation.addScreen命令,字段名与 CDP 协议一一对应(workAreaInsetsdevicePixelRatiorotation等),Puppeteer 层不做任何裁剪或默认值填充,默认值由 Chromium 侧补齐(如devicePixelRatio: 1);
  2. 响应体中的screenInfo字段直接作为Promise的解析值返回,因此上表中的avail*orientationid均由 Chromium 计算;
  3. 同一文件中的screens()Emulation.getScreenInfos)与removeScreenEmulation.removeScreen)走相同的connection.send通道,三者共享同一个 browser-scoped CDP 连接。

BiDi 路径(WebDriver BiDi):在 packages/puppeteer-core/src/bidi/Browser.ts#L335-L337 中,addScreenscreensremoveScreen一起直接抛出UnsupportedOperation

override addScreen(_params: AddScreenParams): Promise<ScreenInfo> { throw new UnsupportedOperation(); }

也就是说,当前仓库中该能力只有 CDP 连接(Chrome/Chromium)支持,Firefox 或 BiDi 模式调用会立即抛错。这是使用该方法时的第一个硬性前提。

实战:搭建双屏环境并验证窗口落位

仓库中最有价值的组合用法出现在Browser.get|setWindowBounds的“window maximized state”用例(browser.test.ts#L198-L214):先用addScreen造出副屏,再用返回对象的availLeft/availTop精确计算窗口坐标,把新窗口打开在第二块屏幕上:

// 1. 添加副屏(位于主屏右侧) const screenInfo = await browser.addScreen({ left: 800, top: 0, width: 1600, height: 1200, }); // 2. 利用 availLeft/availTop 定位,把窗口开在副屏上 const page = await context.newPage({ type: 'window', windowBounds: { left: screenInfo.availLeft + 50, top: screenInfo.availTop + 50, }, });

这个模式说明addScreen的典型价值:

  • 多显示器窗口管理测试windowBounds需要相对副屏的坐标,ScreenInfoavailLeft/availTop正好提供副屏可用区原点,避免手写魔数坐标;
  • window.devicePixelRatio/window.screen相关 Web API 的确定性验证:注入指定devicePixelRatiocolorDepth的屏幕后,页面内读取到的屏幕属性稳定可控,不受 CI 机器真实显示环境干扰;
  • screens()/removeScreen组合做生命周期断言:如 browser.test.ts#L131-L166 所示,screens()返回数组长度变化可作为添加/移除成功与否的断言依据。

一个可直接复制的完整示例

综合上述源码与测试证据,以下示例覆盖了添加、查询、断言、清理的完整流程(仅适用于 headless Chrome over CDP):

import puppeteer from 'puppeteer'; const browser = await puppeteer.launch({headless: true}); // 在默认 800x600 主屏右侧添加一块 1600x1200、底部预留 80px 任务栏的副屏 const screen = await browser.addScreen({ left: 800, top: 0, width: 1600, height: 1200, workAreaInsets: {bottom: 80}, devicePixelRatio: 2, label: 'monitor-b', }); // 查询全部屏幕:[主屏, 副屏] const all = await browser.screens(); console.log(all.length); // 2 // 页面内可读取到仿真屏幕属性 const page = await browser.newPage(); const info = await page.evaluate(() => ({ label: window.screen.label, dpr: window.devicePixelRatio, })); console.log(info); // 清理:用 addScreen 返回的 id 移除副屏 await browser.removeScreen(screen.id); await browser.close();

关键限制与注意事项

  1. headless-only:文档 Remarks 与测试用例均确认 headful 模式不可用(真实屏幕不可注入);
  2. CDP-only:BiDi 实现直接抛UnsupportedOperation(bidi/Browser.ts#L335-L337),Firefox 用户无法使用该 API;
  3. 主屏不可删除removeScreen传入主屏id会失败,addScreen返回的屏幕恒为isPrimary: false
  4. 默认值由 Chromium 补齐devicePixelRatio缺省为 1、label缺省为空串、未旋转时orientation{angle: 0, type: 'landscapePrimary'},这些默认行为来自测试断言,属于当前仓库版本下的可观察事实;
  5. 坐标系是虚拟桌面全局坐标left/top相对整块虚拟桌面原点,不是相对主屏;副屏通常从left = 主屏宽度开始放置以避免重叠。

相关 API 索引

API文档说明
Browser.screens()puppeteer.browser.screens.md返回全部 ScreenInfo 对象
Browser.removeScreen(id)puppeteer.browser.removescreen.mdid移除屏幕,headless-only
AddScreenParamspuppeteer.addscreenparams.md入参接口
ScreenInfo/WorkAreaInsetspuppeteer.screeninfo.md返回对象与 insets 子结构
Browser总览puppeteer.browser.mdBrowser 类全部方法索引

对应源码入口:抽象声明、CDP 实现、BiDi 占位实现、集成测试。

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

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

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

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

立即咨询