Skip to content

fix(screencast): auto-save video when page closes without stop#41744

Open
sarang-code2 wants to merge 2 commits into
microsoft:mainfrom
sarang-code2:fix/screencast-auto-save-on-page-close
Open

fix(screencast): auto-save video when page closes without stop#41744
sarang-code2 wants to merge 2 commits into
microsoft:mainfrom
sarang-code2:fix/screencast-auto-save-on-page-close

Conversation

@sarang-code2

Copy link
Copy Markdown

Closes #41608

Problem

When using page.screencast.start({ path: 'video.webm' }), if the page closes before page.screencast.stop() is called, the video file is never saved to disk. The server already stops the recording gracefully, but the client never calls artifact.saveAs() to write the file.

This makes it impossible to record a video showing "the window closing when a button is pressed."

Solution

Listen for Events.Page.Close in the client-side Screencast constructor. When the page closes while a recording is active (with a path option), automatically save the artifact to the specified path.

Changes:

  • packages/playwright-core/src/client/screencast.ts — Register a one-time Page.Close listener that auto-saves the artifact
  • tests/library/screencast.spec.ts — Add test verifying auto-save on page close

Usage

await page.screencast.start({ path: 'video.webm' });
await page.getByRole('button', { name: 'close' }).click(); // closes the page
// video.webm is now automatically saved!

When using page.screencast.start({ path }), if the page closes before
stop() is called, the video file was never saved. The server already
stops the recording gracefully, but the client never called
artifact.saveAs().

Listen for Events.Page.Close in the client Screencast constructor and
auto-save the artifact when the page closes while a recording is active.

Fixes microsoft#41608
@sarang-code2

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@Git-Raini

Copy link
Copy Markdown
Contributor

The artifact.saveAs(savePath) call inside the once(Events.Page.Close, ...) listener is fire-and-forget - .catch(() => {}), nothing awaits it. stop() awaits the same call inside _wrapApiCall; this path doesn't. Events.Page.Close is emitted from _onClose() as a locally-dispatched event, not something close() itself awaits before resolving, so saveAs() can still be mid-write when await page.close() returns.

Why this looks racy

The new test does:

await page.close();
expect(fs.existsSync(videoPath)).toBeTruthy();

That passes only because the save happens to finish before the assertion runs, not because of any ordering guarantee - could flake on a slower disk or a loaded CI runner. Separately, swallowing the save error with .catch(() => {}) means a legitimate failure (bad path, disk full, permissions) produces no signal at all, which is a quieter version of the same silent-data-loss problem this PR fixes.

Should page.close() (or the context/browser close path) await any pending auto-save before resolving, the way stop() does today?

@Git-Raini

Copy link
Copy Markdown
Contributor

Opened sarang-code2#1 against this branch with a fix for the race condition above, in case it's useful - happy to have it merged in, adjusted, or ignored, your call.

Summary: Screencast now tracks the in-flight auto-save as _pendingAutoSave, and Page.close() awaits it in a finally block before resolving. Confirmed safe by tracing PageDispatcher - the server dispatches the 'close' event (which triggers the auto-save) before it sends the response to the close() call, so the pending-save promise is always already assigned by the time close() reaches its finally.

Test results - screencast.spec.ts, 195 runs (13 tests × 15 repeats), all passing
[182/195] [chromium-library] › tests\library\screencast.spec.ts:237:5 › start dispose stops recording
[183/195] [chromium-library] › tests\library\screencast.spec.ts:28:5 › screencast.start delivers frames via onFrame callback
[184/195] [chromium-library] › tests\library\screencast.spec.ts:57:5 › onFrame receives viewport size
[185/195] [chromium-library] › tests\library\screencast.spec.ts:108:5 › start returns a disposable that stops screencast
[186/195] [chromium-library] › tests\library\screencast.spec.ts:83:5 › start throws if screencast is already started
[187/195] [chromium-library] › tests\library\screencast.spec.ts:131:5 › start/stop twice without path creates two files in artifactsDir
[188/195] [chromium-library] › tests\library\screencast.spec.ts:94:5 › start allows restart with different options after stop
[189/195] [chromium-library] › tests\library\screencast.spec.ts:156:5 › start should work when recordVideo is set
[190/195] [chromium-library] › tests\library\screencast.spec.ts:179:5 › start should fail when another recording is in progress
[191/195] [chromium-library] › tests\library\screencast.spec.ts:186:5 › stop should not fail when no recording is in progress
[192/195] [chromium-library] › tests\library\screencast.spec.ts:194:5 › start should finish when page is closed
[193/195] [chromium-library] › tests\library\screencast.spec.ts:207:5 › should auto-save video when page closes without stop
[194/195] [chromium-library] › tests\library\screencast.spec.ts:225:5 › empty video
[195/195] [chromium-library] › tests\library\screencast.spec.ts:237:5 › start dispose stops recording
  195 passed (3.7m)
