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
9 changes: 7 additions & 2 deletions docs/src/api/class-screencast.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Starts capturing screencast frames.
**Usage**

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

### param: Screencast.start.onFrame
Expand All @@ -39,3 +39,8 @@ Specifies the preferred maximum dimensions of screencast frames. The actual fram
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.

Defaults to 800×800.

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

Stops the screencast started with [`method: Screencast.start`].
10 changes: 7 additions & 3 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16163,11 +16163,11 @@ export interface Screencast {
* **Usage**
*
* ```js
* const disposable = await page.screencast.start(({ data }) => {
* await page.screencast.start(({ data }) => {
* console.log(`frame size: ${data.length}`);
* }, { preferredSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await disposable.dispose();
* await page.screencast.stop();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
Expand All @@ -16179,7 +16179,11 @@ export interface Screencast {
height: number;
};
}): Promise<Disposable>;

/**
* Stops the screencast started with
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
*/
stop(): Promise<void>;
}

type DeviceDescriptor = {
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,24 +21,25 @@ import type { Page } from './page';

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

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

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();
});
if (this._onFrame)
throw new Error('Screencast is already started');
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ 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 @@ -180,8 +179,7 @@ export class DashboardConnection implements Transport, DashboardChannel {
if (this.selectedPage) {
this._pageListeners.forEach(d => d.dispose());
this._pageListeners = [];
await this._screencastSession?.dispose();
this._screencastSession = null;
await this.selectedPage.screencast.stop();
}

this.selectedPage = page;
Expand All @@ -204,7 +202,7 @@ export class DashboardConnection implements Transport, DashboardChannel {
);

const preferredSize = { width: 1280, height: 800 };
this._screencastSession = await page.screencast.start(
await page.screencast.start(
({ data }) => this._writeFrame(data, page.viewportSize()?.width ?? 0, page.viewportSize()?.height ?? 0),
{ preferredSize },
);
Expand All @@ -215,8 +213,7 @@ export class DashboardConnection implements Transport, DashboardChannel {
return;
this._pageListeners.forEach(d => d.dispose());
this._pageListeners = [];
this._screencastSession?.dispose().catch(() => {});
this._screencastSession = null;
this.selectedPage.screencast.stop().catch(() => {});
this.selectedPage = null;
this._lastFrameData = null;
this._lastViewportSize = null;
Expand Down
10 changes: 7 additions & 3 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16163,11 +16163,11 @@ export interface Screencast {
* **Usage**
*
* ```js
* const disposable = await page.screencast.start(({ data }) => {
* await page.screencast.start(({ data }) => {
* console.log(`frame size: ${data.length}`);
* }, { preferredSize: { width: 800, height: 600 } });
* // ... perform actions ...
* await disposable.dispose();
* await page.screencast.stop();
* ```
*
* @param onFrame Callback that receives JPEG-encoded frame data.
Expand All @@ -16179,7 +16179,11 @@ export interface Screencast {
height: number;
};
}): Promise<Disposable>;

/**
* Stops the screencast started with
* [screencast.start(onFrame[, options])](https://playwright.dev/docs/api/class-screencast#screencast-start).
*/
stop(): Promise<void>;
}

type DeviceDescriptor = {
Expand Down
38 changes: 14 additions & 24 deletions tests/library/screencast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ test('screencast.start delivers frames via onFrame callback', async ({ browser,

const frames: Buffer[] = [];
const preferredSize = { width: 500, height: 400 };
const disposable = await page.screencast.start(({ data }) => frames.push(data), { preferredSize });
await page.screencast.start(({ data }) => frames.push(data), { preferredSize });
await page.goto(server.EMPTY_PAGE);
await page.evaluate(() => document.body.style.backgroundColor = 'red');
await rafraf(page, 100);
await disposable.dispose();
await page.screencast.stop();

expect(frames.length).toBeGreaterThan(0);
for (const frame of frames) {
Expand All @@ -47,24 +47,14 @@ test('screencast.start delivers frames via onFrame callback', async ({ browser,
await context.close();
});

test('supports multiple onFrame listeners', async ({ browser, server, trace }) => {
test.skip(trace === 'on', 'trace=on has different screencast image configuration');

test('start throws if screencast is already started', async ({ browser }) => {
const context = await browser.newContext({ viewport: { width: 500, height: 400 } });
const page = await context.newPage();

const frames1: Buffer[] = [];
const frames2: Buffer[] = [];
const disposable1 = await page.screencast.start(({ data }) => frames1.push(data), { preferredSize: { width: 500, height: 400 } });
const disposable2 = await page.screencast.start(({ data }) => frames2.push(data));

await page.goto(server.EMPTY_PAGE);
await rafraf(page, 100);
await disposable1.dispose();
await disposable2.dispose();
await page.screencast.start(() => {});
await expect(page.screencast.start(() => {})).rejects.toThrow('Screencast is already started');

expect(frames1.length).toBeGreaterThan(0);
expect(frames2.length).toBeGreaterThan(0);
await page.screencast.stop();
await context.close();
});

Expand All @@ -74,11 +64,11 @@ test('start allows restart with different options after stop', async ({ browser,
const context = await browser.newContext({ viewport: { width: 500, height: 400 } });
const page = await context.newPage();

const disposable1 = await page.screencast.start(() => {}, { preferredSize: { width: 500, height: 400 } });
await disposable1.dispose();
await page.screencast.start(() => {}, { preferredSize: { width: 500, height: 400 } });
await page.screencast.stop();
// Different options should succeed once the previous screencast is stopped.
const disposable2 = await page.screencast.start(() => {}, { preferredSize: { width: 320, height: 240 } });
await disposable2.dispose();
await page.screencast.start(() => {}, { preferredSize: { width: 320, height: 240 } });
await page.screencast.stop();
await context.close();
});

Expand All @@ -92,10 +82,10 @@ test('start reuses existing screencast when video recording is running', async (
await page.video().start({ size: videoSize });

const frames: Buffer[] = [];
const disposable = await page.screencast.start(({ data }) => frames.push(data), { preferredSize: { width: 320, height: 240 } });
await page.screencast.start(({ data }) => frames.push(data), { preferredSize: { width: 320, height: 240 } });
await page.goto(server.EMPTY_PAGE);
await rafraf(page, 100);
await disposable.dispose();
await page.screencast.stop();

expect(frames.length).toBeGreaterThan(0);
for (const frame of frames) {
Expand All @@ -118,11 +108,11 @@ test('start returns a disposable that stops screencast', async ({ browser, serve
const page = await context.newPage();

const frames: Buffer[] = [];
const disposable = await page.screencast.start(({ data }) => frames.push(data), { preferredSize: { width: 500, height: 400 } });
await page.screencast.start(({ data }) => frames.push(data), { preferredSize: { 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();
await page.screencast.stop();

const frameCountAfterDispose = frames.length;
expect(frameCountAfterDispose).toBeGreaterThan(0);
Expand Down
Loading