Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 4 additions & 44 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions packages/puppeteer-cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 1 addition & 3 deletions packages/puppeteer-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}`
);
});
}

Expand Down
21 changes: 18 additions & 3 deletions packages/puppeteer-cloudflare/src/cloudflare/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'`.
Expand All @@ -23,21 +24,29 @@ export async function connectToCDPBrowser(
const {
acceptInsecureCerts = false,
networkEnabled = true,
issuesEnabled = true,
defaultViewport = DEFAULT_VIEWPORT,
downloadBehavior,
targetFilter,
_isPageTarget: isPageTarget,
slowMo = 0,
protocolTimeout,
handleDevToolsAsPage,
idGenerator = createIncrementalIdGenerator(),
blocklist,
allowlist,
logger = debug,
sessionId = 'unknown',
} = options;

const connection = new Connection(
'',
connectionTransport,
slowMo,
protocolTimeout
protocolTimeout,
/* rawErrors */ false,
idGenerator,
logger
);

const {browserContextIds} = await connection.send(
Expand All @@ -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;
Expand Down
38 changes: 34 additions & 4 deletions packages/puppeteer-cloudflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';

Expand Down
36 changes: 2 additions & 34 deletions packages/puppeteer-cloudflare/tests/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {});
}
Expand Down
Loading
Loading