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
5 changes: 4 additions & 1 deletion docs/src/api/class-screencast.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,12 @@ The quality of the image, between 0-100.
- `height` <[int]> Max frame height in pixels.

Specifies the 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.
If not specified the size will be equal to page viewport scaled down to fit into 800脳800.

A page is captured only once, and all consumers share that single capture. Tracing captures screenshots
this way as well, so with tracing or the `recordVideo` context option already active this option is
ignored, and both the frames and any recorded video use the size of the running capture.

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

Expand Down
18 changes: 18 additions & 0 deletions docs/src/api/class-tracing.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,24 @@ given name prefix inside the [`option: BrowserType.launch.tracesDir`] directory
To specify the final trace zip file name, you need to pass `path` option to
[`method: Tracing.stop`] instead.

### option: Tracing.start.screencast
* since: v1.63
* langs: js
- `screencast` <[boolean]|[Object]>
- `size` ?<[Object]> Dimensions of the captured screenshots. Each screenshot is scaled down to preserve the page's
aspect ratio and may be smaller than these bounds. If not specified the size will be equal to `viewport` scaled
down to fit into 800x800. Optional.
- `width` <[int]> Max screenshot width in pixels.
- `height` <[int]> Max screenshot height in pixels.
- `quality` ?<[int]> The quality of the screenshots, between 0-100. Defaults to 90. Optional.

Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
Passing `true` is a shortcut for `{}`. Takes precedence over [`option: Tracing.start.screenshots`].

A page is captured only once, and tracing shares that capture with [`method: Screencast.start`] and video recording.
The trace viewer renders the timeline preview at a fixed size, so these settings only matter to those other
consumers: raise them to keep tracing from capping what the others receive.

