首页
/ Puppeteer 无头屏幕配置实战:--screen-info 静态多屏与 addScreen 动态屏幕管理

Puppeteer 无头屏幕配置实战:--screen-info 静态多屏与 addScreen 动态屏幕管理

2026-09-07 11:43:55作者:冯爽妲Honey

本指南以 Puppeteer 官方文档 screen-configuration.md 为骨架,系统讲解如何让无头(headless)Chrome 使用真实的多显示器屏幕拓扑:既可通过启动参数 --screen-info 在浏览器启动前一次性描述多个屏幕及其位置、尺寸、方向与标签,也可以在浏览器运行期间通过 Browser.addScreen / Browser.removeScreen 动态增删屏幕,并用 Browser.screens 随时读取当前屏幕集合。读完本文你将掌握多屏无头环境下 Web 应用“跨屏布局、副屏窗口最大化、多屏渲染”等场景的测试与模拟方法。

为什么需要为无头 Chrome 配置屏幕

物理机上的浏览器窗口管理、screen.availWidth/availHeight 等 Web API 都依赖操作系统提供的屏幕信息。而在 headless 模式下,Chrome 没有真实物理屏幕,默认只会模拟一块逻辑屏幕(详见下文“无开关时的默认屏幕”)。如果你的被测页面需要验证多屏布局、副屏弹出窗口位置、双屏拼接广告、竖屏适配等行为,就必须先为无头浏览器构造出符合预期的“虚拟屏幕拓扑”。

该能力在 Chromium 侧由 headless 组件中的 screen_info 实现支撑(仓库文档原文注明了对应 Chromium 内部 README),而在 Puppeteer 侧则暴露为两个层次的 API:静态的 --screen-info 启动开关动态的 Browser.addScreen/removeScreen 方法

使用 --screen-info 开关配置双屏启动环境

--screen-info 是一个传给 Chrome 的命令行开关,用于在启动阶段配置 headless 屏幕。其字符串语法形如:{800x600 label=1st}{600x800 label=2nd}——每个屏幕用一对花括号描述,内部为 宽度x高度,可附带 label=xxx 命名。

下面的脚本让 Chrome 运行在“双屏”环境:主屏 800x600 横屏(landscape),副屏 600x800 竖屏(portrait),且副屏紧贴主屏右侧(即副屏 left 偏移量等于主屏宽度 800):

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  args: ['--screen-info={800x600 label=1st}{600x800 label=2nd}'],
});

const screens = await browser.screens();
const screenInfos = screens.map(
  s =>
    `Screen [${s.id}]` +
    ` ${s.left},${s.top} ${s.width}x${s.height}` +
    ` label='${s.label}'` +
    ` isPrimary=${s.isPrimary}` +
    ` isExtended=${s.isExtended}` +
    ` isInternal=${s.isInternal}` +
    ` colorDepth=${s.colorDepth}` +
    ` devicePixelRatio=${s.devicePixelRatio}` +
    ` avail=${s.availLeft},${s.availTop} ${s.availWidth}x${s.availHeight}` +
    ` orientation.type=${s.orientation.type}` +
    ` orientation.angle=${s.orientation.angle}`,
);

console.log(`Number of screens: ${screens.length}\n` + screenInfos.join('\n'));

await browser.close();

运行输出:

Number of screens: 2
Screen [1] 0,0 800x600 label='1st' isPrimary=true isExtended=true isInternal=false colorDepth=24 devicePixelRatio=1 avail=0,0 800x600 orientation.type=landscapePrimary orientation.angle=0
Screen [2] 800,0 600x800 label='2nd' isPrimary=false isExtended=true isInternal=false colorDepth=24 devicePixelRatio=1 avail=800,0 600x800 orientation.type=portraitPrimary orientation.angle=0

注意输出中的两个关键推论:

  • 第一个声明的屏幕自动成为主屏(isPrimary=true),位于虚拟桌面左上角 (0,0);第二个屏幕的 left=800 说明它从主屏右边缘开始,由此构成“主屏在左、副屏在右”的扩展桌面。
  • 方向(orientation)由宽高自动推导:800x600(宽大于高)为 landscapePrimary600x800(高大于宽)为 portraitPrimary,旋转角 angle 均为 0。这是与 Chromium Emulation.getScreenInfos 返回结果一致的行为(见下文源码部分)。

各 ScreenInfo 字段语义

browser.screens() 返回的是 ScreenInfo 对象的数组。其完整结构定义于 api/Browser.ts,字段含义如下:

