From b6154905f57dcb6812ae208a42754c0a6f250d5f Mon Sep 17 00:00:00 2001 From: sarang-001 Date: Sat, 11 Jul 2026 21:14:11 +0530 Subject: [PATCH] fix(screencast): auto-save video when page closes without stop 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 #41608 --- .../playwright-core/src/client/screencast.ts | 18 ++++++++++++++++-- tests/library/screencast.spec.ts | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/client/screencast.ts b/packages/playwright-core/src/client/screencast.ts index 6b838a80ead92..8aefd9c5c8b46 100644 --- a/packages/playwright-core/src/client/screencast.ts +++ b/packages/playwright-core/src/client/screencast.ts @@ -16,6 +16,7 @@ import { Artifact } from './artifact'; import { DisposableStub } from './disposable'; +import { Events } from './events'; import type * as api from '../../types/types'; import type { Page } from './page'; @@ -32,6 +33,19 @@ export class Screencast implements api.Screencast { this._page._channel.on('screencastFrame', ({ data, timestamp, viewportWidth, viewportHeight }) => { void this._onFrame?.({ data, timestamp, viewportWidth, viewportHeight }); }); + this._page.once(Events.Page.Close, () => { + // Auto-save the video when the page closes, so recordings are not lost + // if the user forgets to call stop(). + if (this._started && this._savePath && this._artifact) { + const artifact = this._artifact; + const savePath = this._savePath; + this._started = false; + this._onFrame = null; + this._artifact = undefined; + this._savePath = undefined; + artifact.saveAs(savePath).catch(() => {}); + } + }); } async start(options: { onFrame?: (frame: { data: Buffer, timestamp: number, viewportWidth: number, viewportHeight: number }) => Promise|any, path?: string, size?: { width: number, height: number }, quality?: number } = {}): Promise { @@ -54,9 +68,9 @@ export class Screencast implements api.Screencast { } async stop(): Promise { + this._started = false; + this._onFrame = null; await this._page._wrapApiCall(async () => { - this._started = false; - this._onFrame = null; await this._page._channel.screencastStop({}, undefined); if (this._savePath) await this._artifact?.saveAs(this._savePath); diff --git a/tests/library/screencast.spec.ts b/tests/library/screencast.spec.ts index a67b0aaaf250a..fdfd054189b20 100644 --- a/tests/library/screencast.spec.ts +++ b/tests/library/screencast.spec.ts @@ -204,6 +204,24 @@ test('start should finish when page is closed', async ({ browser }, testInfo) => await context.close(); }); +test('should auto-save video when page closes without stop', async ({ browser, server }, testInfo) => { + test.slow(); + const size = { width: 800, height: 800 }; + const context = await browser.newContext({ viewport: size }); + const page = await context.newPage(); + const videoPath = testInfo.outputPath('auto-save-video.webm'); + await page.screencast.start({ path: videoPath, size }); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => document.body.style.backgroundColor = 'red'); + await ensureSomeFrames(page); + // Close the page without calling stop() + await page.close(); + // The video file should be saved automatically + expect(fs.existsSync(videoPath)).toBeTruthy(); + expectFrames(videoPath, size, isAlmostRed); + await context.close(); +}); + test('empty video', async ({ browser }, testInfo) => { test.slow(); const size = { width: 800, height: 800 };