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: 3 additions & 2 deletions docs/src/api/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -1303,7 +1303,8 @@ Defaults to `false`.
## screenshot-option-quality
- `quality` <[int]>

The quality of the image, between 0-100. Not applicable to `png` images.
The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`.
For `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression.

## screenshot-option-path
- `path` <[path]>
Expand All @@ -1313,7 +1314,7 @@ relative path, then it is resolved relative to the current working directory. If
saved to the disk.

## screenshot-option-type
- `type` <[ScreenshotType]<"png"|"jpeg">>
- `type` <[ScreenshotType]<"png"|"jpeg"|"webp">>

Specify screenshot type, defaults to `png`.

Expand Down
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const ignores = [
"examples",
"packages/*/lib/",
"packages/playwright-core/bundles/utils/src/third_party/",
"packages/utils/webp/webp_codec.js",
"packages/playwright-core/src/generated/*",
"packages/playwright-core/src/third_party/",
"packages/playwright-core/types/*",
Expand Down
15 changes: 9 additions & 6 deletions packages/playwright-client/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13176,7 +13176,8 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
path?: string;

/**
* The quality of the image, between 0-100. Not applicable to `png` images.
* The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For
* `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression.
*/
quality?: number;

Expand Down Expand Up @@ -13218,7 +13219,7 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
/**
* Specify screenshot type, defaults to `png`.
*/
type?: "png"|"jpeg";
type?: "png"|"jpeg"|"webp";
}): Promise<Buffer>;

/**
Expand Down Expand Up @@ -25108,7 +25109,8 @@ export interface LocatorScreenshotOptions {
path?: string;

/**
* The quality of the image, between 0-100. Not applicable to `png` images.
* The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For
* `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression.
*/
quality?: number;

Expand Down Expand Up @@ -25150,7 +25152,7 @@ export interface LocatorScreenshotOptions {
/**
* Specify screenshot type, defaults to `png`.
*/
type?: "png"|"jpeg";
type?: "png"|"jpeg"|"webp";
}

