@wdio/image-comparison-core 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # @wdio/image-comparison-core
2
2
 
3
+ ## 1.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - c56e1ae: ## #1146 Fix BiDi element screenshots missing composited layers (scrollbars, fixed/sticky overlays)
8
+
9
+ ### Root cause
10
+
11
+ When `checkElement` / `saveElement` is used with the WebDriver BiDi protocol, the screenshot was taken with `browsingContext.captureScreenshot` using `origin: 'document'`. This renders the document layout independently of the browser's compositor, which means **composited layers are never included** — element-level scrollbars, `position: fixed` / `position: sticky` overlays, and elements with a `will-change` CSS property all render as invisible or without their correct visual state.
12
+
13
+ The switch to `origin: 'document'` was introduced in an earlier fix (commit `227f10a`) to avoid a `zero dimensions` error that occurred when `origin: 'viewport'` was used for elements that were outside the visible viewport. That fix was correct for out-of-viewport elements, but it also silently broke composited-layer capture for all elements.
14
+
15
+ ### Fix: new `biDiOrigin` method option
16
+
17
+ A new **method-level** option `biDiOrigin` has been added to `saveElement` / `checkElement`. It is BiDi-only and ignored for the legacy WebDriver screenshot path.
18
+
19
+ | Value | Behaviour |
20
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | `'document'` _(default)_ | Previous behaviour — works for any element position but composited layers (scrollbars, overlays, `will-change`) are not captured |
22
+ | `'viewport'` | Captures the composited frame as the browser painted it — scrollbars, fixed/sticky overlays and `will-change` layers are included. The element must be visible in the viewport; descriptive errors are thrown when it is not |
23
+
24
+ #### Usage
25
+
26
+ ```ts
27
+ // Capture an element with its scrollbar / overlay visible:
28
+ await browser.checkElement(element, "myTag", { biDiOrigin: "viewport" });
29
+ await browser.saveElement(element, "myTag", { biDiOrigin: "viewport" });
30
+ ```
31
+
32
+ #### Error messages when `biDiOrigin: 'viewport'` cannot produce a valid screenshot
33
+
34
+ **Element larger than the viewport** — must fall back to `'document'`:
35
+
36
+ ```
37
+ [BiDi viewport screenshot] The element dimensions (1400x800px) exceed the viewport (1280x720px).
38
+ You must use the default `biDiOrigin: 'document'` for this element.
39
+ Note: with `'document'` origin, composited layers such as scrollbars, fixed/sticky overlays,
40
+ and elements using `will-change` may not appear in the screenshot.
41
+ ```
42
+
43
+ **Element not in the viewport at all** — needs scrolling:
44
+
45
+ ```
46
+ [BiDi viewport screenshot] The element is not in the viewport
47
+ (element: x=0, y=900, 300x200px; viewport: 1280x720px).
48
+ Call `element.scrollIntoView()` before taking the screenshot, or set `autoElementScroll: true`.
49
+ ```
50
+
51
+ **Element partially outside the viewport but fits** — needs to be scrolled fully into view:
52
+
53
+ ```
54
+ [BiDi viewport screenshot] The element is not fully visible in the viewport
55
+ (element: x=-20, y=100, 300x200px; viewport: 1280x720px).
56
+ The element fits within the viewport — scroll it fully into view by calling
57
+ `element.scrollIntoView()` or setting `autoElementScroll: true`.
58
+ ```
59
+
60
+ ### Committers: 1
61
+
62
+ - Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
63
+
3
64
  ## 1.2.2
4
65
 
