diff --git a/package-lock.json b/package-lock.json index 4f4ba3ce5..4c411a030 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8127,10 +8127,8 @@ "version": "1.5.0-rc.0", "license": "Apache-2.0", "dependencies": { - "@types/debug": "^4.1.12", "@types/node": "^18.17.15", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1581282", + "devtools-protocol": "0.0.1666840", "mitt": "3.0.1", "parsel-js": "1.2.2", "rxjs": "7.8.2", @@ -8143,21 +8141,6 @@ "wrangler": "4.137.0" } }, - "packages/puppeteer-cloudflare/node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "packages/puppeteer-cloudflare/node_modules/@types/debug/node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, "packages/puppeteer-cloudflare/node_modules/@types/node": { "version": "18.19.130", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", @@ -8173,33 +8156,10 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, - "packages/puppeteer-cloudflare/node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "packages/puppeteer-cloudflare/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "packages/puppeteer-cloudflare/node_modules/devtools-protocol": { - "version": "0.0.1581282", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz", - "integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==", + "version": "0.0.1666840", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1666840.tgz", + "integrity": "sha512-gCcO42XCHKEs7Ag0S7aGYsnJ7hlgrO3qderYqeiY0Eqk+0GFfuvT13IA0hHreJTa2KCdDVyGMeOhdMNmrrTjVg==", "license": "BSD-3-Clause" }, "packages/puppeteer-cloudflare/node_modules/parsel-js": { diff --git a/packages/puppeteer-cloudflare/README.md b/packages/puppeteer-cloudflare/README.md index 7fed401b3..b7255f8ed 100644 --- a/packages/puppeteer-cloudflare/README.md +++ b/packages/puppeteer-cloudflare/README.md @@ -16,13 +16,31 @@ npm install @cloudflare/puppeteer ## Configuration +`@cloudflare/puppeteer` needs [Node.js compatibility](https://developers.cloudflare.com/workers/runtime-apis/nodejs/#get-started). For a `compatibility_date` of `2026-08-04` or later, Workers enables Node.js compatibility by default. For an earlier date, you must add the `nodejs_compat` compatibility flag. + Add a browser binding to your Wrangler configuration: ```toml +compatibility_date = "2026-08-04" +browser = { binding = "MYBROWSER" } +``` + +If your `compatibility_date` is earlier than `2026-08-04`, also add the `nodejs_compat` flag: + +```toml +compatibility_date = "2026-08-03" compatibility_flags = ["nodejs_compat"] browser = { binding = "MYBROWSER" } ``` +Do not add the `no_nodejs_compat` flag. The package does not work without Node.js compatibility. + +### File options + +Options that read or write files use the [Workers virtual file system](https://developers.cloudflare.com/workers/runtime-apis/nodejs/fs/). Examples are `page.screenshot({path})`, `page.pdf({path})`, and `tracing.start({path})`. These options need a `compatibility_date` of `2025-09-01` or later, or the `enable_nodejs_fs_module` flag. + +Write files to `/tmp`. Files in `/tmp` exist only for the current request. + ## TypeScript The package types use the Browser Run types of the Workers runtime, for example `BrowserRun` and `BrowserRunAcquireGuardrails`. Your project gets these types from `wrangler types` with Wrangler 4.134.0 or later, or from `@cloudflare/workers-types` 5.20260917.1 or later. diff --git a/packages/puppeteer-cloudflare/package.json b/packages/puppeteer-cloudflare/package.json index a8da96041..60e2903c8 100644 --- a/packages/puppeteer-cloudflare/package.json +++ b/packages/puppeteer-cloudflare/package.json @@ -50,10 +50,8 @@ "test:merge": "cd tests && npx playwright merge-reports --reporter html ./all-blob-reports" }, "dependencies": { - "@types/debug": "^4.1.12", "@types/node": "^18.17.15", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1581282", + "devtools-protocol": "0.0.1666840", "mitt": "3.0.1", "parsel-js": "1.2.2", "rxjs": "7.8.2", diff --git a/packages/puppeteer-cloudflare/src/cloudflare/WorkersWebSocketTransport.ts b/packages/puppeteer-cloudflare/src/cloudflare/WorkersWebSocketTransport.ts index 1349eb038..b0157bc07 100644 --- a/packages/puppeteer-cloudflare/src/cloudflare/WorkersWebSocketTransport.ts +++ b/packages/puppeteer-cloudflare/src/cloudflare/WorkersWebSocketTransport.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import type {ConnectionTransport} from 'puppeteer-core/lib/common/ConnectionTransport.js'; -import {debugError} from 'puppeteer-core/lib/common/util.js'; +import {DEBUG_PREFIXES, debug} from 'puppeteer-core/lib/common/Debug.js'; import {packageVersion} from '../packageVersion.js'; import type {BrowserWorker} from './BrowserWorker.js'; @@ -76,7 +76,9 @@ export class WorkersWebSocketTransport implements ConnectionTransport { }); this.ws.addEventListener('error', e => { const message = (e as ErrorEvent).message || 'Unknown error'; - debugError(`WebSocket error: SessionID: ${sessionId} - ${message}`); + debug(DEBUG_PREFIXES.error)?.( + `WebSocket error: SessionID: ${sessionId} - ${message}` + ); }); } diff --git a/packages/puppeteer-cloudflare/src/cloudflare/utils.ts b/packages/puppeteer-cloudflare/src/cloudflare/utils.ts index 3d8bd1f74..643d09bd9 100644 --- a/packages/puppeteer-cloudflare/src/cloudflare/utils.ts +++ b/packages/puppeteer-cloudflare/src/cloudflare/utils.ts @@ -9,7 +9,8 @@ import {CdpBrowser} from 'puppeteer-core/lib/cdp/Browser.js'; import {Connection} from 'puppeteer-core/lib/cdp/Connection.js'; import type {ConnectionTransport} from 'puppeteer-core/lib/common/ConnectionTransport.js'; import type {ConnectOptions} from 'puppeteer-core/lib/common/ConnectOptions.js'; -import {debugError} from 'puppeteer-core/lib/common/util.js'; +import {DEBUG_PREFIXES, debug} from 'puppeteer-core/lib/common/Debug.js'; +import {createIncrementalIdGenerator} from 'puppeteer-core/lib/util/incremental-id-generator.js'; /** * Users should never call this directly; it's called when calling * `puppeteer.connect` with `protocol: 'cdp'`. @@ -23,6 +24,7 @@ export async function connectToCDPBrowser( const { acceptInsecureCerts = false, networkEnabled = true, + issuesEnabled = true, defaultViewport = DEFAULT_VIEWPORT, downloadBehavior, targetFilter, @@ -30,6 +32,10 @@ export async function connectToCDPBrowser( slowMo = 0, protocolTimeout, handleDevToolsAsPage, + idGenerator = createIncrementalIdGenerator(), + blocklist, + allowlist, + logger = debug, sessionId = 'unknown', } = options; @@ -37,7 +43,10 @@ export async function connectToCDPBrowser( '', connectionTransport, slowMo, - protocolTimeout + protocolTimeout, + /* rawErrors */ false, + idGenerator, + logger ); const {browserContextIds} = await connection.send( @@ -51,13 +60,19 @@ export async function connectToCDPBrowser( downloadBehavior, undefined, () => { - return connection.send('Browser.close').catch(debugError); + return connection.send('Browser.close').catch(error => { + logger?.(DEBUG_PREFIXES.error)?.(error); + }); }, targetFilter, isPageTarget, undefined, networkEnabled, + issuesEnabled, handleDevToolsAsPage, + blocklist, + allowlist, + logger, sessionId ); return browser; diff --git a/packages/puppeteer-cloudflare/src/index.ts b/packages/puppeteer-cloudflare/src/index.ts index 5a2a4064b..6f7ecf74d 100644 --- a/packages/puppeteer-cloudflare/src/index.ts +++ b/packages/puppeteer-cloudflare/src/index.ts @@ -11,9 +11,31 @@ import {environment} from 'puppeteer-core/lib/environment.js'; // ProtocolMapping.Commands so session.send('Cloudflare.*', ...) is typed. import './cloudflare/protocol.js'; -Object.defineProperties(environment.value, { - fs: {value: fs}, - path: {value: path}, +// Wire puppeteer-core's file helpers to the Workers node:fs implementation. +// Used for `path` options, e.g. page.pdf({path}) and page.screenshot({path}). +Object.assign(environment.value, { + path, + readFile: (filePath: string, encoding?: 'utf8' | 'ascii') => { + return fs.promises.readFile(filePath, encoding ? {encoding} : undefined); + }, + writeFile: async (filePath: string, data: Uint8Array | string) => { + await fs.promises.writeFile(filePath, data); + }, + openFileForWriting: (filePath: string) => { + return fs.promises.open(filePath, 'w+'); + }, + createWriteStream: ( + filePath: string, + options?: {encoding?: BufferEncoding; overwrite?: boolean}, + ) => { + return fs.createWriteStream(filePath, { + encoding: options?.encoding, + flags: options?.overwrite === false ? 'wx' : 'w', + }); + }, + mkdir: async (dirPath: string, options?: {recursive?: boolean}) => { + await fs.promises.mkdir(dirPath, options); + }, }); export * from 'puppeteer-core/lib/api/api.js'; export * from 'puppeteer-core/lib/common/common.js'; @@ -24,7 +46,15 @@ export * from './cloudflare/protocol.js'; const puppeteer = new PuppeteerWorkers(); -export const {connect, history, launch, limits, sessions, acquire} = puppeteer; +// Explicit types keep the emitted declarations pointing at PuppeteerWorkers. +// Inferred types would reference puppeteer-core's Node entry point, which pulls +// in Node-only modules such as @puppeteer/browsers. +export const connect: PuppeteerWorkers['connect'] = puppeteer.connect; +export const history: PuppeteerWorkers['history'] = puppeteer.history; +export const launch: PuppeteerWorkers['launch'] = puppeteer.launch; +export const limits: PuppeteerWorkers['limits'] = puppeteer.limits; +export const sessions: PuppeteerWorkers['sessions'] = puppeteer.sessions; +export const acquire: PuppeteerWorkers['acquire'] = puppeteer.acquire; export * from './cloudflare/PuppeteerWorkers.js'; diff --git a/packages/puppeteer-cloudflare/tests/package-lock.json b/packages/puppeteer-cloudflare/tests/package-lock.json index 74d2426ed..8f51d0721 100644 --- a/packages/puppeteer-cloudflare/tests/package-lock.json +++ b/packages/puppeteer-cloudflare/tests/package-lock.json @@ -29,10 +29,8 @@ "version": "1.5.0-rc.0", "license": "Apache-2.0", "dependencies": { - "@types/debug": "^4.1.12", "@types/node": "^18.17.15", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1581282", + "devtools-protocol": "0.0.1666840", "mitt": "3.0.1", "parsel-js": "1.2.2", "rxjs": "7.8.2", @@ -52,17 +50,6 @@ "vite": "^6.4.3" } }, - "../node_modules/@types/debug": { - "version": "4.1.13", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "../node_modules/@types/debug/node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" - }, "../node_modules/@types/node": { "version": "18.19.130", "license": "MIT", @@ -74,27 +61,8 @@ "version": "5.26.5", "license": "MIT" }, - "../node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "../node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "license": "MIT" - }, "../node_modules/devtools-protocol": { - "version": "0.0.1581282", + "version": "0.0.1666840", "license": "BSD-3-Clause" }, "../node_modules/parsel-js": { diff --git a/packages/puppeteer-cloudflare/tests/src/browser-rendering/session-management.spec.ts b/packages/puppeteer-cloudflare/tests/src/browser-rendering/session-management.spec.ts index 8edeaab0f..e1e20d8d8 100644 --- a/packages/puppeteer-cloudflare/tests/src/browser-rendering/session-management.spec.ts +++ b/packages/puppeteer-cloudflare/tests/src/browser-rendering/session-management.spec.ts @@ -336,13 +336,13 @@ test(`should keep a connected session open past keep_alive`, async () => { return setTimeout(resolve, 11000); }); await fetchSingleSession(env.BROWSER, sessionId); - expect(browser.isConnected()).toBe(true); + expect(browser.connected).toBe(true); await new Promise(resolve => { return setTimeout(resolve, 5000); }); await fetchSingleSession(env.BROWSER, sessionId); - expect(browser.isConnected()).toBe(true); + expect(browser.connected).toBe(true); } finally { await browser.close().catch(() => {}); } diff --git a/packages/puppeteer-cloudflare/tests/src/skipTests.ts b/packages/puppeteer-cloudflare/tests/src/skipTests.ts index 78de1bd20..4ed40a6b5 100644 --- a/packages/puppeteer-cloudflare/tests/src/skipTests.ts +++ b/packages/puppeteer-cloudflare/tests/src/skipTests.ts @@ -3,164 +3,175 @@ export const skipTests: string[] = [ // newer. Chrome 128 ignores windowBounds/background, lacks setContentsSize, // reports the fixed iframe element as non-clickable, and accepts the inline // script despite the Worker-hosted CSP setup. - "page.spec.ts > Page > Page.newPage > should open pages in a new window at the specified position", - "page.spec.ts > Page > Page.newPage > should open pages in a new window in maximized state", - "page.spec.ts > Page > Page.resize > should resize the browser window to fit page content", - "page.spec.ts > Page > Page.newPage > should create a background page", - "click.spec.ts > Page.click > should click the button with fixed position inside an iframe", - "page.spec.ts > Page > Page.addScriptTag > should throw when added with content to the CSP page", + "page.test.ts > Page > Page.newPage > should open pages in a new window at the specified position", + "page.test.ts > Page > Page.newPage > should open pages in a new window in maximized state", + "page.test.ts > Page > Page.resize > should resize the browser window to fit page content", + "page.test.ts > Page > Page.newPage > should create a background page", + "click.test.ts > Page.click > should click the button with fixed position inside an iframe", + "page.test.ts > Page > Page.addScriptTag > should throw when added with content to the CSP page", // Browser Run page navigator.userAgent returns Cloudflare-Workers, while CDP // Browser.getVersion and request headers use the stock or overridden Chrome // UA. The page UA ignores explicit UA overrides, although platform overrides // work, so upstream platform consistency, device emulation, and reset // assertions cannot hold. - "page.spec.ts > Page > Page.setUserAgent > should work with platform option without userAgent", - "page.spec.ts > Page > Page.setUserAgent > should restore original", - "page.spec.ts > Page > Page.setUserAgent > should emulate device user-agent", + "page.test.ts > Page > Page.setUserAgent > should work with platform option without userAgent", + "page.test.ts > Page > Page.setUserAgent > should restore original", + "page.test.ts > Page > Page.setUserAgent > should emulate device user-agent", // These match Puppeteer's own TestExpectations.json. This Worker runner // cannot represent expected failures, so it records them as explicit skips. - "coverage.spec.ts > Coverage specs > JSCoverage > should not hang when there is a debugger statement", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should transfer RegEx", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should return RegEx", - "navigation.spec.ts > navigation > Page.goto > should work when navigating to a URL with a client redirect", + "coverage.test.ts > Coverage specs > JSCoverage > should not hang when there is a debugger statement", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should transfer RegEx", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should return RegEx", + "navigation.test.ts > navigation > Page.goto > should work when navigating to a URL with a client redirect", - "oopif.spec.ts > OOPIF > should treat OOP iframes and normal iframes the same", - "oopif.spec.ts > OOPIF > should track navigations within OOP iframes", - "oopif.spec.ts > OOPIF > should support OOP iframes becoming normal iframes again", - "oopif.spec.ts > OOPIF > should support frames within OOP frames", - "oopif.spec.ts > OOPIF > should recover cross-origin frames on reconnect", - "oopif.spec.ts > OOPIF > should support OOP iframes getting detached", - "oopif.spec.ts > OOPIF > should support wait for navigation for transitions from local to OOPIF", - "oopif.spec.ts > OOPIF > should keep track of a frames OOP state", - "oopif.spec.ts > OOPIF > should support evaluating in oop iframes", - "oopif.spec.ts > OOPIF > should provide access to elements", - "oopif.spec.ts > OOPIF > should report oopif frames", - "oopif.spec.ts > OOPIF > should wait for inner OOPIFs", - "oopif.spec.ts > OOPIF > should load oopif iframes with subresources and request interception", - "oopif.spec.ts > OOPIF > should support frames within OOP iframes", - "oopif.spec.ts > OOPIF > clickablePoint, boundingBox, boxModel should work for elements inside OOPIFs", - "oopif.spec.ts > OOPIF > should detect existing OOPIFs when Puppeteer connects to an existing page", - "oopif.spec.ts > OOPIF > should support lazy OOP frames", - "oopif.spec.ts > OOPIF > should exposeFunction on a page with a PDF viewer", - "oopif.spec.ts > OOPIF > should evaluate on a page with a PDF viewer", - "oopif.spec.ts > OOPIF > should support evaluateOnNewDocument", - "oopif.spec.ts > OOPIF > should support removing evaluateOnNewDocument scripts", - "oopif.spec.ts > OOPIF > should support exposeFunction", - "oopif.spec.ts > OOPIF > should support removing exposed function", - "oopif.spec.ts > OOPIF > waitForFrame > should resolve immediately if the frame already exists", - "oopif.spec.ts > OOPIF > should report google.com frame", - "oopif.spec.ts > OOPIF > should expose events within OOPIFs", - "page.spec.ts > Page > Page.setUserAgent > should work", - "page.spec.ts > Page > Page.setUserAgent > should work for subframes", - "page.spec.ts > Page > Page.addScriptTag > should work with a path and type=module", - "page.spec.ts > Page > Page.addScriptTag > should work with a path", - "page.spec.ts > Page > Page.addScriptTag > should include sourcemap when path is provided", - "page.spec.ts > Page > Page.addStyleTag > should work with a path", - "page.spec.ts > Page > Page.addStyleTag > should include sourcemap when path is provided", - "elementhandle.spec.ts > ElementHandle specs > ElementHandle[Symbol.dispose] > should work", - "elementhandle.spec.ts > ElementHandle specs > ElementHandle[Symbol.asyncDispose] > should work", - "elementhandle.spec.ts > ElementHandle specs > ElementHandle.move > should work", - "cookies.spec.ts > Cookie specs > Page.cookies > should get a cookie", - "cookies.spec.ts > Cookie specs > Page.cookies > should get multiple cookies", - "cookies.spec.ts > Cookie specs > Page.setCookie > should set cookie with reasonable defaults", - "cookies.spec.ts > Cookie specs > Page.setCookie > should set a cookie with a path", - "cookies.spec.ts > Cookie specs > Page.setCookie > should set cookies from a frame", - "cookies.spec.ts > Cookie specs > Page.deleteCookie > should delete cookie for specified URL", - "locator.spec.ts > Locator > Locator.click > should time out", - "locator.spec.ts > Locator > Locator.click > should retry clicks on errors", - "locator.spec.ts > Locator > Locator.click > can be aborted", - "locator.spec.ts > Locator > Locator.race > can be aborted", - "locator.spec.ts > Locator > Locator.race > should time out when all locators do not match", - "locator.spec.ts > Locator > Locator.prototype.filter > should resolve as soon as the predicate matches", - "navigation.spec.ts > navigation > Page.goto > should fail when navigating to bad SSL", + "oopif.test.ts > OOPIF > should treat OOP iframes and normal iframes the same", + "oopif.test.ts > OOPIF > should track navigations within OOP iframes", + "oopif.test.ts > OOPIF > should support OOP iframes becoming normal iframes again", + "oopif.test.ts > OOPIF > should support frames within OOP frames", + "oopif.test.ts > OOPIF > should recover cross-origin frames on reconnect", + "oopif.test.ts > OOPIF > should support OOP iframes getting detached", + "oopif.test.ts > OOPIF > should support wait for navigation for transitions from local to OOPIF", + "oopif.test.ts > OOPIF > should keep track of a frames OOP state", + "oopif.test.ts > OOPIF > should support evaluating in oop iframes", + "oopif.test.ts > OOPIF > should provide access to elements", + "oopif.test.ts > OOPIF > should report oopif frames", + "oopif.test.ts > OOPIF > should wait for inner OOPIFs", + "oopif.test.ts > OOPIF > should load oopif iframes with subresources and request interception", + "oopif.test.ts > OOPIF > should support frames within OOP iframes", + "oopif.test.ts > OOPIF > clickablePoint, boundingBox, boxModel should work for elements inside OOPIFs", + "oopif.test.ts > OOPIF > should detect existing OOPIFs when Puppeteer connects to an existing page", + "oopif.test.ts > OOPIF > should support lazy OOP frames", + "oopif.test.ts > OOPIF > should exposeFunction on a page with a PDF viewer", + "oopif.test.ts > OOPIF > should evaluate on a page with a PDF viewer", + "oopif.test.ts > OOPIF > should support evaluateOnNewDocument", + "oopif.test.ts > OOPIF > should support removing evaluateOnNewDocument scripts", + "oopif.test.ts > OOPIF > should support exposeFunction", + "oopif.test.ts > OOPIF > should support removing exposed function", + "oopif.test.ts > OOPIF > waitForFrame > should resolve immediately if the frame already exists", + "oopif.test.ts > OOPIF > should report google.com frame", + "oopif.test.ts > OOPIF > should expose events within OOPIFs", + "page.test.ts > Page > Page.setUserAgent > should work", + "page.test.ts > Page > Page.setUserAgent > should work for subframes", + "page.test.ts > Page > Page.addScriptTag > should work with a path and type=module", + "page.test.ts > Page > Page.addScriptTag > should work with a path", + "page.test.ts > Page > Page.addScriptTag > should include sourcemap when path is provided", + "page.test.ts > Page > Page.addStyleTag > should work with a path", + "page.test.ts > Page > Page.addStyleTag > should include sourcemap when path is provided", + "elementhandle.test.ts > ElementHandle specs > ElementHandle[Symbol.dispose] > should work", + "elementhandle.test.ts > ElementHandle specs > ElementHandle[Symbol.asyncDispose] > should work", + "elementhandle.test.ts > ElementHandle specs > ElementHandle.move > should work", + "cookies.test.ts > Cookie specs > Page.cookies > should get a cookie", + "cookies.test.ts > Cookie specs > Page.cookies > should get multiple cookies", + "cookies.test.ts > Cookie specs > Page.setCookie > should set cookie with reasonable defaults", + "cookies.test.ts > Cookie specs > Page.setCookie > should set a cookie with a path", + "cookies.test.ts > Cookie specs > Page.setCookie > should set cookies from a frame", + "cookies.test.ts > Cookie specs > Page.deleteCookie > should delete cookie for specified URL", + "locator.test.ts > Locator > Locator.click > should time out", + "locator.test.ts > Locator > Locator.click > should retry clicks on errors", + "locator.test.ts > Locator > Locator.click > can be aborted", + "locator.test.ts > Locator > Locator.race > can be aborted", + "locator.test.ts > Locator > Locator.race > should time out when all locators do not match", + "locator.test.ts > Locator > Locator.prototype.filter > should resolve as soon as the predicate matches", + "navigation.test.ts > navigation > Page.goto > should fail when navigating to bad SSL", // Workers.dev always presents valid managed TLS, so the remote TestServer // cannot provide the invalid certificate required by this upstream test. - "navigation.spec.ts > navigation > Page.goto > should fail when navigating to bad SSL after redirects", - "navigation.spec.ts > navigation > Page.goto > should not leak listeners during navigation", - "navigation.spec.ts > navigation > Page.goto > should not leak listeners during bad navigation", - "navigation.spec.ts > navigation > Page.goto > should not leak listeners during navigation of 11 pages", - "navigation.spec.ts > navigation > Page.goto > should fail when navigating and show the url at the error message", - "coverage.spec.ts > Coverage specs > JSCoverage > should work with conditionals", - "coverage.spec.ts > Coverage specs > CSSCoverage > should work with complicated usecases", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should replace symbols with undefined", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should work for circular object", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should transfer 100Mb of data from page to node.js", - "evaluation.spec.ts > Evaluation specs > Page.evaluate > should return properly serialize objects with unknown type fields", - "jshandle.spec.ts > JSHandle > JSHandle.toString > should work with window subtypes", - "jshandle.spec.ts > JSHandle > JSHandle[Symbol.dispose] > should work", - "jshandle.spec.ts > JSHandle > JSHandle[Symbol.asyncDispose] > should work", - "jshandle.spec.ts > JSHandle > JSHandle.move > should work", - "dialog.spec.ts > Page.Events.Dialog > should fire", - "dialog.spec.ts > Page.Events.Dialog > should allow accepting prompts", - "frame.spec.ts > Frame specs > Frame Management > should handle nested frames", - "input.spec.ts > input tests > ElementHandle.uploadFile > should read the file", - "input.spec.ts > input tests > FileChooser.accept > should be able to read selected file", - "mouse.spec.ts > Mouse > should not throw if buttons are pressed twice", - "mouse.spec.ts > Mouse > should reset properly", - "drag-and-drop.spec.ts > Drag n' Drop > should drag and drop", - "keyboard.spec.ts > Keyboard > should send a character with sendCharacter in iframe", - "jshandle.spec.ts > JSHandle > JSHandle.jsonValue > should work with dates", - "jshandle.spec.ts > JSHandle > JSHandle.jsonValue > should not throw for circular objects", - "keyboard.spec.ts > Keyboard > ElementHandle.press should not support |text| option", - "page.spec.ts > Page > Page.pdf > can print to PDF and save to file", + "navigation.test.ts > navigation > Page.goto > should fail when navigating to bad SSL after redirects", + "navigation.test.ts > navigation > Page.goto > should not leak listeners during navigation", + "navigation.test.ts > navigation > Page.goto > should not leak listeners during bad navigation", + "navigation.test.ts > navigation > Page.goto > should not leak listeners during navigation of 11 pages", + "navigation.test.ts > navigation > Page.goto > should fail when navigating and show the url at the error message", + "coverage.test.ts > Coverage specs > JSCoverage > should work with conditionals", + "coverage.test.ts > Coverage specs > CSSCoverage > should work with complicated usecases", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should replace symbols with undefined", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should work for circular object", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should transfer 100Mb of data from page to node.js", + "evaluation.test.ts > Evaluation specs > Page.evaluate > should return properly serialize objects with unknown type fields", + "jshandle.test.ts > JSHandle > JSHandle.toString > should work with window subtypes", + "jshandle.test.ts > JSHandle > JSHandle[Symbol.dispose] > should work", + "jshandle.test.ts > JSHandle > JSHandle[Symbol.asyncDispose] > should work", + "jshandle.test.ts > JSHandle > JSHandle.move > should work", + "dialog.test.ts > Page.Events.Dialog > should fire", + "dialog.test.ts > Page.Events.Dialog > should allow accepting prompts", + "frame.test.ts > Frame specs > Frame Management > should handle nested frames", + "input.test.ts > input tests > ElementHandle.uploadFile > should read the file", + "input.test.ts > input tests > FileChooser.accept > should be able to read selected file", + "mouse.test.ts > Mouse > should not throw if buttons are pressed twice", + "mouse.test.ts > Mouse > should reset properly", + "drag-and-drop.test.ts > Drag n' Drop > should drag and drop", + "keyboard.test.ts > Keyboard > should send a character with sendCharacter in iframe", + "jshandle.test.ts > JSHandle > JSHandle.jsonValue > should work with dates", + "jshandle.test.ts > JSHandle > JSHandle.jsonValue > should not throw for circular objects", + "keyboard.test.ts > Keyboard > ElementHandle.press should not support |text| option", + "page.test.ts > Page > Page.pdf > can print to PDF and save to file", // Browser Run shares a remote browser session across tests. Crashing a page // destroys that session rather than only the target under test. - "page.spec.ts > Page > Page.Events.error > should throw when page crashes", + "page.test.ts > Page > Page.Events.error > should throw when page crashes", // Worker isolates disable dynamic code generation. Puppeteer's custom query // registration path compiles user handlers with Function constructors. - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should register and unregister", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should work for multiple elements", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should eval correctly", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should wait correctly with waitForSelector", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should wait correctly with waitForSelector on an element", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should work when both queryOne and queryAll are registered", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should eval when both queryOne and queryAll are registered", - "elementhandle.spec.ts > ElementHandle specs > Custom queries > should work with function shorthands", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should query existing element", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should return empty array for non-existing element", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should return first element", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should pierce shadow DOM", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should query deeply nested text", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should query inputs", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should not query radio", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should query text spanning multiple elements", - "queryhandler.spec.ts > Query handler tests > Text selectors > in Page > should clear caches", - "queryhandler.spec.ts > Query handler tests > Text selectors > in ElementHandles > should query existing element", - "queryhandler.spec.ts > Query handler tests > Text selectors > in ElementHandles > should return null for non-existing element", - "queryhandler.spec.ts > Query handler tests > P selectors > should work with custom selectors", - "queryhandler.spec.ts > Query handler tests > P selectors > should work with custom selectors with args", - "queryselector.spec.ts > querySelector > QueryAll > should have registered handler", - "queryselector.spec.ts > querySelector > QueryAll > $$ should query existing elements", - "queryselector.spec.ts > querySelector > QueryAll > $$ should return empty array for non-existing elements", - "queryselector.spec.ts > querySelector > QueryAll > $$eval should work", - "queryselector.spec.ts > querySelector > QueryAll > $$eval should accept extra arguments", - "queryselector.spec.ts > querySelector > QueryAll > $$eval should accept ElementHandles as arguments", - "queryselector.spec.ts > querySelector > QueryAll > $$eval should handle many elements", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should register and unregister", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should work for multiple elements", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should eval correctly", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should wait correctly with waitForSelector", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should wait correctly with waitForSelector on an element", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should work when both queryOne and queryAll are registered", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should eval when both queryOne and queryAll are registered", + "elementhandle.test.ts > ElementHandle specs > Custom queries > should work with function shorthands", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should query existing element", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should return empty array for non-existing element", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should return first element", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should pierce shadow DOM", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should query deeply nested text", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should query inputs", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should not query radio", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should query text spanning multiple elements", + "queryhandler.test.ts > Query handler tests > Text selectors > in Page > should clear caches", + "queryhandler.test.ts > Query handler tests > Text selectors > in ElementHandles > should query existing element", + "queryhandler.test.ts > Query handler tests > Text selectors > in ElementHandles > should return null for non-existing element", + "queryhandler.test.ts > Query handler tests > P selectors > should work with custom selectors", + "queryhandler.test.ts > Query handler tests > P selectors > should work with custom selectors with args", + "queryselector.test.ts > querySelector > QueryAll > should have registered handler", + "queryselector.test.ts > querySelector > QueryAll > $$ should query existing elements", + "queryselector.test.ts > querySelector > QueryAll > $$ should return empty array for non-existing elements", + "queryselector.test.ts > querySelector > QueryAll > $$eval should work", + "queryselector.test.ts > querySelector > QueryAll > $$eval should accept extra arguments", + "queryselector.test.ts > querySelector > QueryAll > $$eval should accept ElementHandles as arguments", + "queryselector.test.ts > querySelector > QueryAll > $$eval should handle many elements", // These assertions depend on localhost URLs, socket failure modes, or // redirect history that the deployed workers.dev TestServer cannot match. - "evaluation.spec.ts > Evaluation specs > Frame.evaluate > should execute after cross-site navigation", - "navigation.spec.ts > navigation > Page.goto > should fail when main resources failed to load", - "navigation.spec.ts > navigation > Page.goBack > should work", + "evaluation.test.ts > Evaluation specs > Frame.evaluate > should execute after cross-site navigation", + "navigation.test.ts > navigation > Page.goto > should fail when main resources failed to load", + "navigation.test.ts > navigation > Page.goBack > should work", // Browser Run returns concrete CdpCDPSession objects from the bundled copy; // they cannot satisfy instanceof checks against the test bundle's copy. - "frame.spec.ts > Frame specs > Frame.client > should return the client instance", - "page.spec.ts > Page > Page.client > should return the client instance", + "frame.test.ts > Frame specs > Frame.client > should return the client instance", + "page.test.ts > Page > Page.client > should return the client instance", // The Worker bundle renames the injected helper, so createFunction3 is not // available under the identifier expected by this direct utility test. - "injected.spec.ts > PuppeteerUtil tests > createFunction tests > should work", + "injected.test.ts > PuppeteerUtil tests > createFunction tests > should work", // Serialized race candidates lose the concrete locator type required by // Locator.race and produce "Unknown locator for race candidate". - "locator.spec.ts > Locator > Locator.race > races multiple locators", - "locator.spec.ts > Locator > Locator.race > should not time out when one of the locators matches", + "locator.test.ts > Locator > Locator.race > races multiple locators", + "locator.test.ts > Locator > Locator.race > should not time out when one of the locators matches", // The generated Worker test has no Mocha context for this.timeout(). - "queryselector.spec.ts > querySelector > Page.$$eval > should handle many elements", + "queryselector.test.ts > querySelector > Page.$$eval > should handle many elements", // Bundling removes the source filename that this test requires in the stack. - "page.spec.ts > Page > Page.exposeFunction > should throw exception in page context", + "page.test.ts > Page > Page.exposeFunction > should throw exception in page context", + + // These tests connect a second client with browser.wsEndpoint(). Browser + // Run connects through the binding and a session ID, so the endpoint is + // empty and puppeteer.connect() rejects the options. + "cdp/network_restrictions.test.ts > Network Restrictions > should detach from targets violating blocklist when connecting to a running browser", + "cdp/network_restrictions.test.ts > Network Restrictions > should detach from targets violating allowlist when connecting to a running browser", + "dialog.test.ts > Page.Events.Dialog > should see dialogs handled by other connections", + + // Page.startScreenRecording is not available before Chrome 153. This matches + // Puppeteer's own TestExpectations.json. + "page.test.ts > Page > Page.record > should record page", ]; diff --git a/packages/puppeteer-cloudflare/tsconfig.types.json b/packages/puppeteer-cloudflare/tsconfig.types.json index 4fbec9018..b9e8597ed 100644 --- a/packages/puppeteer-cloudflare/tsconfig.types.json +++ b/packages/puppeteer-cloudflare/tsconfig.types.json @@ -9,7 +9,7 @@ "outDir": ".types", "paths": { "devtools-protocol/types/protocol-mapping.js": ["../../submodules/puppeteer/packages/puppeteer-core/node_modules/devtools-protocol/types/protocol-mapping.d.ts"], - "puppeteer-core/lib/*": ["../../submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer/*"] + "puppeteer-core/lib/*": ["../../submodules/puppeteer/packages/puppeteer-core/lib/puppeteer/*"] }, "rootDir": "src" } diff --git a/packages/puppeteer-cloudflare/utils/copy_types.js b/packages/puppeteer-cloudflare/utils/copy_types.js index 31d843905..1b0a019ab 100644 --- a/packages/puppeteer-cloudflare/utils/copy_types.js +++ b/packages/puppeteer-cloudflare/utils/copy_types.js @@ -7,11 +7,11 @@ const generatedDir = path.join(packageDir, '.types'); const outputDir = path.join(packageDir, 'lib'); const puppeteerTypesDir = path.resolve( packageDir, - '../../submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer' + '../../submodules/puppeteer/packages/puppeteer-core/lib/puppeteer' ); const thirdPartyTypesDir = path.resolve( packageDir, - '../../submodules/puppeteer/packages/puppeteer-core/lib/esm/third_party' + '../../submodules/puppeteer/packages/puppeteer-core/lib/third_party' ); function replaceExactlyOnce(contents, search, replacement, source) { @@ -61,9 +61,22 @@ export interface SupportedWebDriverCapabilities { * Headers to use for the web socket connection. * @remarks * Only works in the Node.js environment. + * + * @deprecated Use {@link WsOptions.headers} via + * {@link ConnectOptions.wsOptions} instead. When both are set, + * \`wsOptions.headers\` wins. */ headers?: Record; `; + const wsOptionsOption = ` /** + * Options for the WebSocket connection to the browser. + * + * @remarks + * Only used in the Node.js environment. The browser build has no ping frame + * API, so the keep-alive options are ignored there. + */ + wsOptions?: WsOptions; +`; contents = replaceExactlyOnce( contents, @@ -92,6 +105,7 @@ export interface SupportedWebDriverCapabilities { source ); contents = replaceExactlyOnce(contents, capabilitiesOption, '', source); + contents = replaceExactlyOnce(contents, wsOptionsOption, '', source); return replaceExactlyOnce(contents, headersOption, '', source); } @@ -121,6 +135,15 @@ function copyDeclarations( } if (relative === 'common/ConnectOptions.d.ts') { contents = sanitizeConnectOptions(contents, source); + } else if (relative === 'common/Debug.d.ts') { + // The Workers build has no BiDi, so drop its debug channel prefixes. + contents = replaceExactlyOnce( + contents, + ' readonly bidiSend: "puppeteer:webDriverBiDi:SEND ►";\n' + + ' readonly bidiReceive: "puppeteer:webDriverBiDi:RECV ◀";\n', + '', + source + ); } else if (relative === 'index-browser.d.ts') { contents = replaceExactlyOnce( contents, diff --git a/packages/puppeteer-cloudflare/utils/generate_worker_tests.js b/packages/puppeteer-cloudflare/utils/generate_worker_tests.js index 086eb7bcf..32c351d80 100644 --- a/packages/puppeteer-cloudflare/utils/generate_worker_tests.js +++ b/packages/puppeteer-cloudflare/utils/generate_worker_tests.js @@ -8,50 +8,51 @@ import {deleteDir, listFiles, writeFile} from './utils.js'; const basedir = path.dirname(fileURLToPath(import.meta.url)); const excludedFiles = [ - 'acceptInsecureCerts.spec.ts', - 'accessibility.spec.ts', - 'ariaqueryhandler.spec.ts', - 'autofill.spec.ts', - 'browser.spec.ts', - 'browsercontext-cookies.spec.ts', - 'browsercontext.spec.ts', - 'cdp/a11yLoaderId.spec.ts', - 'cdp/backendNodeId.spec.ts', - 'cdp/bfcache.spec.ts', - 'cdp/CDPSession.spec.ts', - 'cdp/devtools.spec.ts', - 'cdp/extensions.spec.ts', - 'cdp/heapSnapshot.spec.ts', - 'cdp/interventionHeaders.spec.ts', - 'cdp/network.spec.ts', - 'cdp/pdf.spec.ts', - 'cdp/pipe.spec.ts', - 'cdp/prerender.spec.ts', - 'cdp/queryObjects.spec.ts', - 'cdp/screencast.spec.ts', - 'cdp/TargetManager.spec.ts', - 'cdp/userDataDir.spec.ts', - 'chromiumonly.spec.ts', - 'connect.spec.ts', - 'debugInfo.spec.ts', - 'defaultbrowsercontext.spec.ts', - 'device-request-prompt.spec.ts', - 'download.spec.ts', - 'emulation.spec.ts', - 'fixtures.spec.ts', - 'headful.spec.ts', - 'idle_override.spec.ts', - 'ignorehttpserrors.spec.ts', - 'launcher.spec.ts', - 'network.spec.ts', - 'proxy.spec.ts', - 'requestinterception-experimental.spec.ts', - 'requestinterception.spec.ts', - 'screenshot.spec.ts', - 'stacktrace.spec.ts', - 'target.spec.ts', - 'waittask.spec.ts', - 'worker.spec.ts', + 'acceptInsecureCerts.test.ts', + 'accessibility.test.ts', + 'ariaqueryhandler.test.ts', + 'autofill.test.ts', + 'browser.test.ts', + 'browsercontext-cookies.test.ts', + 'browsercontext.test.ts', + 'cdp/a11yLoaderId.test.ts', + 'cdp/backendNodeId.test.ts', + 'cdp/bfcache.test.ts', + 'cdp/CDPSession.test.ts', + 'cdp/devtools.test.ts', + 'cdp/extensions.test.ts', + 'cdp/heapSnapshot.test.ts', + 'cdp/interventionHeaders.test.ts', + 'cdp/network.test.ts', + 'cdp/pdf.test.ts', + 'cdp/pipe.test.ts', + 'cdp/prerender.test.ts', + 'cdp/queryObjects.test.ts', + 'cdp/screencast.test.ts', + 'cdp/TargetManager.test.ts', + 'cdp/userDataDir.test.ts', + 'chromiumonly.test.ts', + 'connect.test.ts', + 'debugInfo.test.ts', + 'defaultbrowsercontext.test.ts', + 'device-request-prompt.test.ts', + 'download.test.ts', + 'emulation.test.ts', + 'fixtures.test.ts', + 'followSymlinks.test.ts', + 'headful.test.ts', + 'idle_override.test.ts', + 'ignorehttpserrors.test.ts', + 'launcher.test.ts', + 'network.test.ts', + 'proxy.test.ts', + 'requestinterception-experimental.test.ts', + 'requestinterception.test.ts', + 'screenshot.test.ts', + 'stacktrace.test.ts', + 'target.test.ts', + 'waittask.test.ts', + 'worker.test.ts', ]; const sourceTestsDir = path.join(basedir, '..', '..', '..', 'submodules', 'puppeteer', 'test', 'src'); @@ -118,7 +119,7 @@ function tracingTempDirectoryPlugin() { name: 'tracing-temp-directory', enforce: 'pre', transform(code, id) { - if (!id.endsWith('/tracing.spec.ts')) { + if (!id.endsWith('/tracing.test.ts')) { return; } return code.replaceAll('import.meta.dirname', JSON.stringify('/tmp')); @@ -131,13 +132,13 @@ function remoteTestServerPlugin() { name: 'remote-test-server', enforce: 'pre', transform(code, id) { - if (id.endsWith('/page.spec.ts')) { + if (id.endsWith('/page.test.ts')) { return code.replace( " it('should work with options parameter', async () => {\n const {page, server} = await getTestState();\n\n expect(\n await page.evaluate(() => {\n return navigator.userAgent;\n }),\n ).toContain('Mozilla');\n await page.setUserAgent({userAgent: 'foobar'});", " it('should work with options parameter', async () => {\n const {page, server} = await getTestState();\n\n await page.setUserAgent({userAgent: 'foobar'});", ); } - if (id.endsWith('/console.spec.ts')) { + if (id.endsWith('/console.test.ts')) { // Worker fixtures use HTTPS, so an HTTP URL tests mixed-content blocking // instead of the network failure and console location asserted upstream. // Browser Run's network proxy can surface the failed lookup as a reset. @@ -148,7 +149,7 @@ function remoteTestServerPlugin() { "expect(message.text()).atLeastOneToContain(['ERR_NAME_NOT_RESOLVED', 'ERR_CONNECTION_RESET']);", ); } - if (id.endsWith('/navigation.spec.ts')) { + if (id.endsWith('/navigation.test.ts')) { return code .replace( "it('should work when subframe issues window.stop()', async function () {", @@ -156,18 +157,18 @@ function remoteTestServerPlugin() { ) .replace('timeout: this.timeout() - 1000,', 'timeout: testInfo.timeout - 1000,'); } - if (!id.endsWith('/cookies.spec.ts')) { + if (!id.endsWith('/cookies.test.ts')) { return; } return code .replace( - "domain: 'localhost',\n path: '/',\n sameParty: false,\n expires: -1,\n httpOnly: false,\n secure: false,\n sourceScheme: 'Unset',", - "domain: new URL(server.EMPTY_PAGE).hostname,\n path: '/',\n sameParty: false,\n expires: -1,\n httpOnly: false,\n secure: new URL(server.EMPTY_PAGE).protocol === 'https:',\n sourceScheme: 'Unset',", + "domain: 'localhost',\n path: '/',\n expires: -1,\n httpOnly: false,\n secure: false,\n sourceScheme: 'Unset',", + "domain: new URL(server.EMPTY_PAGE).hostname,\n path: '/',\n expires: -1,\n httpOnly: false,\n secure: new URL(server.EMPTY_PAGE).protocol === 'https:',\n sourceScheme: 'Unset',", ) .replace( - "domain: 'localhost',\n path: '/',\n sameParty: false,\n expires: -1,\n size: 14,\n httpOnly: false,\n secure: false,\n session: true,\n sourceScheme: 'Unset',", - "domain: new URL(server.EMPTY_PAGE).hostname,\n path: '/',\n sameParty: false,\n expires: -1,\n size: 14,\n httpOnly: false,\n secure: new URL(server.EMPTY_PAGE).protocol === 'https:',\n session: true,\n sourceScheme: 'Unset',", + "domain: 'localhost',\n path: '/',\n expires: -1,\n size: 14,\n httpOnly: false,\n secure: false,\n session: true,\n sourceScheme: 'Unset',", + "domain: new URL(server.EMPTY_PAGE).hostname,\n path: '/',\n expires: -1,\n size: 14,\n httpOnly: false,\n secure: new URL(server.EMPTY_PAGE).protocol === 'https:',\n session: true,\n sourceScheme: 'Unset',", ) .replace( ' await expectCookieEquals(await page.cookies(), [\n {\n name: \'partitionCookie\',', @@ -278,6 +279,11 @@ ${[...testFiles, ...cloudflareTestFiles] basedir, '../tests/src/server/mocha-utils', ), + // Tests in subdirectories, e.g. cdp/, import it from the parent. + '../mocha-utils.js': path.resolve( + basedir, + '../tests/src/server/mocha-utils', + ), sinon: path.resolve(basedir, '../tests/src/server/mocks/sinon.ts'), }, diff --git a/packages/puppeteer-cloudflare/vite.config.ts b/packages/puppeteer-cloudflare/vite.config.ts index 1130d5e0e..747bdd5f3 100644 --- a/packages/puppeteer-cloudflare/vite.config.ts +++ b/packages/puppeteer-cloudflare/vite.config.ts @@ -11,14 +11,14 @@ const baseDir = __dirname.replace(/\\/g, '/'); // Use **/ prefix to match anywhere in absolute paths const pathMappings = [ { glob: '**/packages/puppeteer-cloudflare/src/**', target: 'puppeteer-cloudflare' }, - { glob: '**/submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer/**', target: 'puppeteer-core' }, - { glob: '**/submodules/puppeteer/packages/puppeteer-core/lib/esm/third_party/**', target: 'puppeteer-core/third_party' }, + { glob: '**/submodules/puppeteer/packages/puppeteer-core/lib/puppeteer/**', target: 'puppeteer-core' }, + { glob: '**/submodules/puppeteer/packages/puppeteer-core/lib/third_party/**', target: 'puppeteer-core/third_party' }, { glob: '**/submodules/puppeteer/packages/puppeteer-core/node_modules/**', target: 'puppeteer-core/node_modules' }, ]; // Pre-compile globs to regexes (capture: true creates capture groups for **/*) const compiledMappings = pathMappings.map(({ glob, target }) => ({ - regex: picomatch.makeRe(glob, { capture: true }), + regex: picomatch.makeRe(glob, { capture: true, dot: true }), target, })); @@ -91,15 +91,15 @@ export default defineConfig({ // The TypeScript is pre-compiled with `tsc` to handle decorators correctly { find: /^puppeteer-core\/lib\/esm\/puppeteer\/(.+)\.js$/, - replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer/$1.js'), + replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/puppeteer/$1.js'), }, { find: /^puppeteer-core\/lib\/(.+)\.js$/, - replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer/$1.js'), + replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/puppeteer/$1.js'), }, { find: /^puppeteer-core\/lib\//, - replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/esm/puppeteer/'), + replacement: path.resolve(baseDir, '../../submodules/puppeteer/packages/puppeteer-core/lib/puppeteer/'), }, ], }, @@ -143,7 +143,6 @@ export default defineConfig({ 'node:url', 'node:util', 'node:zlib', - 'debug', 'cloudflare:workers', /^chromium-bidi\/.*/, /^devtools-protocol\/.*/, diff --git a/patches/puppeteer.patch b/patches/puppeteer.patch index 0a1882fe1..cefd3ec85 100644 --- a/patches/puppeteer.patch +++ b/patches/puppeteer.patch @@ -1,11 +1,11 @@ diff --git a/packages/puppeteer-core/src/api/Browser.ts b/packages/puppeteer-core/src/api/Browser.ts -index 6a441ee0789..48cd7c1bd79 100644 +index 712e1eeda53..99e7002dbdd 100644 --- a/packages/puppeteer-core/src/api/Browser.ts +++ b/packages/puppeteer-core/src/api/Browser.ts -@@ -727,4 +727,13 @@ export abstract class Browser extends EventEmitter { +@@ -903,4 +903,13 @@ export abstract class Browser extends EventEmitter { * @internal */ - abstract isNetworkEnabled(): boolean; + abstract isIssuesEnabled(): boolean; + + /** + * Browser Run session ID. @@ -17,53 +17,53 @@ index 6a441ee0789..48cd7c1bd79 100644 + } } diff --git a/packages/puppeteer-core/src/cdp/Browser.ts b/packages/puppeteer-core/src/cdp/Browser.ts -index 6d5c2fccde1..648968363ee 100644 +index 094caa838cb..19d5a867724 100644 --- a/packages/puppeteer-core/src/cdp/Browser.ts +++ b/packages/puppeteer-core/src/cdp/Browser.ts -@@ -68,6 +68,7 @@ export class CdpBrowser extends BrowserBase { - waitForInitiallyDiscoveredTargets = true, - networkEnabled = true, - handleDevToolsAsPage = false, +@@ -87,6 +87,7 @@ export class CdpBrowser extends BrowserBase { + blocklist: string[] | undefined = undefined, + allowlist: string[] | undefined = undefined, + logger: Logger, + sessionId?: string, ): Promise { const browser = new CdpBrowser( connection, -@@ -80,6 +81,7 @@ export class CdpBrowser extends BrowserBase { - waitForInitiallyDiscoveredTargets, - networkEnabled, - handleDevToolsAsPage, +@@ -103,6 +104,7 @@ export class CdpBrowser extends BrowserBase { + blocklist, + allowlist, + logger, + sessionId, ); - if (acceptInsecureCerts) { - await connection.send('Security.setIgnoreCertificateErrors', { -@@ -100,6 +102,8 @@ export class CdpBrowser extends BrowserBase { - #networkEnabled = true; - #targetManager: TargetManager; - #handleDevToolsAsPage = false; + + if (allowlist) { +@@ -139,6 +141,8 @@ export class CdpBrowser extends BrowserBase { + #version?: Deferred; + #hasNetworkRestrictions = false; + #subscriptions = new DisposableStack(); + // Keep the remote Browser Run session available for diagnostics. + #sessionId: string; constructor( connection: Connection, -@@ -112,6 +116,7 @@ export class CdpBrowser extends BrowserBase { - waitForInitiallyDiscoveredTargets = true, - networkEnabled = true, - handleDevToolsAsPage = false, +@@ -155,6 +159,7 @@ export class CdpBrowser extends BrowserBase { + blocklist: string[] | undefined = undefined, + allowlist: string[] | undefined = undefined, + logger: Logger, + sessionId?: string, ) { - super(); + super(logger); this.#networkEnabled = networkEnabled; -@@ -139,6 +144,7 @@ export class CdpBrowser extends BrowserBase { - new CdpBrowserContext(this.#connection, this, contextId), +@@ -197,6 +202,7 @@ export class CdpBrowser extends BrowserBase { + new CdpBrowserContext(this.#connection, this, contextId, logger), ); } + this.#sessionId = sessionId || 'unknown'; } #emitDisconnected = () => { -@@ -545,4 +551,12 @@ export class CdpBrowser extends BrowserBase { - override isNetworkEnabled(): boolean { - return this.#networkEnabled; +@@ -767,4 +773,12 @@ export class CdpBrowser extends BrowserBase { + override isIssuesEnabled(): boolean { + return this.#issuesEnabled; } + + /** @@ -74,11 +74,26 @@ index 6d5c2fccde1..648968363ee 100644 + return this.#sessionId; + } } +diff --git a/packages/puppeteer-core/src/cdp/Frame.ts b/packages/puppeteer-core/src/cdp/Frame.ts +index 6835dbd8253..0835458e8cf 100644 +--- a/packages/puppeteer-core/src/cdp/Frame.ts ++++ b/packages/puppeteer-core/src/cdp/Frame.ts +@@ -13,9 +13,9 @@ import type {SetContentWaitForOptions, WaitForOptions} from '../api/Frame.js'; + import {Frame, FrameEvent, throwIfDetached} from '../api/Frame.js'; + import type {HTTPResponse} from '../api/HTTPResponse.js'; + import type {WaitTimeoutOptions} from '../api/Page.js'; ++import type {Realm} from '../api/Realm.js'; + import {DEBUG_PREFIXES, type Logger} from '../common/Debug.js'; + import {UnsupportedOperation} from '../common/Errors.js'; +-import type {Realm} from '../puppeteer-core.js'; + import {Deferred} from '../util/Deferred.js'; + import {disposeSymbol} from '../util/disposable.js'; + import {isErrorLike} from '../util/ErrorLike.js'; diff --git a/packages/puppeteer-core/src/cdp/Page.ts b/packages/puppeteer-core/src/cdp/Page.ts -index fed1282dddf..a6eef18ed05 100644 +index 3a881be5fc2..648c40f87ee 100644 --- a/packages/puppeteer-core/src/cdp/Page.ts +++ b/packages/puppeteer-core/src/cdp/Page.ts -@@ -698,16 +698,36 @@ export class CdpPage extends Page { +@@ -742,16 +742,36 @@ export class CdpPage extends Page { if (!cookie.url && pageURL.startsWith('http')) { item.url = pageURL; } @@ -123,23 +138,24 @@ index fed1282dddf..a6eef18ed05 100644 } } diff --git a/packages/puppeteer-core/src/common/BrowserConnector.ts b/packages/puppeteer-core/src/common/BrowserConnector.ts -index bc7880ae451..86e22a0cc03 100644 +index 0836243b8eb..f78bb01b4a4 100644 --- a/packages/puppeteer-core/src/common/BrowserConnector.ts +++ b/packages/puppeteer-core/src/common/BrowserConnector.ts -@@ -5,22 +5,14 @@ - */ +@@ -6,23 +6,15 @@ + import {URLPattern} from '../../third_party/urlpattern-polyfill/urlpattern-polyfill.js'; import type {Browser} from '../api/Browser.js'; -import {_connectToBiDiBrowser} from '../bidi/BrowserConnector.js'; import {_connectToCdpBrowser} from '../cdp/BrowserConnector.js'; -import {environment, isNode} from '../environment.js'; import {assert} from '../util/assert.js'; import {isErrorLike} from '../util/ErrorLike.js'; - + +import {BrowserWebSocketTransport} from './BrowserWebSocketTransport.js'; import type {ConnectionTransport} from './ConnectionTransport.js'; import type {ConnectOptions} from './ConnectOptions.js'; - + import type {Logger} from './Debug.js'; + -const getWebSocketTransportClass = async () => { - return isNode - ? (await import('../node/NodeWebSocketTransport.js')).NodeWebSocketTransport @@ -150,7 +166,7 @@ index bc7880ae451..86e22a0cc03 100644 /** * Users should never call this directly; it's called when calling * `puppeteer.connect`. This method attaches Puppeteer to an existing browser instance. -@@ -33,21 +25,13 @@ export async function _connectToBrowser( +@@ -67,23 +59,14 @@ export async function _connectToBrowser( const {connectionTransport, endpointUrl} = await getConnectionTransport(options); @@ -159,6 +175,7 @@ index bc7880ae451..86e22a0cc03 100644 - connectionTransport, - endpointUrl, - options, +- options.logger, - ); - return bidiBrowser; - } else { @@ -166,6 +183,7 @@ index bc7880ae451..86e22a0cc03 100644 - connectionTransport, - endpointUrl, - options, +- options.logger, - ); - return cdpBrowser; - } @@ -174,24 +192,22 @@ index bc7880ae451..86e22a0cc03 100644 + connectionTransport, + endpointUrl, + options, ++ options.logger, + ); + return cdpBrowser; } /** -@@ -57,96 +41,35 @@ export async function _connectToBrowser( +@@ -94,7 +77,7 @@ export async function _connectToBrowser( async function getConnectionTransport( - options: ConnectOptions, + options: ConnectOptions & {logger: Logger}, ): Promise<{connectionTransport: ConnectionTransport; endpointUrl: string}> { -- const { -- browserWSEndpoint, -- browserURL, -- channel, -- transport, -- headers = {}, -- } = options; +- const {browserWSEndpoint, browserURL, channel, transport} = options; + const {browserWSEndpoint, browserURL, transport} = options; - + // `wsOptions.headers` supersedes the deprecated top-level `headers`. + const headers = options.wsOptions?.headers ?? options.headers ?? {}; + const wsOptions = options.wsOptions ?? {}; +@@ -102,18 +85,17 @@ async function getConnectionTransport( assert( Number(!!browserWSEndpoint) + Number(!!browserURL) + @@ -202,27 +218,31 @@ index bc7880ae451..86e22a0cc03 100644 - 'Exactly one of browserWSEndpoint, browserURL, transport or channel must be passed to puppeteer.connect', + 'Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect', ); - + if (transport) { return {connectionTransport: transport, endpointUrl: ''}; } else if (browserWSEndpoint) { - const WebSocketClass = await getWebSocketTransportClass(); + // BrowserWebSocketTransport uses the WebSocket API available in Workers. const connectionTransport: ConnectionTransport = -- await WebSocketClass.create(browserWSEndpoint, headers); -+ await BrowserWebSocketTransport.create(browserWSEndpoint); - return { - connectionTransport: connectionTransport, - endpointUrl: browserWSEndpoint, +- await WebSocketClass.create( ++ await BrowserWebSocketTransport.create( + browserWSEndpoint, + headers, + options.logger, +@@ -125,9 +107,9 @@ async function getConnectionTransport( }; } else if (browserURL) { - const connectionURL = await getWSEndpoint(browserURL); + const connectionURL = await getWSEndpoint(browserURL, headers); - const WebSocketClass = await getWebSocketTransportClass(); + // Use the same Workers-compatible transport for discovered endpoints. const connectionTransport: ConnectionTransport = -- await WebSocketClass.create(connectionURL); -+ await BrowserWebSocketTransport.create(connectionURL); - return { +- await WebSocketClass.create( ++ await BrowserWebSocketTransport.create( + connectionURL, + headers, + options.logger, +@@ -137,59 +119,6 @@ async function getConnectionTransport( connectionTransport: connectionTransport, endpointUrl: connectionURL, }; @@ -243,10 +263,7 @@ index bc7880ae451..86e22a0cc03 100644 - ); - const portPath = join(userDataDir, 'DevToolsActivePort'); - try { -- const fileContent = await environment.value.fs.promises.readFile( -- portPath, -- 'ascii', -- ); +- const fileContent = await environment.value.readFile(portPath, 'ascii'); - const [rawPort, rawPath] = fileContent - .split('\n') - .map(line => { @@ -267,6 +284,8 @@ index bc7880ae451..86e22a0cc03 100644 - const connectionTransport = await WebSocketClass.create( - browserWSEndpoint, - headers, +- options.logger, +- wsOptions, - ); - return { - connectionTransport: connectionTransport, @@ -284,13 +303,13 @@ index bc7880ae451..86e22a0cc03 100644 throw new Error('Invalid connection options'); } diff --git a/packages/puppeteer-core/src/common/util.ts b/packages/puppeteer-core/src/common/util.ts -index 61ea5871dfe..d8f36286d12 100644 +index 1ddc8f540fd..34de0ed2948 100644 --- a/packages/puppeteer-core/src/common/util.ts +++ b/packages/puppeteer-core/src/common/util.ts -@@ -188,7 +188,13 @@ export function evaluationString( +@@ -183,7 +183,13 @@ export function evaluationString( return JSON.stringify(arg); } - + - return `(${fun})(${args.map(serializeArgument).join(',')})`; + // function is most likely bundled with wrangler, + // which uses esbuild with keepNames enabled. @@ -302,7 +321,7 @@ index 61ea5871dfe..d8f36286d12 100644 } /** -@@ -200,7 +206,24 @@ export async function getReadableAsTypedArray( +@@ -196,7 +202,21 @@ export async function getReadableAsTypedArray( ): Promise { const buffers: Uint8Array[] = []; const reader = readable.getReader(); @@ -311,28 +330,25 @@ index 61ea5871dfe..d8f36286d12 100644 + path && + globalThis.navigator?.userAgent === 'Cloudflare-Workers' + ) { -+ // Workers file handles do not preserve repeated writeFile append behavior. -+ await environment.value.fs.promises.writeFile(path, new Uint8Array()); ++ // Workers file handles do not preserve repeated writeFile append behavior, ++ // so write the full content once. + while (true) { + const {done, value} = await reader.read(); + if (done) { + break; + } -+ await environment.value.fs.promises.appendFile(path, value); ++ buffers.push(value); + } -+ const file = await environment.value.fs.promises.readFile(path); -+ return file.length === 0 -+ ? null -+ : new Uint8Array(file.buffer, file.byteOffset, file.byteLength); ++ await environment.value.writeFile(path, mergeUint8Arrays(buffers)); + } else if (path) { - const fileHandle = await environment.value.fs.promises.open(path, 'w+'); + const fileHandle = await environment.value.openFileForWriting(path); try { while (true) { diff --git a/packages/puppeteer-core/src/util/Function.ts b/packages/puppeteer-core/src/util/Function.ts index ae0f161c6d9..f0d5ae5f40c 100644 --- a/packages/puppeteer-core/src/util/Function.ts +++ b/packages/puppeteer-core/src/util/Function.ts -@@ -29,27 +29,29 @@ export const createFunction = ( +@@ -29,29 +29,31 @@ export const createFunction = ( */ export function stringifyFunction(fn: (...args: never) => unknown): string { let value = fn.toString(); @@ -377,3 +393,5 @@ index ae0f161c6d9..f0d5ae5f40c 100644 + ? `((__name => (${value}))(t => t))` + : value; } + + /** diff --git a/submodules/puppeteer b/submodules/puppeteer index 2f1762200..88b517ab3 160000 --- a/submodules/puppeteer +++ b/submodules/puppeteer @@ -1 +1 @@ -Subproject commit 2f176220024de0238d850d64aa6b07cb1e9b8e93 +Subproject commit 88b517ab3beb4a237b541cd18ada98ba43de71a7