字段 类型 含义
id string 屏幕唯一标识,示例输出中显示为 [1][2],也是 removeScreen 需要的参数
left / top number 屏幕在虚拟桌面中的左上角坐标
width / height number 屏幕逻辑分辨率
availLeft / availTop / availWidth / availHeight number 去掉任务栏/工作区留白后页面可用的工作区矩形
devicePixelRatio number 设备像素比(示例中为 1)
colorDepth number 色深(示例中为 24)
orientation ScreenOrientation 方向对象,含 type(如 landscapePrimary/portraitPrimary)与 angle(旋转角)
isExtended boolean 是否为扩展屏(单屏时为 false,多屏时为 true)
isInternal boolean 是否内建屏幕(headless 模拟屏为 false)
isPrimary boolean 是否主屏
label string 屏幕名称,对应 --screen-info 中的 label=addScreen 传入的 label

接口本身的 availWidth/availHeight/availLeft/availTopcolorDepth 等均声明在 puppeteer.screeninfo.md 的 API 参考页中。

无 --screen-info 开关时的默认屏幕

如果不传 --screen-info,headless 默认只有一块 800x600 的屏幕;但若同时指定了 --window-size 开关,则 headless 屏幕会放大到请求的窗口尺寸。也就是说,--screen-info 是比 --window-size 更精细、能力更强的屏幕控制手段——前者描述的是“屏幕硬件拓扑”,后者只影响单个窗口/视口大小。

仓库的集成测试也印证了该用法:在 page.test.ts 中,Page.resize 相关测试专门通过 args: ['--screen-info={3840x2160}'] 启动一个 4K 无头屏,并配合 page.setViewport(null) 移除默认 800x600 视口对窗口尺寸的限制,进而验证浏览器窗口可按页面内容尺寸自动调整。这说明要测试大屏或窗口自适应逻辑时,先声明一块足够大的屏幕是必要前提。

:::caution 使用前提 --screen-info 开关只在 headless 模式下生效。Headful(有头)Chrome 始终使用操作系统真实物理屏幕,该开关会被忽略。 :::

动态屏幕配置:运行期 addScreen / removeScreen / screens

除了启动时一次性声明外,Puppeteer 还允许在 Chrome 运行期间动态调整屏幕集合:

下面的脚本先以单屏启动,随后动态添加一个位于右侧的 800x600 副屏,再将其移除,全程打印每一步的屏幕配置:

import puppeteer from 'puppeteer-core';

const browser = await puppeteer.launch({
  args: ['--screen-info={800x600 label=1st}'],
});

function getScreenInfo(s) {
  return (
    `Screen [${s.id}]` +
    ` ${s.left},${s.top} ${s.width}x${s.height}` +
    ` label='${s.label}'` +
    ` isPrimary=${s.isPrimary}` +
    ` isExtended=${s.isExtended}`
  );
}

async function logScreenConfig(text) {
  if (text !== undefined) {
    console.log(text);
  }
  const screens = await browser.screens();
  const screenInfos = screens.map(s => getScreenInfo(s));

  console.log(
    `Number of screens: ${screens.length}\n` + screenInfos.join('\n'),
  );
}

await logScreenConfig('---- Initial:');

// Add a screen.
const addedScreenInfo = await browser.addScreen({
  left: 800,
  top: 0,
  width: 800,
  height: 600,
  label: '2nd',
});

console.log('Added screen: ' + getScreenInfo(addedScreenInfo));
await logScreenConfig('---- With the screen added:');

// Remove the added screen.
await browser.removeScreen(addedScreenInfo.id);
await logScreenConfig('---- With added screen removed:');

await browser.close();

对应输出:

---- Initial:
Number of screens: 1
Screen [1] 0,0 800x600 label='1st' isPrimary=true isExtended=false
Added screen: Screen [2] 800,0 800x600 label='2nd' isPrimary=false isExtended=true
---- With the screen added:
Number of screens: 2
Screen [1] 0,0 800x600 label='1st' isPrimary=true isExtended=true
Screen [2] 800,0 800x600 label='2nd' isPrimary=false isExtended=true
---- With added screen removed:
Number of screens: 1
Screen [1] 0,0 800x600 label='1st' isPrimary=true isExtended=false

输出清晰展示了 isExtended 的语义变化:单屏时主屏 isExtended=false,一旦存在第二块屏幕,两块屏的 isExtended 都变为 true;移除副屏后回到初始状态。

AddScreenParams:动态新增屏幕可控制哪些属性

addScreen 的参数类型 AddScreenParams 定义在 api/Browser.ts,除必需的几何位置外,还支持若干可选属性:

参数 类型 是否必填 说明
left / top number 必填 新屏幕在虚拟桌面中的左上角坐标
width / height number 必填 新屏幕的分辨率
workAreaInsets WorkAreaInsets 可选 工作区四周留白 {top, left, bottom, right},用于模拟任务栏等占据的区域
devicePixelRatio number 可选 设备像素比
rotation number 可选 屏幕旋转角度,会反映到 orientation.angle
colorDepth number 可选 色深
label string 可选 屏幕标签
isInternal boolean 可选 是否标记为内建屏

