feat(screenshot): introduce new "fonts" option for screenshots (#12661)

This option will wait for webfonts to load before taking screenshots.
This commit is contained in:
Andrey Lushnikov 2022-03-10 17:54:36 -07:00 committed by GitHub
parent 12d8a262be
commit 49e66c7f08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 119 additions and 15 deletions

View File

@ -955,7 +955,12 @@ An object which specifies clipping of the resulting image. Should have the follo
## screenshot-option-size
- `size` <[ScreenshotSize]<"css"|"device">>
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 `device`.
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 `"device"`.
## screenshot-option-fonts
- `fonts` <[ScreenshotFonts]<"ready"|"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-options-common-list
- %%-screenshot-option-animations-%%
@ -963,6 +968,7 @@ When set to `css`, screenshot will have a single pixel per each css pixel on the
- %%-screenshot-option-quality-%%
- %%-screenshot-option-path-%%
- %%-screenshot-option-size-%%
- %%-screenshot-option-fonts-%%
- %%-screenshot-option-type-%%
- %%-screenshot-option-mask-%%
- %%-input-timeout-%%

View File

@ -1550,6 +1550,7 @@ export type PageScreenshotParams = {
animations?: 'disabled',
clip?: Rect,
size?: 'css' | 'device',
fonts?: 'ready' | 'nowait',
mask?: {
frame: FrameChannel,
selector: string,
@ -1564,6 +1565,7 @@ export type PageScreenshotOptions = {
animations?: 'disabled',
clip?: Rect,
size?: 'css' | 'device',
fonts?: 'ready' | 'nowait',
mask?: {
frame: FrameChannel,
selector: string,
@ -2864,6 +2866,7 @@ export type ElementHandleScreenshotParams = {
omitBackground?: boolean,
animations?: 'disabled',
size?: 'css' | 'device',
fonts?: 'ready' | 'nowait',
mask?: {
frame: FrameChannel,
selector: string,
@ -2876,6 +2879,7 @@ export type ElementHandleScreenshotOptions = {
omitBackground?: boolean,
animations?: 'disabled',
size?: 'css' | 'device',
fonts?: 'ready' | 'nowait',
mask?: {
frame: FrameChannel,
selector: string,

View File

@ -1055,6 +1055,11 @@ Page:
literals:
- css
- device
fonts:
type: enum?
literals:
- ready
- nowait
mask:
type: array?
items:
@ -2224,6 +2229,11 @@ ElementHandle:
literals:
- css
- device
fonts:
type: enum?
literals:
- ready
- nowait
mask:
type: array?
items:

View File

@ -573,6 +573,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
animations: tOptional(tEnum(['disabled'])),
clip: tOptional(tType('Rect')),
size: tOptional(tEnum(['css', 'device'])),
fonts: tOptional(tEnum(['ready', 'nowait'])),
mask: tOptional(tArray(tObject({
frame: tChannel('Frame'),
selector: tString,
@ -1068,6 +1069,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
omitBackground: tOptional(tBoolean),
animations: tOptional(tEnum(['disabled'])),
size: tOptional(tEnum(['css', 'device'])),
fonts: tOptional(tEnum(['ready', 'nowait'])),
mask: tOptional(tArray(tObject({
frame: tChannel('Frame'),
selector: tString,

View File

@ -41,6 +41,7 @@ export type ScreenshotOptions = {
fullPage?: boolean,
clip?: Rect,
size?: 'css' | 'device',
fonts?: 'ready' | 'nowait',
};
export class Screenshotter {
@ -84,7 +85,7 @@ export class Screenshotter {
const format = validateScreenshotOptions(options);
return this._queue.postTask(async () => {
const { viewportSize } = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, options.animations === 'disabled');
await this._preparePageForScreenshot(progress, options.animations === 'disabled', options.fonts === 'ready');
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
if (options.fullPage) {
@ -112,7 +113,7 @@ export class Screenshotter {
return this._queue.postTask(async () => {
const { viewportSize } = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, options.animations === 'disabled');
await this._preparePageForScreenshot(progress, options.animations === 'disabled', options.fonts === 'ready');
progress.throwIfAborted(); // Do not do extra work.
await handle._waitAndScrollIntoViewIfNeeded(progress);
@ -136,9 +137,9 @@ export class Screenshotter {
});
}
async _preparePageForScreenshot(progress: Progress, disableAnimations: boolean) {
async _preparePageForScreenshot(progress: Progress, disableAnimations: boolean, waitForFonts: boolean) {
await Promise.all(this._page.frames().map(async frame => {
await frame.nonStallingEvaluateInExistingContext('(' + (function(disableAnimations: boolean) {
await frame.nonStallingEvaluateInExistingContext('(' + (async function(disableAnimations: boolean, waitForFonts: boolean) {
const styleTag = document.createElement('style');
styleTag.textContent = `
*:not(#playwright-aaaaaaaaaa.playwright-bbbbbbbbbbb.playwright-cccccccccc.playwright-dddddddddd.playwright-eeeeeeeee) {
@ -212,7 +213,10 @@ export class Screenshotter {
cleanupCallback();
delete window.__cleanupScreenshot;
};
}).toString() + `)(${disableAnimations || false})`, false, 'utility').catch(() => {});
if (waitForFonts)
await document.fonts.ready;
}).toString() + `)(${disableAnimations}, ${waitForFonts})`, false, 'utility').catch(() => {});
}));
progress.cleanupWhenAborted(() => this._restorePageAfterScreenshot());
}

View File

@ -8072,6 +8072,13 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
*/
animations?: "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 `"nowait"`.
*/
fonts?: "ready"|"nowait";
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
@ -8097,9 +8104,9 @@ export interface ElementHandle<T=Node> extends JSHandle<T> {
quality?: number;
/**
* 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 `device`.
* 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 `"device"`.
*/
size?: "css"|"device";
@ -15593,6 +15600,13 @@ export interface LocatorScreenshotOptions {
*/
animations?: "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 `"nowait"`.
*/
fonts?: "ready"|"nowait";
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlayed with a pink box
* `#FF00FF` that completely covers its bounding box.
@ -15618,9 +15632,9 @@ export interface LocatorScreenshotOptions {
quality?: number;
/**
* 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 `device`.
* 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 `"device"`.
*/
size?: "css"|"device";
@ -15762,6 +15776,13 @@ export interface PageScreenshotOptions {
height: number;
};
/**
* 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"`.
*/
fonts?: "ready"|"nowait";
/**
* When true, takes a screenshot of the full scrollable page, instead of the currently visible viewport. Defaults to
* `false`.
@ -15793,9 +15814,9 @@ export interface PageScreenshotOptions {
quality?: number;
/**
* 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 `device`.
* 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 `"device"`.
*/
size?: "css"|"device";

View File

@ -0,0 +1,3 @@
This icon font was generated:
- using SVG icons from https://github.com/primer/octicons
- bundling icons into webfonts using https://github.com/fontello/fontello

View File

@ -0,0 +1,14 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2022 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="plus" unicode="&#x2b;" d="M531 527a31 31 0 0 1-62 0v-146h-146a31 31 0 0 1 0-62h146v-146a31 31 0 0 1 62 0v146h146a31 31 0 0 1 0 62h-146v146z m-31 281c-253 0-458-205-458-458s205-458 458-458 458 205 458 458-205 458-458 458z m-396-458a396 396 0 1 0 792 0 396 396 0 0 0-792 0z" horiz-adv-x="1000" />
<glyph glyph-name="minus" unicode="&#x2d;" d="M104 350a396 396 0 1 0 792 0 396 396 0 0 0-792 0z m396 458c-253 0-458-205-458-458s205-458 458-458 458 205 458 458-205 458-458 458z m260-489a31 31 0 0 1 0 62h-520a31 31 0 0 1 0-62h520z" horiz-adv-x="1000" />
</font>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

View File

@ -0,0 +1,18 @@
<!doctype html>
<meta charset="utf-8">
<style>
@font-face {
font-family: 'pwtest-iconfont';
src: url('./iconfont.woff2') format('woff2');
font-weight: normal;
font-style: normal;
font-display: swap;
}
body {
font-family: 'pwtest-iconfont';
}
</style>
<span>+-</span>

View File

@ -710,5 +710,27 @@ it.describe('page screenshot animations', () => {
'onfinish', 'animationend'
]);
});
it('should respect fonts option', async ({ page, server }) => {
await page.setViewportSize({ width: 500, height: 500 });
let serverRequest, serverResponse;
// Stall font loading.
server.setRoute('/webfont/iconfont.woff2', (req, res) => {
serverRequest = req;
serverResponse = res;
});
await page.goto(server.PREFIX + '/webfont/webfont.html', {
waitUntil: 'domcontentloaded', // 'load' will not happen if webfont is pending
});
// Make sure we can take screenshot.
const noIconsScreenshot = await page.screenshot();
const [iconsScreenshot] = await Promise.all([
page.screenshot({ fonts: 'ready' }),
server.serveFile(serverRequest, serverResponse),
]);
expect(iconsScreenshot).toMatchSnapshot('screenshot-web-font.png');
expect(noIconsScreenshot).not.toMatchSnapshot('screenshot-web-font.png');
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB