diff --git a/packages/playwright-core/src/server/chromium/crDragDrop.ts b/packages/playwright-core/src/server/chromium/crDragDrop.ts index abec6964f3..33d65a9939 100644 --- a/packages/playwright-core/src/server/chromium/crDragDrop.ts +++ b/packages/playwright-core/src/server/chromium/crDragDrop.ts @@ -84,6 +84,7 @@ export class DragManager { const val = await didStartDrag; window.removeEventListener('mousemove', mouseListener, { capture: true }); window.removeEventListener('dragstart', dragListener, { capture: true }); + delete window.__cleanupDrag; return val; }; }).toString(), true, 'utility').catch(() => {}); diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 5fda7b6595..920a697d6d 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -22,6 +22,12 @@ import * as types from './types'; import { Progress } from './progress'; import { assert } from '../utils/utils'; +declare global { + interface Window { + __cleanupScreenshot?: () => void; + } +} + export class Screenshotter { private _queue = new TaskQueue(); private _page: Page; @@ -115,8 +121,36 @@ export class Screenshotter { progress.cleanupWhenAborted(() => this._page._delegate.setBackgroundColor()); } progress.throwIfAborted(); // Avoid extra work. + + const restoreBlinkingCaret = async () => { + await Promise.all(this._page.frames().map(async frame => { + frame.nonStallingEvaluateInExistingContext('window.__cleanupScreenshot && window.__cleanupScreenshot()', false, 'utility').catch(() => {}); + })); + }; + await Promise.all(this._page.frames().map(async frame => { + await frame.nonStallingEvaluateInExistingContext((function() { + const styleTag = document.createElement('style'); + styleTag.textContent = ` + * { caret-color: transparent !important; } + * > * { caret-color: transparent !important; } + * > * > * { caret-color: transparent !important; } + * > * > * > * { caret-color: transparent !important; } + * > * > * > * > * { caret-color: transparent !important; } + `; + document.documentElement.append(styleTag); + window.__cleanupScreenshot = () => { + styleTag.remove(); + delete window.__cleanupScreenshot; + }; + }).toString(), true, 'utility').catch(() => {}); + })); + progress.cleanupWhenAborted(() => restoreBlinkingCaret()); + progress.throwIfAborted(); // Avoid extra work. + const buffer = await this._page._delegate.takeScreenshot(progress, format, documentRect, viewportRect, options.quality, fitsViewport); progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. + await restoreBlinkingCaret(); + progress.throwIfAborted(); // Avoid restoring after failure - should be done by cleanup. if (shouldSetDefaultBackground) await this._page._delegate.setBackgroundColor(); progress.throwIfAborted(); // Avoid side effects. diff --git a/tests/page/page-screenshot.spec.ts b/tests/page/page-screenshot.spec.ts index 97c635dff9..27a46c4c08 100644 --- a/tests/page/page-screenshot.spec.ts +++ b/tests/page/page-screenshot.spec.ts @@ -31,6 +31,23 @@ it.describe('page screenshot', () => { expect(screenshot).toMatchSnapshot('screenshot-sanity.png'); }); + it('should not capture blinking caret', async ({ page, server }) => { + await page.setContent(` +
+ `); + const div = page.locator('div'); + await div.type('foo bar'); + const screenshot = await div.screenshot(); + for (let i = 0; i < 10; ++i) { + // Caret blinking time is set to 500ms. + // Try to capture variety of screenshots to make + // sure we don't capture blinking caret. + await new Promise(x => setTimeout(x, 150)); + const newScreenshot = await div.screenshot(); + expect(newScreenshot.equals(screenshot)).toBe(true); + } + }); + it('should clip rect', async ({ page, server }) => { await page.setViewportSize({ width: 500, height: 500 }); await page.goto(server.PREFIX + '/grid.html');