5
66
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"checkWebElement.d.ts","sourceRoot":"","sources":["../../src/commands/checkWebElement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAIzE,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAA;AAE9E;;GAEG;AACH,wBAA8B,eAAe,CACzC,EACI,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,GAAG,EACH,mBAAmB,EACnB,WAAW,EACX,eAAuB,GAC1B,EAAE,iCAAiC,GACrC,OAAO,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAgEtC"}
1
+ {"version":3,"file":"checkWebElement.d.ts","sourceRoot":"","sources":["../../src/commands/checkWebElement.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAIzE,OAAO,KAAK,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAA;AAE9E;;GAEG;AACH,wBAA8B,eAAe,CACzC,EACI,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,GAAG,EACH,mBAAmB,EACnB,WAAW,EACX,eAAuB,GAC1B,EAAE,iCAAiC,GACrC,OAAO,CAAC,kBAAkB,GAAG,MAAM,CAAC,CAkEtC"}
@@ -8,11 +8,12 @@ import { extractCommonCheckVariables, buildBaseExecuteCompareOptions } from '../
8
8
  export default async function checkWebElement({ browserInstance, instanceData, folders, element, tag, checkElementOptions, testContext, isNativeContext = false, }) {
9
9
  // 1. Extract common variables
10
10
  const commonCheckVariables = extractCommonCheckVariables({ folders, instanceData, wicOptions: checkElementOptions.wic });
11
- const { disableBlinkingCursor, disableCSSAnimation, enableLayoutTesting, enableLegacyScreenshotMethod, hideScrollBars, resizeDimensions, ignoreRegionPadding, hideElements = [], removeElements = [], waitForFontsLoaded = false, } = checkElementOptions.method;
11
+ const { biDiOrigin, disableBlinkingCursor, disableCSSAnimation, enableLayoutTesting, enableLegacyScreenshotMethod, hideScrollBars, resizeDimensions, ignoreRegionPadding, hideElements = [], removeElements = [], waitForFontsLoaded = false, } = checkElementOptions.method;
12
12
  // 2. Take the actual element screenshot and retrieve the needed data
13
13
  const saveElementOptions = {
14
14
  wic: checkElementOptions.wic,
15
15
  method: {
16
+ biDiOrigin,
16
17
  disableBlinkingCursor,
17
18
  disableCSSAnimation,
18
19
  enableLayoutTesting,
@@ -14,6 +14,16 @@ export interface SaveElementMethodOptions extends Partial<Folders>, BaseWebScree
14
14
  * @default undefined
15
15
  */
16
16
  resizeDimensions?: ResizeDimensions;
17
+ /**
18
+ * BiDi-only: which coordinate origin to use when capturing element screenshots via the BiDi protocol.
19
+ * - `'document'` (default): renders the document layout, works for any element position but
20
+ * does NOT capture composited layers (scrollbars, fixed/sticky overlays, `will-change` elements).
21
+ * - `'viewport'`: captures the composited frame as painted, which includes scrollbars and overlays,
22
+ * but requires the element to be fully visible in the viewport. Throws a descriptive error when the
23
+ * element is outside, partially outside, or larger than the viewport.
24
+ * @default 'document'
25
+ */
26
+ biDiOrigin?: 'document' | 'viewport';
17
27
  }
18
28
  export interface CheckElementMethodOptions extends SaveElementMethodOptions, CheckMethodOptions {
19
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"element.interfaces.d.ts","sourceRoot":"","sources":["../../src/commands/element.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,KAAK,EAAE,8BAA8B,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC9G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAE3E,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,wBAAwB,CAAC;CACpC;AAED,MAAM,WAAW,wBAAyB,SAAQ,OAAO,CAAC,OAAO,CAAC,EAAE,wBAAwB,EAAE,8BAA8B;IACxH;;;OAGG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACvC;AAED,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB,EAAE,kBAAkB;CAAI;AAEnG,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,yBAAyB,CAAC;CACrC;AAED,sBAAsB;AACtB,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAA;AAEtE,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,UAAU,CAAA"}
1
+ {"version":3,"file":"element.interfaces.d.ts","sourceRoot":"","sources":["../../src/commands/element.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,KAAK,EAAE,8BAA8B,EAAE,wBAAwB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAC9G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAA;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAE3E,MAAM,WAAW,kBAAkB;IAC/B,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,wBAAwB,CAAC;CACpC;AAED,MAAM,WAAW,wBAAyB,SAAQ,OAAO,CAAC,OAAO,CAAC,EAAE,wBAAwB,EAAE,8BAA8B;IACxH;;;OAGG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;CACxC;AAED,MAAM,WAAW,yBAA0B,SAAQ,wBAAwB,EAAE,kBAAkB;CAAI;AAEnG,MAAM,WAAW,mBAAmB;IAChC,GAAG,EAAE,cAAc,CAAC;IACpB,MAAM,EAAE,yBAAyB,CAAC;CACrC;AAED,sBAAsB;AACtB,MAAM,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAA;AAEtE,MAAM,MAAM,aAAa,GAAG,gBAAgB,GAAG,UAAU,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"saveWebElement.d.ts","sourceRoot":"","sources":["../../src/commands/saveWebElement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAOhF,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAA;AAG5E;;GAEG;AACH,wBAA8B,cAAc,CACxC,EACI,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,GAAG,EACH,MAAM,EACN,kBAAkB,GACrB,EAAE,gCAAgC,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA0F3B"}
1
+ {"version":3,"file":"saveWebElement.d.ts","sourceRoot":"","sources":["../../src/commands/saveWebElement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAA;AAOhF,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,sBAAsB,CAAA;AAG5E;;GAEG;AACH,wBAA8B,cAAc,CACxC,EACI,eAAe,EACf,YAAY,EACZ,OAAO,EACP,OAAO,EACP,GAAG,EACH,MAAM,EACN,kBAAkB,GACrB,EAAE,gCAAgC,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA8F3B"}
@@ -16,11 +16,13 @@ export default async function saveWebElement({ browserInstance, instanceData, fo
16
16
  // 2. Prepare the screenshot
17
17
  const beforeOptions = createBeforeScreenshotOptions(instanceData, saveElementOptions.method, saveElementOptions.wic);
18
18
  const enrichedInstanceData = await beforeScreenshot(browserInstance, beforeOptions, true);
19
- const { deviceName, dimensions: { window: { devicePixelRatio, innerHeight, isEmulated, isLandscape, }, }, initialDevicePixelRatio, isAndroid, isAndroidChromeDriverScreenshot, isAndroidNativeWebScreenshot, isIOS, isMobile, } = enrichedInstanceData;
19
+ const { deviceName, dimensions: { window: { devicePixelRatio, innerHeight, innerWidth, isEmulated, isLandscape, }, }, initialDevicePixelRatio, isAndroid, isAndroidChromeDriverScreenshot, isAndroidNativeWebScreenshot, isIOS, isMobile, } = enrichedInstanceData;
20
20
  // 3. Take the screenshot
21
+ const biDiOrigin = saveElementOptions.method.biDiOrigin ?? 'document';
21
22
  const elementScreenshotOptions = {
22
23
  addressBarShadowPadding,
23
24
  autoElementScroll,
25
+ biDiOrigin,
24
26
  deviceName,
25
27
  devicePixelRatio: devicePixelRatio || 1,
26
28
  deviceRectangles: instanceData.deviceRectangles,
@@ -28,6 +30,7 @@ export default async function saveWebElement({ browserInstance, instanceData, fo
28
30
  isEmulated,
29
31
  initialDevicePixelRatio: initialDevicePixelRatio || 1,
30
32
  innerHeight,
33
+ innerWidth,
31
34
  isAndroidNativeWebScreenshot,
32
35
  isAndroidChromeDriverScreenshot,
33
36
  isAndroid,
@@ -192,10 +192,14 @@ export interface WebScreenshotDataOptions extends ScreenshotInfo, MobileDeviceIn
192
192
  export interface ElementScreenshotDataOptions extends ScreenshotInfo, MobileDeviceInfo, AndroidScreenshotOptions, MobileCroppingOptions {
193
193
  /** Whether to automatically scroll the element into view. */
194
194
  autoElementScroll: boolean;
195
+ /** BiDi-only: coordinate origin for element screenshots ('document' | 'viewport'). */
196
+ biDiOrigin?: 'document' | 'viewport';
195
197
  /** The element to take a screenshot of. */
196
198
  element: HTMLElement | WebdriverIO.Element | ChainablePromiseElement;
197
- /** The inner height. */
199
+ /** The inner height of the viewport. */
198
200
  innerHeight?: number;
201
+ /** The inner width of the viewport. */
202
+ innerWidth?: number;
199
203
  /** Resize dimensions for the screenshot. */
200
204
  resizeDimensions: any;
201
205
  }
@@ -1 +1 @@
1
- {"version":3,"file":"screenshots.interfaces.d.ts","sourceRoot":"","sources":["../../src/methods/screenshots.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAIpF;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC3B,8BAA8B;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACvB,iDAAiD;IACjD,SAAS,EAAE,OAAO,CAAC;IACnB,6CAA6C;IAC7C,KAAK,EAAE,OAAO,CAAC;IACf,qCAAqC;IACrC,WAAW,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB,wBAAwB;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAID;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACrC,wDAAwD;IACxD,4BAA4B,EAAE,OAAO,CAAC;IACtC,0DAA0D;IAC1D,+BAA+B,EAAE,OAAO,CAAC;CAC5C;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACjC,wCAAwC;IACxC,kBAAkB,EAAE,OAAO,CAAC;CAC/B;AAID;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAChD,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,sCAAsC;IACtC,UAAU,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC,kDAAkD;IAClD,uBAAuB,EAAE,MAAM,CAAC;IAChC,8CAA8C;IAC9C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oCAAoC;IACpC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,2DAA2D;IAC3D,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oFAAoF;IACpF,oBAAoB,EAAE,CAAC,WAAW,GAAG,WAAW,EAAE,CAAC,EAAE,CAAC;CACzD;AAID;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,kCAAkC;IAClC,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,IAAI,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED;;GAEG;AACH,UAAU,cAAc;IACpB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,oCAAoC;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,cAAc,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;CAE5D;AAED;;GAEG;AACH,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC7D,uDAAuD;IACvD,4BAA4B,EAAE,OAAO,CAAC;CACzC;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAAQ,kBAAkB;IACpE,uDAAuD;IACvD,4BAA4B,EAAE,OAAO,CAAC;IACtC,6BAA6B;IAC7B,UAAU,EAAE,gBAAgB,CAAC;CAChC;AAID;;GAEG;AACH,MAAM,WAAW,6BAA8B,SAC3C,cAAc,EACd,UAAU,EACV,wBAAwB,EACxB,YAAY,EACZ,qBAAqB,EACrB,aAAa;IACb,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,qCAAsC,SACnD,cAAc,EACd,UAAU,EACV,YAAY,EACZ,qBAAqB,EACrB,aAAa;IACb,2BAA2B;IAC3B,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,yBAA0B,SACvC,cAAc,EACd,YAAY,EACZ,aAAa;CAEhB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAyB,SACtC,cAAc,EACd,UAAU,EACV,wBAAwB,EACxB,qBAAqB;IACrB,4BAA4B;IAC5B,eAAe,EAAE,WAAW,CAAC,OAAO,CAAC;IACrC,2CAA2C;IAC3C,OAAO,EAAE,GAAG,CAAC;IACb,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,UAAU,EAAE,OAAO,CAAC;IACpB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAyB,SACtC,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB;IACpB,kDAAkD;IAClD,4BAA4B,EAAE,OAAO,CAAC;IACtC,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,4BAA6B,SAC1C,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB;IACrB,6DAA6D;IAC7D,iBAAiB,EAAE,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,OAAO,EAAE,WAAW,GAAG,WAAW,CAAC,OAAO,GAAG,uBAAuB,CAAC;IACrE,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,gBAAgB,EAAE,GAAG,CAAC;CACzB"}
1
+ {"version":3,"file":"screenshots.interfaces.d.ts","sourceRoot":"","sources":["../../src/methods/screenshots.interfaces.ts"],"names":[],"mappings":"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"}
@@ -12,11 +12,12 @@ export async function takeElementScreenshot(browserInstance, options, shouldUseB
12
12
  return await takeWebDriverElementScreenshot(browserInstance, options);
13
13
  }
14
14
  async function takeBiDiElementScreenshot(browserInstance, options) {
15
- const isWebDriverElementScreenshot = false;
16
15
  // Fix #1129: scrollElementIntoView receives a promise
17
- // The element might be a promise, so we need to resolve it before using it as a browser.execute() argument
18
- // if we need to use it in browser.execute()
19
16
  const element = await options.element;
17
+ if (options.biDiOrigin === 'viewport') {
18
+ return takeBiDiElementScreenshotFromViewport(browserInstance, element, options);
19
+ }
20
+ // Default: origin: 'document' path.
20
21
  // Scroll the element into the viewport so any lazy‑load / intersection
21
22
  // observers are triggered. We always capture from the *document* origin,
22
23
  // so the clip coordinates are document‑relative and independent of scroll.
@@ -35,13 +36,63 @@ async function takeBiDiElementScreenshot(browserInstance, options) {
35
36
  origin: 'document',
36
37
  clip,
37
38
  });
38
- // Restore scroll position
39
39
  if (options.autoElementScroll && currentPosition) {
40
40
  await browserInstance.execute(scrollToPosition, currentPosition);
41
41
  }
42
42
  return {
43
43
  base64Image,
44
- isWebDriverElementScreenshot,
44
+ isWebDriverElementScreenshot: false,
45
+ };
46
+ }
47
+ async function takeBiDiElementScreenshotFromViewport(browserInstance, element, options) {
48
+ // Scroll element into view first so getElementRect reflects its viewport position.
49
+ let currentPosition;
50
+ if (options.autoElementScroll) {
51
+ currentPosition = await browserInstance.execute(scrollElementIntoView, element, options.addressBarShadowPadding);
52
+ await waitFor(100);
53
+ }
54
+ // getElementRect returns viewport-relative coordinates per W3C BiDi spec.
55
+ const rect = await browserInstance.getElementRect(element.elementId);
56
+ const elX = Math.floor(rect.x);
57
+ const elY = Math.floor(rect.y);
58
+ const elW = Math.floor(rect.width);
59
+ const elH = Math.floor(rect.height);
60
+ const vpW = options.innerWidth ?? 0;
61
+ const vpH = options.innerHeight ?? 0;
62
+ // Case 1: element larger than viewport, can never be fully captured with 'viewport' origin.
63
+ if (elW > vpW || elH > vpH) {
64
+ throw new Error(`[BiDi viewport screenshot] The element dimensions (${elW}x${elH}px) exceed the viewport ` +
65
+ `(${vpW}x${vpH}px). You must use the default \`biDiOrigin: 'document'\` for this element. ` +
66
+ 'Note: with `\'document\'` origin, composited layers such as scrollbars, fixed/sticky overlays, ' +
67
+ 'and elements using `will-change` may not appear in the screenshot.');
68
+ }
69
+ // Case 2: element has no overlap with the viewport at all.
70
+ const isOutsideViewport = elX + elW <= 0 || elX >= vpW || elY + elH <= 0 || elY >= vpH;
71
+ if (isOutsideViewport) {
72
+ throw new Error('[BiDi viewport screenshot] The element is not in the viewport ' +
73
+ `(element: x=${elX}, y=${elY}, ${elW}x${elH}px; viewport: ${vpW}x${vpH}px). ` +
74
+ 'Call `element.scrollIntoView()` before taking the screenshot, or set `autoElementScroll: true`.');
75
+ }
76
+ // Case 3: element is partially outside the viewport but fits, suggest scrolling it fully into view.
77
+ const isPartiallyOutside = elX < 0 || elX + elW > vpW || elY < 0 || elY + elH > vpH;
78
+ if (isPartiallyOutside) {
79
+ throw new Error('[BiDi viewport screenshot] The element is not fully visible in the viewport ' +
80
+ `(element: x=${elX}, y=${elY}, ${elW}x${elH}px; viewport: ${vpW}x${vpH}px). ` +
81
+ 'The element fits within the viewport, scroll it fully into view by calling ' +
82
+ '`element.scrollIntoView()` or setting `autoElementScroll: true`.');
83
+ }
84
+ const clip = { x: elX, y: elY, width: elW, height: elH };
85
+ const base64Image = await takeBase64BiDiScreenshot({
86
+ browserInstance,
87
+ origin: 'viewport',
88
+ clip,
89
+ });
90
+ if (options.autoElementScroll && currentPosition) {
91
+ await browserInstance.execute(scrollToPosition, currentPosition);
92
+ }
93
+ return {
94
+ base64Image,
95
+ isWebDriverElementScreenshot: false,
45
96
  };
46
97
  }
47
98
  async function takeWebDriverElementScreenshot(browserInstance, options) {
@@ -129,6 +129,72 @@ describe('takeElementScreenshot', () => {
129
129
  expect(executeMock.mock.calls[0][1]).toEqual(resolvedElement);
130
130
  });
131
131
  });
132
+ describe('BiDi viewport screenshots', () => {
133
+ const vpOptions = {
134
+ ...baseOptions,
135
+ biDiOrigin: 'viewport',
136
+ innerWidth: 1280,
137
+ innerHeight: 720,
138
+ };
139
+ it('should take viewport screenshot when element is fully inside the viewport', async () => {
140
+ // element at (10, 20, 100x200) — fits in 1280x720 viewport
141
+ const result = await takeElementScreenshot(browserInstance, vpOptions, true);
142
+ expect(result).toEqual({
143
+ base64Image: 'bidi-screenshot-data',
144
+ isWebDriverElementScreenshot: false,
145
+ });
146
+ expect(takeBase64BiDiScreenshotSpy).toHaveBeenCalledWith({
147
+ browserInstance,
148
+ origin: 'viewport',
149
+ clip: { x: 10, y: 20, width: 100, height: 200 },
150
+ });
151
+ });
152
+ it('should throw when element dimensions exceed the viewport', async () => {
153
+ getElementRectMock.mockResolvedValueOnce({ x: 0, y: 0, width: 1400, height: 800 });
154
+ const err = await takeElementScreenshot(browserInstance, vpOptions, true).catch(e => e);
155
+ expect(err.message).toMatch(/element dimensions \(1400x800px\) exceed the viewport \(1280x720px\)/);
156
+ expect(err.message).toMatch(/biDiOrigin: 'document'/);
157
+ expect(err.message).toMatch(/composited layers/);
158
+ });
159
+ it('should throw when element is completely outside the viewport', async () => {
160
+ // element below the fold
161
+ getElementRectMock.mockResolvedValueOnce({ x: 0, y: 800, width: 100, height: 200 });
162
+ const err = await takeElementScreenshot(browserInstance, vpOptions, true).catch(e => e);
163
+ expect(err.message).toMatch(/element is not in the viewport/);
164
+ expect(err.message).toMatch(/scrollIntoView/);
165
+ expect(err.message).toMatch(/autoElementScroll: true/);
166
+ });
167
+ it('should throw when element is partially outside the viewport but fits', async () => {
168
+ // element starts at x=-10, so it bleeds left of the viewport
169
+ getElementRectMock.mockResolvedValueOnce({ x: -10, y: 0, width: 100, height: 200 });
170
+ const err = await takeElementScreenshot(browserInstance, vpOptions, true).catch(e => e);
171
+ expect(err.message).toMatch(/not fully visible in the viewport/);
172
+ expect(err.message).toMatch(/fits within the viewport/);
173
+ expect(err.message).toMatch(/autoElementScroll: true/);
174
+ });
175
+ it('should include element and viewport dimensions in the error messages', async () => {
176
+ getElementRectMock.mockResolvedValueOnce({ x: 0, y: 900, width: 200, height: 100 });
177
+ const err = await takeElementScreenshot(browserInstance, vpOptions, true).catch(e => e);
178
+ expect(err.message).toMatch(/x=0, y=900, 200x100px/);
179
+ expect(err.message).toMatch(/viewport: 1280x720px/);
180
+ });
181
+ it('should scroll element into view before validating when autoElementScroll is true', async () => {
182
+ const vpScrollOptions = { ...vpOptions, autoElementScroll: true };
183
+ // After scroll, element is fully in viewport
184
+ executeMock.mockResolvedValueOnce(300); // previous scroll position
185
+ const result = await takeElementScreenshot(browserInstance, vpScrollOptions, true);
186
+ expect(result.base64Image).toBe('bidi-screenshot-data');
187
+ // scrollElementIntoView + scrollToPosition (restore)
188
+ expect(executeMock).toHaveBeenCalledTimes(2);
189
+ expect(takeBase64BiDiScreenshotSpy).toHaveBeenCalledWith(expect.objectContaining({ origin: 'viewport' }));
190
+ });
191
+ it('should use origin: document for the default (no biDiOrigin set)', async () => {
192
+ const defaultOptions = { ...vpOptions, biDiOrigin: undefined };
193
+ const result = await takeElementScreenshot(browserInstance, defaultOptions, true);
194
+ expect(result.base64Image).toBe('bidi-screenshot-data');
195
+ expect(takeBase64BiDiScreenshotSpy).toHaveBeenCalledWith(expect.objectContaining({ origin: 'document' }));
196
+ });
197
+ });
132
198
  describe('Legacy screenshots', () => {
133
199
  let logErrorSpy;
134
200
  beforeEach(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/image-comparison-core",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "author": "Wim Selles - wswebcreation",
5
5
  "description": "Image comparison core module for @wdio/visual-service - WebdriverIO visual testing framework",
6
6
  "keywords": [