feat(toHaveScreenshot): enable sensible defaults (#12675)

This patch:
- Enables configuration of certain defaults for some options of `expect.toHaveScreenshot` method via `TestProject.expect.toHaveScreenshot` property
- Sets sensible defaults for these options:
  * `fonts: "ready"`
  * `animations: "disabled"`
  * `size: "css"`
This commit is contained in:
Andrey Lushnikov 2022-03-11 09:45:36 -07:00 committed by GitHub
parent 9d4f330411
commit e3ab6388a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 3 deletions

View File

@ -40,6 +40,10 @@ export default config;
- `threshold` <[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`.
- `maxDiffPixels` <[int]> an acceptable amount of pixels that could be different, unset by default.
- `maxDiffPixelRatio` <[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
- `animations` <[ScreenshotAnimations]<"allow"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`.
- `fonts` <[ScreenshotFonts]<"ready"|"nowait">> See [`option: fonts`] in [`method: Page.screenshot`]. Defaults to `"ready"`.
- `size` <[ScreenshotSize]<"css"|"device">> See [`option: size`] in [`method: Page.screenshot`]. Defaults to `"css"`.
- `toMatchSnapshot` <[Object]>
- `threshold` <[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`.
- `maxDiffPixels` <[int]> an acceptable amount of pixels that could be different, unset by default.

View File

@ -111,6 +111,9 @@ export default config;
- `threshold` <[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`.
- `maxDiffPixels` <[int]> an acceptable amount of pixels that could be different, unset by default.
- `maxDiffPixelRatio` <[float]> an acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
- `animations` <[ScreenshotAnimations]<"allow"|"disable">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disable"`.
- `fonts` <[ScreenshotFonts]<"ready"|"nowait">> See [`option: fonts`] in [`method: Page.screenshot`]. Defaults to `"ready"`.
- `size` <[ScreenshotSize]<"css"|"device">> See [`option: size`] in [`method: Page.screenshot`]. Defaults to `"css"`.
- `toMatchSnapshot` <[Object]>
- `threshold` <[float]> an acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the same pixel in compared images, between zero (strict) and one (lax). Defaults to `0.2`.
- `maxDiffPixels` <[int]> an acceptable amount of pixels that could be different, unset by default.

View File

@ -285,12 +285,20 @@ export async function toHaveScreenshot(
const testInfo = currentTestInfo();
if (!testInfo)
throw new Error(`toHaveScreenshot() must be called during the test`);
const config = testInfo.project.expect?.toHaveScreenshot;
const helper = new SnapshotHelper(
testInfo, testInfo._screenshotPath.bind(testInfo), 'png',
testInfo.project.expect?.toHaveScreenshot || {},
{
maxDiffPixels: config?.maxDiffPixels,
maxDiffPixelRatio: config?.maxDiffPixelRatio,
threshold: config?.threshold,
},
nameOrOptions, optOptions);
const [page, locator] = pageOrLocator.constructor.name === 'Page' ? [(pageOrLocator as PageEx), undefined] : [(pageOrLocator as Locator).page() as PageEx, pageOrLocator as LocatorEx];
const screenshotOptions = {
animations: config?.animations ?? 'disabled',
fonts: config?.fonts ?? 'ready',
size: config?.size ?? 'css',
...helper.allOptions,
mask: (helper.allOptions.mask || []) as LocatorEx[],
name: undefined,

View File

@ -55,6 +55,27 @@ type ExpectSettings = {
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
*/
maxDiffPixelRatio?: number,
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that leaves animations untouched.
*/
animations?: 'allow'|'disabled',
/**
* When set to `"ready"`, screenshot will wait for
* [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all
* frames. Defaults to `"ready"`.
*/
fonts?: 'ready'|'nowait',
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
*/
size?: 'css'|'device',
}
toMatchSnapshot?: {
/** An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between pixels in compared images, between zero (strict) and one (lax). Defaults to `0.2`.

View File

@ -35,6 +35,13 @@ const blueImage = createImage(IMG_WIDTH, IMG_HEIGHT, 0, 0, 255);
test('should fail to screenshot a page with infinite animation', async ({ runInlineTest }, testInfo) => {
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
const result = await runInlineTest({
...playwrightConfig({
expect: {
toHaveScreenshot: {
animations: 'allow',
},
},
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${infiniteAnimationURL}');
@ -50,6 +57,62 @@ test('should fail to screenshot a page with infinite animation', async ({ runInl
expect(fs.existsSync(testInfo.outputPath('a.spec.js-snapshots', 'is-a-test-1.png'))).toBe(false);
});
test('should disable animations by default', async ({ runInlineTest }, testInfo) => {
const cssTransitionURL = pathToFileURL(path.join(__dirname, '../assets/css-transition.html'));
const result = await runInlineTest({
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${cssTransitionURL}');
await expect(page).toHaveScreenshot({ timeout: 2000 });
});
`
}, { 'update-snapshots': true });
expect(result.exitCode).toBe(0);
});
test('should have size as css by default', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...playwrightConfig({ screenshotsDir: '__screenshots__' }),
'a.spec.js': `
pwt.test('is a test', async ({ browser }) => {
const context = await browser.newContext({
viewport: { width: ${IMG_WIDTH}, height: ${IMG_HEIGHT} },
deviceScaleFactor: 2,
});
const page = await context.newPage();
await expect(page).toHaveScreenshot('snapshot.png');
await context.close();
});
`
}, { 'update-snapshots': true });
expect(result.exitCode).toBe(0);
const snapshotOutputPath = testInfo.outputPath('__screenshots__', 'a.spec.js', 'snapshot.png');
expect(pngComparator(fs.readFileSync(snapshotOutputPath), whiteImage)).toBe(null);
});
test('should ignore non-documented options in toHaveScreenshot config', async ({ runInlineTest }, testInfo) => {
const result = await runInlineTest({
...playwrightConfig({
screenshotsDir: '__screenshots__',
expect: {
toHaveScreenshot: {
clip: { x: 0, y: 0, width: 10, height: 10 },
},
},
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await expect(page).toHaveScreenshot('snapshot.png');
});
`
}, { 'update-snapshots': true });
expect(result.exitCode).toBe(0);
const snapshotOutputPath = testInfo.outputPath('__screenshots__', 'a.spec.js', 'snapshot.png');
expect(pngComparator(fs.readFileSync(snapshotOutputPath), whiteImage)).toBe(null);
});
test('screenshotPath should include platform and project name by default', async ({ runInlineTest }, testInfo) => {
const PROJECT_NAME = 'woof-woof';
const result = await runInlineTest({
@ -203,7 +266,16 @@ test('should support omitBackground option for locator', async ({ runInlineTest
test('should fail to screenshot an element with infinite animation', async ({ runInlineTest }, testInfo) => {
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
const result = await runInlineTest({
...playwrightConfig({ screenshotsDir: '__screenshots__' }),
...playwrightConfig({
screenshotsDir: '__screenshots__',
projects: [{
expect: {
toHaveScreenshot: {
animations: 'allow',
},
},
}],
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${infiniteAnimationURL}');
@ -222,7 +294,14 @@ test('should fail to screenshot an element with infinite animation', async ({ ru
test('should fail to screenshot an element that keeps moving', async ({ runInlineTest }, testInfo) => {
const infiniteAnimationURL = pathToFileURL(path.join(__dirname, '../assets/rotate-z.html'));
const result = await runInlineTest({
...playwrightConfig({ screenshotsDir: '__screenshots__' }),
...playwrightConfig({
screenshotsDir: '__screenshots__',
expect: {
toHaveScreenshot: {
animations: 'allow',
},
},
}),
'a.spec.js': `
pwt.test('is a test', async ({ page }) => {
await page.goto('${infiniteAnimationURL}');

View File

@ -54,6 +54,27 @@ type ExpectSettings = {
* An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1` , unset by default.
*/
maxDiffPixelRatio?: number,
/**
* When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different treatment
* depending on their duration:
* - finite animations are fast-forwarded to completion, so they'll fire `transitionend` event.
* - infinite animations are canceled to initial state, and then played over after the screenshot.
*
* Defaults to `"disabled"` that leaves animations untouched.
*/
animations?: 'allow'|'disabled',
/**
* When set to `"ready"`, screenshot will wait for
* [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all
* frames. Defaults to `"ready"`.
*/
fonts?: 'ready'|'nowait',
/**
* When set to `"css"`, screenshot will have a single pixel per each css pixel on the page. For high-dpi devices, this will
* keep screenshots small. Using `"device"` option will produce a single pixel per each device pixel, so screenhots of
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
*/
size?: 'css'|'device',
}
toMatchSnapshot?: {
/** An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between pixels in compared images, between zero (strict) and one (lax). Defaults to `0.2`.