Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 11 additions & 20 deletions docs/src/api/class-screencast.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,29 @@ Starts capturing screencast frames.
**Usage**

```js
await page.screencast.start(buffer => {
console.log(`frame size: ${buffer.length}`);
}, { maxSize: { width: 800, height: 600 } });
const disposable = await page.screencast.start(({ data }) => {
console.log(`frame size: ${data.length}`);
}, { preferredSize: { width: 800, height: 600 } });
// ... perform actions ...
await page.screencast.stop();
await disposable.dispose();
```

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

Callback that receives JPEG-encoded frame data.

### option: Screencast.start.maxSize
### option: Screencast.start.preferredSize
* since: v1.59
- `maxSize` ?<[Object]>
- `preferredSize` ?<[Object]>
- `width` <[int]> Max frame width in pixels.
- `height` <[int]> Max frame height in pixels.

Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to 800×800.
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.

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.

## async method: Screencast.stop
* since: v1.59

Stops the screencast started with [`method: Screencast.start`].

**Usage**

```js
await screencast.start(buffer => { /* handle frame */ });
// ... perform actions ...
await screencast.stop();
```
Defaults to 800×800.
83 changes: 29 additions & 54 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16155,6 +16155,35 @@ export interface WebSocketRoute {
[Symbol.asyncDispose](): Promise<void>;
}

/**
* Interface for capturing screencast frames from a page.
*/
export interface Screencast {
/**
* Starts capturing screencast frames.
*
* **Usage**
*
* ```js
* const disposable = await page.screencast.start(({ data }) => {
* console.log(`frame size: ${data.length}`);
* }, { preferredSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await disposable.dispose();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
* @param options
*/
start(onFrame: ((frame: { data: Buffer }) => Promise<any>|any), options?: {
preferredSize?: {
width: number;
height: number;
};
}): Promise<Disposable>;

}