Test results - full page-close.spec.ts suite (18 tests), no regressions
[5/18] [chromium-library] › tests\library\page-close.spec.ts:65:5 › should set the page close state
[6/18] [chromium-library] › tests\library\page-close.spec.ts:71:5 › should pass page to close event
[7/18] [chromium-library] › tests\library\page-close.spec.ts:79:5 › should terminate network waiters
[8/18] [chromium-library] › tests\library\page-close.spec.ts:100:5 › should return null if parent page has been closed
[9/18] [chromium-library] › tests\library\page-close.spec.ts:92:5 › should be callable twice
[10/18] [chromium-library] › tests\library\page-close.spec.ts:110:5 › should fail with error upon disconnect
[11/18] [chromium-library] › tests\library\page-close.spec.ts:118:5 › page.close should work with window.close
[12/18] [chromium-library] › tests\library\page-close.spec.ts:124:5 › should not throw UnhandledPromiseRejection when page closes
[13/18] [chromium-library] › tests\library\page-close.spec.ts:133:5 › interrupt request.response() and request.allHeaders() on page.close
[14/18] [chromium-library] › tests\library\page-close.spec.ts:152:5 › should not treat navigations as new popups
[15/18] [chromium-library] › tests\library\page-close.spec.ts:166:5 › should not result in unhandled rejection
[16/18] [chromium-library] › tests\library\page-close.spec.ts:180:5 › should reject response.finished if page closes
[17/18] [chromium-library] › tests\library\page-close.spec.ts:200:5 › should not throw when continuing while page is closing
[18/18] [chromium-library] › tests\library\page-close.spec.ts:212:5 › should not throw when continuing after page is closed
  18 passed (3.3s)

@Git-Raini

Copy link
Copy Markdown
Contributor

For visibility: also opened #41746 directly against microsoft/playwright:main, carrying this PR's commit unchanged plus the auto-save-await fix from sarang-code2#1. Full credit to @sarang-code2 for the original diagnosis and fix in the PR description - not trying to route around this one, just making sure there's a mergeable option against main in case that's easier to land. Happy for either this PR or #41746 to be the one that merges.

sarang-code2 added a commit to sarang-code2/playwright that referenced this pull request Jul 12, 2026
Follow-up to review feedback on PR microsoft#41744.

The auto-save triggered from the once(Events.Page.Close, ...) listener was
fire-and-forget — artifact.saveAs(savePath).catch(() => {}) with nothing
awaiting it. That meant page.close() could resolve before the video was
actually on disk, and a failed save produced no signal at all.

Screencast now tracks the in-flight save as _pendingAutoSave, and
Page.close() awaits it in a finally block before resolving.

Co-authored-by: Git-Raini <216259955+Git-Raini@users.noreply.github.com>
@sarang-code2

Copy link
Copy Markdown
Author

Applied the fix from @Git-Raini - Screencast now tracks the in-flight auto-save as _pendingAutoSave, and Page.close() awaits it in a finally block. Thanks for the review and fix!

sarang-code2 added a commit to sarang-code2/playwright that referenced this pull request Jul 12, 2026
Follow-up to review feedback on PR microsoft#41744.

The auto-save triggered from the once(Events.Page.Close, ...) listener was
fire-and-forget — artifact.saveAs(savePath).catch(() => {}) with nothing
awaiting it. That meant page.close() could resolve before the video was
actually on disk, and a failed save produced no signal at all.

Screencast now tracks the in-flight save as _pendingAutoSave, and
Page.close() awaits it in a finally block before resolving.

Co-authored-by: Git-Raini <216259955+Git-Raini@users.noreply.github.com>
@sarang-code2 sarang-code2 force-pushed the fix/screencast-auto-save-on-page-close branch from 60bf317 to 25133ae Compare July 12, 2026 17:32
Follow-up to review feedback on PR microsoft#41744.

The auto-save triggered from the once(Events.Page.Close, ...) listener was
fire-and-forget — artifact.saveAs(savePath).catch(() => {}) with nothing
awaiting it. That meant page.close() could resolve before the video was
actually on disk, and a failed save produced no signal at all.

Screencast now tracks the in-flight save as _pendingAutoSave, and
Page.close() awaits it in a finally block before resolving.

Co-authored-by: Git-Raini <216259955+Git-Raini@users.noreply.github.com>
@sarang-code2 sarang-code2 force-pushed the fix/screencast-auto-save-on-page-close branch from 25133ae to a5c1fb4 Compare July 12, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Automatically stop the screencast video recording and save the video file when the page is closed.

2 participants