### option: Tracing.start.screenshots
* since: v1.12
- `screenshots` <[boolean]>
Expand Down
5 changes: 5 additions & 0 deletions docs/src/test-api/class-testoptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ export default defineConfig({
- type: <[Object]|[TraceMode]<"off"|"on"|"retain-on-failure"|"on-first-retry"|"retain-on-first-failure"|"retain-on-failure-and-retries">>
- `mode` <[TraceMode]<"off"|"on"|"retain-on-failure"|"on-first-retry"|"on-all-retries"|"retain-on-first-failure"|"retain-on-failure-and-retries">> Trace recording mode.
- `attachments` ?<[boolean]> Whether to include test attachments. Defaults to true. Optional.
- `screencast` ?<[boolean]|[Object]> Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview. Passing `true` is a shortcut for `{}`. Takes precedence over `screenshots`. Optional.
- `size` ?<[Object]> Dimensions of the captured screenshots. Each screenshot is scaled down to preserve the page's aspect ratio and may be smaller than these bounds. If not specified the size will be equal to `viewport` scaled down to fit into 800x800. Optional.
- `width` <[int]> Max screenshot width in pixels.
- `height` <[int]> Max screenshot height in pixels.
- `quality` ?<[int]> The quality of the screenshots, between 0-100. Defaults to 90. Optional.
- `screenshots` ?<[boolean]> Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview. Defaults to true. Optional.
- `snapshots` ?<[boolean]|[Object]> Which snapshots to capture on every action. Passing `true` is a shortcut for `{ dom: true }`. Defaults to true. Optional.
- `dom` ?<[boolean]> Capture DOM snapshot on every action and record network activity. Optional.
Expand Down
34 changes: 34 additions & 0 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23683,6 +23683,40 @@ export interface Tracing {
*/
name?: string;

/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview. Passing `true` is
* a shortcut for `{}`. Takes precedence over
* [`screenshots`](https://playwright.dev/docs/api/class-tracing#tracing-start-option-screenshots).
*
* A page is captured only once, and tracing shares that capture with
* [screencast.start([options])](https://playwright.dev/docs/api/class-screencast#screencast-start) and video
* recording. The trace viewer renders the timeline preview at a fixed size, so these settings only matter to those
* other consumers: raise them to keep tracing from capping what the others receive.
*/
screencast?: boolean|{
/**
* Dimensions of the captured screenshots. Each screenshot is scaled down to preserve the page's aspect ratio and may
* be smaller than these bounds. If not specified the size will be equal to `viewport` scaled down to fit into
* 800x800. Optional.
*/
size?: {
/**
* Max screenshot width in pixels.
*/
width: number;

/**
* Max screenshot height in pixels.
*/
height: number;
};

/**
* The quality of the screenshots, between 0-100. Defaults to 90. Optional.
*/
quality?: number;
};

/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
Expand Down
10 changes: 10 additions & 0 deletions packages/playwright-core/src/client/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5051,6 +5051,11 @@ export type TracingTracingStartParams = {
snapshotAria?: boolean,
snapshotScreen?: boolean,
screencast?: boolean,
screencastSize?: {
width: number,
height: number,
},
screencastQuality?: number,
live?: boolean,
};
export type TracingTracingStartOptions = {
Expand All @@ -5059,6 +5064,11 @@ export type TracingTracingStartOptions = {
snapshotAria?: boolean,
snapshotScreen?: boolean,
screencast?: boolean,
screencastSize?: {
width: number,
height: number,
},
screencastQuality?: number,
live?: boolean,
};
export type TracingTracingStartResult = void;
Expand Down
8 changes: 6 additions & 2 deletions packages/playwright-core/src/client/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,21 @@ export class Tracing extends ChannelOwner<channels.TracingChannel> implements ap
super(parent, type, guid, initializer);
}

async start(options: { name?: string, title?: string, snapshots?: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, screenshots?: boolean, sources?: boolean, live?: boolean } = {}) {
async start(options: { name?: string, title?: string, snapshots?: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, screencast?: boolean | { size?: { width: number, height: number }, quality?: number }, screenshots?: boolean, sources?: boolean, live?: boolean } = {}) {
await this._wrapApiCall(async () => {
this._includeSources = !!options.sources;
this._isLive = !!options.live;
const snapshots = typeof options.snapshots === 'object' ? options.snapshots : { dom: options.snapshots };
const screencastOption = options.screencast ?? options.screenshots;
const screencast = typeof screencastOption === 'object' ? screencastOption : {};
await this._channel.tracingStart({
name: options.name,
snapshotDom: snapshots.dom,
snapshotAria: snapshots.aria,
snapshotScreen: snapshots.screen,
screencast: options.screenshots,
screencast: !!screencastOption,
screencastSize: screencast.size,
screencastQuality: screencast.quality,
live: options.live,
}, kNoTimeout);
const { traceName } = await this._channel.tracingStartChunk({ name: options.name, title: options.title }, kNoTimeout);
Expand Down
10 changes: 10 additions & 0 deletions packages/playwright-core/src/server/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5052,6 +5052,11 @@ export type TracingTracingStartParams = {
snapshotAria?: boolean,
snapshotScreen?: boolean,
screencast?: boolean,
screencastSize?: {
width: number,
height: number,
},
screencastQuality?: number,
live?: boolean,
};
export type TracingTracingStartOptions = {
Expand All @@ -5060,6 +5065,11 @@ export type TracingTracingStartOptions = {
snapshotAria?: boolean,
snapshotScreen?: boolean,
screencast?: boolean,
screencastSize?: {
width: number,
height: number,
},
screencastQuality?: number,
live?: boolean,
};
export type TracingTracingStartResult = void;
Expand Down
8 changes: 6 additions & 2 deletions packages/playwright-core/src/server/trace/recorder/tracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export type TracerOptions = {
snapshotAria?: boolean;
snapshotScreen?: boolean;
screencast?: boolean;
screencastSize?: types.Size;
screencastQuality?: number;
live?: boolean;
};

Expand Down Expand Up @@ -746,7 +748,7 @@ export class Tracing extends SdkObject implements InstrumentationListener, Snaps
this._appendResource(file, params.buffer);
this._appendTraceEvent(event);
};
this._pageTracingRecorders.set(page, new ScreencastTracingRecorder(page.screencast, onFrame));
this._pageTracingRecorders.set(page, new ScreencastTracingRecorder(page.screencast, onFrame, this._state!.options.screencastSize, this._state!.options.screencastQuality));
}

private _appendTraceEvent(event: trace.TraceEvent) {
Expand Down Expand Up @@ -846,9 +848,11 @@ class ScreencastTracingRecorder {
private _pendingAck: ManualPromise<void> | undefined;
private _timer: NodeJS.Timeout | undefined;

constructor(screencast: Screencast, onFrame: (frame: types.ScreencastFrame) => void) {
constructor(screencast: Screencast, onFrame: (frame: types.ScreencastFrame) => void, size: types.Size | undefined, quality: number | undefined) {
this._screencast = screencast;
this._client = {
size,
quality,
onFrame: (frame: types.ScreencastFrame) => {
const time = monotonicTime();

Expand Down
7 changes: 4 additions & 3 deletions packages/playwright-core/src/server/videoRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ export class VideoRecorder {
size: options.size,
};

// Encode into the size of the running capture: it may differ from the requested one when
// another client (e.g. tracing) started the screencast first, and padding a smaller frame
// into the requested size would leave gray borders.
const { size } = this._screencast.addClient(this._client);
// For video files only, prioritize encoding into the given size, regardless of the actual pixel data.
const videoSize = options.size ?? size;
this._videoRecorder = new FfmpegVideoRecorder(ffmpegPath, videoSize, outputFile, this._screencast.page.delegate);
this._videoRecorder = new FfmpegVideoRecorder(ffmpegPath, size, outputFile, this._screencast.page.delegate);
this._artifact = new Artifact(this._screencast.page.browserContext, outputFile);
return this._artifact;
}
Expand Down
34 changes: 34 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23683,6 +23683,40 @@ export interface Tracing {
*/
name?: string;

/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview. Passing `true` is
* a shortcut for `{}`. Takes precedence over
* [`screenshots`](https://playwright.dev/docs/api/class-tracing#tracing-start-option-screenshots).
*
* A page is captured only once, and tracing shares that capture with
* [screencast.start([options])](https://playwright.dev/docs/api/class-screencast#screencast-start) and video
* recording. The trace viewer renders the timeline preview at a fixed size, so these settings only matter to those
* other consumers: raise them to keep tracing from capping what the others receive.
*/
screencast?: boolean|{
/**
* Dimensions of the captured screenshots. Each screenshot is scaled down to preserve the page's aspect ratio and may
* be smaller than these bounds. If not specified the size will be equal to `viewport` scaled down to fit into
* 800x800. Optional.
*/
size?: {
/**
* Max screenshot width in pixels.
*/
width: number;

/**
* Max screenshot height in pixels.
*/
height: number;
};

/**
* The quality of the screenshots, between 0-100. Defaults to 90. Optional.
*/
quality?: number;
};

/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/src/worker/testTracing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const version: trace.VERSION = 8;
let traceOrdinal = 0;

type TraceFixtureValue = PlaywrightWorkerOptions['trace'] | undefined;
type TraceOptions = { screenshots: boolean, snapshots: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, sources: boolean, attachments: boolean, live: boolean, mode: TraceMode };
type TraceOptions = { screencast?: boolean | { size?: { width: number, height: number }, quality?: number }, screenshots: boolean, snapshots: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, sources: boolean, attachments: boolean, live: boolean, mode: TraceMode };

export class TestTracing {
private _testInfo: TestInfoImpl;
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7191,7 +7191,7 @@ export interface PlaywrightWorkerOptions {
*
* Learn more about [recording trace](https://playwright.dev/docs/test-use-options#recording-options).
*/
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, screenshots?: boolean, sources?: boolean, attachments?: boolean };
trace: TraceMode | /** deprecated */ 'retry-with-trace' | { mode: TraceMode, snapshots?: boolean | { dom?: boolean, aria?: boolean, screen?: boolean }, screencast?: boolean | { size?: { width: number, height: number }, quality?: number }, screenshots?: boolean, sources?: boolean, attachments?: boolean };
/**
* Whether to record video for each test. Defaults to `'off'`. The initial run of a test is the "first run";
* subsequent runs caused by [retries](https://playwright.dev/docs/test-retries) are "retries".
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/spec/tracing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Tracing:
snapshotAria: boolean?
snapshotScreen: boolean?
screencast: boolean?
screencastSize:
type: object?
properties:
width: int
height: int
screencastQuality: int?
live: boolean?

tracingStartChunk:
Expand Down
5 changes: 5 additions & 0 deletions packages/protocol/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3041,6 +3041,11 @@ scheme.TracingTracingStartParams = tObject({
snapshotAria: tOptional(tBoolean),
snapshotScreen: tOptional(tBoolean),
screencast: tOptional(tBoolean),
screencastSize: tOptional(tObject({
width: tInt,
height: tInt,
})),
screencastQuality: tOptional(tInt),
live: tOptional(tBoolean),
});
scheme.TracingTracingStartResult = tOptional(tObject({}));
Expand Down
45 changes: 44 additions & 1 deletion tests/library/screencast.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ test('start should finish when page is closed', async ({ browser }, testInfo) =>
await context.close();
});

test('empty video', async ({ browser }, testInfo) => {
test('empty video', async ({ browser, trace }, testInfo) => {
test.skip(trace === 'on', 'tracing keeps the capture running, so the video receives a frame and is not empty');
const size = { width: 800, height: 800 };
const context = await browser.newContext({ viewport: size });
const page = await context.newPage();
Expand All @@ -278,6 +279,48 @@ test('start dispose stops recording', async ({ browser }, testInfo) => {
await context.close();
});

test('start size is ignored while tracing is active', async ({ browser, trace }, testInfo) => {
test.skip(trace === 'on', 'the test starts its own tracing');
test.slow();

const context = await browser.newContext({ viewport: { width: 1600, height: 1200 } });
await context.tracing.start({ screenshots: true });
const page = await context.newPage();

const videoPath = testInfo.outputPath('video.webm');
// Tracing already captures at the viewport scaled down to 800x600, so this size is ignored.
await page.screencast.start({ path: videoPath, size: { width: 1000, height: 750 } });
await page.evaluate(() => document.body.style.backgroundColor = 'red');
await ensureSomeFrames(page);
await page.screencast.stop();

await context.tracing.stop();
await context.close();
// The video must fill its frame rather than pad the smaller capture with gray.
expectRedFrames(videoPath, { width: 800, height: 600 });
});

test('tracing screenshots size sets the shared capture size', async ({ browser, trace }, testInfo) => {
test.skip(trace === 'on', 'the test starts its own tracing');
test.slow();

// Sizing the tracing screencast is the way to lift the cap it would otherwise put on the capture.
const size = { width: 1000, height: 750 };
const context = await browser.newContext({ viewport: { width: 1600, height: 1200 } });
await context.tracing.start({ screencast: { size } });
const page = await context.newPage();

const videoPath = testInfo.outputPath('video.webm');
await page.screencast.start({ path: videoPath });
await page.evaluate(() => document.body.style.backgroundColor = 'red');
await ensureSomeFrames(page);
await page.screencast.stop();

await context.tracing.stop();
await context.close();
expectRedFrames(videoPath, size);
});

type Pixel = { r: number, g: number, b: number, alpha: number };
type PixelPredicate = (pixel: Pixel) => boolean;

Expand Down
Loading
Loading