Skip to content

Commit 1ae5bfe

Browse files
authored
feat(api): update Screencast.start API (#39756)
1 parent d818d97 commit 1ae5bfe

13 files changed

Lines changed: 151 additions & 188 deletions

File tree

docs/src/api/class-screencast.md

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,29 @@ Starts capturing screencast frames.
1313
**Usage**
1414

1515
```js
16-
await page.screencast.start(buffer => {
17-
console.log(`frame size: ${buffer.length}`);
18-
}, { maxSize: { width: 800, height: 600 } });
16+
const disposable = await page.screencast.start(({ data }) => {
17+
console.log(`frame size: ${data.length}`);
18+
}, { preferredSize: { width: 800, height: 600 } });
1919
// ... perform actions ...
20-
await page.screencast.stop();
20+
await disposable.dispose();
2121
```
2222

2323
### param: Screencast.start.onFrame
2424
* since: v1.59
2525
* langs: js
26-
- `onFrame` <[function]\([Buffer]\): [Promise<any>|any]>
26+
- `onFrame` <[function]\([Object]\): [Promise]>
27+
- `data` <[Buffer]> JPEG-encoded frame data.
2728

2829
Callback that receives JPEG-encoded frame data.
2930

30-
### option: Screencast.start.maxSize
31+
### option: Screencast.start.preferredSize
3132
* since: v1.59
32-
- `maxSize` ?<[Object]>
33+
- `preferredSize` ?<[Object]>
3334
- `width` <[int]> Max frame width in pixels.
3435
- `height` <[int]> Max frame height in pixels.
3536

36-
Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to 800×800.
37+
Specifies the preferred maximum dimensions of screencast frames. The actual frame is scaled to preserve the page’s aspect ratio and may be smaller than these bounds.
3738

39+
If a screencast is already active (e.g. started by tracing or video recording), the existing configuration takes precedence and the frame size may exceed these bounds or this option may be ignored.
3840

39-
## async method: Screencast.stop
40-
* since: v1.59
41-
42-
Stops the screencast started with [`method: Screencast.start`].
43-
44-
**Usage**
45-
46-
```js
47-
await screencast.start(buffer => { /* handle frame */ });
48-
// ... perform actions ...
49-
await screencast.stop();
50-
```
41+
Defaults to 800×800.

packages/playwright-client/types/types.d.ts

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16155,6 +16155,35 @@ export interface WebSocketRoute {
1615516155
[Symbol.asyncDispose](): Promise<void>;
1615616156
}
1615716157

16158+
/**
16159+
* Interface for capturing screencast frames from a page.
16160+
*/
16161+
export interface Screencast {
16162+
/**
16163+
* Starts capturing screencast frames.
16164+
*
16165+
* **Usage**
16166+
*
16167+
* ```js
16168+
* const disposable = await page.screencast.start(({ data }) => {
16169+
* console.log(`frame size: ${data.length}`);
16170+
* }, { preferredSize: { width: 800, height: 600 } });
16171+
* // ... perform actions ...
16172+
* await disposable.dispose();
16173+
* ```
16174+
*
16175+
* @param onFrame Callback that receives JPEG-encoded frame data.
16176+
* @param options
16177+
*/
16178+
start(onFrame: ((frame: { data: Buffer }) => Promise<any>|any), options?: {
16179+
preferredSize?: {
16180+
width: number;
16181+
height: number;
16182+
};
16183+
}): Promise<Disposable>;
16184+
16185+
}
16186+
1615816187
type DeviceDescriptor = {
1615916188
viewport: ViewportSize;
1616016189
userAgent: string;
@@ -21639,60 +21668,6 @@ export interface Route {
2163921668
request(): Request;
2164021669
}
2164121670

21642-
/**
21643-
* Interface for capturing screencast frames from a page.
21644-
*/
21645-
export interface Screencast {
21646-
/**
21647-
* Starts capturing screencast frames.
21648-
*
21649-
* **Usage**
21650-
*
21651-
* ```js
21652-
* await page.screencast.start(buffer => {
21653-
* console.log(`frame size: ${buffer.length}`);
21654-
* }, { maxSize: { width: 800, height: 600 } });
21655-
* // ... perform actions ...
21656-
* await page.screencast.stop();
21657-
* ```
21658-
*
21659-
* @param onFrame Callback that receives JPEG-encoded frame data.
21660-
* @param options
21661-
*/
21662-
start(onFrame: ((buffer: Buffer) => Promise<any>|any), options?: {
21663-
/**
21664-
* Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to
21665-
* 800×800.
21666-
*/
21667-
maxSize?: {
21668-
/**
21669-
* Max frame width in pixels.
21670-
*/
21671-
width: number;
21672-
21673-
/**
21674-
* Max frame height in pixels.
21675-
*/
21676-
height: number;
21677-
};
21678-
}): Promise<Disposable>;
21679-
21680-
/**
21681-
* Stops the screencast started with
21682-
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
21683-
*
21684-
* **Usage**
21685-
*
21686-
* ```js
21687-
* await screencast.start(buffer => { /* handle frame *\/ });
21688-
* // ... perform actions ...
21689-
* await screencast.stop();
21690-
* ```
21691-
*
21692-
*/
21693-
stop(): Promise<void>;
21694-
}
21695-
2169621671
/**
2169721672
* Selectors can be used to install custom selector engines. See [extensibility](https://playwright.dev/docs/extensibility) for more
2169821673
* information.

packages/playwright-core/src/client/screencast.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,24 @@ import type { Page } from './page';
2121

2222
export class Screencast implements api.Screencast {
2323
private readonly _page: Page;
24-
private _onFrame: ((buffer: Buffer) => any) | null = null;
24+
private _listeners = new Set<(frame: { data: Buffer }) => any>();
2525

2626
constructor(page: Page) {
2727
this._page = page;
2828
this._page._channel.on('screencastFrame', ({ data }) => {
29-
this._onFrame?.(data);
29+
for (const listener of this._listeners)
30+
void listener({ data });
3031
});
3132
}
3233

33-
async start(onFrame: (buffer: Buffer) => any, options: { maxSize?: { width: number, height: number } } = {}): Promise<DisposableStub> {
34-
this._onFrame = onFrame;
35-
await this._page._channel.startScreencast(options);
36-
return new DisposableStub(() => this.stop());
37-
}
38-
39-
async stop(): Promise<void> {
40-
this._onFrame = null;
41-
await this._page._channel.stopScreencast();
34+
async start(onFrame: (frame: { data: Buffer }) => Promise<any>|any, options: { preferredSize?: { width: number, height: number } } = {}): Promise<DisposableStub> {
35+
this._listeners.add(onFrame);
36+
if (this._listeners.size === 1)
37+
await this._page._channel.startScreencast(options);
38+
return new DisposableStub(async () => {
39+
this._listeners.delete(onFrame);
40+
if (!this._listeners.size)
41+
await this._page._channel.stopScreencast();
42+
});
4243
}
4344
}

packages/playwright-core/src/protocol/validator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1550,7 +1550,7 @@ scheme.PagePickLocatorResult = tObject({
15501550
scheme.PageCancelPickLocatorParams = tOptional(tObject({}));
15511551
scheme.PageCancelPickLocatorResult = tOptional(tObject({}));
15521552
scheme.PageStartScreencastParams = tObject({
1553-
maxSize: tOptional(tObject({
1553+
preferredSize: tOptional(tObject({
15541554
width: tInt,
15551555
height: tInt,
15561556
})),

packages/playwright-core/src/server/dispatchers/pageDispatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
366366
async startScreencast(params: channels.PageStartScreencastParams, progress?: Progress): Promise<channels.PageStartScreencastResult> {
367367
if (this._screencastListener)
368368
throw new Error('Screencast is already running');
369-
const size = params.maxSize || { width: 800, height: 800 };
369+
const size = params.preferredSize || { width: 800, height: 800 };
370370
this._screencastListener = (frame: ScreencastFrame) => {
371371
this._dispatchEvent('screencastFrame', { data: frame.buffer });
372372
};

packages/playwright-core/src/server/screencast.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ export class Screencast {
3131
private _videoRecorder: VideoRecorder | null = null;
3232
private _videoId: string | null = null;
3333
private _listeners = new Set<ScreencastListener>();
34-
private _screencastOptions: { width: number, height: number, quality: number } | null = null;
35-
3634
// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
3735
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
3836
private _frameThrottler = new FrameThrottler(10, 35, 200);
@@ -47,9 +45,7 @@ export class Screencast {
4745
}
4846

4947
startForTracing(listener: ScreencastListener) {
50-
// If screencast is already running, use the same options, it's ok for tracing.
51-
const options = this._screencastOptions || { width: 800, height: 800, quality: 90 };
52-
this.startScreencast(listener, options).catch(e => debugLogger.log('error', e));
48+
this.startScreencast(listener, { width: 800, height: 800, quality: 90 }).catch(e => debugLogger.log('error', e));
5349
this._frameThrottler.setThrottlingEnabled(true);
5450
}
5551

@@ -142,27 +138,15 @@ export class Screencast {
142138
}
143139

144140
async startScreencast(listener: ScreencastListener, options: { width: number, height: number, quality: number }) {
145-
if (this._screencastOptions) {
146-
if (options.width !== this._screencastOptions.width || options.height !== this._screencastOptions.height || options.quality !== this._screencastOptions.quality)
147-
throw new Error(`Screencast is already running with different options (${this._screencastOptions.width}x${this._screencastOptions.height} quality=${this._screencastOptions.quality})`);
148-
}
149141
this._listeners.add(listener);
150-
if (this._listeners.size === 1) {
151-
this._screencastOptions = options;
152-
await this._page.delegate.startScreencast({
153-
width: options.width,
154-
height: options.height,
155-
quality: options.quality,
156-
});
157-
}
142+
if (this._listeners.size === 1)
143+
await this._page.delegate.startScreencast(options);
158144
}
159145

160146
async stopScreencast(listener: ScreencastListener) {
161147
this._listeners.delete(listener);
162-
if (!this._listeners.size) {
163-
this._screencastOptions = null;
148+
if (!this._listeners.size)
164149
await this._page.delegate.stopScreencast();
165-
}
166150
}
167151

168152
onScreencastFrame(frame: types.ScreencastFrame) {

packages/playwright-core/src/tools/dashboard/dashboardController.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export class DashboardConnection implements Transport, DashboardChannel {
3232
private _lastFrameData: string | null = null;
3333
private _lastViewportSize: { width: number, height: number } | null = null;
3434
private _pageListeners: { dispose: () => Promise<void> }[] = [];
35+
private _screencastSession: { dispose: () => Promise<void> } | null = null;
3536
private _contextListeners: { dispose: () => Promise<void> }[] = [];
3637
private _eventListeners = new Map<string, Set<Function>>();
3738

@@ -179,7 +180,8 @@ export class DashboardConnection implements Transport, DashboardChannel {
179180
if (this.selectedPage) {
180181
this._pageListeners.forEach(d => d.dispose());
181182
this._pageListeners = [];
182-
await this.selectedPage.screencast.stop();
183+
await this._screencastSession?.dispose();
184+
this._screencastSession = null;
183185
}
184186

185187
this.selectedPage = page;
@@ -201,10 +203,10 @@ export class DashboardConnection implements Transport, DashboardChannel {
201203
}),
202204
);
203205

204-
const maxSize = { width: 1280, height: 800 };
205-
await page.screencast.start(
206-
data => this._writeFrame(data, page.viewportSize()?.width ?? 0, page.viewportSize()?.height ?? 0),
207-
{ maxSize },
206+
const preferredSize = { width: 1280, height: 800 };
207+
this._screencastSession = await page.screencast.start(
208+
({ data }) => this._writeFrame(data, page.viewportSize()?.width ?? 0, page.viewportSize()?.height ?? 0),
209+
{ preferredSize },
208210
);
209211
}
210212

@@ -213,7 +215,8 @@ export class DashboardConnection implements Transport, DashboardChannel {
213215
return;
214216
this._pageListeners.forEach(d => d.dispose());
215217
this._pageListeners = [];
216-
this.selectedPage.screencast.stop().catch(() => {});
218+
this._screencastSession?.dispose().catch(() => {});
219+
this._screencastSession = null;
217220
this.selectedPage = null;
218221
this._lastFrameData = null;
219222
this._lastViewportSize = null;

packages/playwright-core/types/types.d.ts

Lines changed: 29 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -16155,6 +16155,35 @@ export interface WebSocketRoute {
1615516155
[Symbol.asyncDispose](): Promise<void>;
1615616156
}
1615716157

16158+
/**
16159+
* Interface for capturing screencast frames from a page.
16160+
*/
16161+
export interface Screencast {
16162+
/**
16163+
* Starts capturing screencast frames.
16164+
*
16165+
* **Usage**
16166+
*
16167+
* ```js
16168+
* const disposable = await page.screencast.start(({ data }) => {
16169+
* console.log(`frame size: ${data.length}`);
16170+
* }, { preferredSize: { width: 800, height: 600 } });
16171+
* // ... perform actions ...
16172+
* await disposable.dispose();
16173+
* ```
16174+
*
16175+
* @param onFrame Callback that receives JPEG-encoded frame data.
16176+
* @param options
16177+
*/
16178+
start(onFrame: ((frame: { data: Buffer }) => Promise<any>|any), options?: {
16179+
preferredSize?: {
16180+
width: number;
16181+
height: number;
16182+
};
16183+
}): Promise<Disposable>;
16184+
16185+
}
16186+
1615816187
type DeviceDescriptor = {
1615916188
viewport: ViewportSize;
1616016189
userAgent: string;
@@ -21639,60 +21668,6 @@ export interface Route {
2163921668
request(): Request;
2164021669
}
2164121670

21642-
/**
21643-
* Interface for capturing screencast frames from a page.
21644-
*/
21645-
export interface Screencast {
21646-
/**
21647-
* Starts capturing screencast frames.
21648-
*
21649-
* **Usage**
21650-
*
21651-
* ```js
21652-
* await page.screencast.start(buffer => {
21653-
* console.log(`frame size: ${buffer.length}`);
21654-
* }, { maxSize: { width: 800, height: 600 } });
21655-
* // ... perform actions ...
21656-
* await page.screencast.stop();
21657-
* ```
21658-
*
21659-
* @param onFrame Callback that receives JPEG-encoded frame data.
21660-
* @param options
21661-
*/
21662-
start(onFrame: ((buffer: Buffer) => Promise<any>|any), options?: {
21663-
/**
21664-
* Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to
21665-
* 800×800.
21666-
*/
21667-
maxSize?: {
21668-
/**
21669-
* Max frame width in pixels.
21670-
*/
21671-
width: number;
21672-
21673-
/**
21674-
* Max frame height in pixels.
21675-
*/
21676-
height: number;
21677-
};
21678-
}): Promise<Disposable>;
21679-
21680-
/**
21681-
* Stops the screencast started with
21682-
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
21683-
*
21684-
* **Usage**
21685-
*
21686-
* ```js
21687-
* await screencast.start(buffer => { /* handle frame *\/ });
21688-
* // ... perform actions ...
21689-
* await screencast.stop();
21690-
* ```
21691-
*
21692-
*/
21693-
stop(): Promise<void>;
21694-
}
21695-
2169621671
/**
2169721672
* Selectors can be used to install custom selector engines. See [extensibility](https://playwright.dev/docs/extensibility) for more
2169821673
* information.

0 commit comments

Comments
 (0)