diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index d6103786b6..1ca0542198 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -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(); + 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 = 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'); -} diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index ef7bdd522c..8b105403c2 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -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 => {