feat(vrt): new option "caret" for taking screenshots (#13164)
This has two values: - `"hide"` to hide input caret for taking screenshot - `"initial"` to keep caret behavior unchanged Defaults to `"hide"`. Fixes #12643
This commit is contained in:
parent
5e17ed137b
commit
a9989852d5
|
|
@ -140,6 +140,8 @@ await expect(page).toHaveScreenshot();
|
||||||
|
|
||||||
### option: PageAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%%
|
### option: PageAssertions.toHaveScreenshot.mask = %%-screenshot-option-mask-%%
|
||||||
|
|
||||||
|
### option: PageAssertions.toHaveScreenshot.caret = %%-screenshot-option-caret-%%
|
||||||
|
|
||||||
### option: PageAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%%
|
### option: PageAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%%
|
||||||
|
|
||||||
### option: PageAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
|
### option: PageAssertions.toHaveScreenshot.maxDiffPixelRatio = %%-assertions-max-diff-pixel-ratio-%%
|
||||||
|
|
|
||||||
|
|
@ -964,6 +964,11 @@ When set to `"css"`, screenshot will have a single pixel per each css pixel on t
|
||||||
|
|
||||||
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 `"nowait"`.
|
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 `"nowait"`.
|
||||||
|
|
||||||
|
## screenshot-option-caret
|
||||||
|
- `caret` <[ScreenshotCaret]<"hide"|"initial">>
|
||||||
|
|
||||||
|
When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`.
|
||||||
|
|
||||||
## screenshot-options-common-list
|
## screenshot-options-common-list
|
||||||
- %%-screenshot-option-animations-%%
|
- %%-screenshot-option-animations-%%
|
||||||
- %%-screenshot-option-omit-background-%%
|
- %%-screenshot-option-omit-background-%%
|
||||||
|
|
@ -971,6 +976,7 @@ When set to `"ready"`, screenshot will wait for [`document.fonts.ready`](https:/
|
||||||
- %%-screenshot-option-path-%%
|
- %%-screenshot-option-path-%%
|
||||||
- %%-screenshot-option-size-%%
|
- %%-screenshot-option-size-%%
|
||||||
- %%-screenshot-option-fonts-%%
|
- %%-screenshot-option-fonts-%%
|
||||||
|
- %%-screenshot-option-caret-%%
|
||||||
- %%-screenshot-option-type-%%
|
- %%-screenshot-option-type-%%
|
||||||
- %%-screenshot-option-mask-%%
|
- %%-screenshot-option-mask-%%
|
||||||
- %%-input-timeout-%%
|
- %%-input-timeout-%%
|
||||||
|
|
|
||||||
|
|
@ -1517,6 +1517,7 @@ export type PageExpectScreenshotParams = {
|
||||||
fullPage?: boolean,
|
fullPage?: boolean,
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
@ -1542,6 +1543,7 @@ export type PageExpectScreenshotOptions = {
|
||||||
fullPage?: boolean,
|
fullPage?: boolean,
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
@ -1565,6 +1567,7 @@ export type PageScreenshotParams = {
|
||||||
fullPage?: boolean,
|
fullPage?: boolean,
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
@ -1580,6 +1583,7 @@ export type PageScreenshotOptions = {
|
||||||
fullPage?: boolean,
|
fullPage?: boolean,
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
@ -2899,6 +2903,7 @@ export type ElementHandleScreenshotParams = {
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg',
|
||||||
quality?: number,
|
quality?: number,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
@ -2912,6 +2917,7 @@ export type ElementHandleScreenshotOptions = {
|
||||||
type?: 'png' | 'jpeg',
|
type?: 'png' | 'jpeg',
|
||||||
quality?: number,
|
quality?: number,
|
||||||
omitBackground?: boolean,
|
omitBackground?: boolean,
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
animations?: 'disabled' | 'allow',
|
animations?: 'disabled' | 'allow',
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,11 @@ CommonScreenshotOptions:
|
||||||
type: mixin
|
type: mixin
|
||||||
properties:
|
properties:
|
||||||
omitBackground: boolean?
|
omitBackground: boolean?
|
||||||
|
caret:
|
||||||
|
type: enum?
|
||||||
|
literals:
|
||||||
|
- hide
|
||||||
|
- initial
|
||||||
animations:
|
animations:
|
||||||
type: enum?
|
type: enum?
|
||||||
literals:
|
literals:
|
||||||
|
|
|
||||||
|
|
@ -561,6 +561,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
fullPage: tOptional(tBoolean),
|
fullPage: tOptional(tBoolean),
|
||||||
clip: tOptional(tType('Rect')),
|
clip: tOptional(tType('Rect')),
|
||||||
omitBackground: tOptional(tBoolean),
|
omitBackground: tOptional(tBoolean),
|
||||||
|
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||||
size: tOptional(tEnum(['css', 'device'])),
|
size: tOptional(tEnum(['css', 'device'])),
|
||||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||||
|
|
@ -577,6 +578,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
fullPage: tOptional(tBoolean),
|
fullPage: tOptional(tBoolean),
|
||||||
clip: tOptional(tType('Rect')),
|
clip: tOptional(tType('Rect')),
|
||||||
omitBackground: tOptional(tBoolean),
|
omitBackground: tOptional(tBoolean),
|
||||||
|
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||||
size: tOptional(tEnum(['css', 'device'])),
|
size: tOptional(tEnum(['css', 'device'])),
|
||||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||||
|
|
@ -1081,6 +1083,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||||
type: tOptional(tEnum(['png', 'jpeg'])),
|
type: tOptional(tEnum(['png', 'jpeg'])),
|
||||||
quality: tOptional(tNumber),
|
quality: tOptional(tNumber),
|
||||||
omitBackground: tOptional(tBoolean),
|
omitBackground: tOptional(tBoolean),
|
||||||
|
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||||
size: tOptional(tEnum(['css', 'device'])),
|
size: tOptional(tEnum(['css', 'device'])),
|
||||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ export type ScreenshotOptions = {
|
||||||
clip?: Rect,
|
clip?: Rect,
|
||||||
size?: 'css' | 'device',
|
size?: 'css' | 'device',
|
||||||
fonts?: 'ready' | 'nowait',
|
fonts?: 'ready' | 'nowait',
|
||||||
|
caret?: 'hide' | 'initial',
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Screenshotter {
|
export class Screenshotter {
|
||||||
|
|
@ -86,7 +87,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.animations === 'disabled', options.fonts === 'ready');
|
await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled', options.fonts === 'ready');
|
||||||
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) {
|
||||||
|
|
@ -115,7 +116,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.animations === 'disabled', options.fonts === 'ready');
|
await this._preparePageForScreenshot(progress, options.caret !== 'initial', options.animations === 'disabled', options.fonts === 'ready');
|
||||||
progress.throwIfAborted(); // Do not do extra work.
|
progress.throwIfAborted(); // Do not do extra work.
|
||||||
|
|
||||||
await handle._waitAndScrollIntoViewIfNeeded(progress);
|
await handle._waitAndScrollIntoViewIfNeeded(progress);
|
||||||
|
|
@ -139,20 +140,22 @@ export class Screenshotter {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _preparePageForScreenshot(progress: Progress, disableAnimations: boolean, waitForFonts: boolean) {
|
async _preparePageForScreenshot(progress: Progress, hideCaret: boolean, disableAnimations: boolean, waitForFonts: boolean) {
|
||||||
if (disableAnimations)
|
if (disableAnimations)
|
||||||
progress.log(' disabled all CSS animations');
|
progress.log(' disabled all CSS animations');
|
||||||
if (waitForFonts)
|
if (waitForFonts)
|
||||||
progress.log(' waiting for fonts to load...');
|
progress.log(' waiting for fonts to load...');
|
||||||
await Promise.all(this._page.frames().map(async frame => {
|
await Promise.all(this._page.frames().map(async frame => {
|
||||||
await frame.nonStallingEvaluateInExistingContext('(' + (async function(disableAnimations: boolean, waitForFonts: boolean) {
|
await frame.nonStallingEvaluateInExistingContext('(' + (async function(hideCaret: boolean, disableAnimations: boolean, waitForFonts: boolean) {
|
||||||
const styleTag = document.createElement('style');
|
const styleTag = document.createElement('style');
|
||||||
styleTag.textContent = `
|
if (hideCaret) {
|
||||||
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
styleTag.textContent = `
|
||||||
caret-color: transparent !important;
|
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
||||||
}
|
caret-color: transparent !important;
|
||||||
`;
|
}
|
||||||
document.documentElement.append(styleTag);
|
`;
|
||||||
|
document.documentElement.append(styleTag);
|
||||||
|
}
|
||||||
const infiniteAnimationsToResume: Set<Animation> = new Set();
|
const infiniteAnimationsToResume: Set<Animation> = new Set();
|
||||||
const cleanupCallbacks: (() => void)[] = [];
|
const cleanupCallbacks: (() => void)[] = [];
|
||||||
|
|
||||||
|
|
@ -222,7 +225,7 @@ export class Screenshotter {
|
||||||
|
|
||||||
if (waitForFonts)
|
if (waitForFonts)
|
||||||
await document.fonts.ready;
|
await document.fonts.ready;
|
||||||
}).toString() + `)(${disableAnimations}, ${waitForFonts})`, false, 'utility').catch(() => {});
|
}).toString() + `)(${hideCaret}, ${disableAnimations}, ${waitForFonts})`, false, 'utility').catch(() => {});
|
||||||
}));
|
}));
|
||||||
if (waitForFonts)
|
if (waitForFonts)
|
||||||
progress.log(' fonts in all frames are loaded');
|
progress.log(' fonts in all frames are loaded');
|
||||||
|
|
|
||||||
18
packages/playwright-core/types/types.d.ts
vendored
18
packages/playwright-core/types/types.d.ts
vendored
|
|
@ -8146,6 +8146,12 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
|
||||||
*/
|
*/
|
||||||
animations?: "disabled"|"allow";
|
animations?: "disabled"|"allow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
|
||||||
|
* Defaults to `"hide"`.
|
||||||
|
*/
|
||||||
|
caret?: "hide"|"initial";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When set to `"ready"`, screenshot will wait for
|
* 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
|
* [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all
|
||||||
|
|
@ -15731,6 +15737,12 @@ export interface LocatorScreenshotOptions {
|
||||||
*/
|
*/
|
||||||
animations?: "disabled"|"allow";
|
animations?: "disabled"|"allow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
|
||||||
|
* Defaults to `"hide"`.
|
||||||
|
*/
|
||||||
|
caret?: "hide"|"initial";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When set to `"ready"`, screenshot will wait for
|
* 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
|
* [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) promise to resolve in all
|
||||||
|
|
@ -15884,6 +15896,12 @@ export interface PageScreenshotOptions {
|
||||||
*/
|
*/
|
||||||
animations?: "disabled"|"allow";
|
animations?: "disabled"|"allow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When set to `"hide"`, screenshot will hide text caret. When set to `"initial"`, text caret behavior will not be changed.
|
||||||
|
* Defaults to `"hide"`.
|
||||||
|
*/
|
||||||
|
caret?: "hide"|"initial";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object which specifies clipping of the resulting image. Should have the following fields:
|
* An object which specifies clipping of the resulting image. Should have the following fields:
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
5
packages/playwright-test/types/test.d.ts
vendored
5
packages/playwright-test/types/test.d.ts
vendored
|
|
@ -76,6 +76,11 @@ type ExpectSettings = {
|
||||||
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
|
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
|
||||||
*/
|
*/
|
||||||
size?: 'css'|'device',
|
size?: 'css'|'device',
|
||||||
|
/**
|
||||||
|
* When set to `"hide"`, screenshot will hide text caret.
|
||||||
|
* When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`.
|
||||||
|
*/
|
||||||
|
caret?: 'hide'|'initia',
|
||||||
}
|
}
|
||||||
toMatchSnapshot?: {
|
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`.
|
/** 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`.
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ it.describe('page screenshot', () => {
|
||||||
expect(screenshot).toMatchSnapshot('screenshot-sanity.png');
|
expect(screenshot).toMatchSnapshot('screenshot-sanity.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not capture blinking caret', async ({ page, server }) => {
|
it('should not capture blinking caret by default', async ({ page, server }) => {
|
||||||
await page.setContent(`
|
await page.setContent(`
|
||||||
<!-- Refer to stylesheet from other origin. Accessing this
|
<!-- Refer to stylesheet from other origin. Accessing this
|
||||||
stylesheet rules will throw.
|
stylesheet rules will throw.
|
||||||
|
|
@ -60,6 +60,35 @@ it.describe('page screenshot', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should capture blinking caret if explicitly asked for', async ({ page, server }) => {
|
||||||
|
await page.setContent(`
|
||||||
|
<!-- Refer to stylesheet from other origin. Accessing this
|
||||||
|
stylesheet rules will throw.
|
||||||
|
-->
|
||||||
|
<link rel=stylesheet href="${server.CROSS_PROCESS_PREFIX + '/injectedstyle.css'}">
|
||||||
|
<!-- make life harder: define caret color in stylesheet -->
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
caret-color: #000 !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div contenteditable="true"></div>
|
||||||
|
`);
|
||||||
|
const div = page.locator('div');
|
||||||
|
await div.type('foo bar');
|
||||||
|
const screenshot = await div.screenshot();
|
||||||
|
let hasDifferentScreenshots = false;
|
||||||
|
for (let i = 0; !hasDifferentScreenshots && i < 10; ++i) {
|
||||||
|
// Caret blinking time is set to 500ms.
|
||||||
|
// Try to capture variety of screenshots to make
|
||||||
|
// sure we capture blinking caret.
|
||||||
|
await new Promise(x => setTimeout(x, 150));
|
||||||
|
const newScreenshot = await div.screenshot({ caret: 'initial' });
|
||||||
|
hasDifferentScreenshots = !newScreenshot.equals(screenshot);
|
||||||
|
}
|
||||||
|
expect(hasDifferentScreenshots).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('should clip rect', async ({ page, server }) => {
|
it('should clip rect', async ({ page, server }) => {
|
||||||
await page.setViewportSize({ width: 500, height: 500 });
|
await page.setViewportSize({ width: 500, height: 500 });
|
||||||
await page.goto(server.PREFIX + '/grid.html');
|
await page.goto(server.PREFIX + '/grid.html');
|
||||||
|
|
|
||||||
5
utils/generate_types/overrides-test.d.ts
vendored
5
utils/generate_types/overrides-test.d.ts
vendored
|
|
@ -75,6 +75,11 @@ type ExpectSettings = {
|
||||||
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
|
* high-dpi devices will be twice as large or even larger. Defaults to `"css"`.
|
||||||
*/
|
*/
|
||||||
size?: 'css'|'device',
|
size?: 'css'|'device',
|
||||||
|
/**
|
||||||
|
* When set to `"hide"`, screenshot will hide text caret.
|
||||||
|
* When set to `"initial"`, text caret behavior will not be changed. Defaults to `"hide"`.
|
||||||
|
*/
|
||||||
|
caret?: 'hide'|'initia',
|
||||||
}
|
}
|
||||||
toMatchSnapshot?: {
|
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`.
|
/** 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`.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue