mirror of
https://github.com/microsoft/playwright.git
synced 2025-01-05 19:04:43 +03:00
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.caret = %%-screenshot-option-caret-%%
|
||||
|
||||
### option: PageAssertions.toHaveScreenshot.maxDiffPixels = %%-assertions-max-diff-pixels-%%
|
||||
|
||||
### 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"`.
|
||||
|
||||
## 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-option-animations-%%
|
||||
- %%-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-size-%%
|
||||
- %%-screenshot-option-fonts-%%
|
||||
- %%-screenshot-option-caret-%%
|
||||
- %%-screenshot-option-type-%%
|
||||
- %%-screenshot-option-mask-%%
|
||||
- %%-input-timeout-%%
|
||||
|
@ -1517,6 +1517,7 @@ export type PageExpectScreenshotParams = {
|
||||
fullPage?: boolean,
|
||||
clip?: Rect,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
@ -1542,6 +1543,7 @@ export type PageExpectScreenshotOptions = {
|
||||
fullPage?: boolean,
|
||||
clip?: Rect,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
@ -1565,6 +1567,7 @@ export type PageScreenshotParams = {
|
||||
fullPage?: boolean,
|
||||
clip?: Rect,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
@ -1580,6 +1583,7 @@ export type PageScreenshotOptions = {
|
||||
fullPage?: boolean,
|
||||
clip?: Rect,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
@ -2899,6 +2903,7 @@ export type ElementHandleScreenshotParams = {
|
||||
type?: 'png' | 'jpeg',
|
||||
quality?: number,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
@ -2912,6 +2917,7 @@ export type ElementHandleScreenshotOptions = {
|
||||
type?: 'png' | 'jpeg',
|
||||
quality?: number,
|
||||
omitBackground?: boolean,
|
||||
caret?: 'hide' | 'initial',
|
||||
animations?: 'disabled' | 'allow',
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
|
@ -313,6 +313,11 @@ CommonScreenshotOptions:
|
||||
type: mixin
|
||||
properties:
|
||||
omitBackground: boolean?
|
||||
caret:
|
||||
type: enum?
|
||||
literals:
|
||||
- hide
|
||||
- initial
|
||||
animations:
|
||||
type: enum?
|
||||
literals:
|
||||
|
@ -561,6 +561,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
fullPage: tOptional(tBoolean),
|
||||
clip: tOptional(tType('Rect')),
|
||||
omitBackground: tOptional(tBoolean),
|
||||
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||
size: tOptional(tEnum(['css', 'device'])),
|
||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||
@ -577,6 +578,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
fullPage: tOptional(tBoolean),
|
||||
clip: tOptional(tType('Rect')),
|
||||
omitBackground: tOptional(tBoolean),
|
||||
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||
size: tOptional(tEnum(['css', 'device'])),
|
||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||
@ -1081,6 +1083,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
|
||||
type: tOptional(tEnum(['png', 'jpeg'])),
|
||||
quality: tOptional(tNumber),
|
||||
omitBackground: tOptional(tBoolean),
|
||||
caret: tOptional(tEnum(['hide', 'initial'])),
|
||||
animations: tOptional(tEnum(['disabled', 'allow'])),
|
||||
size: tOptional(tEnum(['css', 'device'])),
|
||||
fonts: tOptional(tEnum(['ready', 'nowait'])),
|
||||
|
@ -42,6 +42,7 @@ export type ScreenshotOptions = {
|
||||
clip?: Rect,
|
||||
size?: 'css' | 'device',
|
||||
fonts?: 'ready' | 'nowait',
|
||||
caret?: 'hide' | 'initial',
|
||||
};
|
||||
|
||||
export class Screenshotter {
|
||||
@ -86,7 +87,7 @@ export class Screenshotter {
|
||||
return this._queue.postTask(async () => {
|
||||
progress.log('taking page screenshot');
|
||||
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.
|
||||
|
||||
if (options.fullPage) {
|
||||
@ -115,7 +116,7 @@ export class Screenshotter {
|
||||
progress.log('taking element screenshot');
|
||||
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.
|
||||
|
||||
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)
|
||||
progress.log(' disabled all CSS animations');
|
||||
if (waitForFonts)
|
||||
progress.log(' waiting for fonts to load...');
|
||||
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');
|
||||
styleTag.textContent = `
|
||||
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
||||
caret-color: transparent !important;
|
||||
}
|
||||
`;
|
||||
document.documentElement.append(styleTag);
|
||||
if (hideCaret) {
|
||||
styleTag.textContent = `
|
||||
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
|
||||
caret-color: transparent !important;
|
||||
}
|
||||
`;
|
||||
document.documentElement.append(styleTag);
|
||||
}
|
||||
const infiniteAnimationsToResume: Set<Animation> = new Set();
|
||||
const cleanupCallbacks: (() => void)[] = [];
|
||||
|
||||
@ -222,7 +225,7 @@ export class Screenshotter {
|
||||
|
||||
if (waitForFonts)
|
||||
await document.fonts.ready;
|
||||
}).toString() + `)(${disableAnimations}, ${waitForFonts})`, false, 'utility').catch(() => {});
|
||||
}).toString() + `)(${hideCaret}, ${disableAnimations}, ${waitForFonts})`, false, 'utility').catch(() => {});
|
||||
}));
|
||||
if (waitForFonts)
|
||||
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";
|
||||
|
||||
/**
|
||||
* 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
|
||||
* [`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";
|
||||
|
||||
/**
|
||||
* 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
|
||||
* [`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";
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*/
|
||||
|
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"`.
|
||||
*/
|
||||
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?: {
|
||||
/** 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');
|
||||
});
|
||||
|
||||
it('should not capture blinking caret', async ({ page, server }) => {
|
||||
it('should not capture blinking caret by default', async ({ page, server }) => {
|
||||
await page.setContent(`
|
||||
<!-- Refer to stylesheet from other origin. Accessing this
|
||||
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 }) => {
|
||||
await page.setViewportSize({ width: 500, height: 500 });
|
||||
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"`.
|
||||
*/
|
||||
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?: {
|
||||
/** 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 New Issue
Block a user