diff --git a/docs/src/api/class-locatorassertions.md b/docs/src/api/class-locatorassertions.md index 1dec8cf901932..30b0874cff64f 100644 --- a/docs/src/api/class-locatorassertions.md +++ b/docs/src/api/class-locatorassertions.md @@ -1960,6 +1960,9 @@ Snapshot name. ### option: LocatorAssertions.toHaveScreenshot#1.threshold = %%-assertions-threshold-%% * since: v1.23 +### option: LocatorAssertions.toHaveScreenshot#1.scrollIntoView = %%-assertions-scroll-into-view-%% +* since: v1.59 + ## async method: LocatorAssertions.toHaveScreenshot#2 * since: v1.23 * langs: js @@ -2009,6 +2012,9 @@ Note that screenshot assertions only work with Playwright test runner. ### option: LocatorAssertions.toHaveScreenshot#2.threshold = %%-assertions-threshold-%% * since: v1.23 +### option: LocatorAssertions.toHaveScreenshot#2.scrollIntoView = %%-assertions-scroll-into-view-%% +* since: v1.59 + ## async method: LocatorAssertions.toHaveText * since: v1.20 * langs: diff --git a/docs/src/api/params.md b/docs/src/api/params.md index 14ab34518b2fe..9a0a3d7f697b3 100644 --- a/docs/src/api/params.md +++ b/docs/src/api/params.md @@ -1015,6 +1015,12 @@ An acceptable perceived color difference in the [YIQ color space](https://en.wik between the same pixel in compared images, between zero (strict) and one (lax), default is configurable with `TestConfig.expect`. Defaults to `0.2`. +## assertions-scroll-into-view +* langs: js +- `scrollIntoView` <[boolean]> + +When set to `false`, do not perform automatic scroll-into-view before taking a screenshot in `expect(locator).toHaveScreenshot()`. Defaults to `true`. + ## shared-context-params-list-v1.8 - %%-context-option-acceptdownloads-%% - %%-context-option-ignorehttpserrors-%% diff --git a/packages/playwright-core/src/protocol/validator.ts b/packages/playwright-core/src/protocol/validator.ts index a22619d8dc5a6..4abbe36981443 100644 --- a/packages/playwright-core/src/protocol/validator.ts +++ b/packages/playwright-core/src/protocol/validator.ts @@ -1334,6 +1334,7 @@ scheme.PageExpectScreenshotParams = tObject({ threshold: tOptional(tFloat), fullPage: tOptional(tBoolean), clip: tOptional(tType('Rect')), + scrollIntoView: tOptional(tBoolean), omitBackground: tOptional(tBoolean), caret: tOptional(tEnum(['hide', 'initial'])), animations: tOptional(tEnum(['disabled', 'allow'])), diff --git a/packages/playwright-core/src/server/screenshotter.ts b/packages/playwright-core/src/server/screenshotter.ts index 93765cc3372f5..9f0db44b995c7 100644 --- a/packages/playwright-core/src/server/screenshotter.ts +++ b/packages/playwright-core/src/server/screenshotter.ts @@ -46,6 +46,7 @@ export type ScreenshotOptions = { scale?: 'css' | 'device'; caret?: 'hide' | 'initial'; style?: string; + scrollIntoView?: boolean; }; function inPagePrepareForScreenshots(screenshotStyle: string, hideCaret: boolean, disableAnimations: boolean, syncAnimations: boolean) { @@ -231,7 +232,8 @@ export class Screenshotter { await this._preparePageForScreenshot(progress, handle._frame, options.style, options.caret !== 'initial', options.animations === 'disabled'); try { - await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */); + if (options.scrollIntoView !== false) + await handle._waitAndScrollIntoViewIfNeeded(progress, true /* waitForVisible */); const boundingBox = await progress.race(handle.boundingBox()); assert(boundingBox, 'Node is either not visible or not an HTMLElement'); diff --git a/packages/playwright/src/matchers/toMatchSnapshot.ts b/packages/playwright/src/matchers/toMatchSnapshot.ts index f2858657f165b..f9e0bfb7e86e8 100644 --- a/packages/playwright/src/matchers/toMatchSnapshot.ts +++ b/packages/playwright/src/matchers/toMatchSnapshot.ts @@ -51,6 +51,7 @@ type ToHaveScreenshotOptions = ToHaveScreenshotConfigOptions & { mask?: Array; maskColor?: string; omitBackground?: boolean; + scrollIntoView?: boolean; timeout?: number; }; @@ -61,6 +62,7 @@ const NonConfigProperties: (keyof ToHaveScreenshotOptions)[] = [ 'mask', 'maskColor', 'omitBackground', + 'scrollIntoView', 'timeout', ]; // Keep in sync with above (end). @@ -350,6 +352,7 @@ export async function toHaveScreenshot( mask: helper.options.mask, maskColor: helper.options.maskColor, omitBackground: helper.options.omitBackground, + scrollIntoView: helper.options.scrollIntoView, scale: helper.options.scale ?? 'css', style, isNot: !!this.isNot, diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 6ced6b68d5cdf..082d45e1baac7 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -9314,6 +9314,11 @@ interface LocatorAssertions { */ scale?: "css"|"device"; + /** + * When set to `false`, the locator will not be scrolled into view before taking a screenshot. Defaults to `true`. + */ + scrollIntoView?: boolean; + /** * File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic * elements, make elements invisible or change their properties to help you creating repeatable screenshots. This @@ -9407,6 +9412,11 @@ interface LocatorAssertions { */ scale?: "css"|"device"; + /** + * When set to `false`, the locator will not be scrolled into view before taking a screenshot. Defaults to `true`. + */ + scrollIntoView?: boolean; + /** * File name containing the stylesheet to apply while making the screenshot. This is where you can hide dynamic * elements, make elements invisible or change their properties to help you creating repeatable screenshots. This diff --git a/packages/protocol/src/channels.d.ts b/packages/protocol/src/channels.d.ts index f9d1d4613fd42..350e542e2a058 100644 --- a/packages/protocol/src/channels.d.ts +++ b/packages/protocol/src/channels.d.ts @@ -2336,6 +2336,7 @@ export type PageExpectScreenshotParams = { threshold?: number, fullPage?: boolean, clip?: Rect, + scrollIntoView?: boolean, omitBackground?: boolean, caret?: 'hide' | 'initial', animations?: 'disabled' | 'allow', @@ -2359,6 +2360,7 @@ export type PageExpectScreenshotOptions = { threshold?: number, fullPage?: boolean, clip?: Rect, + scrollIntoView?: boolean, omitBackground?: boolean, caret?: 'hide' | 'initial', animations?: 'disabled' | 'allow', diff --git a/packages/protocol/src/protocol.yml b/packages/protocol/src/protocol.yml index 5cb737443885b..cdd50bff5c2a5 100644 --- a/packages/protocol/src/protocol.yml +++ b/packages/protocol/src/protocol.yml @@ -1754,6 +1754,7 @@ Page: threshold: float? fullPage: boolean? clip: Rect? + scrollIntoView: boolean? $mixin: CommonScreenshotOptions returns: diff: binary? diff --git a/tests/playwright-test/to-have-screenshot.spec.ts b/tests/playwright-test/to-have-screenshot.spec.ts index 6ec17e52f79dd..94fbaf8da0443 100644 --- a/tests/playwright-test/to-have-screenshot.spec.ts +++ b/tests/playwright-test/to-have-screenshot.spec.ts @@ -1439,6 +1439,27 @@ test('should support stylePath option in config', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); }); +test('should support scrollIntoView option for locator', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + ...playwrightConfig({ + snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}', + }), + 'a.spec.js': ` + const { test, expect } = require('@playwright/test'); + test('is a test', async ({ page }) => { + await page.setContent('
'); + await expect(page.locator('div')).toHaveScreenshot({ + name: 'snapshot.png', + scrollIntoView: false, + }); + }); + ` + }, { 'update-snapshots': true }); + expect(result.exitCode).toBe(0); + const snapshotPath = testInfo.outputPath('__screenshots__', 'a.spec.js', 'snapshot.png'); + expect(fs.existsSync(snapshotPath)).toBe(true); +}); + function playwrightConfig(obj: any) { return { 'playwright.config.js': `