@wdio/image-comparison-core 1.2.1 → 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 +82 -0
- package/dist/commands/checkWebElement.d.ts.map +1 -1
- package/dist/commands/checkWebElement.js +2 -1
- package/dist/commands/element.interfaces.d.ts +10 -0
- package/dist/commands/element.interfaces.d.ts.map +1 -1
- package/dist/commands/saveWebElement.d.ts.map +1 -1
- package/dist/commands/saveWebElement.js +4 -1
- package/dist/methods/screenshots.interfaces.d.ts +5 -1
- package/dist/methods/screenshots.interfaces.d.ts.map +1 -1
- package/dist/methods/takeElementScreenshots.js +56 -5
- package/dist/methods/takeElementScreenshots.test.js +66 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
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
|
+
|
|
64
|
+
## 1.2.2
|
|
65
|
+
|
|
66
|
+
### Patch Changes
|
|
67
|
+
|
|
68
|
+
- db33fa7: #### `@wdio/image-comparison-core` and `@wdio/ocr-service` Security: update jimp (CVE in `file-type` transitive dep)
|
|
69
|
+
|
|
70
|
+
Bumped `jimp` to the latest version to resolve a reported vulnerability in its `file-type` transitive dependency (see [#1130](https://github.com/webdriverio/visual-testing/issues/1130), raised by [@denis-sokolov](https://github.com/denis-sokolov), thank you!).
|
|
71
|
+
|
|
72
|
+
**Actual impact on these packages**
|
|
73
|
+
`file-type` is used by `@jimp/core` solely to detect image MIME types when reading a buffer. In both `@wdio/image-comparison-core` and `@wdio/ocr-service`, every image passed to jimp originates from either WebDriver screenshots (browser-controlled base64 data) or local files written by the framework itself. There is no code path where untrusted external input is fed directly into jimp, which removes the exploitability that the CVE describes.
|
|
74
|
+
|
|
75
|
+
That said, the reputational and compliance risk was real, security scanners flag the package as vulnerable, enterprise users hit audit failures, and some organisations block installation of packages with known CVEs. The update addresses all of that.
|
|
76
|
+
|
|
77
|
+
#### `@wdio/visual-reporter` and `@wdio/visual-service`
|
|
78
|
+
|
|
79
|
+
Updated internal dependencies to pick up the jimp bump in `@wdio/image-comparison-core`.
|
|
80
|
+
|
|
81
|
+
### Committers: 1
|
|
82
|
+
|
|
83
|
+
- Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
|
|
84
|
+
|
|
3
85
|
## 1.2.1
|
|
4
86
|
|
|
5
87
|
### 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,
|
|
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;
|
|
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,
|
|
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":"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
|
|
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.
|
|
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": [
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"url": "https://github.com/webdriverio/visual-testing/issues"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"jimp": "^1.6.
|
|
29
|
+
"jimp": "^1.6.1",
|
|
30
30
|
"@wdio/logger": "^9.18.0",
|
|
31
|
-
"@wdio/types": "^9.
|
|
31
|
+
"@wdio/types": "^9.27.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"webdriverio": "^9.
|
|
34
|
+
"webdriverio": "^9.27.0"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|