diff --git a/docs/src/api/class-screencast.md b/docs/src/api/class-screencast.md index b013d1e54ac8d..8813760712031 100644 --- a/docs/src/api/class-screencast.md +++ b/docs/src/api/class-screencast.md @@ -26,6 +26,7 @@ await screencast.stop(); ## async method: Screencast.start * since: v1.59 +- returns: <[Disposable]> Starts capturing screencast frames. Frames are emitted as [`event: Screencast.screencastFrame`] events. diff --git a/docs/src/api/class-video.md b/docs/src/api/class-video.md index 294478f29373b..945419c3f68c5 100644 --- a/docs/src/api/class-video.md +++ b/docs/src/api/class-video.md @@ -26,33 +26,33 @@ Console.WriteLine(await page.Video.GetPathAsync()); Alternatively, you can use [`method: Video.start`] and [`method: Video.stop`] to record video manually. This approach is mutually exclusive with the `recordVideo` option. ```js -await page.video().start(); +await page.video().start({ path: 'video.webm' }); // ... perform actions ... -await page.video().stop({ path: 'video.webm' }); +await page.video().stop(); ``` ```java -page.video().start(); +page.video().start(new Video.StartOptions().setPath(Paths.get("video.webm"))); // ... perform actions ... -page.video().stop(new Video.StopOptions().setPath(Paths.get("video.webm"))); +page.video().stop(); ``` ```python async -await page.video.start() +await page.video.start(path="video.webm") # ... perform actions ... -await page.video.stop(path="video.webm") +await page.video.stop() ``` ```python sync -page.video.start() +page.video.start(path="video.webm") # ... perform actions ... -page.video.stop(path="video.webm") +page.video.stop() ``` ```csharp -await page.Video.StartAsync(); +await page.Video.StartAsync(new() { Path = "video.webm" }); // ... perform actions ... -await page.Video.StopAsync(new() { Path = "video.webm" }); +await page.Video.StopAsync(); ``` ## async method: Video.delete @@ -94,41 +94,48 @@ Path where the video should be saved. ## async method: Video.start * since: v1.59 +- returns: <[Disposable]> Starts video recording. This method is mutually exclusive with the `recordVideo` context option. **Usage** ```js -await page.video().start(); +await page.video().start({ path: 'video.webm' }); // ... perform actions ... -await page.video().stop({ path: 'video.webm' }); +await page.video().stop(); ``` ```java -page.video().start(); +page.video().start(new Video.StartOptions().setPath(Paths.get("video.webm"))); // ... perform actions ... -page.video().stop(new Video.StopOptions().setPath(Paths.get("video.webm"))); +page.video().stop(); ``` ```python async -await page.video.start() +await page.video.start(path="video.webm") # ... perform actions ... -await page.video.stop(path="video.webm") +await page.video.stop() ``` ```python sync -page.video.start() +page.video.start(path="video.webm") # ... perform actions ... -page.video.stop(path="video.webm") +page.video.stop() ``` ```csharp -await page.Video.StartAsync(); +await page.Video.StartAsync(new() { Path = "video.webm" }); // ... perform actions ... -await page.Video.StopAsync(new() { Path = "video.webm" }); +await page.Video.StopAsync(); ``` +### option: Video.start.path +* since: v1.59 +- `path` <[path]> + +Path where the video should be saved when the recording is stopped. + ### option: Video.start.size * since: v1.59 - `size` ?<[Object]> @@ -141,9 +148,3 @@ Optional dimensions of the recorded video. If not specified the size will be equ * since: v1.59 Stops video recording started with [`method: Video.start`]. - -### option: Video.stop.path -* since: v1.59 -- `path` <[path]> - -Path where the video should be saved. diff --git a/packages/playwright-client/types/types.d.ts b/packages/playwright-client/types/types.d.ts index 2449c65b39211..f2bc66fbe4d24 100644 --- a/packages/playwright-client/types/types.d.ts +++ b/packages/playwright-client/types/types.d.ts @@ -4823,8 +4823,8 @@ export interface Page { /** * Video object associated with this page. Can be used to control video recording with * [video.start([options])](https://playwright.dev/docs/api/class-video#video-start) and - * [video.stop([options])](https://playwright.dev/docs/api/class-video#video-stop), or to access the video file when - * using the `recordVideo` context option. + * [video.stop()](https://playwright.dev/docs/api/class-video#video-stop), or to access the video file when using the + * `recordVideo` context option. */ video(): Video; @@ -21779,7 +21779,7 @@ export interface Screencast { */ height: number; }; - }): Promise; + }): Promise; /** * Stops the screencast started with @@ -22111,13 +22111,13 @@ export interface Tracing { * ``` * * Alternatively, you can use [video.start([options])](https://playwright.dev/docs/api/class-video#video-start) and - * [video.stop([options])](https://playwright.dev/docs/api/class-video#video-stop) to record video manually. This - * approach is mutually exclusive with the `recordVideo` option. + * [video.stop()](https://playwright.dev/docs/api/class-video#video-stop) to record video manually. This approach is + * mutually exclusive with the `recordVideo` option. * * ```js - * await page.video().start(); + * await page.video().start({ path: 'video.webm' }); * // ... perform actions ... - * await page.video().stop({ path: 'video.webm' }); + * await page.video().stop(); * ``` * */ @@ -22146,14 +22146,19 @@ export interface Video { * **Usage** * * ```js - * await page.video().start(); + * await page.video().start({ path: 'video.webm' }); * // ... perform actions ... - * await page.video().stop({ path: 'video.webm' }); + * await page.video().stop(); * ``` * * @param options */ start(options?: { + /** + * Path where the video should be saved when the recording is stopped. + */ + path?: string; + /** * Optional dimensions of the recorded video. If not specified the size will be equal to page viewport scaled down to * fit into 800x800. Actual picture of the page will be scaled down if necessary to fit the specified size. @@ -22169,19 +22174,13 @@ export interface Video { */ height: number; }; - }): Promise; + }): Promise; /** * Stops video recording started with * [video.start([options])](https://playwright.dev/docs/api/class-video#video-start). - * @param options */ - stop(options?: { - /** - * Path where the video should be saved. - */ - path?: string; - }): Promise; + stop(): Promise; } /** diff --git a/packages/playwright-core/src/client/screencast.ts b/packages/playwright-core/src/client/screencast.ts index 618e54e8c71cd..bd1ac3dd82daa 100644 --- a/packages/playwright-core/src/client/screencast.ts +++ b/packages/playwright-core/src/client/screencast.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { DisposableStub } from './disposable'; import { EventEmitter } from './eventEmitter'; import type * as api from '../../types/types'; @@ -28,8 +29,9 @@ export class Screencast extends EventEmitter implements api.Screencast { this._page._channel.on('screencastFrame', ({ data }) => this.emit('screencastframe', { data })); } - async start(options: { maxSize?: { width: number, height: number } } = {}): Promise { + async start(options: { maxSize?: { width: number, height: number } } = {}) { await this._page._channel.startScreencast(options); + return new DisposableStub(() => this.stop()); } async stop(): Promise { diff --git a/packages/playwright-core/src/client/video.ts b/packages/playwright-core/src/client/video.ts index 47e594ae536a7..8b3a026d4b069 100644 --- a/packages/playwright-core/src/client/video.ts +++ b/packages/playwright-core/src/client/video.ts @@ -15,6 +15,7 @@ */ import { Artifact } from './artifact'; +import { DisposableStub } from './disposable'; import { EventEmitter } from './eventEmitter'; import type { Connection } from './connection'; @@ -25,6 +26,7 @@ export class Video extends EventEmitter implements api.Video { private _artifact: Artifact | undefined; private _isRemote = false; private _page: Page; + private _savePath: string | undefined; constructor(page: Page, connection: Connection, artifact: Artifact | undefined) { super(page._platform); @@ -33,16 +35,18 @@ export class Video extends EventEmitter implements api.Video { this._artifact = artifact; } - async start(options: { size?: { width: number, height: number } } = {}): Promise { - const result = await this._page._channel.videoStart(options); + async start(options: { path?: string, size?: { width: number, height: number } } = {}) { + const result = await this._page._channel.videoStart({ size: options.size }); this._artifact = Artifact.from(result.artifact); + this._savePath = options.path; + return new DisposableStub(() => this.stop()); } - async stop(options: { path?: string } = {}): Promise { + async stop(): Promise { await this._page._wrapApiCall(async () => { await this._page._channel.videoStop(); - if (options.path) - await this.saveAs(options.path); + if (this._savePath) + await this.saveAs(this._savePath); }); } diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 2449c65b39211..f2bc66fbe4d24 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -4823,8 +4823,8 @@ export interface Page { /** * Video object associated with this page. Can be used to control video recording with * [video.start([options])](https://playwright.dev/docs/api/class-video#video-start) and - * [video.stop([options])](https://playwright.dev/docs/api/class-video#video-stop), or to access the video file when - * using the `recordVideo` context option. + * [video.stop()](https://playwright.dev/docs/api/class-video#video-stop), or to access the video file when using the + * `recordVideo` context option. */ video(): Video; @@ -21779,7 +21779,7 @@ export interface Screencast { */ height: number; }; - }): Promise; + }): Promise; /** * Stops the screencast started with @@ -22111,13 +22111,13 @@ export interface Tracing { * ``` * * Alternatively, you can use [video.start([options])](https://playwright.dev/docs/api/class-video#video-start) and - * [video.stop([options])](https://playwright.dev/docs/api/class-video#video-stop) to record video manually. This - * approach is mutually exclusive with the `recordVideo` option. + * [video.stop()](https://playwright.dev/docs/api/class-video#video-stop) to record video manually. This approach is + * mutually exclusive with the `recordVideo` option. * * ```js - * await page.video().start(); + * await page.video().start({ path: 'video.webm' }); * // ... perform actions ... - * await page.video().stop({ path: 'video.webm' }); + * await page.video().stop(); * ``` * */ @@ -22146,14 +22146,19 @@ export interface Video { * **Usage** * * ```js - * await page.video().start(); + * await page.video().start({ path: 'video.webm' }); * // ... perform actions ... - * await page.video().stop({ path: 'video.webm' }); + * await page.video().stop(); * ``` * * @param options */ start(options?: { + /** + * Path where the video should be saved when the recording is stopped. + */ + path?: string; + /** * Optional dimensions of the recorded video. If not specified the size will be equal to page viewport scaled down to * fit into 800x800. Actual picture of the page will be scaled down if necessary to fit the specified size. @@ -22169,19 +22174,13 @@ export interface Video { */ height: number; }; - }): Promise; + }): Promise; /** * Stops video recording started with * [video.start([options])](https://playwright.dev/docs/api/class-video#video-start). - * @param options */ - stop(options?: { - /** - * Path where the video should be saved. - */ - path?: string; - }): Promise; + stop(): Promise; } /** diff --git a/tests/library/screencast.spec.ts b/tests/library/screencast.spec.ts index 2b2d207f4fd5b..4ab195b8d0bfb 100644 --- a/tests/library/screencast.spec.ts +++ b/tests/library/screencast.spec.ts @@ -91,6 +91,24 @@ test('start throws when video recording is running with different params', async await context.close(); }); +test('screencast.start dispose stops screencast', async ({ browser, server, trace }) => { + test.skip(trace === 'on', 'trace=on has different screencast image configuration'); + const context = await browser.newContext({ viewport: { width: 1000, height: 400 } }); + const page = await context.newPage(); + + const frames: { data: Buffer }[] = []; + page.screencast().on('screencastframe', frame => frames.push(frame)); + + const disposable = await page.screencast().start({ maxSize: { width: 500, height: 400 } }); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => document.body.style.backgroundColor = 'red'); + await rafraf(page, 100); + await disposable.dispose(); + + expect(frames.length).toBeGreaterThan(0); + await context.close(); +}); + test('video.start does not emit screencastframe events', async ({ page, server, trace }) => { test.skip(trace === 'on', 'trace=on enables screencast frame events'); diff --git a/tests/library/video.spec.ts b/tests/library/video.spec.ts index c373c1f38e51b..138345bccf884 100644 --- a/tests/library/video.spec.ts +++ b/tests/library/video.spec.ts @@ -826,14 +826,14 @@ it.describe('screencast', () => { await page.video().stop(); expectRedFrames(videoPath1, size); - await page.video().start({ size }); + const videoPath3 = testInfo.outputPath('video3.webm'); + await page.video().start({ size, path: videoPath3 }); await page.evaluate(() => document.body.style.backgroundColor = 'rgb(100,100,100)'); await rafraf(page, 100); const videoPath2 = await page.video().path(); expect(videoPath2).toBeDefined(); expect(videoPath2).not.toEqual(videoPath1); - const videoPath3 = testInfo.outputPath('video3.webm'); - await page.video().stop({ path: videoPath3 }); + await page.video().stop(); const contents2 = fs.readFileSync(videoPath2).toString('base64'); const contents3 = fs.readFileSync(videoPath3).toString('base64'); expect(contents2 === contents3).toBeTruthy(); @@ -851,7 +851,7 @@ it.describe('screencast', () => { const page = await context.newPage(); const error = await page.video().start().catch(e => e); expect(error.message).toContain('Video is already being recorded'); - await page.video().stop({ path: testInfo.outputPath('video.webm') }); + await page.video().stop(); await context.close(); }); @@ -891,13 +891,26 @@ it.describe('screencast', () => { const size = { width: 800, height: 800 }; const context = await browser.newContext({ viewport: size }); const page = await context.newPage(); - await page.video().start({ size }); const videoPath = testInfo.outputPath('empty-video.webm'); - await page.video().stop({ path: videoPath }); + await page.video().start({ size, path: videoPath }); + await page.video().stop(); await context.close(); expectFrames(videoPath, size, isAlmostWhite); }); + it('video.start dispose stops recording', async ({ browser }, testInfo) => { + const size = { width: 800, height: 800 }; + const context = await browser.newContext({ viewport: size }); + const page = await context.newPage(); + const videoPath = testInfo.outputPath('dispose-video.webm'); + const disposable = await page.video().start({ size, path: videoPath }); + await page.evaluate(() => document.body.style.backgroundColor = 'red'); + await rafraf(page, 100); + await disposable.dispose(); + expectRedFrames(videoPath, size); + await context.close(); + }); + }); it('should saveAs video', async ({ browser }, testInfo) => {