interface ElementHandleWaitForSelectorOptions {
Expand Down Expand Up @@ -25825,7 +25827,8 @@ export interface PageScreenshotOptions {
path?: string;

/**
* The quality of the image, between 0-100. Not applicable to `png` images.
* The quality of the image, between 0-100. Not applicable to `png` images. For `jpeg` the default is `80`. For
* `webp`, a quality of `100` (the default) produces a lossless image, while lower values use lossy compression.
*/
quality?: number;

Expand Down Expand Up @@ -25867,7 +25870,7 @@ export interface PageScreenshotOptions {
/**
* Specify screenshot type, defaults to `png`.
*/
type?: "png"|"jpeg";
type?: "png"|"jpeg"|"webp";
}

type Devices = {
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
!lib/**/*.ttf
!lib/**/*.json
!lib/**/*.md
# Include the WebP codec WASM binary (see @utils/webp/webp).
!lib/**/*.wasm
!lib/xdg-open
!lib/**/manifest.webmanifest
# Exclude injected files. A preprocessed version of these is included via lib/generated.
Expand Down
10 changes: 6 additions & 4 deletions packages/playwright-core/src/client/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3332,7 +3332,7 @@ export type ElementHandleQuerySelectorAllResult = {
};
export type ElementHandleScreenshotParams = {
timeout: number,
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
omitBackground?: boolean,
caret?: 'hide' | 'initial',
Expand All @@ -3346,7 +3346,7 @@ export type ElementHandleScreenshotParams = {
style?: string,
};
export type ElementHandleScreenshotOptions = {
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
omitBackground?: boolean,
caret?: 'hide' | 'initial',
Expand Down Expand Up @@ -4192,6 +4192,7 @@ export type PageExpectScreenshotParams = {
threshold?: number,
fullPage?: boolean,
clip?: Rect,
type?: 'png' | 'webp',
omitBackground?: boolean,
caret?: 'hide' | 'initial',
animations?: 'disabled' | 'allow',
Expand All @@ -4215,6 +4216,7 @@ export type PageExpectScreenshotOptions = {
threshold?: number,
fullPage?: boolean,
clip?: Rect,
type?: 'png' | 'webp',
omitBackground?: boolean,
caret?: 'hide' | 'initial',
animations?: 'disabled' | 'allow',
Expand All @@ -4239,7 +4241,7 @@ export type PageExpectScreenshotErrorDetails = {
};
export type PageScreenshotParams = {
timeout: number,
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
fullPage?: boolean,
clip?: Rect,
Expand All @@ -4255,7 +4257,7 @@ export type PageScreenshotParams = {
style?: string,
};
export type PageScreenshotOptions = {
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
fullPage?: boolean,
clip?: Rect,
Expand Down
4 changes: 3 additions & 1 deletion packages/playwright-core/src/client/elementHandle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,15 @@ export async function convertInputFiles(files: string | FilePayload | string[] |
return { payloads };
}

export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' }): 'png' | 'jpeg' | undefined {
export function determineScreenshotType(options: { path?: string, type?: 'png' | 'jpeg' | 'webp' }): 'png' | 'jpeg' | 'webp' | undefined {
if (options.path) {
const mimeType = getMimeTypeForPath(options.path);
if (mimeType === 'image/png')
return 'png';
else if (mimeType === 'image/jpeg')
return 'jpeg';
else if (mimeType === 'image/webp')
return 'webp';
throw new Error(`path: unsupported mime type "${mimeType}"`);
}
return options.type;
Expand Down
2 changes: 2 additions & 0 deletions packages/playwright-core/src/server/bidi/bidiPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ export class BidiPage implements PageDelegate {
}

async takeScreenshot(progress: Progress, format: string, documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
if (format === 'webp')
throw new Error('webp screenshots are not supported via WebDriver BiDi');
const rect = (documentRect || viewportRect)!;
const { data } = await progress.race(this._session.send('browsingContext.captureScreenshot', {
context: this._session.sessionId,
Expand Down
10 changes: 6 additions & 4 deletions packages/playwright-core/src/server/channels.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3335,7 +3335,7 @@ export type ElementHandleQuerySelectorAllResult = {
};
export type ElementHandleScreenshotParams = {
timeout: number,
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
omitBackground?: boolean,
caret?: 'hide' | 'initial',
Expand All @@ -3349,7 +3349,7 @@ export type ElementHandleScreenshotParams = {
style?: string,
};
export type ElementHandleScreenshotOptions = {
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
omitBackground?: boolean,
caret?: 'hide' | 'initial',
Expand Down Expand Up @@ -4195,6 +4195,7 @@ export type PageExpectScreenshotParams = {
threshold?: number,
fullPage?: boolean,
clip?: Rect,
type?: 'png' | 'webp',
omitBackground?: boolean,
caret?: 'hide' | 'initial',
animations?: 'disabled' | 'allow',
Expand All @@ -4218,6 +4219,7 @@ export type PageExpectScreenshotOptions = {
threshold?: number,
fullPage?: boolean,
clip?: Rect,
type?: 'png' | 'webp',
omitBackground?: boolean,
caret?: 'hide' | 'initial',
animations?: 'disabled' | 'allow',
Expand All @@ -4242,7 +4244,7 @@ export type PageExpectScreenshotErrorDetails = {
};
export type PageScreenshotParams = {
timeout: number,
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
fullPage?: boolean,
clip?: Rect,
Expand All @@ -4258,7 +4260,7 @@ export type PageScreenshotParams = {
style?: string,
};
export type PageScreenshotOptions = {
type?: 'png' | 'jpeg',
type?: 'png' | 'jpeg' | 'webp',
quality?: number,
fullPage?: boolean,
clip?: Rect,
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright-core/src/server/chromium/crPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export class CRPage implements PageDelegate {
await this._mainFrameSession._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
}

async takeScreenshot(progress: Progress, format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
async takeScreenshot(progress: Progress, format: 'png' | 'jpeg' | 'webp', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
const { visualViewport, contentSize, cssContentSize } = await progress.race(this._mainFrameSession._client.send('Page.getLayoutMetrics'));
if (!documentRect) {
documentRect = {
Expand Down
4 changes: 2 additions & 2 deletions packages/playwright-core/src/server/firefox/ffPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ export class FFPage implements PageDelegate {
throw new Error('Not implemented');
}

async takeScreenshot(progress: Progress, format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
async takeScreenshot(progress: Progress, format: 'png' | 'jpeg' | 'webp', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, quality: number | undefined, fitsViewport: boolean, scale: 'css' | 'device'): Promise<Buffer> {
if (!documentRect) {
const scrollOffset = await this._page.mainFrame().waitForFunctionValueInUtility(progress, () => ({ x: window.scrollX, y: window.scrollY }));
documentRect = {
Expand All @@ -470,7 +470,7 @@ export class FFPage implements PageDelegate {
};
}
const { data } = await progress.race(this._session.send('Page.screenshot', {
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg'),
mimeType: ('image/' + format) as ('image/png' | 'image/jpeg' | 'image/webp'),
clip: documentRect,
quality,
omitDeviceScaleFactor: scale === 'css',
Expand Down
20 changes: 9 additions & 11 deletions packages/playwright-core/src/server/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,28 +720,26 @@ export class Page extends SdkObject<PageEventMap> {
return await this.screenshotter.screenshotPage(progress, options || {});
};

const comparator = getComparator('image/png');
let intermediateResult: {
actual?: Buffer,
previous?: Buffer,
errorMessage: string,
diff?: Buffer,
} | undefined;
const areEqualScreenshots = (actual: Buffer | undefined, expected: Buffer | undefined, previous: Buffer | undefined) => {
const comparatorResult = actual && expected ? comparator(actual, expected, options) : undefined;
if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot)
return true;
if (comparatorResult)
intermediateResult = { errorMessage: comparatorResult.errorMessage, diff: comparatorResult.diff, actual, previous };
return false;
};

try {
if (!options.expected && options.isNot)
throw new Error('"not" matcher requires expected result');
const format = validateScreenshotOptions(options || {});
if (format !== 'png')
throw new Error('Only PNG screenshots are supported');
const comparator = getComparator(`image/${format}`);
const areEqualScreenshots = (actual: Buffer | undefined, expected: Buffer | undefined, previous: Buffer | undefined) => {
const comparatorResult = actual && expected ? comparator(actual, expected, options) : undefined;
if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot)
return true;
if (comparatorResult)
intermediateResult = { errorMessage: comparatorResult.errorMessage, diff: comparatorResult.diff, actual, previous };
return false;
};
let actual: Buffer | undefined;
let previous: Buffer | undefined;
const pollIntervals = [0, 100, 250, 500];
Expand Down
14 changes: 7 additions & 7 deletions packages/playwright-core/src/server/screenshotter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ declare global {
}

export type ScreenshotOptions = {
type?: 'png' | 'jpeg';
type?: 'png' | 'jpeg' | 'webp';
quality?: number;
omitBackground?: boolean;
animations?: 'disabled' | 'allow';
Expand Down Expand Up @@ -288,7 +288,7 @@ export class Screenshotter {
}
}

private async _screenshot(progress: Progress, format: 'png' | 'jpeg', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, fitsViewport: boolean, options: ScreenshotOptions): Promise<Buffer> {
private async _screenshot(progress: Progress, format: 'png' | 'jpeg' | 'webp', documentRect: types.Rect | undefined, viewportRect: types.Rect | undefined, fitsViewport: boolean, options: ScreenshotOptions): Promise<Buffer> {
if ((options as any).__testHookBeforeScreenshot)
await progress.race((options as any).__testHookBeforeScreenshot());

Expand All @@ -298,7 +298,7 @@ export class Screenshotter {
const cleanupHighlight = await this._maskElements(progress, options);

try {
const quality = format === 'jpeg' ? options.quality ?? 80 : undefined;
const quality = format === 'jpeg' ? options.quality ?? 80 : format === 'webp' ? options.quality ?? 100 : undefined;
const buffer = await this._page.delegate.takeScreenshot(progress, format, documentRect, viewportRect, quality, fitsViewport, options.scale || 'device');
await progress.race(cleanupHighlight());
if (shouldSetDefaultBackground)
Expand Down Expand Up @@ -344,20 +344,20 @@ function trimClipToSize(clip: types.Rect, size: types.Size): types.Rect {
return result;
}

export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | 'jpeg' {
let format: 'png' | 'jpeg' | null = null;
export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | 'jpeg' | 'webp' {
let format: 'png' | 'jpeg' | 'webp' | null = null;
// options.type takes precedence over inferring the type from options.path
// because it may be a 0-length file with no extension created beforehand (i.e. as a temp file).
if (options.type) {
assert(options.type === 'png' || options.type === 'jpeg', 'Unknown options.type value: ' + options.type);
assert(options.type === 'png' || options.type === 'jpeg' || options.type === 'webp', 'Unknown options.type value: ' + options.type);
format = options.type;
}

if (!format)
format = 'png';

if (options.quality !== undefined) {
assert(format === 'jpeg', 'options.quality is unsupported for the ' + format + ' screenshots');
assert(format !== 'png', 'options.quality is unsupported for the ' + format + ' screenshots');
assert(typeof options.quality === 'number', 'Expected options.quality to be a number but found ' + (typeof options.quality));
assert(Number.isInteger(options.quality), 'Expected options.quality to be an integer');
assert(options.quality >= 0 && options.quality <= 100, 'Expected options.quality to be between 0 and 100 (inclusive), got ' + options.quality);
Expand Down
8 changes: 7 additions & 1 deletion packages/playwright-core/src/server/webkit/webview/wvPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ManualPromise } from '@isomorphic/manualPromise';
import { splitErrorMessage } from '@utils/stackTrace';
import { debugLogger } from '@utils/debugLogger';
import { eventsHelper } from '@utils/eventsHelper';
import { encodeWebp } from '@utils/webp/webp';
import * as dialog from '../../dialog';
import * as dom from '../../dom';
import { TargetClosedError } from '../../errors';
Expand Down Expand Up @@ -767,8 +768,13 @@ export class WVPage implements PageDelegate {
const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' }));
const prefix = 'data:image/png;base64,';
let buffer: Buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
if (format === 'jpeg')
if (format === 'jpeg') {
buffer = jpegjs.encode(PNG.sync.read(buffer), quality).data;
} else if (format === 'webp') {
const png = PNG.sync.read(buffer);
// Match the native WebKit encoder: webp quality 100 (or omitted) is lossless.
buffer = (quality === undefined || quality >= 100) ? encodeWebp(png, { lossless: true }) : encodeWebp(png, { quality });
}
return buffer;
}

Expand Down
16 changes: 14 additions & 2 deletions packages/playwright-core/src/server/webkit/wkPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@
* limitations under the License.
*/

import { PNG } from 'pngjs';

import { headersArrayToObject, headersObjectToArray } from '@isomorphic/headers';
import { splitErrorMessage } from '@utils/stackTrace';
import { eventsHelper } from '@utils/eventsHelper';
import { hostPlatform } from '@utils/hostPlatform';
import { encodeWebp } from '@utils/webp/webp';
import { assert } from '@isomorphic/assert';
import * as dialog from '../dialog';
import * as dom from '../dom';
Expand Down Expand Up @@ -868,9 +871,18 @@ export class WKPage implements PageDelegate {
const omitDeviceScaleFactor = scale === 'css';
this.validateScreenshotDimension(rect.width, omitDeviceScaleFactor);
this.validateScreenshotDimension(rect.height, omitDeviceScaleFactor);
const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor, format: format as 'png' | 'jpeg', quality }));
// WebKit on macOS has no built-in WebP encoder, so capture a PNG and re-encode it.
const recodePngToWebp = format === 'webp' && process.platform === 'darwin';
const result = await progress.race(this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport', omitDeviceScaleFactor, format: (recodePngToWebp ? 'png' : format) as 'png' | 'jpeg' | 'webp', quality: recodePngToWebp ? undefined : quality }));
// Strip the 'data:image/<format>;base64,' prefix.
return Buffer.from(result.dataURL.substring(result.dataURL.indexOf(',') + 1), 'base64');
const buffer = Buffer.from(result.dataURL.substring(result.dataURL.indexOf(',') + 1), 'base64');
if (recodePngToWebp) {
const png = PNG.sync.read(buffer);
const image = { width: png.width, height: png.height, data: png.data };
// Match the native WebKit encoder: webp quality 100 (or omitted) is lossless.
return (quality === undefined || quality >= 100) ? encodeWebp(image, { lossless: true }) : encodeWebp(image, { quality });
}
return buffer;
}

async getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
Expand Down
Loading
Loading