fix: speed up caret hiding while screenshotting (#28460)

Fixes #28375
This commit is contained in:
Pavel Feldman 2023-12-03 21:06:40 -08:00 committed by GitHub
parent ea7fe340c1
commit facec88c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 30 deletions

View File

@ -46,7 +46,10 @@ export type ScreenshotOptions = {
style?: string;
};
function inPagePrepareForScreenshots(screenshotStyle: string, disableAnimations: boolean) {
function inPagePrepareForScreenshots(screenshotStyle: string, hideCaret: boolean, disableAnimations: boolean) {
if (!screenshotStyle && !hideCaret && !disableAnimations)
return;
const collectRoots = (root: Document | ShadowRoot, roots: (Document|ShadowRoot)[] = []): (Document|ShadowRoot)[] => {
roots.push(root);
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
@ -61,19 +64,39 @@ function inPagePrepareForScreenshots(screenshotStyle: string, disableAnimations:
const roots = collectRoots(document);
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 (screenshotStyle) {
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 (hideCaret) {
const elements = new Map<HTMLElement, { value: string, priority: string }>();
for (const root of roots) {
root.querySelectorAll('input,textarea,[contenteditable]').forEach(element => {
elements.set(element as HTMLElement, {
value: (element as HTMLElement).style.getPropertyValue('caret-color'),
priority: (element as HTMLElement).style.getPropertyPriority('caret-color')
});
(element as HTMLElement).style.setProperty('caret-color', 'transparent', 'important');
});
}
cleanupCallbacks.push(() => {
for (const [element, value] of elements)
element.style.setProperty('caret-color', value.value, value.priority);
});
}
if (disableAnimations) {
const infiniteAnimationsToResume: Set<Animation> = new Set();
const handleAnimations = (root: Document|ShadowRoot): void => {
@ -172,7 +195,7 @@ export class Screenshotter {
return this._queue.postTask(async () => {
progress.log('taking page screenshot');
const { viewportSize } = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled');
await this._preparePageForScreenshot(progress, options.style, options.caret !== 'initial', options.animations === 'disabled');
progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup.
if (options.fullPage) {
@ -201,7 +224,7 @@ export class Screenshotter {
progress.log('taking element screenshot');
const { viewportSize } = await this._originalViewportSize(progress);
await this._preparePageForScreenshot(progress, screenshotStyle(options), options.animations === 'disabled');
await this._preparePageForScreenshot(progress, options.style, options.caret !== 'initial', options.animations === 'disabled');
progress.throwIfAborted(); // Do not do extra work.
await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */);
@ -225,11 +248,11 @@ export class Screenshotter {
});
}
async _preparePageForScreenshot(progress: Progress, screenshotStyle: string, disableAnimations: boolean) {
async _preparePageForScreenshot(progress: Progress, screenshotStyle: string | undefined, hideCaret: boolean, disableAnimations: boolean) {
if (disableAnimations)
progress.log(' disabled all CSS animations');
await Promise.all(this._page.frames().map(async frame => {
await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${disableAnimations})`, false, 'utility').catch(() => {});
await frame.nonStallingEvaluateInExistingContext('(' + inPagePrepareForScreenshots.toString() + `)(${JSON.stringify(screenshotStyle)}, ${hideCaret}, ${disableAnimations})`, false, 'utility').catch(() => {});
}));
if (!process.env.PW_TEST_SCREENSHOT_NO_FONTS_READY) {
progress.log('waiting for fonts to load...');
@ -359,16 +382,3 @@ export function validateScreenshotOptions(options: ScreenshotOptions): 'png' | '
}
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');
}

View File

@ -688,7 +688,8 @@ it.describe('page screenshot animations', () => {
expect(comparePNGs(buffer1, buffer2, { maxDiffPixels: 50 })).not.toBe(null);
});
it('should fire transitionend for finite transitions', async ({ page, server }) => {
it('should fire transitionend for finite transitions', async ({ page, server, browserName, platform }) => {
it.fixme(browserName === 'webkit' && platform === 'linux');
await page.goto(server.PREFIX + '/css-transition.html');
const div = page.locator('div');
await div.evaluate(el => {
@ -714,7 +715,8 @@ it.describe('page screenshot animations', () => {
expect(await page.evaluate(() => window['__TRANSITION_END'])).toBe(true);
});
it('should capture screenshots after layoutchanges in transitionend event', async ({ page, server }) => {
it('should capture screenshots after layoutchanges in transitionend event', async ({ page, server, browserName, platform }) => {
it.fixme(browserName === 'webkit' && platform === 'linux');
await page.goto(server.PREFIX + '/css-transition.html');
const div = page.locator('div');
await div.evaluate(el => {