chore: follow up to the stylePath review (#28636)

This commit is contained in:
Pavel Feldman 2023-12-14 08:19:24 -08:00 committed by GitHub
parent ff3b70b625
commit d242ff67ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 128 additions and 49 deletions

View File

@ -1541,7 +1541,7 @@ Snapshot name.
### option: LocatorAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
* since: v1.35
### option: LocatorAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%%
### option: LocatorAssertions.toHaveScreenshot#1.stylePath = %%-screenshot-option-style-path-%%
* since: v1.41
### option: LocatorAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
@ -1590,7 +1590,7 @@ Note that screenshot assertions only work with Playwright test runner.
### option: LocatorAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
* since: v1.35
### option: LocatorAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%%
### option: LocatorAssertions.toHaveScreenshot#2.stylePath = %%-screenshot-option-style-path-%%
* since: v1.41
### option: LocatorAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%

View File

@ -161,7 +161,7 @@ Snapshot name.
### option: PageAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
* since: v1.35
### option: PageAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%%
### option: PageAssertions.toHaveScreenshot#1.stylePath = %%-screenshot-option-style-path-%%
* since: v1.41
### option: PageAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
@ -215,7 +215,7 @@ Note that screenshot assertions only work with Playwright test runner.
### option: PageAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
* since: v1.35
### option: PageAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%%
### option: PageAssertions.toHaveScreenshot#2.stylePath = %%-screenshot-option-style-path-%%
* since: v1.41
### option: PageAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%

View File

@ -1137,10 +1137,15 @@ When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`,
## screenshot-option-style
- `style` <string>
Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements invisible
Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements invisible
or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM and applies
to the inner frames.
## screenshot-option-style-path
- `stylePath` <[string]|[Array]<[string]>>
File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM and applies to the inner frames.
## screenshot-options-common-list-v1.8
- %%-screenshot-option-animations-%%
- %%-screenshot-option-omit-background-%%

View File

@ -64,7 +64,7 @@ export default defineConfig({
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
- `style` ?<[string]> See [`option: style`] in [`method: Page.screenshot`].
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: style`] in [`method: Page.screenshot`].
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
- `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.

View File

@ -97,6 +97,7 @@ export default defineConfig({
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
- `caret` ?<[ScreenshotCaret]<"hide"|"initial">> See [`option: caret`] in [`method: Page.screenshot`]. Defaults to `"hide"`.
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
- `stylePath` ?<[string]|[Array]<[string]>> See [`option: style`] in [`method: Page.screenshot`].
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
- `threshold` ?<[float]> an acceptable perceived color difference between the same pixel in compared images, ranging from `0` (strict) and `1` (lax). `"pixelmatch"` comparator computes color difference in [YIQ color space](https://en.wikipedia.org/wiki/YIQ) and defaults `threshold` value to `0.2`.
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.

View File

@ -16,6 +16,8 @@ test('example test', async ({ page }) => {
});
```
## Generating screenshots
When you run above for the first time, test runner will say:
```txt
@ -42,13 +44,7 @@ The snapshot name `example-test-1-chromium-darwin.png` consists of a few parts:
- `chromium-darwin` - the browser name and the platform. Screenshots differ between browsers and platforms due to different rendering, fonts and more, so you will need different snapshots for them. If you use multiple projects in your [configuration file](./test-configuration.md), project name will be used instead of `chromium`.
If you are not on the same operating system as your CI system, you can use Docker to generate/update the screenshots:
```bash
docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v%%VERSION%%-jammy /bin/bash
npm install
npx playwright test --update-snapshots
```
## Updating screenshots
Sometimes you need to update the reference screenshot, for example when the page has changed. Do this with the `--update-snapshots` flag.
@ -59,6 +55,10 @@ npx playwright test --update-snapshots
> Note that `snapshotName` also accepts an array of path segments to the snapshot file such as `expect().toHaveScreenshot(['relative', 'path', 'to', 'snapshot.png'])`.
> However, this path must stay within the snapshots directory for each test file (i.e. `a.spec.js-snapshots`), otherwise it will throw.
## Options
### maxDiffPixels
Playwright Test uses the [pixelmatch](https://github.com/mapbox/pixelmatch) library. You can [pass various options](./api/class-pageassertions.md#page-assertions-to-have-screenshot-1) to modify its behavior:
```js title="example.spec.ts"
@ -81,6 +81,42 @@ export default defineConfig({
});
```
### stylePath
You can apply a custom stylesheet to your page while taking screenshot. This
allows filtering out dynamic or volatile elements, hence improving the screenshot
determinism.
```css title="screenshot.css"
iframe {
visibility: hidden;
}
```
```js title="example.spec.ts"
import { test, expect } from '@playwright/test';
test('example test', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveScreenshot({ styleFile: path.join(__dirname, 'screenshot.css') });
});
```
If you'd like to share the default value among all the tests in the project, you can specify it in the playwright config, either globally or per project:
```js title="playwright.config.ts"
import { defineConfig } from '@playwright/test';
export default defineConfig({
expect: {
toHaveScreenshot: {
styleFile: './screenshot.css'
},
},
});
```
## Non-image snapshots
Apart from screenshots, you can use `expect(value).toMatchSnapshot(snapshotName)` to compare text or arbitrary binary data. Playwright Test auto-detects the content type and uses the appropriate comparison algorithm.
Here we compare text content against the reference.

View File

@ -10134,9 +10134,9 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make
* elements invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces
* the Shadow DOM and applies to the inner frames.
*/
style?: string;
@ -20256,9 +20256,9 @@ export interface LocatorScreenshotOptions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make
* elements invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces
* the Shadow DOM and applies to the inner frames.
*/
style?: string;
@ -20456,9 +20456,9 @@ export interface PageScreenshotOptions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make
* elements invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces
* the Shadow DOM and applies to the inner frames.
*/
style?: string;

View File

@ -189,6 +189,10 @@ export class FullProjectInternal {
};
this.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, undefined);
this.expect = takeFirst(projectConfig.expect, config.expect, {});
if (this.expect.toHaveScreenshot?.stylePath) {
const stylePaths = Array.isArray(this.expect.toHaveScreenshot.stylePath) ? this.expect.toHaveScreenshot.stylePath : [this.expect.toHaveScreenshot.stylePath];
this.expect.toHaveScreenshot.stylePath = stylePaths.map(stylePath => path.resolve(configDir, stylePath));
}
this.respectGitIgnore = !projectConfig.testDir && !config.testDir;
}
}

View File

@ -19,7 +19,7 @@ import type { 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 type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils';
import { getComparator, sanitizeForFilePath } from 'playwright-core/lib/utils';
import { getComparator, sanitizeForFilePath, zones } from 'playwright-core/lib/utils';
import type { PageScreenshotOptions } from 'playwright-core/types/types';
import {
addSuffixToFilePath, serializeError,
@ -308,7 +308,7 @@ export function toMatchSnapshot(
return helper.handleDifferent(received, expected, undefined, result.diff, result.errorMessage, undefined);
}
type HaveScreenshotOptions = ImageComparatorOptions & Omit<PageScreenshotOptions, 'type' | 'quality' | 'path'>;
type HaveScreenshotOptions = ImageComparatorOptions & Omit<PageScreenshotOptions, 'type' | 'quality' | 'path' | 'style'> & { stylePath?: string | string[] };
export function toHaveScreenshotStepTitle(
nameOrOptions: NameOrSegments | { name?: NameOrSegments } & HaveScreenshotOptions = {},
@ -351,12 +351,25 @@ export async function toHaveScreenshot(
if (!helper.snapshotPath.toLowerCase().endsWith('.png'))
throw new Error(`Screenshot name "${path.basename(helper.snapshotPath)}" must have '.png' extension`);
expectTypes(pageOrLocator, ['Page', 'Locator'], 'toHaveScreenshot');
return await zones.preserve(async () => {
// Loading from filesystem resets zones.
const style = await loadScreenshotStyles(optOptions.stylePath || config?.stylePath);
return toHaveScreenshotContinuation.call(this, helper, page, locator, config, style);
});
}
const screenshotOptions = {
async function toHaveScreenshotContinuation(
this: ExpectMatcherContext,
helper: SnapshotHelper<HaveScreenshotOptions>,
page: PageEx,
locator: LocatorEx | undefined,
config?: HaveScreenshotOptions,
style?: string) {
const screenshotOptions: any = {
animations: config?.animations ?? 'disabled',
scale: config?.scale ?? 'css',
caret: config?.caret ?? 'hide',
style: config?.style ?? '',
style,
...helper.allOptions,
mask: (helper.allOptions.mask || []) as LocatorEx[],
maskColor: helper.allOptions.maskColor,
@ -462,3 +475,15 @@ function determineFileExtension(file: string | Buffer): string {
function compareMagicBytes(file: Buffer, magicBytes: number[]): boolean {
return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0;
}
async function loadScreenshotStyles(stylePath?: string | string[]): Promise<string | undefined> {
if (!stylePath)
return;
const stylePaths = Array.isArray(stylePath) ? stylePath : [stylePath];
const styles = await Promise.all(stylePaths.map(async stylePath => {
const text = await fs.promises.readFile(stylePath, 'utf8');
return text.trim();
}));
return styles.join('\n').trim() || undefined;
}

View File

@ -685,7 +685,7 @@ interface TestConfig {
/**
* See `style` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot).
*/
style?: string;
stylePath?: string|Array<string>;
};
/**
@ -5993,11 +5993,11 @@ interface LocatorAssertions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
* stylesheet pierces the Shadow DOM and applies to the inner frames.
*/
style?: string;
stylePath?: string|Array<string>;
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
@ -6083,11 +6083,11 @@ interface LocatorAssertions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
* stylesheet pierces the Shadow DOM and applies to the inner frames.
*/
style?: string;
stylePath?: string|Array<string>;
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
@ -6354,11 +6354,11 @@ interface PageAssertions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
* stylesheet pierces the Shadow DOM and applies to the inner frames.
*/
style?: string;
stylePath?: string|Array<string>;
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
@ -6474,11 +6474,11 @@ interface PageAssertions {
scale?: "css"|"device";
/**
* Stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the
* Shadow DOM and applies to the inner frames.
* File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic
* elements, make elements invisible or change their properties to help you creating repeatable screenshots. This
* stylesheet pierces the Shadow DOM and applies to the inner frames.
*/
style?: string;
stylePath?: string|Array<string>;
/**
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
@ -6830,6 +6830,11 @@ interface TestProject {
* to `"css"`.
*/
scale?: "css"|"device";
/**
* See `style` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot).
*/
stylePath?: string|Array<string>;
};
/**

View File

@ -1216,18 +1216,19 @@ test('should support maskColor option', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0);
});
test('should support style option', async ({ runInlineTest }) => {
test('should support stylePath option', async ({ runInlineTest }) => {
const result = await runInlineTest({
...playwrightConfig({
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
}),
'__screenshots__/a.spec.js/snapshot.png': createImage(IMG_WIDTH, IMG_HEIGHT, 0, 255, 0),
'a.spec.js': `
'__screenshots__/tests/a.spec.js/snapshot.png': createImage(IMG_WIDTH, IMG_HEIGHT, 0, 255, 0),
'screenshot.css': 'body { background: #00FF00; }',
'tests/a.spec.js': `
const { test, expect } = require('@playwright/test');
test('png', async ({ page }) => {
await page.setContent('<style> html,body { padding: 0; margin: 0; }</style>');
await expect(page).toHaveScreenshot('snapshot.png', {
style: 'body { background: #00FF00; }',
stylePath: './screenshot.css',
});
});
`,
@ -1235,16 +1236,17 @@ test('should support style option', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0);
});
test('should support style option in config', async ({ runInlineTest }) => {
test('should support stylePath option in config', async ({ runInlineTest }) => {
const result = await runInlineTest({
...playwrightConfig({
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
expect: {
toHaveScreenshot: {
style: 'body { background: #00FF00; }',
stylePath: './screenshot.css',
},
},
}),
'screenshot.css': 'body { background: #00FF00; }',
'__screenshots__/a.spec.js/snapshot.png': createImage(IMG_WIDTH, IMG_HEIGHT, 0, 255, 0),
'a.spec.js': `
const { test, expect } = require('@playwright/test');

View File

@ -181,6 +181,7 @@ async function run() {
const allowedCodeLangs = new Set([
'csharp',
'java',
'css',
'js',
'ts',
'python',