Puppeteer 无头屏幕配置实战:--screen-info 静态多屏与 addScreen 动态屏幕管理
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
本指南以 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(宽大于高)为landscapePrimary,600x800(高大于宽)为portraitPrimary,旋转角angle均为 0。这是与 ChromiumEmulation.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/availTop、colorDepth等均声明在 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 运行期间动态调整屏幕集合:
Browser.screens():读取当前屏幕配置;Browser.addScreen(params):新增一个屏幕并返回其ScreenInfo;Browser.removeScreen(screenId):移除一个屏幕。
下面的脚本先以单屏启动,随后动态添加一个位于右侧的 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: 1600、colorDepth: 32、devicePixelRatio: 1、orientation: {angle: 0, type: 'landscapePrimary'}等,同时isPrimary: false、isExtended: true、id为任意字符串。这说明 addScreen 返回的对象就是 Chrome 内部屏幕状态的真实回显。
动态副屏的实际用途
动态副屏最常见的价值是配合窗口管理 API 使用:可以在副屏上打开窗口并执行最大化。同样在 browser.test.ts 的测试中,先addScreen添加一个 1600x1200 副屏,再通过context.newPage({type: 'window', windowBounds: ...})在副屏avail区域内开窗,随后用browser.setWindowBounds(windowId, {windowState: 'maximized'})将该窗口最大化到副屏。这组测试证明:多屏无头环境完全支持“在不同屏幕上创建并最大化窗口”的桌面类应用行为验证。
:::caution 使用前提Browser.addScreen与Browser.removeScreen仅在 headless 模式下可用;Browser.screens则在 headful 与 headless 两种模式下均可调用。此外,从 api/Browser.ts 中removeScreen的源码注释可知:移除主屏(primary screen)会失败——至少保留一块主屏是屏幕拓扑的不变约束。 :::
源码实现:从 Puppeteer API 到 Chromium 命令
从仓库源码结构看,这三组屏幕 API 是一套典型的“协议无关抽象 + 协议实现”体系:
协议无关的抽象层:在 api/Browser.ts 中,
Browser基类将screens()、addScreen(params)、removeScreen(screenId)声明为抽象方法,并同步导出ScreenInfo、ScreenOrientation、AddScreenParams、WorkAreaInsets等公开类型。CDP(Chrome DevTools Protocol)实现:在 cdp/Browser.ts 中,三者被映射为三条
Emulation域命令:screens()→Emulation.getScreenInfosaddScreen(params)→Emulation.addScreen(params 原样透传)removeScreen(screenId)→Emulation.removeScreen
WebDriver BiDi 协议下的限制:在 bidi/Browser.ts 中,三个方法目前直接抛出
UnsupportedOperation。可以推断,通过 WebDriver BiDi 协议连接的 Firefox/浏览器暂不支持这套屏幕模拟 API,当前仅 CDP 通道(Chromium headless)具备完整能力。
也就是说:--screen-info与addScreen/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.md、puppeteer.screeninfo.md、puppeteer.browser.addscreen.md 与 puppeteer.browser.removescreen.md 等 API 文档。
【免费下载链接】puppeteerJavaScript API for Chrome and Firefox项目地址: https://gitcode.com/GitHub_Trending/puppeteer1/puppeteer
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考