type DeviceDescriptor = {
viewport: ViewportSize;
userAgent: string;
Expand Down Expand Up @@ -21639,60 +21668,6 @@ export interface Route {
request(): Request;
}

/**
* Interface for capturing screencast frames from a page.
*/
export interface Screencast {
/**
* Starts capturing screencast frames.
*
* **Usage**
*
* ```js
* await page.screencast.start(buffer => {
* console.log(`frame size: ${buffer.length}`);
* }, { maxSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await page.screencast.stop();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
* @param options
*/
start(onFrame: ((buffer: Buffer) => Promise<any>|any), options?: {
/**
* Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to
* 800×800.
*/
maxSize?: {
/**
* Max frame width in pixels.
*/
width: number;

/**
* Max frame height in pixels.
*/
height: number;
};
}): Promise<Disposable>;

/**
* Stops the screencast started with
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
*
* **Usage**
*
* ```js
* await screencast.start(buffer => { /* handle frame *\/ });
* // ... perform actions ...
* await screencast.stop();
* ```
*
*/
stop(): Promise<void>;
}

/**
* Selectors can be used to install custom selector engines. See [extensibility](https://playwright.dev/docs/extensibility) for more
* information.
Expand Down
23 changes: 12 additions & 11 deletions packages/playwright-core/src/client/screencast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,24 @@ import type { Page } from './page';

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

constructor(page: Page) {
this._page = page;
this._page._channel.on('screencastFrame', ({ data }) => {
this._onFrame?.(data);
for (const listener of this._listeners)
void listener({ data });
});
}

async start(onFrame: (buffer: Buffer) => any, options: { maxSize?: { width: number, height: number } } = {}): Promise<DisposableStub> {
this._onFrame = onFrame;
await this._page._channel.startScreencast(options);
return new DisposableStub(() => this.stop());
}

async stop(): Promise<void> {
this._onFrame = null;
await this._page._channel.stopScreencast();
async start(onFrame: (frame: { data: Buffer }) => Promise<any>|any, options: { preferredSize?: { width: number, height: number } } = {}): Promise<DisposableStub> {
this._listeners.add(onFrame);
if (this._listeners.size === 1)
await this._page._channel.startScreencast(options);
return new DisposableStub(async () => {
this._listeners.delete(onFrame);
if (!this._listeners.size)
await this._page._channel.stopScreencast();
});
}
}
2 changes: 1 addition & 1 deletion packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1550,7 +1550,7 @@ scheme.PagePickLocatorResult = tObject({
scheme.PageCancelPickLocatorParams = tOptional(tObject({}));
scheme.PageCancelPickLocatorResult = tOptional(tObject({}));
scheme.PageStartScreencastParams = tObject({
maxSize: tOptional(tObject({
preferredSize: tOptional(tObject({
width: tInt,
height: tInt,
})),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
async startScreencast(params: channels.PageStartScreencastParams, progress?: Progress): Promise<channels.PageStartScreencastResult> {
if (this._screencastListener)
throw new Error('Screencast is already running');
const size = params.maxSize || { width: 800, height: 800 };
const size = params.preferredSize || { width: 800, height: 800 };
this._screencastListener = (frame: ScreencastFrame) => {
this._dispatchEvent('screencastFrame', { data: frame.buffer });
};
Expand Down
24 changes: 4 additions & 20 deletions packages/playwright-core/src/server/screencast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ export class Screencast {
private _videoRecorder: VideoRecorder | null = null;
private _videoId: string | null = null;
private _listeners = new Set<ScreencastListener>();
private _screencastOptions: { width: number, height: number, quality: number } | null = null;

// Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
// When throttling for tracing, 200ms between frames, except for 10 frames around the action.
private _frameThrottler = new FrameThrottler(10, 35, 200);
Expand All @@ -47,9 +45,7 @@ export class Screencast {
}

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

Expand Down Expand Up @@ -142,27 +138,15 @@ export class Screencast {
}

async startScreencast(listener: ScreencastListener, options: { width: number, height: number, quality: number }) {
if (this._screencastOptions) {
if (options.width !== this._screencastOptions.width || options.height !== this._screencastOptions.height || options.quality !== this._screencastOptions.quality)
throw new Error(`Screencast is already running with different options (${this._screencastOptions.width}x${this._screencastOptions.height} quality=${this._screencastOptions.quality})`);
}
this._listeners.add(listener);
if (this._listeners.size === 1) {
this._screencastOptions = options;
await this._page.delegate.startScreencast({
width: options.width,
height: options.height,
quality: options.quality,
});
}
if (this._listeners.size === 1)
await this._page.delegate.startScreencast(options);
}

async stopScreencast(listener: ScreencastListener) {
this._listeners.delete(listener);
if (!this._listeners.size) {
this._screencastOptions = null;
if (!this._listeners.size)
await this._page.delegate.stopScreencast();
}
}

onScreencastFrame(frame: types.ScreencastFrame) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class DashboardConnection implements Transport, DashboardChannel {
private _lastFrameData: string | null = null;
private _lastViewportSize: { width: number, height: number } | null = null;
private _pageListeners: { dispose: () => Promise<void> }[] = [];
private _screencastSession: { dispose: () => Promise<void> } | null = null;
private _contextListeners: { dispose: () => Promise<void> }[] = [];
private _eventListeners = new Map<string, Set<Function>>();

Expand Down Expand Up @@ -179,7 +180,8 @@ export class DashboardConnection implements Transport, DashboardChannel {
if (this.selectedPage) {
this._pageListeners.forEach(d => d.dispose());
this._pageListeners = [];
await this.selectedPage.screencast.stop();
await this._screencastSession?.dispose();
this._screencastSession = null;
}

this.selectedPage = page;
Expand All @@ -201,10 +203,10 @@ export class DashboardConnection implements Transport, DashboardChannel {
}),
);

const maxSize = { width: 1280, height: 800 };
await page.screencast.start(
data => this._writeFrame(data, page.viewportSize()?.width ?? 0, page.viewportSize()?.height ?? 0),
{ maxSize },
const preferredSize = { width: 1280, height: 800 };
this._screencastSession = await page.screencast.start(
({ data }) => this._writeFrame(data, page.viewportSize()?.width ?? 0, page.viewportSize()?.height ?? 0),
{ preferredSize },
);
}

Expand All @@ -213,7 +215,8 @@ export class DashboardConnection implements Transport, DashboardChannel {
return;
this._pageListeners.forEach(d => d.dispose());
this._pageListeners = [];
this.selectedPage.screencast.stop().catch(() => {});
this._screencastSession?.dispose().catch(() => {});
this._screencastSession = null;
this.selectedPage = null;
this._lastFrameData = null;
this._lastViewportSize = null;
Expand Down
83 changes: 29 additions & 54 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16155,6 +16155,35 @@ export interface WebSocketRoute {
[Symbol.asyncDispose](): Promise<void>;
}

/**
* Interface for capturing screencast frames from a page.
*/
export interface Screencast {
/**
* Starts capturing screencast frames.
*
* **Usage**
*
* ```js
* const disposable = await page.screencast.start(({ data }) => {
* console.log(`frame size: ${data.length}`);
* }, { preferredSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await disposable.dispose();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
* @param options
*/
start(onFrame: ((frame: { data: Buffer }) => Promise<any>|any), options?: {
preferredSize?: {
width: number;
height: number;
};
}): Promise<Disposable>;

}

type DeviceDescriptor = {
viewport: ViewportSize;
userAgent: string;
Expand Down Expand Up @@ -21639,60 +21668,6 @@ export interface Route {
request(): Request;
}

/**
* Interface for capturing screencast frames from a page.
*/
export interface Screencast {
/**
* Starts capturing screencast frames.
*
* **Usage**
*
* ```js
* await page.screencast.start(buffer => {
* console.log(`frame size: ${buffer.length}`);
* }, { maxSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await page.screencast.stop();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
* @param options
*/
start(onFrame: ((buffer: Buffer) => Promise<any>|any), options?: {
/**
* Maximum screencast frame dimensions. The output frame may be smaller to preserve the page aspect ratio. Defaults to
* 800×800.
*/
maxSize?: {
/**
* Max frame width in pixels.
*/
width: number;

/**
* Max frame height in pixels.
*/
height: number;
};
}): Promise<Disposable>;

/**
* Stops the screencast started with
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
*
* **Usage**
*
* ```js
* await screencast.start(buffer => { /* handle frame *\/ });
* // ... perform actions ...
* await screencast.stop();
* ```
*
*/
stop(): Promise<void>;
}

/**
* Selectors can be used to install custom selector engines. See [extensibility](https://playwright.dev/docs/extensibility) for more
* information.
Expand Down
Loading
Loading