chore: simplify types handling in toHaveScreenshot (#29374)
This commit is contained in:
parent
fb29d90052
commit
20699c36ba
|
|
@ -62,14 +62,13 @@ type PDFOptions = Omit<channels.PagePdfParams, 'width' | 'height' | 'margin'> &
|
||||||
path?: string,
|
path?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExpectScreenshotOptions = Omit<channels.PageExpectScreenshotOptions, 'screenshotOptions' | 'locator' | 'expected'> & {
|
export type ExpectScreenshotOptions = Omit<channels.PageExpectScreenshotOptions, 'locator' | 'expected' | 'mask'> & {
|
||||||
expected?: Buffer,
|
expected?: Buffer,
|
||||||
locator?: Locator,
|
locator?: api.Locator,
|
||||||
isNot: boolean,
|
isNot: boolean,
|
||||||
screenshotOptions: Omit<channels.PageExpectScreenshotOptions['screenshotOptions'], 'mask'> & { mask?: Locator[] }
|
mask?: api.Locator[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class Page extends ChannelOwner<channels.PageChannel> implements api.Page {
|
export class Page extends ChannelOwner<channels.PageChannel> implements api.Page {
|
||||||
private _browserContext: BrowserContext;
|
private _browserContext: BrowserContext;
|
||||||
_ownedContext: BrowserContext | undefined;
|
_ownedContext: BrowserContext | undefined;
|
||||||
|
|
@ -547,22 +546,19 @@ export class Page extends ChannelOwner<channels.PageChannel> implements api.Page
|
||||||
}
|
}
|
||||||
|
|
||||||
async _expectScreenshot(options: ExpectScreenshotOptions): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[]}> {
|
async _expectScreenshot(options: ExpectScreenshotOptions): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[]}> {
|
||||||
const mask = options.screenshotOptions?.mask ? options.screenshotOptions?.mask.map(locator => ({
|
const mask = options?.mask ? options?.mask.map(locator => ({
|
||||||
frame: locator._frame._channel,
|
frame: (locator as Locator)._frame._channel,
|
||||||
selector: locator._selector,
|
selector: (locator as Locator)._selector,
|
||||||
})) : undefined;
|
})) : undefined;
|
||||||
const locator = options.locator ? {
|
const locator = options.locator ? {
|
||||||
frame: options.locator._frame._channel,
|
frame: (options.locator as Locator)._frame._channel,
|
||||||
selector: options.locator._selector,
|
selector: (options.locator as Locator)._selector,
|
||||||
} : undefined;
|
} : undefined;
|
||||||
return await this._channel.expectScreenshot({
|
return await this._channel.expectScreenshot({
|
||||||
...options,
|
...options,
|
||||||
isNot: !!options.isNot,
|
isNot: !!options.isNot,
|
||||||
locator,
|
locator,
|
||||||
screenshotOptions: {
|
mask,
|
||||||
...options.screenshotOptions,
|
|
||||||
mask,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1059,26 +1059,22 @@ scheme.PageExpectScreenshotParams = tObject({
|
||||||
frame: tChannel(['Frame']),
|
frame: tChannel(['Frame']),
|
||||||
selector: tString,
|
selector: tString,
|
||||||
})),
|
})),
|
||||||
comparatorOptions: tOptional(tObject({
|
comparator: tOptional(tString),
|
||||||
comparator: tOptional(tString),
|
maxDiffPixels: tOptional(tNumber),
|
||||||
maxDiffPixels: tOptional(tNumber),
|
maxDiffPixelRatio: tOptional(tNumber),
|
||||||
maxDiffPixelRatio: tOptional(tNumber),
|
threshold: tOptional(tNumber),
|
||||||
threshold: tOptional(tNumber),
|
fullPage: tOptional(tBoolean),
|
||||||
})),
|
clip: tOptional(tType('Rect')),
|
||||||
screenshotOptions: tOptional(tObject({
|
omitBackground: tOptional(tBoolean),
|
||||||
fullPage: tOptional(tBoolean),
|
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||||
clip: tOptional(tType('Rect')),
|
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||||
omitBackground: tOptional(tBoolean),
|
scale: tOptional(tEnum(['css', 'device'])),
|
||||||
caret: tOptional(tEnum(['hide', 'initial'])),
|
mask: tOptional(tArray(tObject({
|
||||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
frame: tChannel(['Frame']),
|
||||||
scale: tOptional(tEnum(['css', 'device'])),
|
selector: tString,
|
||||||
mask: tOptional(tArray(tObject({
|
}))),
|
||||||
frame: tChannel(['Frame']),
|
maskColor: tOptional(tString),
|
||||||
selector: tString,
|
style: tOptional(tString),
|
||||||
}))),
|
|
||||||
maskColor: tOptional(tString),
|
|
||||||
style: tOptional(tString),
|
|
||||||
})),
|
|
||||||
});
|
});
|
||||||
scheme.PageExpectScreenshotResult = tObject({
|
scheme.PageExpectScreenshotResult = tObject({
|
||||||
diff: tOptional(tBinary),
|
diff: tOptional(tBinary),
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
||||||
}
|
}
|
||||||
|
|
||||||
async expectScreenshot(params: channels.PageExpectScreenshotParams, metadata: CallMetadata): Promise<channels.PageExpectScreenshotResult> {
|
async expectScreenshot(params: channels.PageExpectScreenshotParams, metadata: CallMetadata): Promise<channels.PageExpectScreenshotResult> {
|
||||||
const mask: { frame: Frame, selector: string }[] = (params.screenshotOptions?.mask || []).map(({ frame, selector }) => ({
|
const mask: { frame: Frame, selector: string }[] = (params.mask || []).map(({ frame, selector }) => ({
|
||||||
frame: (frame as FrameDispatcher)._object,
|
frame: (frame as FrameDispatcher)._object,
|
||||||
selector,
|
selector,
|
||||||
}));
|
}));
|
||||||
|
|
@ -190,14 +190,7 @@ export class PageDispatcher extends Dispatcher<Page, channels.PageChannel, Brows
|
||||||
return await this._page.expectScreenshot(metadata, {
|
return await this._page.expectScreenshot(metadata, {
|
||||||
...params,
|
...params,
|
||||||
locator,
|
locator,
|
||||||
comparatorOptions: {
|
mask,
|
||||||
...params.comparatorOptions,
|
|
||||||
_comparator: params.comparatorOptions?.comparator,
|
|
||||||
},
|
|
||||||
screenshotOptions: {
|
|
||||||
...params.screenshotOptions,
|
|
||||||
mask,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ type EmulatedMedia = {
|
||||||
forcedColors: types.ForcedColors;
|
forcedColors: types.ForcedColors;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ExpectScreenshotOptions = {
|
type ExpectScreenshotOptions = ImageComparatorOptions & ScreenshotOptions & {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
expected?: Buffer,
|
expected?: Buffer,
|
||||||
isNot?: boolean,
|
isNot?: boolean,
|
||||||
|
|
@ -118,8 +118,6 @@ type ExpectScreenshotOptions = {
|
||||||
frame: frames.Frame,
|
frame: frames.Frame,
|
||||||
selector: string,
|
selector: string,
|
||||||
},
|
},
|
||||||
comparatorOptions?: ImageComparatorOptions,
|
|
||||||
screenshotOptions?: ScreenshotOptions,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Page extends SdkObject {
|
export class Page extends SdkObject {
|
||||||
|
|
@ -537,11 +535,11 @@ export class Page extends SdkObject {
|
||||||
async expectScreenshot(metadata: CallMetadata, options: ExpectScreenshotOptions = {}): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[] }> {
|
async expectScreenshot(metadata: CallMetadata, options: ExpectScreenshotOptions = {}): Promise<{ actual?: Buffer, previous?: Buffer, diff?: Buffer, errorMessage?: string, log?: string[] }> {
|
||||||
const locator = options.locator;
|
const locator = options.locator;
|
||||||
const rafrafScreenshot = locator ? async (progress: Progress, timeout: number) => {
|
const rafrafScreenshot = locator ? async (progress: Progress, timeout: number) => {
|
||||||
return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options.screenshotOptions || {});
|
return await locator.frame.rafrafTimeoutScreenshotElementWithProgress(progress, locator.selector, timeout, options || {});
|
||||||
} : async (progress: Progress, timeout: number) => {
|
} : async (progress: Progress, timeout: number) => {
|
||||||
await this.performLocatorHandlersCheckpoint(progress);
|
await this.performLocatorHandlersCheckpoint(progress);
|
||||||
await this.mainFrame().rafrafTimeout(timeout);
|
await this.mainFrame().rafrafTimeout(timeout);
|
||||||
return await this._screenshotter.screenshotPage(progress, options.screenshotOptions || {});
|
return await this._screenshotter.screenshotPage(progress, options || {});
|
||||||
};
|
};
|
||||||
|
|
||||||
const comparator = getComparator('image/png');
|
const comparator = getComparator('image/png');
|
||||||
|
|
@ -549,7 +547,7 @@ export class Page extends SdkObject {
|
||||||
if (!options.expected && options.isNot)
|
if (!options.expected && options.isNot)
|
||||||
return { errorMessage: '"not" matcher requires expected result' };
|
return { errorMessage: '"not" matcher requires expected result' };
|
||||||
try {
|
try {
|
||||||
const format = validateScreenshotOptions(options.screenshotOptions || {});
|
const format = validateScreenshotOptions(options || {});
|
||||||
if (format !== 'png')
|
if (format !== 'png')
|
||||||
throw new Error('Only PNG screenshots are supported');
|
throw new Error('Only PNG screenshots are supported');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -562,7 +560,7 @@ export class Page extends SdkObject {
|
||||||
diff?: Buffer,
|
diff?: Buffer,
|
||||||
} | undefined = undefined;
|
} | undefined = undefined;
|
||||||
const areEqualScreenshots = (actual: Buffer | undefined, expected: Buffer | undefined, previous: Buffer | undefined) => {
|
const areEqualScreenshots = (actual: Buffer | undefined, expected: Buffer | undefined, previous: Buffer | undefined) => {
|
||||||
const comparatorResult = actual && expected ? comparator(actual, expected, options.comparatorOptions) : undefined;
|
const comparatorResult = actual && expected ? comparator(actual, expected, options) : undefined;
|
||||||
if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot)
|
if (comparatorResult !== undefined && !!comparatorResult === !!options.isNot)
|
||||||
return true;
|
return true;
|
||||||
if (comparatorResult)
|
if (comparatorResult)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import { compare } from '../image_tools/compare';
|
||||||
const { diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL } = require('../third_party/diff_match_patch');
|
const { diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL } = require('../third_party/diff_match_patch');
|
||||||
import { PNG } from '../utilsBundle';
|
import { PNG } from '../utilsBundle';
|
||||||
|
|
||||||
export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number, _comparator?: string };
|
export type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxDiffPixelRatio?: number, comparator?: string };
|
||||||
export type ComparatorResult = { diff?: Buffer; errorMessage: string; } | null;
|
export type ComparatorResult = { diff?: Buffer; errorMessage: string; } | null;
|
||||||
export type Comparator = (actualBuffer: Buffer | string, expectedBuffer: Buffer, options?: any) => ComparatorResult;
|
export type Comparator = (actualBuffer: Buffer | string, expectedBuffer: Buffer, options?: any) => ComparatorResult;
|
||||||
|
|
||||||
|
|
@ -65,18 +65,18 @@ function compareImages(mimeType: string, actualBuffer: Buffer | string, expected
|
||||||
}
|
}
|
||||||
const diff = new PNG({ width: size.width, height: size.height });
|
const diff = new PNG({ width: size.width, height: size.height });
|
||||||
let count;
|
let count;
|
||||||
if (options._comparator === 'ssim-cie94') {
|
if (options.comparator === 'ssim-cie94') {
|
||||||
count = compare(expected.data, actual.data, diff.data, size.width, size.height, {
|
count = compare(expected.data, actual.data, diff.data, size.width, size.height, {
|
||||||
// All ΔE* formulae are originally designed to have the difference of 1.0 stand for a "just noticeable difference" (JND).
|
// All ΔE* formulae are originally designed to have the difference of 1.0 stand for a "just noticeable difference" (JND).
|
||||||
// See https://en.wikipedia.org/wiki/Color_difference#CIELAB_%CE%94E*
|
// See https://en.wikipedia.org/wiki/Color_difference#CIELAB_%CE%94E*
|
||||||
maxColorDeltaE94: 1.0,
|
maxColorDeltaE94: 1.0,
|
||||||
});
|
});
|
||||||
} else if ((options._comparator ?? 'pixelmatch') === 'pixelmatch') {
|
} else if ((options.comparator ?? 'pixelmatch') === 'pixelmatch') {
|
||||||
count = pixelmatch(expected.data, actual.data, diff.data, size.width, size.height, {
|
count = pixelmatch(expected.data, actual.data, diff.data, size.width, size.height, {
|
||||||
threshold: options.threshold ?? 0.2,
|
threshold: options.threshold ?? 0.2,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Configuration specifies unknown comparator "${options._comparator}"`);
|
throw new Error(`Configuration specifies unknown comparator "${options.comparator}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxDiffPixels1 = options.maxDiffPixels;
|
const maxDiffPixels1 = options.maxDiffPixels;
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Locator, Page } from 'playwright-core';
|
import type { Locator, Page } from 'playwright-core';
|
||||||
import type { Page as PageEx } from 'playwright-core/lib/client/page';
|
import type { ExpectScreenshotOptions, Page as PageEx } from 'playwright-core/lib/client/page';
|
||||||
import type { Locator as LocatorEx } from 'playwright-core/lib/client/locator';
|
|
||||||
import { currentTestInfo, currentExpectTimeout } from '../common/globals';
|
import { currentTestInfo, currentExpectTimeout } from '../common/globals';
|
||||||
import type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils';
|
import type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils';
|
||||||
import { getComparator, sanitizeForFilePath, zones } from 'playwright-core/lib/utils';
|
import { getComparator, sanitizeForFilePath, zones } from 'playwright-core/lib/utils';
|
||||||
import type { PageScreenshotOptions } from 'playwright-core/types/types';
|
|
||||||
import {
|
import {
|
||||||
addSuffixToFilePath,
|
addSuffixToFilePath,
|
||||||
trimLongString, callLogText,
|
trimLongString, callLogText,
|
||||||
|
|
@ -32,6 +30,7 @@ import { mime } from 'playwright-core/lib/utilsBundle';
|
||||||
import type { TestInfoImpl } from '../worker/testInfo';
|
import type { TestInfoImpl } from '../worker/testInfo';
|
||||||
import type { ExpectMatcherContext } from './expect';
|
import type { ExpectMatcherContext } from './expect';
|
||||||
import type { MatcherResult } from './matcherHint';
|
import type { MatcherResult } from './matcherHint';
|
||||||
|
import type { FullProjectInternal } from '../common/config';
|
||||||
|
|
||||||
type NameOrSegments = string | string[];
|
type NameOrSegments = string | string[];
|
||||||
const snapshotNamesSymbol = Symbol('snapshotNames');
|
const snapshotNamesSymbol = Symbol('snapshotNames');
|
||||||
|
|
@ -43,7 +42,36 @@ type SnapshotNames = {
|
||||||
|
|
||||||
type ImageMatcherResult = MatcherResult<string, string> & { diff?: string };
|
type ImageMatcherResult = MatcherResult<string, string> & { diff?: string };
|
||||||
|
|
||||||
class SnapshotHelper<T extends ImageComparatorOptions> {
|
type ToHaveScreenshotConfigOptions = NonNullable<NonNullable<FullProjectInternal['expect']>['toHaveScreenshot']> & {
|
||||||
|
_comparator?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ToHaveScreenshotOptions = ToHaveScreenshotConfigOptions & {
|
||||||
|
clip?: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
fullPage?: boolean;
|
||||||
|
mask?: Array<Locator>;
|
||||||
|
maskColor?: string;
|
||||||
|
omitBackground?: boolean;
|
||||||
|
timeout?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keep in sync with above (begin).
|
||||||
|
const NonConfigProperties: (keyof ToHaveScreenshotOptions)[] = [
|
||||||
|
'clip',
|
||||||
|
'fullPage',
|
||||||
|
'mask',
|
||||||
|
'maskColor',
|
||||||
|
'omitBackground',
|
||||||
|
'timeout',
|
||||||
|
];
|
||||||
|
// Keep in sync with above (end).
|
||||||
|
|
||||||
|
class SnapshotHelper {
|
||||||
readonly testInfo: TestInfoImpl;
|
readonly testInfo: TestInfoImpl;
|
||||||
readonly snapshotName: string;
|
readonly snapshotName: string;
|
||||||
readonly legacyExpectedPath: string;
|
readonly legacyExpectedPath: string;
|
||||||
|
|
@ -54,9 +82,8 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
readonly mimeType: string;
|
readonly mimeType: string;
|
||||||
readonly kind: 'Screenshot'|'Snapshot';
|
readonly kind: 'Screenshot'|'Snapshot';
|
||||||
readonly updateSnapshots: 'all' | 'none' | 'missing';
|
readonly updateSnapshots: 'all' | 'none' | 'missing';
|
||||||
readonly comparatorOptions: ImageComparatorOptions;
|
|
||||||
readonly comparator: Comparator;
|
readonly comparator: Comparator;
|
||||||
readonly allOptions: T;
|
readonly options: Omit<ToHaveScreenshotOptions, '_comparator'> & { comparator?: string };
|
||||||
readonly matcherName: string;
|
readonly matcherName: string;
|
||||||
readonly locator: Locator | undefined;
|
readonly locator: Locator | undefined;
|
||||||
|
|
||||||
|
|
@ -66,19 +93,18 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
locator: Locator | undefined,
|
locator: Locator | undefined,
|
||||||
snapshotPathResolver: (...pathSegments: string[]) => string,
|
snapshotPathResolver: (...pathSegments: string[]) => string,
|
||||||
anonymousSnapshotExtension: string,
|
anonymousSnapshotExtension: string,
|
||||||
configOptions: ImageComparatorOptions,
|
configOptions: ToHaveScreenshotConfigOptions,
|
||||||
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & T,
|
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & ToHaveScreenshotOptions,
|
||||||
optOptions: T,
|
optOptions: ToHaveScreenshotOptions,
|
||||||
) {
|
) {
|
||||||
let options: T;
|
|
||||||
let name: NameOrSegments | undefined;
|
let name: NameOrSegments | undefined;
|
||||||
if (Array.isArray(nameOrOptions) || typeof nameOrOptions === 'string') {
|
if (Array.isArray(nameOrOptions) || typeof nameOrOptions === 'string') {
|
||||||
name = nameOrOptions;
|
name = nameOrOptions;
|
||||||
options = optOptions;
|
this.options = { ...optOptions };
|
||||||
} else {
|
} else {
|
||||||
name = nameOrOptions.name;
|
name = nameOrOptions.name;
|
||||||
options = { ...nameOrOptions };
|
this.options = { ...nameOrOptions };
|
||||||
delete (options as any).name;
|
delete (this.options as any).name;
|
||||||
}
|
}
|
||||||
|
|
||||||
let snapshotNames = (testInfo as any)[snapshotNamesSymbol] as SnapshotNames;
|
let snapshotNames = (testInfo as any)[snapshotNamesSymbol] as SnapshotNames;
|
||||||
|
|
@ -116,15 +142,24 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options = {
|
const filteredConfigOptions = { ...configOptions };
|
||||||
...configOptions,
|
for (const prop of NonConfigProperties)
|
||||||
...options,
|
delete (filteredConfigOptions as any)[prop];
|
||||||
|
this.options = {
|
||||||
|
...filteredConfigOptions,
|
||||||
|
...this.options,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.maxDiffPixels !== undefined && options.maxDiffPixels < 0)
|
// While comparator is not a part of the public API, it is translated here.
|
||||||
|
if ((this.options as any)._comparator) {
|
||||||
|
this.options.comparator = (this.options as any)._comparator;
|
||||||
|
delete (this.options as any)._comparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.maxDiffPixels !== undefined && this.options.maxDiffPixels < 0)
|
||||||
throw new Error('`maxDiffPixels` option value must be non-negative integer');
|
throw new Error('`maxDiffPixels` option value must be non-negative integer');
|
||||||
|
|
||||||
if (options.maxDiffPixelRatio !== undefined && (options.maxDiffPixelRatio < 0 || options.maxDiffPixelRatio > 1))
|
if (this.options.maxDiffPixelRatio !== undefined && (this.options.maxDiffPixelRatio < 0 || this.options.maxDiffPixelRatio > 1))
|
||||||
throw new Error('`maxDiffPixelRatio` option value must be between 0 and 1');
|
throw new Error('`maxDiffPixelRatio` option value must be between 0 and 1');
|
||||||
|
|
||||||
// sanitizes path if string
|
// sanitizes path if string
|
||||||
|
|
@ -145,13 +180,6 @@ class SnapshotHelper<T extends ImageComparatorOptions> {
|
||||||
this.comparator = getComparator(this.mimeType);
|
this.comparator = getComparator(this.mimeType);
|
||||||
|
|
||||||
this.testInfo = testInfo;
|
this.testInfo = testInfo;
|
||||||
this.allOptions = options;
|
|
||||||
this.comparatorOptions = {
|
|
||||||
maxDiffPixels: options.maxDiffPixels,
|
|
||||||
maxDiffPixelRatio: options.maxDiffPixelRatio,
|
|
||||||
threshold: options.threshold,
|
|
||||||
_comparator: options._comparator,
|
|
||||||
};
|
|
||||||
this.kind = this.mimeType.startsWith('image/') ? 'Screenshot' : 'Snapshot';
|
this.kind = this.mimeType.startsWith('image/') ? 'Screenshot' : 'Snapshot';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,7 +312,7 @@ export function toMatchSnapshot(
|
||||||
if (this.isNot) {
|
if (this.isNot) {
|
||||||
if (!fs.existsSync(helper.snapshotPath))
|
if (!fs.existsSync(helper.snapshotPath))
|
||||||
return helper.handleMissingNegated();
|
return helper.handleMissingNegated();
|
||||||
const isDifferent = !!helper.comparator(received, fs.readFileSync(helper.snapshotPath), helper.comparatorOptions);
|
const isDifferent = !!helper.comparator(received, fs.readFileSync(helper.snapshotPath), helper.options);
|
||||||
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,7 +320,7 @@ export function toMatchSnapshot(
|
||||||
return helper.handleMissing(received);
|
return helper.handleMissing(received);
|
||||||
|
|
||||||
const expected = fs.readFileSync(helper.snapshotPath);
|
const expected = fs.readFileSync(helper.snapshotPath);
|
||||||
const result = helper.comparator(received, expected, helper.comparatorOptions);
|
const result = helper.comparator(received, expected, helper.options);
|
||||||
if (!result)
|
if (!result)
|
||||||
return helper.handleMatching();
|
return helper.handleMatching();
|
||||||
|
|
||||||
|
|
@ -306,11 +334,9 @@ export function toMatchSnapshot(
|
||||||
return helper.handleDifferent(received, expected, undefined, result.diff, result.errorMessage, undefined);
|
return helper.handleDifferent(received, expected, undefined, result.diff, result.errorMessage, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
type HaveScreenshotOptions = ImageComparatorOptions & Omit<PageScreenshotOptions, 'type' | 'quality' | 'path' | 'style'> & { stylePath?: string | string[] };
|
|
||||||
|
|
||||||
export function toHaveScreenshotStepTitle(
|
export function toHaveScreenshotStepTitle(
|
||||||
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & HaveScreenshotOptions = {},
|
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & ToHaveScreenshotOptions = {},
|
||||||
optOptions: HaveScreenshotOptions = {}
|
optOptions: ToHaveScreenshotOptions = {}
|
||||||
): string {
|
): string {
|
||||||
let name: NameOrSegments | undefined;
|
let name: NameOrSegments | undefined;
|
||||||
if (typeof nameOrOptions === 'object' && !Array.isArray(nameOrOptions))
|
if (typeof nameOrOptions === 'object' && !Array.isArray(nameOrOptions))
|
||||||
|
|
@ -323,8 +349,8 @@ export function toHaveScreenshotStepTitle(
|
||||||
export async function toHaveScreenshot(
|
export async function toHaveScreenshot(
|
||||||
this: ExpectMatcherContext,
|
this: ExpectMatcherContext,
|
||||||
pageOrLocator: Page | Locator,
|
pageOrLocator: Page | Locator,
|
||||||
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & HaveScreenshotOptions = {},
|
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & ToHaveScreenshotOptions = {},
|
||||||
optOptions: HaveScreenshotOptions = {}
|
optOptions: ToHaveScreenshotOptions = {}
|
||||||
): Promise<MatcherResult<NameOrSegments | { name?: NameOrSegments }, string>> {
|
): Promise<MatcherResult<NameOrSegments | { name?: NameOrSegments }, string>> {
|
||||||
const testInfo = currentTestInfo();
|
const testInfo = currentTestInfo();
|
||||||
if (!testInfo)
|
if (!testInfo)
|
||||||
|
|
@ -334,47 +360,45 @@ export async function toHaveScreenshot(
|
||||||
return { pass: !this.isNot, message: () => '', name: 'toHaveScreenshot', expected: nameOrOptions };
|
return { pass: !this.isNot, message: () => '', name: 'toHaveScreenshot', expected: nameOrOptions };
|
||||||
|
|
||||||
expectTypes(pageOrLocator, ['Page', 'Locator'], 'toHaveScreenshot');
|
expectTypes(pageOrLocator, ['Page', 'Locator'], 'toHaveScreenshot');
|
||||||
const [page, locator] = pageOrLocator.constructor.name === 'Page' ? [(pageOrLocator as PageEx), undefined] : [(pageOrLocator as Locator).page() as PageEx, pageOrLocator as LocatorEx];
|
const [page, locator] = pageOrLocator.constructor.name === 'Page' ? [(pageOrLocator as PageEx), undefined] : [(pageOrLocator as Locator).page() as PageEx, pageOrLocator as Locator];
|
||||||
const config = (testInfo._projectInternal.expect as any)?.toHaveScreenshot;
|
const configOptions = testInfo._projectInternal.expect?.toHaveScreenshot || {};
|
||||||
const snapshotPathResolver = testInfo.snapshotPath.bind(testInfo);
|
const snapshotPathResolver = testInfo.snapshotPath.bind(testInfo);
|
||||||
const helper = new SnapshotHelper(
|
const helper = new SnapshotHelper(
|
||||||
testInfo, 'toHaveScreenshot', locator, snapshotPathResolver, 'png',
|
testInfo, 'toHaveScreenshot', locator, snapshotPathResolver, 'png',
|
||||||
{
|
configOptions, nameOrOptions, optOptions);
|
||||||
_comparator: config?._comparator,
|
|
||||||
maxDiffPixels: config?.maxDiffPixels,
|
|
||||||
maxDiffPixelRatio: config?.maxDiffPixelRatio,
|
|
||||||
threshold: config?.threshold,
|
|
||||||
},
|
|
||||||
nameOrOptions, optOptions);
|
|
||||||
if (!helper.snapshotPath.toLowerCase().endsWith('.png'))
|
if (!helper.snapshotPath.toLowerCase().endsWith('.png'))
|
||||||
throw new Error(`Screenshot name "${path.basename(helper.snapshotPath)}" must have '.png' extension`);
|
throw new Error(`Screenshot name "${path.basename(helper.snapshotPath)}" must have '.png' extension`);
|
||||||
expectTypes(pageOrLocator, ['Page', 'Locator'], 'toHaveScreenshot');
|
expectTypes(pageOrLocator, ['Page', 'Locator'], 'toHaveScreenshot');
|
||||||
return await zones.preserve(async () => {
|
return await zones.preserve(async () => {
|
||||||
// Loading from filesystem resets zones.
|
// Loading from filesystem resets zones.
|
||||||
const style = await loadScreenshotStyles(helper.allOptions.stylePath || config?.stylePath);
|
const style = await loadScreenshotStyles(helper.options.stylePath);
|
||||||
return toHaveScreenshotContinuation.call(this, helper, page, locator, config, style);
|
return toHaveScreenshotContinuation.call(this, helper, page, locator, style);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toHaveScreenshotContinuation(
|
async function toHaveScreenshotContinuation(
|
||||||
this: ExpectMatcherContext,
|
this: ExpectMatcherContext,
|
||||||
helper: SnapshotHelper<HaveScreenshotOptions>,
|
helper: SnapshotHelper,
|
||||||
page: PageEx,
|
page: PageEx,
|
||||||
locator: LocatorEx | undefined,
|
locator: Locator | undefined,
|
||||||
config?: HaveScreenshotOptions,
|
|
||||||
style?: string) {
|
style?: string) {
|
||||||
const screenshotOptions: any = {
|
const expectScreenshotOptions: ExpectScreenshotOptions = {
|
||||||
animations: config?.animations ?? 'disabled',
|
locator,
|
||||||
scale: config?.scale ?? 'css',
|
animations: helper.options.animations ?? 'disabled',
|
||||||
caret: config?.caret ?? 'hide',
|
caret: helper.options.caret ?? 'hide',
|
||||||
|
clip: helper.options.clip,
|
||||||
|
fullPage: helper.options.fullPage,
|
||||||
|
mask: helper.options.mask,
|
||||||
|
maskColor: helper.options.maskColor,
|
||||||
|
omitBackground: helper.options.omitBackground,
|
||||||
|
scale: helper.options.scale ?? 'css',
|
||||||
style,
|
style,
|
||||||
...helper.allOptions,
|
isNot: !!this.isNot,
|
||||||
mask: (helper.allOptions.mask || []) as LocatorEx[],
|
timeout: currentExpectTimeout(helper.options),
|
||||||
maskColor: helper.allOptions.maskColor,
|
comparator: helper.options.comparator,
|
||||||
name: undefined,
|
maxDiffPixels: helper.options.maxDiffPixels,
|
||||||
threshold: undefined,
|
maxDiffPixelRatio: helper.options.maxDiffPixelRatio,
|
||||||
maxDiffPixels: undefined,
|
threshold: helper.options.threshold,
|
||||||
maxDiffPixelRatio: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasSnapshot = fs.existsSync(helper.snapshotPath);
|
const hasSnapshot = fs.existsSync(helper.snapshotPath);
|
||||||
|
|
@ -385,17 +409,8 @@ async function toHaveScreenshotContinuation(
|
||||||
// Having `errorMessage` means we timed out while waiting
|
// Having `errorMessage` means we timed out while waiting
|
||||||
// for screenshots not to match, so screenshots
|
// for screenshots not to match, so screenshots
|
||||||
// are actually the same in the end.
|
// are actually the same in the end.
|
||||||
const isDifferent = !(await page._expectScreenshot({
|
expectScreenshotOptions.expected = await fs.promises.readFile(helper.snapshotPath);
|
||||||
expected: await fs.promises.readFile(helper.snapshotPath),
|
const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
|
||||||
isNot: true,
|
|
||||||
locator,
|
|
||||||
comparatorOptions: {
|
|
||||||
...helper.comparatorOptions,
|
|
||||||
comparator: helper.comparatorOptions._comparator,
|
|
||||||
},
|
|
||||||
screenshotOptions,
|
|
||||||
timeout: currentExpectTimeout(helper.allOptions),
|
|
||||||
})).errorMessage;
|
|
||||||
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,15 +420,7 @@ async function toHaveScreenshotContinuation(
|
||||||
|
|
||||||
if (!hasSnapshot) {
|
if (!hasSnapshot) {
|
||||||
// Regenerate a new screenshot by waiting until two screenshots are the same.
|
// Regenerate a new screenshot by waiting until two screenshots are the same.
|
||||||
const timeout = currentExpectTimeout(helper.allOptions);
|
const { actual, previous, diff, errorMessage, log } = await page._expectScreenshot(expectScreenshotOptions);
|
||||||
const { actual, previous, diff, errorMessage, log } = await page._expectScreenshot({
|
|
||||||
expected: undefined,
|
|
||||||
isNot: false,
|
|
||||||
locator,
|
|
||||||
comparatorOptions: { ...helper.comparatorOptions, comparator: helper.comparatorOptions._comparator },
|
|
||||||
screenshotOptions,
|
|
||||||
timeout,
|
|
||||||
});
|
|
||||||
// We tried re-generating new snapshot but failed.
|
// We tried re-generating new snapshot but failed.
|
||||||
// This can be due to e.g. spinning animation, so we want to show it as a diff.
|
// This can be due to e.g. spinning animation, so we want to show it as a diff.
|
||||||
if (errorMessage)
|
if (errorMessage)
|
||||||
|
|
@ -427,15 +434,8 @@ async function toHaveScreenshotContinuation(
|
||||||
// - snapshot exists
|
// - snapshot exists
|
||||||
// - regular matcher (i.e. not a `.not`)
|
// - regular matcher (i.e. not a `.not`)
|
||||||
// - perhaps an 'all' flag to update non-matching screenshots
|
// - perhaps an 'all' flag to update non-matching screenshots
|
||||||
const expected = await fs.promises.readFile(helper.snapshotPath);
|
expectScreenshotOptions.expected = await fs.promises.readFile(helper.snapshotPath);
|
||||||
const { actual, diff, errorMessage, log } = await page._expectScreenshot({
|
const { actual, diff, errorMessage, log } = await page._expectScreenshot(expectScreenshotOptions);
|
||||||
expected,
|
|
||||||
isNot: false,
|
|
||||||
locator,
|
|
||||||
comparatorOptions: { ...helper.comparatorOptions, comparator: helper.comparatorOptions._comparator },
|
|
||||||
screenshotOptions,
|
|
||||||
timeout: currentExpectTimeout(helper.allOptions),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!errorMessage)
|
if (!errorMessage)
|
||||||
return helper.handleMatching();
|
return helper.handleMatching();
|
||||||
|
|
@ -448,7 +448,7 @@ async function toHaveScreenshotContinuation(
|
||||||
return helper.createMatcherResult(helper.snapshotPath + ' running with --update-snapshots, writing actual.', true);
|
return helper.createMatcherResult(helper.snapshotPath + ' running with --update-snapshots, writing actual.', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return helper.handleDifferent(actual, expected, undefined, diff, errorMessage, log);
|
return helper.handleDifferent(actual, expectScreenshotOptions.expected, undefined, diff, errorMessage, log);
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeFileSync(aPath: string, content: Buffer | string) {
|
function writeFileSync(aPath: string, content: Buffer | string) {
|
||||||
|
|
|
||||||
|
|
@ -1939,26 +1939,22 @@ export type PageExpectScreenshotParams = {
|
||||||
frame: FrameChannel,
|
frame: FrameChannel,
|
||||||
selector: string,
|
selector: string,
|
||||||
},
|
},
|
||||||
comparatorOptions?: {
|
comparator?: string,
|
||||||
comparator?: string,
|
maxDiffPixels?: number,
|
||||||
maxDiffPixels?: number,
|
maxDiffPixelRatio?: number,
|
||||||
maxDiffPixelRatio?: number,
|
threshold?: number,
|
||||||
threshold?: number,
|
fullPage?: boolean,
|
||||||
},
|
clip?: Rect,
|
||||||
screenshotOptions?: {
|
omitBackground?: boolean,
|
||||||
fullPage?: boolean,
|
caret?: 'hide' | 'initial',
|
||||||
clip?: Rect,
|
animations?: 'disabled' | 'allow',
|
||||||
omitBackground?: boolean,
|
scale?: 'css' | 'device',
|
||||||
caret?: 'hide' | 'initial',
|
mask?: {
|
||||||
animations?: 'disabled' | 'allow',
|
frame: FrameChannel,
|
||||||
scale?: 'css' | 'device',
|
selector: string,
|
||||||
mask?: {
|
}[],
|
||||||
frame: FrameChannel,
|
maskColor?: string,
|
||||||
selector: string,
|
style?: string,
|
||||||
}[],
|
|
||||||
maskColor?: string,
|
|
||||||
style?: string,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export type PageExpectScreenshotOptions = {
|
export type PageExpectScreenshotOptions = {
|
||||||
expected?: Binary,
|
expected?: Binary,
|
||||||
|
|
@ -1967,26 +1963,22 @@ export type PageExpectScreenshotOptions = {
|
||||||
frame: FrameChannel,
|
frame: FrameChannel,
|
||||||
selector: string,
|
selector: string,
|
||||||
},
|
},
|
||||||
comparatorOptions?: {
|
comparator?: string,
|
||||||
comparator?: string,
|
maxDiffPixels?: number,
|
||||||
maxDiffPixels?: number,
|
maxDiffPixelRatio?: number,
|
||||||
maxDiffPixelRatio?: number,
|
threshold?: number,
|
||||||
threshold?: number,
|
fullPage?: boolean,
|
||||||
},
|
clip?: Rect,
|
||||||
screenshotOptions?: {
|
omitBackground?: boolean,
|
||||||
fullPage?: boolean,
|
caret?: 'hide' | 'initial',
|
||||||
clip?: Rect,
|
animations?: 'disabled' | 'allow',
|
||||||
omitBackground?: boolean,
|
scale?: 'css' | 'device',
|
||||||
caret?: 'hide' | 'initial',
|
mask?: {
|
||||||
animations?: 'disabled' | 'allow',
|
frame: FrameChannel,
|
||||||
scale?: 'css' | 'device',
|
selector: string,
|
||||||
mask?: {
|
}[],
|
||||||
frame: FrameChannel,
|
maskColor?: string,
|
||||||
selector: string,
|
style?: string,
|
||||||
}[],
|
|
||||||
maskColor?: string,
|
|
||||||
style?: string,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
export type PageExpectScreenshotResult = {
|
export type PageExpectScreenshotResult = {
|
||||||
diff?: Binary,
|
diff?: Binary,
|
||||||
|
|
|
||||||
|
|
@ -1367,19 +1367,13 @@ Page:
|
||||||
properties:
|
properties:
|
||||||
frame: Frame
|
frame: Frame
|
||||||
selector: string
|
selector: string
|
||||||
comparatorOptions:
|
comparator: string?
|
||||||
type: object?
|
maxDiffPixels: number?
|
||||||
properties:
|
maxDiffPixelRatio: number?
|
||||||
comparator: string?
|
threshold: number?
|
||||||
maxDiffPixels: number?
|
fullPage: boolean?
|
||||||
maxDiffPixelRatio: number?
|
clip: Rect?
|
||||||
threshold: number?
|
$mixin: CommonScreenshotOptions
|
||||||
screenshotOptions:
|
|
||||||
type: object?
|
|
||||||
properties:
|
|
||||||
fullPage: boolean?
|
|
||||||
clip: Rect?
|
|
||||||
$mixin: CommonScreenshotOptions
|
|
||||||
returns:
|
returns:
|
||||||
diff: binary?
|
diff: binary?
|
||||||
errorMessage: string?
|
errorMessage: string?
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@ type ImageComparatorOptions = { threshold?: number, maxDiffPixels?: number, maxD
|
||||||
|
|
||||||
export function comparePNGs(actual: Buffer, expected: Buffer, options: ImageComparatorOptions = {}): ComparatorResult {
|
export function comparePNGs(actual: Buffer, expected: Buffer, options: ImageComparatorOptions = {}): ComparatorResult {
|
||||||
// Strict threshold by default in our tests.
|
// Strict threshold by default in our tests.
|
||||||
return pngComparator(actual, expected, { _comparator: 'ssim-cie94', threshold: 0, ...options });
|
return pngComparator(actual, expected, { comparator: 'ssim-cie94', threshold: 0, ...options });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue