feat(vrt): allow providing screenshot style (#28229)
This commit is contained in:
parent
da6a36062e
commit
0a7a10d0f6
|
|
@ -749,6 +749,9 @@ Returns the buffer with the captured screenshot.
|
||||||
### option: ElementHandle.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
### option: ElementHandle.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.34
|
* since: v1.34
|
||||||
|
|
||||||
|
### option: ElementHandle.screenshot.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
## async method: ElementHandle.scrollIntoViewIfNeeded
|
## async method: ElementHandle.scrollIntoViewIfNeeded
|
||||||
* since: v1.8
|
* since: v1.8
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1913,6 +1913,9 @@ Returns the buffer with the captured screenshot.
|
||||||
### option: Locator.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
### option: Locator.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.34
|
* since: v1.34
|
||||||
|
|
||||||
|
### option: Locator.screenshot.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
## async method: Locator.scrollIntoViewIfNeeded
|
## async method: Locator.scrollIntoViewIfNeeded
|
||||||
* since: v1.14
|
* since: v1.14
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1541,6 +1541,9 @@ Snapshot name.
|
||||||
### option: LocatorAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
|
### option: LocatorAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.35
|
* since: v1.35
|
||||||
|
|
||||||
|
### option: LocatorAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
### option: LocatorAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
|
### option: LocatorAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
|
||||||
* since: v1.23
|
* since: v1.23
|
||||||
|
|
||||||
|
|
@ -1587,6 +1590,9 @@ Note that screenshot assertions only work with Playwright test runner.
|
||||||
### option: LocatorAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
|
### option: LocatorAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.35
|
* since: v1.35
|
||||||
|
|
||||||
|
### option: LocatorAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
### option: LocatorAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
|
### option: LocatorAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
|
||||||
* since: v1.23
|
* since: v1.23
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3399,6 +3399,9 @@ Returns the buffer with the captured screenshot.
|
||||||
### option: Page.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
### option: Page.screenshot.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.34
|
* since: v1.34
|
||||||
|
|
||||||
|
### option: Page.screenshot.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
## async method: Page.selectOption
|
## async method: Page.selectOption
|
||||||
* since: v1.8
|
* since: v1.8
|
||||||
* discouraged: Use locator-based [`method: Locator.selectOption`] instead. Read more about [locators](../locators.md).
|
* discouraged: Use locator-based [`method: Locator.selectOption`] instead. Read more about [locators](../locators.md).
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,9 @@ Snapshot name.
|
||||||
### option: PageAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
|
### option: PageAssertions.toHaveScreenshot#1.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.35
|
* since: v1.35
|
||||||
|
|
||||||
|
### option: PageAssertions.toHaveScreenshot#1.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
### option: PageAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
|
### option: PageAssertions.toHaveScreenshot#1.omitBackground = %%-screenshot-option-omit-background-%%
|
||||||
* since: v1.23
|
* since: v1.23
|
||||||
|
|
||||||
|
|
@ -212,6 +215,9 @@ Note that screenshot assertions only work with Playwright test runner.
|
||||||
### option: PageAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
|
### option: PageAssertions.toHaveScreenshot#2.maskColor = %%-screenshot-option-mask-color-%%
|
||||||
* since: v1.35
|
* since: v1.35
|
||||||
|
|
||||||
|
### option: PageAssertions.toHaveScreenshot#2.style = %%-screenshot-option-style-%%
|
||||||
|
* since: v1.41
|
||||||
|
|
||||||
### option: PageAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
|
### option: PageAssertions.toHaveScreenshot#2.omitBackground = %%-screenshot-option-omit-background-%%
|
||||||
* since: v1.23
|
* since: v1.23
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1132,6 +1132,13 @@ Defaults to `"css"`.
|
||||||
|
|
||||||
When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`.
|
When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`.
|
||||||
|
|
||||||
|
## screenshot-option-style
|
||||||
|
- `style` <string>
|
||||||
|
|
||||||
|
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-options-common-list-v1.8
|
||||||
- %%-screenshot-option-animations-%%
|
- %%-screenshot-option-animations-%%
|
||||||
- %%-screenshot-option-omit-background-%%
|
- %%-screenshot-option-omit-background-%%
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ export default defineConfig({
|
||||||
- `animations` ?<[ScreenshotAnimations]<"allow"|"disabled">> See [`option: animations`] in [`method: Page.screenshot`]. Defaults to `"disabled"`.
|
- `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"`.
|
- `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"`.
|
- `scale` ?<[ScreenshotScale]<"css"|"device">> See [`option: scale`] in [`method: Page.screenshot`]. Defaults to `"css"`.
|
||||||
|
- `style` ?<[string]> See [`option: style`] in [`method: Page.screenshot`].
|
||||||
- `toMatchSnapshot` ?<[Object]> Configuration for the [`method: SnapshotAssertions.toMatchSnapshot#1`] method.
|
- `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`.
|
- `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.
|
- `maxDiffPixels` ?<[int]> an acceptable amount of pixels that could be different, unset by default.
|
||||||
|
|
|
||||||
|
|
@ -1071,6 +1071,7 @@ scheme.PageExpectScreenshotParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
}))),
|
}))),
|
||||||
maskColor: tOptional(tString),
|
maskColor: tOptional(tString),
|
||||||
|
style: tOptional(tString),
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
scheme.PageExpectScreenshotResult = tObject({
|
scheme.PageExpectScreenshotResult = tObject({
|
||||||
|
|
@ -1095,6 +1096,7 @@ scheme.PageScreenshotParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
}))),
|
}))),
|
||||||
maskColor: tOptional(tString),
|
maskColor: tOptional(tString),
|
||||||
|
style: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.PageScreenshotResult = tObject({
|
scheme.PageScreenshotResult = tObject({
|
||||||
binary: tBinary,
|
binary: tBinary,
|
||||||
|
|
@ -1896,6 +1898,7 @@ scheme.ElementHandleScreenshotParams = tObject({
|
||||||
selector: tString,
|
selector: tString,
|
||||||
}))),
|
}))),
|
||||||
maskColor: tOptional(tString),
|
maskColor: tOptional(tString),
|
||||||
|
style: tOptional(tString),
|
||||||
});
|
});
|
||||||
scheme.ElementHandleScreenshotResult = tObject({
|
scheme.ElementHandleScreenshotResult = tObject({
|
||||||
binary: tBinary,
|
binary: tBinary,
|
||||||
|
|
|
||||||
|
|
@ -847,7 +847,7 @@ export class Frame extends SdkObject {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async maskSelectors(selectors: ParsedSelector[], color?: string): Promise<void> {
|
async maskSelectors(selectors: ParsedSelector[], color: string): Promise<void> {
|
||||||
const context = await this._utilityContext();
|
const context = await this._utilityContext();
|
||||||
const injectedScript = await context.injectedScript();
|
const injectedScript = await context.injectedScript();
|
||||||
await injectedScript.evaluate((injected, { parsed, color }) => {
|
await injectedScript.evaluate((injected, { parsed, color }) => {
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,8 @@ export class Highlight {
|
||||||
this._innerUpdateHighlight(elements, options);
|
this._innerUpdateHighlight(elements, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
maskElements(elements: Element[], color?: string) {
|
maskElements(elements: Element[], color: string) {
|
||||||
this._innerUpdateHighlight(elements, { color: color ? color : '#F0F' });
|
this._innerUpdateHighlight(elements, { color: color });
|
||||||
}
|
}
|
||||||
|
|
||||||
private _innerUpdateHighlight(elements: Element[], options: HighlightOptions) {
|
private _innerUpdateHighlight(elements: Element[], options: HighlightOptions) {
|
||||||
|
|
|
||||||
|
|
@ -1125,7 +1125,7 @@ export class InjectedScript {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
maskSelectors(selectors: ParsedSelector[], color?: string) {
|
maskSelectors(selectors: ParsedSelector[], color: string) {
|
||||||
if (this._highlight)
|
if (this._highlight)
|
||||||
this.hideHighlight();
|
this.hideHighlight();
|
||||||
this._highlight = new Highlight(this);
|
this._highlight = new Highlight(this);
|
||||||
|
|
|
||||||
|
|
@ -28,24 +28,25 @@ import { MultiMap } from '../utils/multimap';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
__cleanupScreenshot?: () => void;
|
__pwCleanupScreenshot?: () => void;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScreenshotOptions = {
|
export type ScreenshotOptions = {
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg';
|
||||||
quality?: number,
|
quality?: number;
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean;
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow';
|
||||||
mask?: { frame: Frame, selector: string}[],
|
mask?: { frame: Frame, selector: string}[];
|
||||||
maskColor?: string,
|
maskColor?: string;
|
||||||
fullPage?: boolean,
|
fullPage?: boolean;
|
||||||
clip?: Rect,
|
clip?: Rect;
|
||||||
scale?: 'css' | 'device',
|
scale?: 'css' | 'device';
|
||||||
caret?: 'hide' | 'initial',
|
caret?: 'hide' | 'initial';
|
||||||
|
style?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: boolean) {
|
function inPagePrepareForScreenshots(screenshotStyle: string, disableAnimations: boolean) {
|
||||||
const collectRoots = (root: Document | ShadowRoot, roots: (Document|ShadowRoot)[] = []): (Document|ShadowRoot)[] => {
|
const collectRoots = (root: Document | ShadowRoot, roots: (Document|ShadowRoot)[] = []): (Document|ShadowRoot)[] => {
|
||||||
roots.push(root);
|
roots.push(root);
|
||||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
|
||||||
|
|
@ -58,29 +59,23 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool
|
||||||
return roots;
|
return roots;
|
||||||
};
|
};
|
||||||
|
|
||||||
let documentRoots: (Document|ShadowRoot)[] | undefined;
|
const roots = collectRoots(document);
|
||||||
const memoizedRoots = () => documentRoots ??= collectRoots(document);
|
|
||||||
|
|
||||||
const styleTags: Element[] = [];
|
|
||||||
if (hideCaret) {
|
|
||||||
for (const root of memoizedRoots()) {
|
|
||||||
const styleTag = document.createElement('style');
|
|
||||||
styleTag.textContent = `
|
|
||||||
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
|
||||||
caret-color: transparent !important;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
if (root === document)
|
|
||||||
document.documentElement.append(styleTag);
|
|
||||||
else
|
|
||||||
root.append(styleTag);
|
|
||||||
styleTags.push(styleTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const infiniteAnimationsToResume: Set<Animation> = new Set();
|
|
||||||
const cleanupCallbacks: (() => void)[] = [];
|
const cleanupCallbacks: (() => void)[] = [];
|
||||||
|
for (const root of roots) {
|
||||||
|
const styleTag = document.createElement('style');
|
||||||
|
styleTag.textContent = screenshotStyle;
|
||||||
|
if (root === document)
|
||||||
|
document.documentElement.append(styleTag);
|
||||||
|
else
|
||||||
|
root.append(styleTag);
|
||||||
|
|
||||||
|
cleanupCallbacks.push(() => {
|
||||||
|
styleTag.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
if (disableAnimations) {
|
if (disableAnimations) {
|
||||||
|
const infiniteAnimationsToResume: Set<Animation> = new Set();
|
||||||
const handleAnimations = (root: Document|ShadowRoot): void => {
|
const handleAnimations = (root: Document|ShadowRoot): void => {
|
||||||
for (const animation of root.getAnimations()) {
|
for (const animation of root.getAnimations()) {
|
||||||
if (!animation.effect || animation.playbackRate === 0 || infiniteAnimationsToResume.has(animation))
|
if (!animation.effect || animation.playbackRate === 0 || infiniteAnimationsToResume.has(animation))
|
||||||
|
|
@ -106,7 +101,7 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (const root of memoizedRoots()) {
|
for (const root of roots) {
|
||||||
const handleRootAnimations: (() => void) = handleAnimations.bind(null, root);
|
const handleRootAnimations: (() => void) = handleAnimations.bind(null, root);
|
||||||
handleRootAnimations();
|
handleRootAnimations();
|
||||||
root.addEventListener('transitionrun', handleRootAnimations);
|
root.addEventListener('transitionrun', handleRootAnimations);
|
||||||
|
|
@ -116,23 +111,22 @@ function inPagePrepareForScreenshots(hideCaret: boolean, disableAnimations: bool
|
||||||
root.removeEventListener('animationstart', handleRootAnimations);
|
root.removeEventListener('animationstart', handleRootAnimations);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
cleanupCallbacks.push(() => {
|
||||||
|
for (const animation of infiniteAnimationsToResume) {
|
||||||
|
try {
|
||||||
|
animation.play();
|
||||||
|
} catch (e) {
|
||||||
|
// animation.play() should never throw, but
|
||||||
|
// we'd like to be on the safe side.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.__cleanupScreenshot = () => {
|
window.__pwCleanupScreenshot = () => {
|
||||||
for (const styleTag of styleTags)
|
|
||||||
styleTag.remove();
|
|
||||||
|
|
||||||
for (const animation of infiniteAnimationsToResume) {
|
|
||||||
try {
|
|
||||||
animation.play();
|
|
||||||
} catch (e) {
|
|
||||||
// animation.play() should never throw, but
|
|
||||||
// we'd like to be on the safe side.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const cleanupCallback of cleanupCallbacks)
|
for (const cleanupCallback of cleanupCallbacks)
|
||||||
cleanupCallback();
|
cleanupCallback();
|
||||||
delete window.__cleanupScreenshot;
|
delete window.__pwCleanupScreenshot;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,7 +172,7 @@ export class Screenshotter {
|
||||||
return this._queue.postTask(async () => {
|
return this._queue.postTask(async () => {
|
||||||
progress.log('taking page screenshot');
|
progress.log('taking page screenshot');
|
||||||
const { viewportSize } = await this._originalViewportSize(progress);
|
const { viewportSize } = await this._originalViewportSize(progress);
|
||||||
await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled');
|
await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled');
|
||||||
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
|
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
|
||||||
|
|
||||||
if (options.fullPage) {
|
if (options.fullPage) {
|
||||||
|
|
@ -207,7 +201,7 @@ export class Screenshotter {
|
||||||
progress.log('taking element screenshot');
|
progress.log('taking element screenshot');
|
||||||
const { viewportSize } = await this._originalViewportSize(progress);
|
const { viewportSize } = await this._originalViewportSize(progress);
|
||||||
|
|
||||||
await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled');
|
await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled');
|
||||||
progress.throwIfAborted(); // Do not do extra work.
|
progress.throwIfAborted(); // Do not do extra work.
|
||||||
|
|
||||||
await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */);
|
await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */);
|
||||||
|
|
@ -231,14 +225,11 @@ export class Screenshotter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preparePageForScreenshot(progress: Progress, hideCaret: boolean, disableAnimations: boolean) {
|
async _preparePageForScreenshot(progress: Progress, screenshotStyle: string, disableAnimations: boolean) {
|
||||||
if (!hideCaret && !disableAnimations)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (disableAnimations)
|
if (disableAnimations)
|
||||||
progress.log(' disabled all CSS animations');
|
progress.log(' disabled all CSS animations');
|
||||||
await Promise.all(this._page.frames().map(async frame => {
|
await Promise.all(this._page.frames().map(async frame => {
|
||||||
await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${hideCaret}, ${disableAnimations})`, false, 'utility').catch(() => {});
|
await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${disableAnimations})`, false, 'utility').catch(() => {});
|
||||||
}));
|
}));
|
||||||
if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) {
|
if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) {
|
||||||
progress.log('waiting for fonts to load...');
|
progress.log('waiting for fonts to load...');
|
||||||
|
|
@ -252,7 +243,7 @@ export class Screenshotter {
|
||||||
|
|
||||||
async _restorePageAfterScreenshot() {
|
async _restorePageAfterScreenshot() {
|
||||||
await Promise.all(this._page.frames().map(async frame => {
|
await Promise.all(this._page.frames().map(async frame => {
|
||||||
frame.nonStallingEvaluateInExistingContext('window.__cleanupScreenshot && window.__cleanupScreenshot()', false, 'utility').catch(() => {});
|
frame.nonStallingEvaluateInExistingContext('window.__pwCleanupScreenshot && window.__pwCleanupScreenshot()', false, 'utility').catch(() => {});
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -276,7 +267,7 @@ export class Screenshotter {
|
||||||
progress.throwIfAborted(); // Avoid extra work.
|
progress.throwIfAborted(); // Avoid extra work.
|
||||||
|
|
||||||
await Promise.all([...framesToParsedSelectors.keys()].map(async frame => {
|
await Promise.all([...framesToParsedSelectors.keys()].map(async frame => {
|
||||||
await frame.maskSelectors(framesToParsedSelectors.get(frame), options.maskColor);
|
await frame.maskSelectors(framesToParsedSelectors.get(frame), options.maskColor || '#F0F');
|
||||||
}));
|
}));
|
||||||
progress.cleanupWhenAborted(cleanup);
|
progress.cleanupWhenAborted(cleanup);
|
||||||
return cleanup;
|
return cleanup;
|
||||||
|
|
@ -368,3 +359,16 @@ export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | '
|
||||||
}
|
}
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function screenshotStyle(options: ScreenshotOptions): string {
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (options.caret !== 'initial') {
|
||||||
|
parts.push(`
|
||||||
|
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
||||||
|
caret-color: transparent !important;
|
||||||
|
}`);
|
||||||
|
}
|
||||||
|
if (options.style)
|
||||||
|
parts.push(options.style);
|
||||||
|
return parts.join('\n');
|
||||||
|
}
|
||||||
|
|
|
||||||
21
packages/playwright-core/types/types.d.ts
vendored
21
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -9967,6 +9967,13 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
||||||
* option in the config, or by using the
|
* option in the config, or by using the
|
||||||
|
|
@ -20037,6 +20044,13 @@ export interface LocatorScreenshotOptions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
||||||
* option in the config, or by using the
|
* option in the config, or by using the
|
||||||
|
|
@ -20230,6 +20244,13 @@ export interface PageScreenshotOptions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
* Maximum time in milliseconds. Defaults to `0` - no timeout. The default value can be changed via `actionTimeout`
|
||||||
* option in the config, or by using the
|
* option in the config, or by using the
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,7 @@ export async function toHaveScreenshot(
|
||||||
animations: config?.animations ?? 'disabled',
|
animations: config?.animations ?? 'disabled',
|
||||||
scale: config?.scale ?? 'css',
|
scale: config?.scale ?? 'css',
|
||||||
caret: config?.caret ?? 'hide',
|
caret: config?.caret ?? 'hide',
|
||||||
|
style: config?.style ?? '',
|
||||||
...helper.allOptions,
|
...helper.allOptions,
|
||||||
mask: (helper.allOptions.mask || []) as LocatorEx[],
|
mask: (helper.allOptions.mask || []) as LocatorEx[],
|
||||||
maskColor: helper.allOptions.maskColor,
|
maskColor: helper.allOptions.maskColor,
|
||||||
|
|
|
||||||
33
packages/playwright/types/test.d.ts
vendored
33
packages/playwright/types/test.d.ts
vendored
|
|
@ -662,6 +662,11 @@ interface TestConfig {
|
||||||
* to `"css"`.
|
* to `"css"`.
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
scale?: "css"|"device";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See `style` in [page.screenshot([options])](https://playwright.dev/docs/api/class-page#page-screenshot).
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -5951,6 +5956,13 @@ interface LocatorAssertions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
|
* 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), default is configurable with
|
* same pixel in compared images, between zero (strict) and one (lax), default is configurable with
|
||||||
|
|
@ -6034,6 +6046,13 @@ interface LocatorAssertions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
|
* 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), default is configurable with
|
* same pixel in compared images, between zero (strict) and one (lax), default is configurable with
|
||||||
|
|
@ -6298,6 +6317,13 @@ interface PageAssertions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
|
* 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), default is configurable with
|
* same pixel in compared images, between zero (strict) and one (lax), default is configurable with
|
||||||
|
|
@ -6411,6 +6437,13 @@ interface PageAssertions {
|
||||||
*/
|
*/
|
||||||
scale?: "css"|"device";
|
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.
|
||||||
|
*/
|
||||||
|
style?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An acceptable perceived color difference in the [YIQ color space](https://en.wikipedia.org/wiki/YIQ) between the
|
* 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), default is configurable with
|
* same pixel in compared images, between zero (strict) and one (lax), default is configurable with
|
||||||
|
|
|
||||||
|
|
@ -1944,6 +1944,7 @@ export type PageExpectScreenshotParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export type PageExpectScreenshotOptions = {
|
export type PageExpectScreenshotOptions = {
|
||||||
|
|
@ -1971,6 +1972,7 @@ export type PageExpectScreenshotOptions = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
export type PageExpectScreenshotResult = {
|
export type PageExpectScreenshotResult = {
|
||||||
|
|
@ -1995,6 +1997,7 @@ export type PageScreenshotParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
};
|
};
|
||||||
export type PageScreenshotOptions = {
|
export type PageScreenshotOptions = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
@ -2011,6 +2014,7 @@ export type PageScreenshotOptions = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
};
|
};
|
||||||
export type PageScreenshotResult = {
|
export type PageScreenshotResult = {
|
||||||
binary: Binary,
|
binary: Binary,
|
||||||
|
|
@ -3355,6 +3359,7 @@ export type ElementHandleScreenshotParams = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
};
|
};
|
||||||
export type ElementHandleScreenshotOptions = {
|
export type ElementHandleScreenshotOptions = {
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
|
|
@ -3369,6 +3374,7 @@ export type ElementHandleScreenshotOptions = {
|
||||||
selector: string,
|
selector: string,
|
||||||
}[],
|
}[],
|
||||||
maskColor?: string,
|
maskColor?: string,
|
||||||
|
style?: string,
|
||||||
};
|
};
|
||||||
export type ElementHandleScreenshotResult = {
|
export type ElementHandleScreenshotResult = {
|
||||||
binary: Binary,
|
binary: Binary,
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,7 @@ CommonScreenshotOptions:
|
||||||
frame: Frame
|
frame: Frame
|
||||||
selector: string
|
selector: string
|
||||||
maskColor: string?
|
maskColor: string?
|
||||||
|
style: string?
|
||||||
|
|
||||||
LaunchOptions:
|
LaunchOptions:
|
||||||
type: mixin
|
type: mixin
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,36 @@ it.describe('page screenshot', () => {
|
||||||
maskColor: '#00FF00',
|
maskColor: '#00FF00',
|
||||||
})).toMatchSnapshot('mask-color-should-work.png');
|
})).toMatchSnapshot('mask-color-should-work.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should hide elements based on attr', async ({ page, server }) => {
|
||||||
|
await page.setViewportSize({ width: 500, height: 500 });
|
||||||
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
await page.locator('div').nth(5).evaluate(element => {
|
||||||
|
element.setAttribute('data-test-screenshot', 'hide');
|
||||||
|
});
|
||||||
|
expect(await page.screenshot({
|
||||||
|
style: `[data-test-screenshot="hide"] {
|
||||||
|
visibility: hidden;
|
||||||
|
}`
|
||||||
|
})).toMatchSnapshot('hide-should-work.png');
|
||||||
|
const visibility = await page.locator('div').nth(5).evaluate(element => element.style.visibility);
|
||||||
|
expect(visibility).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove elements based on attr', async ({ page, server }) => {
|
||||||
|
await page.setViewportSize({ width: 500, height: 500 });
|
||||||
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
await page.locator('div').nth(5).evaluate(element => {
|
||||||
|
element.setAttribute('data-test-screenshot', 'remove');
|
||||||
|
});
|
||||||
|
expect(await page.screenshot({
|
||||||
|
style: `[data-test-screenshot="remove"] {
|
||||||
|
display: none;
|
||||||
|
}`
|
||||||
|
})).toMatchSnapshot('remove-should-work.png');
|
||||||
|
const display = await page.locator('div').nth(5).evaluate(element => element.style.display);
|
||||||
|
expect(display).toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
|
|
@ -1216,6 +1216,47 @@ test('should support maskColor option', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should support style 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': `
|
||||||
|
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; }',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should support style option in config', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
...playwrightConfig({
|
||||||
|
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
|
||||||
|
expect: {
|
||||||
|
toHaveScreenshot: {
|
||||||
|
style: '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');
|
||||||
|
test('png', async ({ page }) => {
|
||||||
|
await page.setContent('<style> html,body { padding: 0; margin: 0; }</style>');
|
||||||
|
await expect(page).toHaveScreenshot('snapshot.png');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
function playwrightConfig(obj: any) {
|
function playwrightConfig(obj: any) {
|
||||||
return {
|
return {
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue