-
Notifications
You must be signed in to change notification settings - Fork 13.2k
[Browser Rendering] Add session recording docs #29284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: production
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| --- | ||
| pcx_content_type: how-to | ||
| title: Session recording | ||
| description: Record and replay Browser Rendering sessions to visually debug browser automation scripts. | ||
| sidebar: | ||
| order: 2 | ||
| --- | ||
|
|
||
| import { Details, Tabs, TabItem, DashButton } from "~/components"; | ||
|
|
||
| When browser automation fails or behaves unexpectedly, it can be difficult to understand what happened. Session recording captures DOM changes, mouse and keyboard events, and page navigation as structured JSON events — not a video — so it is lightweight and easy to inspect. Recordings are powered by [rrweb](https://github.com/rrweb-io/rrweb) and are opt-in per session. | ||
|
|
||
| ## Enable session recording | ||
|
|
||
| Pass `recording: true` to `puppeteer.launch()` or `playwright.launch()`: | ||
|
|
||
| <Tabs> | ||
| <TabItem label="Puppeteer"> | ||
|
|
||
| ```ts | ||
| import puppeteer from "@cloudflare/puppeteer"; | ||
|
|
||
| interface Env { | ||
| MYBROWSER: Fetcher; | ||
| } | ||
|
|
||
| export default { | ||
| async fetch(request: Request, env: Env): Promise<Response> { | ||
| const browser = await puppeteer.launch(env.MYBROWSER, { recording: true }); | ||
| const page = await browser.newPage(); | ||
|
|
||
| await page.goto("https://example.com"); | ||
| // ... your automation steps ... | ||
|
|
||
| const sessionId = browser.sessionId(); | ||
| await browser.close(); | ||
|
|
||
| return new Response(`Session recorded: ${sessionId}`); | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem label="Playwright"> | ||
|
|
||
| ```ts | ||
| import { launch } from "@cloudflare/playwright"; | ||
|
|
||
| interface Env { | ||
| MYBROWSER: Fetcher; | ||
| } | ||
|
|
||
| export default { | ||
| async fetch(request: Request, env: Env): Promise<Response> { | ||
| const browser = await launch(env.MYBROWSER, { recording: true }); | ||
| const page = await browser.newPage(); | ||
|
|
||
| await page.goto("https://example.com"); | ||
| // ... your automation steps ... | ||
|
|
||
| const sessionId = browser.sessionId(); | ||
| await browser.close(); | ||
|
|
||
| return new Response(`Session recorded: ${sessionId}`); | ||
| }, | ||
| }; | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
|
|
||
| :::note | ||
| The recording is finalized when the browser session closes — whether you call `browser.close()` explicitly, the session reaches its idle timeout, or the Worker terminates for any other reason. The recording is not available until after the session ends. | ||
| ::: | ||
|
|
||
| ## View recordings | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will need to change to Runs instead of Logs for the dash link, check with Visal to confirm if its just |
||
|
|
||
| After a session closes, its recording is available in the Cloudflare dashboard under **Browser Rendering** > **Logs**. Select a session to open the recording viewer, where you can scrub the timeline and inspect DOM state, console output, and browser interactions at each point in time. | ||
|
|
||
| <DashButton url="/?to=/:account/workers/browser-rendering/logs" /> | ||
|
|
||
| ## Retrieve a recording via API | ||
|
|
||
| You can also retrieve a recording programmatically using the session ID. Use `browser.sessionId()` to capture the session ID before closing the browser, then pass it to the recordings endpoint. | ||
|
|
||
| ```bash | ||
| curl https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-rendering/recording/<SESSION_ID> \ | ||
| -H "Authorization: Bearer <API_TOKEN>" | ||
| ``` | ||
|
|
||
| A successful response returns the following fields: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think typically we don't have a table showing response fields. typically its an example response in a code block |
||
|
|
||
| | Field | Type | Description | | ||
| | ----------- | ------ | ----------------------------------------- | | ||
| | `sessionId` | string | Unique identifier for the session. | | ||
| | `startTime` | string | ISO timestamp when the recording started. | | ||
| | `endTime` | string | ISO timestamp when the recording ended. | | ||
| | `events` | object | rrweb events keyed by target ID. | | ||
|
|
||
| ## Replay a recording locally | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dumb q - why might someone want to replay locally? bc they dont want to use our dash? does that mean they need to have downloaded the recording file? |
||
|
|
||
| <Details header="Replaying recordings with rrweb-player"> | ||
|
|
||
| The `events` values in the API response are standard rrweb event arrays. You can pass them directly to [`rrweb-player`](https://github.com/rrweb-io/rrweb/tree/master/packages/rrweb-player) to self-host a replay UI with a timeline scrubber, DOM inspection, and playback controls. | ||
|
|
||
| </Details> | ||
|
|
||
| ## Limits | ||
|
|
||
| - Recording is opt-in. It is not enabled by default. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should mention it's opt-in in the intro! |
||
| - Session recording is only available with Workers Bindings via `launch()`. It is not available with the REST API. | ||
| - The minimum recording duration is 1 second. Sessions shorter than 1 second will not produce a viewable recording. | ||
| - The maximum recording duration is 2 hours. | ||
|
|
||
| ## rrweb limitations | ||
|
|
||
| Session recording uses [rrweb](https://github.com/rrweb-io/rrweb), which records DOM state and events rather than pixels. This approach is lightweight but has the following limitations: | ||
|
|
||
| - **Canvas elements** — The content of `<canvas>` elements is not captured. The element itself appears in the recording as a blank placeholder. | ||
| - **Cross-origin iframes** — Content inside cross-origin `<iframe>` elements is not recorded. Same-origin iframes are recorded normally. | ||
| - **Video and audio** — The DOM structure of `<video>` and `<audio>` elements is captured, but media playback state and content are not. | ||
| - **WebGL** — WebGL rendering is not captured. | ||
| - **Input fields** — The content of all input fields is masked by default and will not be visible in the replay. | ||
| - **Large or complex pages** — Pages with frequent DOM mutations (for example, pages with real-time data feeds or heavy animations) can generate a high volume of events, which increases the size of the recording. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@omarmosid will this session recordings work for CDP? if so we should include