diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef7ff7b..148692a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - lts/jod env: - PUPPETEER_VERSION: 23.9.0 + PUPPETEER_VERSION: 23.11.0 steps: @@ -82,16 +82,8 @@ jobs: - ubuntu-latest - windows-latest puppeteer-version: - - '23.0.2' - - '23.1.1' - - '23.2.2' - - '23.3.1' - - '23.4.1' - - '23.5.3' - - '23.6.1' - - '23.7.1' - - '23.8.0' - - '23.9.0' + - '23.10.4' + - '23.11.0' steps: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5cc48cc..513fac3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: package: runs-on: ubuntu-latest env: - PUPPETEER_VERSION: 23.9.0 + PUPPETEER_VERSION: 23.11.0 steps: - name: Checkout diff --git a/README.md b/README.md index 2ec6657..49935e6 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,8 @@ await recorder.waitForTimeout(1000) ### `--headless=new` is not supported -Sadly, [it is so](https://issues.chromium.org/issues/361863270#comment2). Starting with Puppeteer v22, `--headless=new` -is used by default so the plugin overrides with `--headless=old`. +Sadly, [it is so](https://issues.chromium.org/issues/361863270#comment2). For Puppeteer v23+, the plugin enforces use +of the `chrome-headless-shell` binary. ### Bad Chrome versions diff --git a/package-lock.json b/package-lock.json index feee646..74e1227 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,8 +20,8 @@ "@types/which": "^3.0.4", "@types/ws": "^8.5.13", "jest": "^29.7.0", - "puppeteer": "23.9.0", - "puppeteer-core": "23.9.0", + "puppeteer": "23.11.0", + "puppeteer-core": "23.11.0", "rimraf": "^6.0.1", "ts-jest": "^29.2.5", "ts-standard": "^12.0.2", @@ -35,7 +35,7 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0" }, "peerDependencies": { - "puppeteer-core": "^23.0.0 <23.10.0" + "puppeteer-core": "^23.10.0" } }, "node_modules/@ampproject/remapping": { @@ -1329,16 +1329,16 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.1.tgz", - "integrity": "sha512-0kdAbmic3J09I6dT8e9vE2JOCSt13wHCW5x/ly8TSt2bDtuIWe2TgLZZDHdcziw9AVCzflMAXCrVyRIhIs44Ng==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "debug": "^4.3.7", + "debug": "^4.4.0", "extract-zip": "^2.0.1", "progress": "^2.0.3", - "proxy-agent": "^6.4.0", + "proxy-agent": "^6.5.0", "semver": "^7.6.3", "tar-fs": "^3.0.6", "unbzip2-stream": "^1.4.3", @@ -2587,9 +2587,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001689", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", - "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -2635,14 +2635,13 @@ } }, "node_modules/chromium-bidi": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.8.0.tgz", - "integrity": "sha512-uJydbGdTw0DEUjhoogGveneJVWX/9YuqkWePzMmkBYwtdAqo5d3J/ovNKFr+/2hWXYmYCr6it8mSSTIj6SS6Ug==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", "dev": true, "license": "Apache-2.0", "dependencies": { "mitt": "3.0.1", - "urlpattern-polyfill": "10.0.0", "zod": "3.23.8" }, "peerDependencies": { @@ -2893,13 +2892,13 @@ } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -6422,9 +6421,9 @@ } }, "node_modules/math-intrinsics": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -6684,13 +6683,14 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -7262,18 +7262,18 @@ } }, "node_modules/puppeteer": { - "version": "23.9.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.9.0.tgz", - "integrity": "sha512-WfB8jGwFV+qrD9dcJJVvWPFJBU6kxeu2wxJz9WooDGfM3vIiKLgzImEDBxUQnCBK/2cXB3d4dV6gs/LLpgfLDg==", + "version": "23.11.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-23.11.0.tgz", + "integrity": "sha512-UaHfTIcg02bTahmZjrjrpU8efyjNeItrNvANu+DdnYMEcQ24X8LOkBWv2Z4bqDzkOzFymqJkADS0bdSDMUNi1A==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.4.1", - "chromium-bidi": "0.8.0", + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", "cosmiconfig": "^9.0.0", "devtools-protocol": "0.0.1367902", - "puppeteer-core": "23.9.0", + "puppeteer-core": "23.11.0", "typed-query-selector": "^2.12.0" }, "bin": { @@ -7284,15 +7284,15 @@ } }, "node_modules/puppeteer-core": { - "version": "23.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.9.0.tgz", - "integrity": "sha512-hLVrav2HYMVdK0YILtfJwtnkBAwNOztUdR4aJ5YKDvgsbtagNr6urUJk9HyjRA9e+PaLI3jzJ0wM7A4jSZ7Qxw==", + "version": "23.11.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.11.0.tgz", + "integrity": "sha512-fr5Xp8KeZGRiLrYmosAxPAObi1vmb09vmwak9lqS7KvKMbcN+mk+bDpnDKXPd7QN9b7b/mb9Fgp0A6+024XbVA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.4.1", - "chromium-bidi": "0.8.0", - "debug": "^4.3.7", + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", "devtools-protocol": "0.0.1367902", "typed-query-selector": "^2.12.0", "ws": "^8.18.0" @@ -8580,19 +8580,19 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.3.tgz", - "integrity": "sha512-GsvTyUHTriq6o/bHcTd0vM7OQ9JEdlvluu9YISaA7+KzDzPaIzEeDFNkTfhdE3MYcNhNi0vq/LlegYgIs5yPAw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "reflect.getprototypeof": "^1.0.6" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -8721,13 +8721,6 @@ "punycode": "^2.1.0" } }, - "node_modules/urlpattern-polyfill": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", - "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", - "dev": true, - "license": "MIT" - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", diff --git a/package.json b/package.json index 169b8c2..12c1ced 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@ffmpeg-installer/ffmpeg": "^1.1.0" }, "peerDependencies": { - "puppeteer-core": "^23.0.0 <23.10.0" + "puppeteer-core": "^23.10.0" }, "devDependencies": { "@types/fluent-ffmpeg": "^2.1.27", @@ -56,8 +56,8 @@ "@types/which": "^3.0.4", "@types/ws": "^8.5.13", "jest": "^29.7.0", - "puppeteer": "23.9.0", - "puppeteer-core": "23.9.0", + "puppeteer": "23.11.0", + "puppeteer-core": "23.11.0", "rimraf": "^6.0.1", "ts-jest": "^29.2.5", "ts-standard": "^12.0.2", diff --git a/src/NotChromeHeadlessShell.ts b/src/NotChromeHeadlessShell.ts new file mode 100644 index 0000000..14ecde3 --- /dev/null +++ b/src/NotChromeHeadlessShell.ts @@ -0,0 +1,7 @@ +export class NotChromeHeadlessShell extends Error { + constructor (executablePath: string) { + super('Not chrome-headless-shell: ' + executablePath) + this.name = this.constructor.name + Error.captureStackTrace(this, this.constructor) + } +} diff --git a/src/PuppeteerCaptureViaHeadlessExperimental.test.ts b/src/PuppeteerCaptureViaHeadlessExperimental.test.ts index d1263da..77ecb50 100644 --- a/src/PuppeteerCaptureViaHeadlessExperimental.test.ts +++ b/src/PuppeteerCaptureViaHeadlessExperimental.test.ts @@ -19,7 +19,7 @@ afterEach(async () => { } }) -test('that capture fails if required args are missing', async () => { +test('that capture fails if not chrome-headless-shell', async () => { browser = await puppeteer.launch({ executablePath: executablePath(), args: PUPPETEER_LAUNCH_ARGS @@ -31,9 +31,21 @@ test('that capture fails if required args are missing', async () => { }).rejects.toThrow() }) +test('that capture fails if required args are missing', async () => { + browser = await puppeteer.launch({ + executablePath: executablePath({ headless: 'shell' }), + args: PUPPETEER_LAUNCH_ARGS + }) + const page = await browser.newPage() + const capture = new PuppeteerCaptureViaHeadlessExperimental() + await expect(async () => { + await capture.attach(page) + }).rejects.toThrow() +}) + test('that capture does not fail if required args are present', async () => { browser = await puppeteer.launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: [ ...PUPPETEER_LAUNCH_ARGS, ...PuppeteerCaptureViaHeadlessExperimental.REQUIRED_ARGS @@ -46,7 +58,7 @@ test('that capture does not fail if required args are present', async () => { test('that capture works in headless mode', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -62,7 +74,7 @@ test('that capture works in headless mode', async () => { // test('that capture works repeatedly in headless mode', async () => { // browser = await launch({ -// executablePath: executablePath(), +// executablePath: executablePath({ headless: 'shell' }), // args: PUPPETEER_LAUNCH_ARGS // }) // const page = await browser.newPage() @@ -85,7 +97,7 @@ test('that capture works in headless mode', async () => { test('that capture works with custom viewport size', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -102,7 +114,7 @@ test('that capture works with custom viewport size', async () => { test('that capture drops captured frames', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -119,7 +131,7 @@ test('that capture drops captured frames', async () => { test('that capture stops gracefully on FFMPEG error', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -137,7 +149,7 @@ test('that capture stops gracefully on FFMPEG error', async () => { test('that capture stops gracefully on page close', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -154,7 +166,7 @@ test('that capture stops gracefully on page close', async () => { test('that capture stops gracefully on session connection drop', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -171,7 +183,7 @@ test('that capture stops gracefully on session connection drop', async () => { test('that capture is compatible with Date.now()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -192,7 +204,7 @@ test('that capture is compatible with Date.now()', async () => { test('that capture is compatible with new Date().getTime()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -213,7 +225,7 @@ test('that capture is compatible with new Date().getTime()', async () => { test('that capture is compatible with performance.now()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -234,7 +246,7 @@ test('that capture is compatible with performance.now()', async () => { test('that capture is compatible with setInterval()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -253,7 +265,7 @@ test('that capture is compatible with setInterval()', async () => { test('that capture is compatible with setTimeout()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -272,7 +284,7 @@ test('that capture is compatible with setTimeout()', async () => { test('that capture is compatible with requestAnimationFrame()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -308,7 +320,7 @@ test('that capture is compatible with requestAnimationFrame()', async () => { test('that capture is compatible with bound window.requestAnimationFrame()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -345,7 +357,7 @@ test('that capture is compatible with bound window.requestAnimationFrame()', asy test('that inactive capture is compatible with setInterval()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -368,7 +380,7 @@ test('that inactive capture is compatible with setInterval()', async () => { test('that inactive capture is compatible with setTimeout()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() @@ -391,7 +403,7 @@ test('that inactive capture is compatible with setTimeout()', async () => { test('that inactive capture is compatible with requestAnimationFrame()', async () => { browser = await launch({ - executablePath: executablePath(), + executablePath: executablePath({ headless: 'shell' }), args: PUPPETEER_LAUNCH_ARGS }) const page = await browser.newPage() diff --git a/src/PuppeteerCaptureViaHeadlessExperimental.ts b/src/PuppeteerCaptureViaHeadlessExperimental.ts index 29ea52d..142606d 100644 --- a/src/PuppeteerCaptureViaHeadlessExperimental.ts +++ b/src/PuppeteerCaptureViaHeadlessExperimental.ts @@ -5,12 +5,12 @@ import type { Page as PuppeteerPage } from 'puppeteer-core' import { MissingHeadlessExperimentalRequiredArgs } from './MissingHeadlessExperimentalRequiredArgs' +import { NotChromeHeadlessShell } from './NotChromeHeadlessShell' import { PuppeteerCaptureBase } from './PuppeteerCaptureBase' import { PuppeteerCaptureOptions } from './PuppeteerCaptureOptions' export class PuppeteerCaptureViaHeadlessExperimental extends PuppeteerCaptureBase { public static readonly REQUIRED_ARGS = [ - '--headless=old', '--deterministic-mode', '--enable-begin-frame-control', '--disable-new-content-rendering-timeout', @@ -213,8 +213,17 @@ export class PuppeteerCaptureViaHeadlessExperimental extends PuppeteerCaptureBas } protected static validateBrowserArgs (browser: PuppeteerBrowser): void { + const spawnfile = browser.process()?.spawnfile + if (spawnfile === null || spawnfile === undefined || !spawnfile.includes('chrome-headless-shell')) { + throw new NotChromeHeadlessShell(spawnfile ?? 'unknown') + } + const spawnargs = browser.process()?.spawnargs - if (spawnargs == null || !PuppeteerCaptureViaHeadlessExperimental.REQUIRED_ARGS.every(arg => spawnargs.includes(arg))) { + if ( + spawnargs === null || + spawnargs === undefined || + !PuppeteerCaptureViaHeadlessExperimental.REQUIRED_ARGS.every(arg => spawnargs.includes(arg)) + ) { throw new MissingHeadlessExperimentalRequiredArgs() } } diff --git a/src/index.ts b/src/index.ts index dedf7dc..8bf3141 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ export { capture } from './capture' export { launch } from './launch' export { MissingHeadlessExperimentalRequiredArgs as MissingRequiredArgs } from './MissingHeadlessExperimentalRequiredArgs' +export { NotChromeHeadlessShell } from './NotChromeHeadlessShell' export { PuppeteerCapture } from './PuppeteerCapture' export { PuppeteerCaptureBase } from './PuppeteerCaptureBase' export { PuppeteerCaptureEvents } from './PuppeteerCaptureEvents' diff --git a/src/launch.ts b/src/launch.ts index dfb848a..0604bbc 100644 --- a/src/launch.ts +++ b/src/launch.ts @@ -1,6 +1,6 @@ import type { Browser as PuppeteerBrowser, - PuppeteerLaunchOptions + LaunchOptions as PuppeteerLaunchOptions } from 'puppeteer-core' import puppeteer from 'puppeteer-core' import { PuppeteerCaptureViaHeadlessExperimental } from './PuppeteerCaptureViaHeadlessExperimental' @@ -10,6 +10,7 @@ export async function launch ( ): Promise { options = { ...(options != null ? options : {}), + headless: 'shell', args: [ ...(options?.args != null ? options?.args : []), ...PuppeteerCaptureViaHeadlessExperimental.REQUIRED_ARGS