其中 workAreaInsets 对页面可感知尺寸影响很大:可用工作区 availWidth/availHeight = 屏幕 width/height 减去对应方向的 insets。仓库测试 browser.test.ts 便验证了这一关系——向 addScreen 传入 width: 1600, height: 1200, workAreaInsets: {bottom: 80} 后,断言返回结果满足 availHeight: 1120(即 1200 - 80)、availWidth: 1600colorDepth: 32devicePixelRatio: 1orientation: {angle: 0, type: 'landscapePrimary'} 等,同时 isPrimary: falseisExtended: trueid 为任意字符串。这说明 addScreen 返回的对象就是 Chrome 内部屏幕状态的真实回显。

动态副屏的实际用途

动态副屏最常见的价值是配合窗口管理 API 使用:可以在副屏上打开窗口并执行最大化。同样在 browser.test.ts 的测试中,先 addScreen 添加一个 1600x1200 副屏,再通过 context.newPage({type: 'window', windowBounds: ...}) 在副屏 avail 区域内开窗,随后用 browser.setWindowBounds(windowId, {windowState: 'maximized'}) 将该窗口最大化到副屏。这组测试证明:多屏无头环境完全支持“在不同屏幕上创建并最大化窗口”的桌面类应用行为验证。

:::caution 使用前提 Browser.addScreenBrowser.removeScreen 仅在 headless 模式下可用;Browser.screens 则在 headful 与 headless 两种模式下均可调用。此外,从 api/Browser.tsremoveScreen 的源码注释可知:移除主屏(primary screen)会失败——至少保留一块主屏是屏幕拓扑的不变约束。 :::

源码实现:从 Puppeteer API 到 Chromium 命令

从仓库源码结构看,这三组屏幕 API 是一套典型的“协议无关抽象 + 协议实现”体系:

  1. 协议无关的抽象层:在 api/Browser.ts 中,Browser 基类将 screens()addScreen(params)removeScreen(screenId) 声明为抽象方法,并同步导出 ScreenInfoScreenOrientationAddScreenParamsWorkAreaInsets 等公开类型。

  2. CDP(Chrome DevTools Protocol)实现:在 cdp/Browser.ts 中,三者被映射为三条 Emulation 域命令:

    • screens()Emulation.getScreenInfos
    • addScreen(params)Emulation.addScreen(params 原样透传)
    • removeScreen(screenId)Emulation.removeScreen
  3. WebDriver BiDi 协议下的限制:在 bidi/Browser.ts 中,三个方法目前直接抛出 UnsupportedOperation。可以推断,通过 WebDriver BiDi 协议连接的 Firefox/浏览器暂不支持这套屏幕模拟 API,当前仅 CDP 通道(Chromium headless)具备完整能力。

也就是说:--screen-infoaddScreen/removeScreen 最终都收敛到 Chromium 的 headless 屏幕模拟能力,Puppeteer 只是把 CDP 命令包装成了类型安全的 TypeScript 方法,并在运行时经由 browser.screens() 与页面内 window.screen / screen.avail* 等 Web API 保持一致。

小结与使用建议

  • 启动前已知拓扑--screen-info:语法为连续花括号块,每块 {宽x高 label=名称},先声明者为主屏,方向由宽高比自动推导,屏幕按 left/top 拼接成虚拟桌面。
  • 运行期变化Browser.addScreen / Browser.removeScreen:适合在同一个浏览器会话中先后模拟“单屏→双屏→单屏”等切换场景,其中 workAreaInsets 可用于精细模拟任务栏对 avail* 的影响。
  • 读取现状统一用 Browser.screens():其返回字段与 Emulation.getScreenInfos 的产出一一对应,可同时用于 headful 与 headless。
  • 协议注意:上述屏幕 API 的完整实现位于 CDP 通道;经 WebDriver BiDi 连接时 addScreen/removeScreen/screens 均不可用。且一切屏幕模拟都发生在 headless 模式内,headful 模式始终走真实物理屏幕。
  • 与视口/窗口的关系:默认无头屏为 800x600,--window-size 可放大之;若配合 --screen-info 声明大屏后仍受 800x600 默认视口限制,可参照 page.test.ts 的做法先 page.setViewport(null) 解除视口约束,再测试窗口级行为。

如需进一步查阅类型细节,可参考 puppeteer.addscreenparams.mdpuppeteer.screeninfo.mdpuppeteer.browser.addscreen.mdpuppeteer.browser.removescreen.md 等 API 文档。

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