@wdio/image-comparison-core 1.2.4 → 2.0.0
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 +48 -0
- package/dist/helpers/options.d.ts +1 -4
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +2 -11
- package/dist/methods/compareReport.interfaces.d.ts +1 -1
- package/dist/methods/compareReport.interfaces.d.ts.map +1 -1
- package/dist/methods/createCompareReport.test.js +4 -2
- package/dist/methods/images.d.ts +2 -1
- package/dist/methods/images.d.ts.map +1 -1
- package/dist/methods/images.executeImageCompare.test.js +39 -44
- package/dist/methods/images.js +31 -31
- package/dist/methods/images.test.js +80 -220
- package/dist/methods/processDiffPixels.d.ts +1 -1
- package/dist/methods/processDiffPixels.d.ts.map +1 -1
- package/dist/methods/processDiffPixels.js +11 -2
- package/dist/methods/rectangles.d.ts.map +1 -1
- package/dist/methods/rectangles.interfaces.d.ts +1 -1
- package/dist/methods/rectangles.interfaces.d.ts.map +1 -1
- package/dist/methods/rectangles.js +12 -9
- package/dist/pixelmatch/compare.interfaces.d.ts +43 -0
- package/dist/pixelmatch/compare.interfaces.d.ts.map +1 -0
- package/dist/pixelmatch/compareImages.d.ts.map +1 -0
- package/dist/pixelmatch/compareImages.js +173 -0
- package/dist/pixelmatch/compareImages.test.d.ts +2 -0
- package/dist/pixelmatch/compareImages.test.d.ts.map +1 -0
- package/dist/pixelmatch/compareImages.test.js +262 -0
- package/dist/utils/imageUtils.d.ts +17 -0
- package/dist/utils/imageUtils.d.ts.map +1 -0
- package/dist/utils/imageUtils.js +189 -0
- package/dist/utils/imageUtils.test.d.ts +2 -0
- package/dist/utils/imageUtils.test.d.ts.map +1 -0
- package/dist/utils/imageUtils.test.js +277 -0
- package/package.json +6 -5
- package/dist/resemble/compare.interfaces.d.ts +0 -136
- package/dist/resemble/compare.interfaces.d.ts.map +0 -1
- package/dist/resemble/compareImages.d.ts.map +0 -1
- package/dist/resemble/compareImages.js +0 -21
- package/dist/resemble/resemble.jimp.cjs +0 -981
- package/dist/resemble/resemble.jimp.d.cts +0 -46
- package/dist/resemble/resemble.jimp.d.cts.map +0 -1
- /package/dist/{resemble → pixelmatch}/compare.interfaces.js +0 -0
- /package/dist/{resemble → pixelmatch}/compareImages.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @wdio/image-comparison-core
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- d2758ce: ### 💥 Breaking change: new image comparison engine
|
|
8
|
+
|
|
9
|
+
We replaced the engine that powers every visual comparison. This is a breaking change, so please read the migration note below before upgrading.
|
|
10
|
+
|
|
11
|
+
**The problem**
|
|
12
|
+
|
|
13
|
+
Visual tests were flaky. Tests failed on differences that are impossible to see by eye, like sub-pixel font rendering, 1px anti-aliasing on edges and small shadow shifts between runs. The old engine (resemble.js) compared raw RGB values, which does not match how human vision works, and on larger screenshots it quietly skipped about a third of the pixels. So you got failures that were not real, and in some cases real changes that could slip through.
|
|
14
|
+
|
|
15
|
+
On top of that, all the image handling (decode, crop, composite, rotate, resize) ran through [`jimp`](https://github.com/jimp-dev/jimp), a large dependency that we only used a small slice of and that is no longer actively maintained.
|
|
16
|
+
|
|
17
|
+
**The solution**
|
|
18
|
+
|
|
19
|
+
Two things changed under the hood:
|
|
20
|
+
|
|
21
|
+
1. The comparison engine is now [pixelmatch](https://github.com/mapbox/pixelmatch). It compares images the way the eye perceives them (in the YIQ colour space) and detects anti-aliasing by checking both images at once. Invisible rendering noise now passes, and real regressions still fail.
|
|
22
|
+
2. `jimp` has been removed completely. PNG decode and encode now go through the small [`fast-png`](https://github.com/image-js/fast-png) library, and the handful of image operations we still need (crop, composite, canvas, opacity, rotate, resize) live in a tiny internal helper. The bundled resemble file is gone too. The net effect is a much lighter dependency footprint with no loss in functionality.
|
|
23
|
+
|
|
24
|
+
**What you need to do**
|
|
25
|
+
|
|
26
|
+
- Your public API does not change. `checkScreen`, `checkElement`, `checkFullPageScreen` and the matchers all work exactly as before, and the same `ignore` options are supported.
|
|
27
|
+
- Because the new engine measures differences differently, mismatch percentages will not match the old numbers exactly. You should re-run your suite once and re-accept your baselines so they are generated with the new engine. After that your tests should be noticeably more stable.
|
|
28
|
+
|
|
29
|
+
**Also fixed in this release**
|
|
30
|
+
|
|
31
|
+
- **Top-row artifact on full page screenshots:** Jimp's `contain()` centred the image, shifting content by 1px and creating a false diff across the top row. Replaced with buffer-level padding that anchors content at (0,0).
|
|
32
|
+
- **Ignored region 1px under-coverage:** The device-pixel size of an ignored region used `Math.floor`, which could drop a pixel when `cssSize * DPR` had a fractional part. Width and height now use `Math.ceil` so the full element is always covered. Position still uses `Math.floor`.
|
|
33
|
+
- **Comparison sensitivity matches what you were used to:** Switching engines meant retuning how strict a comparison is. The pixelmatch threshold is now aligned with the old resemble tolerances, so a difference that used to fail still fails and one that used to pass still passes. The diff highlight also uses a single consistent colour instead of varying per run.
|
|
34
|
+
- **Different image sizes no longer crash the comparison:** When a baseline and the actual screenshot had slightly different dimensions, the old flow threw an error and you lost the result. Both images are now normalised to the same size before they are compared, so a size change is reported as a visual difference you can review instead of a hard failure.
|
|
35
|
+
- **More reliable ignore regions with WebDriver BiDi:** With BiDi the calculated element bounds can be off by a pixel or two, which sometimes left part of an ignored element just outside the ignored area and caused a false diff. The BiDi emulated flow now uses a larger `ignoreRegionPadding` so the whole element stays covered.
|
|
36
|
+
|
|
37
|
+
### Committers: 1
|
|
38
|
+
|
|
39
|
+
- Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 6cd5742: ### Dependency updates
|
|
44
|
+
|
|
45
|
+
Updated dependencies across all packages to their latest compatible versions. This includes the WebdriverIO toolchain (`webdriverio`, `@wdio/*`) to `9.29.1`, the TypeScript ESLint plugins to `8.62.0`, Vitest to `3.2.6`, and various other packages such as `sharp`, `@remix-run/*`, `fuse.js` and `expect-webdriverio`. There are no functional or API changes.
|
|
46
|
+
|
|
47
|
+
### Committers: 1
|
|
48
|
+
|
|
49
|
+
- Wim Selles ([@wswebcreation](https://github.com/wswebcreation))
|
|
50
|
+
|
|
3
51
|
## 1.2.4
|
|
4
52
|
|
|
5
53
|
### Patch Changes
|
|
@@ -3,7 +3,7 @@ import type { MethodImageCompareCompareOptions, ScreenMethodImageCompareCompareO
|
|
|
3
3
|
import type { BeforeScreenshotOptions, BeforeScreenshotResult } from './beforeScreenshot.interfaces.js';
|
|
4
4
|
import type { AfterScreenshotOptions } from './afterScreenshot.interfaces.js';
|
|
5
5
|
import type { InstanceData } from '../methods/instanceData.interfaces.js';
|
|
6
|
-
import type { ComparisonIgnoreOption } from '../
|
|
6
|
+
import type { ComparisonIgnoreOption } from '../pixelmatch/compare.interfaces.js';
|
|
7
7
|
/**
|
|
8
8
|
* Determine the default options by merging user options with sensible defaults
|
|
9
9
|
*/
|
|
@@ -57,8 +57,5 @@ export interface BuildAfterScreenshotOptionsInput {
|
|
|
57
57
|
* Handles differences between native and web commands automatically
|
|
58
58
|
*/
|
|
59
59
|
export declare function buildAfterScreenshotOptions({ base64Image, folders, tag, isNativeContext, instanceData, enrichedInstanceData, beforeOptions, wicOptions }: BuildAfterScreenshotOptionsInput): AfterScreenshotOptions;
|
|
60
|
-
/**
|
|
61
|
-
* Prepare ignore options for resemble.js comparison
|
|
62
|
-
*/
|
|
63
60
|
export declare function prepareIgnoreOptions(imageCompareOptions: MethodImageCompareCompareOptions): ComparisonIgnoreOption[];
|
|
64
61
|
//# sourceMappingURL=options.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/helpers/options.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,KAAK,EAAE,gCAAgC,EAAE,sCAAsC,EAAE,MAAM,iCAAiC,CAAA;AAC/H,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACvG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/helpers/options.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AAC3E,OAAO,KAAK,EAAE,gCAAgC,EAAE,sCAAsC,EAAE,MAAM,iCAAiC,CAAA;AAC/H,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AACvG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACzE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAA;AASjF;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,cAAc,CAuDpE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACtC,OAAO,EAAE,sCAAsC,GAChD,sCAAsC,CAOxC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,gCAAgC,GAAG,gCAAgC,CAoBhH;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CACzC,YAAY,EAAE,GAAG,EACjB,aAAa,EAAE;IACX,YAAY,CAAC,EAAE,WAAW,EAAE,CAAA;IAC5B,cAAc,CAAC,EAAE,WAAW,EAAE,CAAA;IAC9B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC/B,EACD,UAAU,EAAE;IACR,uBAAuB,EAAE,MAAM,CAAA;IAC/B,oBAAoB,EAAE,MAAM,CAAA;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC/B,GACF,uBAAuB,CAazB;AAED,MAAM,WAAW,gCAAgC;IAE7C,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IACjC,GAAG,EAAE,MAAM,CAAA;IACX,eAAe,EAAE,OAAO,CAAA;IACxB,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,EAAE;QACR,eAAe,EAAE,MAAM,CAAA;QACvB,eAAe,EAAE,OAAO,CAAA;QACxB,qBAAqB,CAAC,EAAE,OAAO,CAAA;KAClC,CAAA;IAGD,oBAAoB,CAAC,EAAE,sBAAsB,CAAA;IAC7C,aAAa,CAAC,EAAE,uBAAuB,CAAA;CAC1C;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,EACxC,WAAW,EACX,OAAO,EACP,GAAG,EACH,eAAe,EACf,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,UAAU,EACb,EAAE,gCAAgC,GAAG,sBAAsB,CA0E3D;AAED,wBAAgB,oBAAoB,CAAC,mBAAmB,EAAE,gCAAgC,GAAG,sBAAsB,EAAE,CAQpH"}
|
package/dist/helpers/options.js
CHANGED
|
@@ -35,12 +35,6 @@ export function defaultOptions(options) {
|
|
|
35
35
|
ignoreRegionPadding: options.ignoreRegionPadding ?? 1,
|
|
36
36
|
waitForFontsLoaded: options.waitForFontsLoaded ?? true,
|
|
37
37
|
alwaysSaveActualImage: options.alwaysSaveActualImage ?? true,
|
|
38
|
-
/**
|
|
39
|
-
* Compare options (merged sequentially):
|
|
40
|
-
* 1. Default options (fallback)
|
|
41
|
-
* 2. Root compareOptions (deprecated but supported)
|
|
42
|
-
* 3. User-provided compareOptions
|
|
43
|
-
*/
|
|
44
38
|
compareOptions: {
|
|
45
39
|
...DEFAULT_COMPARE_OPTIONS,
|
|
46
40
|
...logAllDeprecatedCompareOptions(options),
|
|
@@ -178,10 +172,7 @@ export function buildAfterScreenshotOptions({ base64Image, folders, tag, isNativ
|
|
|
178
172
|
}
|
|
179
173
|
return afterOptions;
|
|
180
174
|
}
|
|
181
|
-
/**
|
|
182
|
-
* Prepare ignore options for resemble.js comparison
|
|
183
|
-
*/
|
|
184
175
|
export function prepareIgnoreOptions(imageCompareOptions) {
|
|
185
|
-
const
|
|
186
|
-
return
|
|
176
|
+
const ignoreDefaults = ['alpha', 'antialiasing', 'colors', 'less', 'nothing'];
|
|
177
|
+
return ignoreDefaults.filter((option) => Object.keys(imageCompareOptions).find((key) => key.toLowerCase().includes(option) && imageCompareOptions[key]));
|
|
187
178
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompareData } from 'src/
|
|
1
|
+
import type { CompareData } from 'src/pixelmatch/compare.interfaces.js';
|
|
2
2
|
import type { WicImageCompareOptions } from './images.interfaces.js';
|
|
3
3
|
import type { BoundingBoxes, ReportFileSizes } from './rectangles.interfaces.js';
|
|
4
4
|
import type { FilePaths, FolderPaths } from 'src/base.interfaces.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compareReport.interfaces.d.ts","sourceRoot":"","sources":["../../src/methods/compareReport.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"compareReport.interfaces.d.ts","sourceRoot":"","sources":["../../src/methods/compareReport.interfaces.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,7 +13,8 @@ describe('createCompareReport', () => {
|
|
|
13
13
|
const createMockData = (misMatchPercentage = 0) => ({
|
|
14
14
|
misMatchPercentage,
|
|
15
15
|
rawMisMatchPercentage: misMatchPercentage,
|
|
16
|
-
|
|
16
|
+
getRawPixels: () => ({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
17
|
+
getBuffer: () => Promise.resolve(Buffer.from('')),
|
|
17
18
|
diffBounds: { top: 0, left: 0, bottom: 0, right: 0 },
|
|
18
19
|
analysisTime: 0,
|
|
19
20
|
diffPixels: [],
|
|
@@ -116,7 +117,8 @@ describe('createJsonReportIfNeeded', () => {
|
|
|
116
117
|
const createMockData = (misMatchPercentage = 0) => ({
|
|
117
118
|
misMatchPercentage,
|
|
118
119
|
rawMisMatchPercentage: misMatchPercentage,
|
|
119
|
-
|
|
120
|
+
getRawPixels: () => ({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
121
|
+
getBuffer: () => Promise.resolve(Buffer.from('')),
|
|
120
122
|
diffBounds: { top: 0, left: 0, bottom: 0, right: 0 },
|
|
121
123
|
analysisTime: 0,
|
|
122
124
|
diffPixels: [],
|
package/dist/methods/images.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare function makeFullPageBase64Image(screenshotsData: FullPageScreens
|
|
|
49
49
|
* Save the base64 image to a file
|
|
50
50
|
*/
|
|
51
51
|
export declare function saveBase64Image(base64Image: string, filePath: string): Promise<void>;
|
|
52
|
+
export declare function savePngBuffer(buffer: Buffer, filePath: string): Promise<void>;
|
|
52
53
|
/**
|
|
53
54
|
* Create a canvas with the ignore boxes if they are present
|
|
54
55
|
*/
|
|
@@ -57,7 +58,7 @@ export declare function addBlockOuts(screenshot: string, ignoredBoxes: IgnoreBox
|
|
|
57
58
|
* Rotate a base64 image
|
|
58
59
|
* Tnx to https://gist.github.com/Zyndoras/6897abdf53adbedf02564808aaab94db
|
|
59
60
|
*/
|
|
60
|
-
export declare function rotateBase64Image({ base64Image, degrees }: RotateBase64ImageOptions):
|
|
61
|
+
export declare function rotateBase64Image({ base64Image, degrees }: RotateBase64ImageOptions): string;
|
|
61
62
|
/**
|
|
62
63
|
* Take a based64 screenshot of an element and resize it
|
|
63
64
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/methods/images.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACR,YAAY,EACZ,wBAAwB,EACxB,uBAAuB,EACvB,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,8BAA8B,EAC9B,wBAAwB,EACxB,YAAY,EACZ,kCAAkC,EAClC,kCAAkC,EACrC,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAQ1E;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO3E;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAWjF;AAED;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,EAC3C,cAAc,EACd,gBAAgB,EAChB,gBAAwB,EACxB,cAAsB,EACtB,iBAAiB,GACpB,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD1C;AACD;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,EAAE,4BAA4B,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAKvI;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAChC,SAAS,EACT,YAAY,EACZ,QAAQ,EACR,IAAI,GACP,EAAE,iBAAiB,GAAG,IAAI,CAW1B;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC5B,MAAM,EACN,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,KAAK,EACL,WAAW,GACd,EAAE,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwBjC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,EACxC,kBAAkB,EAClB,KAAK,EACL,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,WAAW,EACX,KAAK,GACR,EAAE,qBAAqB,iBAiDvB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,EAC1C,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAO,EACP,OAAO,EACP,KAAK,GACR,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/methods/images.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import * as utils from '../helpers/utils.js';
|
|
|
6
6
|
import * as rectangles from './rectangles.js';
|
|
7
7
|
import * as processDiffPixels from './processDiffPixels.js';
|
|
8
8
|
import * as createCompareReport from './createCompareReport.js';
|
|
9
|
-
import * as
|
|
9
|
+
import * as compareImagesPixelmatch from '../pixelmatch/compareImages.js';
|
|
10
10
|
const log = logger('test');
|
|
11
11
|
vi.mock('@wdio/logger', () => import(join(process.cwd(), '__mocks__', '@wdio/logger')));
|
|
12
12
|
vi.mock('jimp', () => {
|
|
@@ -75,7 +75,7 @@ vi.mock('./createCompareReport.js', () => ({
|
|
|
75
75
|
createCompareReport: vi.fn(),
|
|
76
76
|
createJsonReportIfNeeded: vi.fn()
|
|
77
77
|
}));
|
|
78
|
-
vi.mock('../
|
|
78
|
+
vi.mock('../pixelmatch/compareImages.js', () => ({
|
|
79
79
|
default: vi.fn()
|
|
80
80
|
}));
|
|
81
81
|
vi.mock('../helpers/constants.js', () => ({
|
|
@@ -156,27 +156,6 @@ describe('executeImageCompare', () => {
|
|
|
156
156
|
let logWarnSpy;
|
|
157
157
|
beforeEach(async () => {
|
|
158
158
|
vi.clearAllMocks();
|
|
159
|
-
const jimp = await import('jimp');
|
|
160
|
-
const jimpReadMock = vi.mocked(jimp.Jimp.read);
|
|
161
|
-
const mockImage = {
|
|
162
|
-
composite: vi.fn().mockReturnThis(),
|
|
163
|
-
getBase64: vi.fn().mockResolvedValue('data:image/png;base64,mock-image-data'),
|
|
164
|
-
opacity: vi.fn().mockReturnThis(),
|
|
165
|
-
width: 100,
|
|
166
|
-
height: 200,
|
|
167
|
-
bitmap: { width: 100, height: 200 },
|
|
168
|
-
background: 0,
|
|
169
|
-
formats: [],
|
|
170
|
-
inspect: vi.fn().mockReturnValue('MockImage'),
|
|
171
|
-
toString: vi.fn().mockReturnValue('MockImage'),
|
|
172
|
-
scanIterator: vi.fn(),
|
|
173
|
-
scan: vi.fn(),
|
|
174
|
-
scanQuiet: vi.fn(),
|
|
175
|
-
scanIteratorQuiet: vi.fn(),
|
|
176
|
-
scanQuietIterator: vi.fn(),
|
|
177
|
-
scanQuietIteratorQuiet: vi.fn(),
|
|
178
|
-
};
|
|
179
|
-
jimpReadMock.mockResolvedValue(mockImage);
|
|
180
159
|
vi.mocked(fsPromises.access).mockResolvedValue(undefined);
|
|
181
160
|
vi.mocked(fsPromises.unlink).mockResolvedValue(undefined);
|
|
182
161
|
vi.mocked(fsPromises.mkdir).mockResolvedValue(undefined);
|
|
@@ -207,14 +186,16 @@ describe('executeImageCompare', () => {
|
|
|
207
186
|
});
|
|
208
187
|
vi.mocked(createCompareReport.createCompareReport).mockReturnValue(undefined);
|
|
209
188
|
vi.mocked(createCompareReport.createJsonReportIfNeeded).mockResolvedValue(undefined);
|
|
210
|
-
|
|
189
|
+
const mockCompareData = {
|
|
211
190
|
rawMisMatchPercentage: 0.5,
|
|
212
191
|
misMatchPercentage: 0.5,
|
|
192
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
213
193
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
214
194
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
215
195
|
analysisTime: 100,
|
|
216
196
|
diffPixels: []
|
|
217
|
-
}
|
|
197
|
+
};
|
|
198
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue(mockCompareData);
|
|
218
199
|
vi.mocked(images.checkBaselineImageExists).mockResolvedValue(undefined);
|
|
219
200
|
vi.mocked(images.removeDiffImageIfExists).mockResolvedValue(undefined);
|
|
220
201
|
vi.mocked(images.saveBase64Image).mockResolvedValue(undefined);
|
|
@@ -244,7 +225,7 @@ describe('executeImageCompare', () => {
|
|
|
244
225
|
savePerInstance: false,
|
|
245
226
|
fileName: 'test.png'
|
|
246
227
|
});
|
|
247
|
-
expect(
|
|
228
|
+
expect(compareImagesPixelmatch.default).toHaveBeenCalledWith(Buffer.from('mock-image-data'), Buffer.from('mock-image-data'), {
|
|
248
229
|
ignore: [],
|
|
249
230
|
scaleToSameSize: true
|
|
250
231
|
});
|
|
@@ -418,9 +399,10 @@ describe('executeImageCompare', () => {
|
|
|
418
399
|
}
|
|
419
400
|
}
|
|
420
401
|
};
|
|
421
|
-
vi.mocked(
|
|
402
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
422
403
|
rawMisMatchPercentage: 0.5,
|
|
423
404
|
misMatchPercentage: 0.5,
|
|
405
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
424
406
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
425
407
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
426
408
|
analysisTime: 100,
|
|
@@ -504,9 +486,10 @@ describe('executeImageCompare', () => {
|
|
|
504
486
|
}
|
|
505
487
|
}
|
|
506
488
|
};
|
|
507
|
-
vi.mocked(
|
|
489
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
508
490
|
rawMisMatchPercentage: 0.123456,
|
|
509
491
|
misMatchPercentage: 0.12,
|
|
492
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
510
493
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
511
494
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
512
495
|
analysisTime: 100,
|
|
@@ -540,9 +523,10 @@ describe('executeImageCompare', () => {
|
|
|
540
523
|
autoSaveBaseline: false,
|
|
541
524
|
}
|
|
542
525
|
};
|
|
543
|
-
vi.mocked(
|
|
526
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
544
527
|
rawMisMatchPercentage: 0,
|
|
545
528
|
misMatchPercentage: 0,
|
|
529
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
546
530
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
547
531
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
548
532
|
analysisTime: 10,
|
|
@@ -556,7 +540,7 @@ describe('executeImageCompare', () => {
|
|
|
556
540
|
actualBase64Image: base64Image,
|
|
557
541
|
});
|
|
558
542
|
expect(images.saveBase64Image).not.toHaveBeenCalled();
|
|
559
|
-
expect(
|
|
543
|
+
expect(compareImagesPixelmatch.default).toHaveBeenCalledWith(expect.any(Buffer), Buffer.from(base64Image, 'base64'), expect.any(Object));
|
|
560
544
|
expect(fsPromises.writeFile).not.toHaveBeenCalled();
|
|
561
545
|
});
|
|
562
546
|
it('should disable JSON reports when actuals are not saved', async () => {
|
|
@@ -603,9 +587,10 @@ describe('executeImageCompare', () => {
|
|
|
603
587
|
}
|
|
604
588
|
return undefined;
|
|
605
589
|
});
|
|
606
|
-
vi.mocked(
|
|
590
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
607
591
|
rawMisMatchPercentage: 0,
|
|
608
592
|
misMatchPercentage: 0,
|
|
593
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
609
594
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
610
595
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
611
596
|
analysisTime: 10,
|
|
@@ -638,9 +623,10 @@ describe('executeImageCompare', () => {
|
|
|
638
623
|
},
|
|
639
624
|
},
|
|
640
625
|
};
|
|
641
|
-
vi.mocked(
|
|
626
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
642
627
|
rawMisMatchPercentage: 0.5,
|
|
643
628
|
misMatchPercentage: 0.5,
|
|
629
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
644
630
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
645
631
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
646
632
|
analysisTime: 10,
|
|
@@ -672,9 +658,10 @@ describe('executeImageCompare', () => {
|
|
|
672
658
|
},
|
|
673
659
|
},
|
|
674
660
|
};
|
|
675
|
-
vi.mocked(
|
|
661
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
676
662
|
rawMisMatchPercentage: 0.5,
|
|
677
663
|
misMatchPercentage: 0.5,
|
|
664
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
678
665
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
679
666
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
680
667
|
analysisTime: 10,
|
|
@@ -705,9 +692,10 @@ describe('executeImageCompare', () => {
|
|
|
705
692
|
},
|
|
706
693
|
},
|
|
707
694
|
};
|
|
708
|
-
vi.mocked(
|
|
695
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
709
696
|
rawMisMatchPercentage: 0.05,
|
|
710
697
|
misMatchPercentage: 0.05,
|
|
698
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
711
699
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
712
700
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
713
701
|
analysisTime: 10,
|
|
@@ -738,9 +726,10 @@ describe('executeImageCompare', () => {
|
|
|
738
726
|
},
|
|
739
727
|
},
|
|
740
728
|
};
|
|
741
|
-
vi.mocked(
|
|
729
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
742
730
|
rawMisMatchPercentage: 0.2,
|
|
743
731
|
misMatchPercentage: 0.2,
|
|
732
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
744
733
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
745
734
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
746
735
|
analysisTime: 10,
|
|
@@ -765,9 +754,10 @@ describe('executeImageCompare', () => {
|
|
|
765
754
|
alwaysSaveActualImage: false,
|
|
766
755
|
}
|
|
767
756
|
};
|
|
768
|
-
vi.mocked(
|
|
757
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
769
758
|
rawMisMatchPercentage: 0,
|
|
770
759
|
misMatchPercentage: 0,
|
|
760
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
771
761
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
772
762
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
773
763
|
analysisTime: 10,
|
|
@@ -836,7 +826,7 @@ describe('executeImageCompare', () => {
|
|
|
836
826
|
options: optionsWithIgnore,
|
|
837
827
|
testContext: mockTestContext
|
|
838
828
|
});
|
|
839
|
-
expect(
|
|
829
|
+
expect(compareImagesPixelmatch.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
|
|
840
830
|
ignore: ['alpha', 'antialiasing', 'colors', 'less', 'nothing'],
|
|
841
831
|
scaleToSameSize: true
|
|
842
832
|
});
|
|
@@ -876,7 +866,7 @@ describe('executeImageCompare', () => {
|
|
|
876
866
|
options: mockOptions,
|
|
877
867
|
testContext: mockTestContext
|
|
878
868
|
});
|
|
879
|
-
expect(
|
|
869
|
+
expect(compareImagesPixelmatch.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
|
|
880
870
|
ignore: [],
|
|
881
871
|
scaleToSameSize: true
|
|
882
872
|
});
|
|
@@ -901,7 +891,7 @@ describe('executeImageCompare', () => {
|
|
|
901
891
|
options: optionsWithBlockOut,
|
|
902
892
|
testContext: mockTestContext
|
|
903
893
|
});
|
|
904
|
-
expect(
|
|
894
|
+
expect(compareImagesPixelmatch.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
|
|
905
895
|
ignore: [],
|
|
906
896
|
output: { ignoredBoxes: [{ bottom: 50, right: 100, left: 0, top: 0 }] },
|
|
907
897
|
scaleToSameSize: true
|
|
@@ -937,9 +927,10 @@ describe('executeImageCompare', () => {
|
|
|
937
927
|
}
|
|
938
928
|
}
|
|
939
929
|
};
|
|
940
|
-
vi.mocked(
|
|
930
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
941
931
|
rawMisMatchPercentage: 0.5,
|
|
942
932
|
misMatchPercentage: 0.5,
|
|
933
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
943
934
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
944
935
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
945
936
|
analysisTime: 100,
|
|
@@ -972,9 +963,10 @@ describe('executeImageCompare', () => {
|
|
|
972
963
|
}
|
|
973
964
|
}
|
|
974
965
|
};
|
|
975
|
-
vi.mocked(
|
|
966
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
976
967
|
rawMisMatchPercentage: 0.5,
|
|
977
968
|
misMatchPercentage: 0.5,
|
|
969
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
978
970
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
979
971
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
980
972
|
analysisTime: 100,
|
|
@@ -1006,9 +998,10 @@ describe('executeImageCompare', () => {
|
|
|
1006
998
|
}
|
|
1007
999
|
}
|
|
1008
1000
|
};
|
|
1009
|
-
vi.mocked(
|
|
1001
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
1010
1002
|
rawMisMatchPercentage: 0.5,
|
|
1011
1003
|
misMatchPercentage: 0.5,
|
|
1004
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
1012
1005
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
1013
1006
|
diffBounds: { left: 0, top: 0, right: 100, bottom: 200 },
|
|
1014
1007
|
analysisTime: 100,
|
|
@@ -1039,9 +1032,10 @@ describe('executeImageCompare', () => {
|
|
|
1039
1032
|
vi.mocked(images.checkBaselineImageExists).mockImplementation(async () => {
|
|
1040
1033
|
return Promise.resolve();
|
|
1041
1034
|
});
|
|
1042
|
-
vi.mocked(
|
|
1035
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
1043
1036
|
rawMisMatchPercentage: 0,
|
|
1044
1037
|
misMatchPercentage: 0,
|
|
1038
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
1045
1039
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
1046
1040
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
1047
1041
|
analysisTime: 10,
|
|
@@ -1076,9 +1070,10 @@ describe('executeImageCompare', () => {
|
|
|
1076
1070
|
return undefined;
|
|
1077
1071
|
});
|
|
1078
1072
|
vi.mocked(images.checkBaselineImageExists).mockImplementation(checkBaselineImageExists);
|
|
1079
|
-
vi.mocked(
|
|
1073
|
+
vi.mocked(compareImagesPixelmatch.default).mockResolvedValue({
|
|
1080
1074
|
rawMisMatchPercentage: 0,
|
|
1081
1075
|
misMatchPercentage: 0,
|
|
1076
|
+
getRawPixels: vi.fn().mockReturnValue({ data: new Uint8Array(4), width: 1, height: 1 }),
|
|
1082
1077
|
getBuffer: vi.fn().mockResolvedValue(Buffer.from('diff-image-data')),
|
|
1083
1078
|
diffBounds: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
1084
1079
|
analysisTime: 10,
|
package/dist/methods/images.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { readFileSync, writeFileSync, promises as fsPromises, constants } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { decodeImage, toBase64Png, createCanvas, cropImage, compositeImage, setOpacity, rotate90CW, rotate90CCW, rotate180 } from '../utils/imageUtils.js';
|
|
5
5
|
import logger from '@wdio/logger';
|
|
6
|
-
import
|
|
6
|
+
import compareImagesPixelmatch from '../pixelmatch/compareImages.js';
|
|
7
7
|
import { calculateDprData, getIosBezelImageNames, getBase64ScreenshotSize, prepareComparisonFilePaths, updateVisualBaseline } from '../helpers/utils.js';
|
|
8
8
|
import { prepareIgnoreOptions } from '../helpers/options.js';
|
|
9
9
|
import { DEFAULT_RESIZE_DIMENSIONS, supportedIosBezelDevices } from '../helpers/constants.js';
|
|
@@ -92,7 +92,7 @@ ${filePathMessage}
|
|
|
92
92
|
export async function getRotatedImageIfNeeded({ isWebDriverElementScreenshot, isLandscape, base64Image }) {
|
|
93
93
|
const { height: screenshotHeight, width: screenshotWidth } = getBase64ScreenshotSize(base64Image);
|
|
94
94
|
const isRotated = !isWebDriverElementScreenshot && isLandscape && screenshotHeight > screenshotWidth;
|
|
95
|
-
return isRotated ?
|
|
95
|
+
return isRotated ? rotateBase64Image({ base64Image, degrees: 90 }) : base64Image;
|
|
96
96
|
}
|
|
97
97
|
/**
|
|
98
98
|
* Log a warning when the crop goes out of the screen
|
|
@@ -150,10 +150,10 @@ export async function handleIOSBezelCorners({ addIOSBezelCorners, image, deviceN
|
|
|
150
150
|
const __dirname = dirname(__filename);
|
|
151
151
|
const topImage = readFileSync(join(__dirname, '..', '..', 'assets', 'ios', `${topImageName}.png`), { encoding: 'base64' });
|
|
152
152
|
const bottomImage = readFileSync(join(__dirname, '..', '..', 'assets', 'ios', `${bottomImageName}.png`), { encoding: 'base64' });
|
|
153
|
-
const topBase64Image = isLandscape ?
|
|
154
|
-
const bottomBase64Image = isLandscape ?
|
|
155
|
-
image
|
|
156
|
-
image
|
|
153
|
+
const topBase64Image = isLandscape ? rotateBase64Image({ base64Image: topImage, degrees: 270 }) : topImage;
|
|
154
|
+
const bottomBase64Image = isLandscape ? rotateBase64Image({ base64Image: bottomImage, degrees: 270 }) : bottomImage;
|
|
155
|
+
compositeImage(image, decodeImage(Buffer.from(topBase64Image, 'base64')), 0, 0);
|
|
156
|
+
compositeImage(image, decodeImage(Buffer.from(bottomBase64Image, 'base64')), isLandscape ? width - getBase64ScreenshotSize(bottomImage).height : 0, isLandscape ? 0 : height - getBase64ScreenshotSize(bottomImage).height);
|
|
157
157
|
}
|
|
158
158
|
else {
|
|
159
159
|
isIosBezelError = true;
|
|
@@ -178,13 +178,12 @@ ${supportedIosBezelDevices.join(', ')}
|
|
|
178
178
|
* Crop the image and convert it to a base64 image
|
|
179
179
|
*/
|
|
180
180
|
export async function cropAndConvertToDataURL({ addIOSBezelCorners, base64Image, deviceName, devicePixelRatio, height, isIOS, isLandscape, sourceX, sourceY, width, }) {
|
|
181
|
-
const image =
|
|
182
|
-
const croppedImage = image
|
|
181
|
+
const image = decodeImage(Buffer.from(base64Image, 'base64'));
|
|
182
|
+
const croppedImage = cropImage(image, sourceX, sourceY, width, height);
|
|
183
183
|
if (isIOS) {
|
|
184
184
|
await handleIOSBezelCorners({ addIOSBezelCorners, image: croppedImage, deviceName, devicePixelRatio, height, isLandscape, width });
|
|
185
185
|
}
|
|
186
|
-
|
|
187
|
-
return base64CroppedImage.replace(/^data:image\/png;base64,/, '');
|
|
186
|
+
return toBase64Png(croppedImage);
|
|
188
187
|
}
|
|
189
188
|
/**
|
|
190
189
|
* Make a cropped image with Canvas
|
|
@@ -309,7 +308,7 @@ export async function executeImageCompare({ isViewPortScreenshot, isNativeContex
|
|
|
309
308
|
scaleToSameSize: imageCompareOptions.scaleImagesToSameSize,
|
|
310
309
|
};
|
|
311
310
|
// 5. Execute the compare and retrieve the data
|
|
312
|
-
const data = await
|
|
311
|
+
const data = await compareImagesPixelmatch(readFileSync(baselineFilePath), actualImageBuffer, compareOptions);
|
|
313
312
|
const rawMisMatchPercentage = data.rawMisMatchPercentage;
|
|
314
313
|
const reportMisMatchPercentage = imageCompareOptions.rawMisMatchPercentage
|
|
315
314
|
? rawMisMatchPercentage
|
|
@@ -372,19 +371,19 @@ export async function executeImageCompare({ isViewPortScreenshot, isNativeContex
|
|
|
372
371
|
export async function makeFullPageBase64Image(screenshotsData, { devicePixelRatio, isLandscape }) {
|
|
373
372
|
const amountOfScreenshots = screenshotsData.data.length;
|
|
374
373
|
const { fullPageHeight: canvasHeight, fullPageWidth: canvasWidth } = screenshotsData;
|
|
375
|
-
const canvas =
|
|
374
|
+
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
376
375
|
// Load all the images
|
|
377
376
|
for (let i = 0; i < amountOfScreenshots; i++) {
|
|
378
377
|
const currentScreenshot = screenshotsData.data[i].screenshot;
|
|
379
378
|
const { height: screenshotHeight, width: screenshotWidth } = getBase64ScreenshotSize(currentScreenshot, devicePixelRatio);
|
|
380
379
|
const isRotated = isLandscape && screenshotHeight > screenshotWidth;
|
|
381
|
-
const newBase64Image = isRotated ?
|
|
380
|
+
const newBase64Image = isRotated ? rotateBase64Image({ base64Image: currentScreenshot, degrees: 90 }) : currentScreenshot;
|
|
382
381
|
const { canvasYPosition, imageHeight, imageXPosition, imageYPosition } = screenshotsData.data[i];
|
|
383
|
-
const image =
|
|
382
|
+
const image = decodeImage(Buffer.from(newBase64Image, 'base64'));
|
|
384
383
|
// Clamp crop dimensions to fit within the actual image bounds
|
|
385
384
|
// This is especially important for the last image where the calculated height might exceed available pixels
|
|
386
|
-
const actualImageWidth = image.
|
|
387
|
-
const actualImageHeight = image.
|
|
385
|
+
const actualImageWidth = image.width;
|
|
386
|
+
const actualImageHeight = image.height;
|
|
388
387
|
const clampedCropX = Math.max(0, Math.min(imageXPosition, actualImageWidth - 1));
|
|
389
388
|
const clampedCropY = Math.max(0, Math.min(imageYPosition, actualImageHeight - 1));
|
|
390
389
|
// Ensure the cropped width matches the canvas width to avoid 1px gaps due to rounding
|
|
@@ -392,10 +391,9 @@ export async function makeFullPageBase64Image(screenshotsData, { devicePixelRati
|
|
|
392
391
|
const maxAvailableWidth = actualImageWidth - clampedCropX;
|
|
393
392
|
const clampedCropWidth = Math.min(canvasWidth, maxAvailableWidth);
|
|
394
393
|
const clampedCropHeight = Math.min(imageHeight, actualImageHeight - clampedCropY);
|
|
395
|
-
canvas
|
|
394
|
+
compositeImage(canvas, cropImage(image, clampedCropX, clampedCropY, clampedCropWidth, clampedCropHeight), 0, canvasYPosition);
|
|
396
395
|
}
|
|
397
|
-
|
|
398
|
-
return base64FullPageImage.replace(/^data:image\/png;base64,/, '');
|
|
396
|
+
return toBase64Png(canvas);
|
|
399
397
|
}
|
|
400
398
|
/**
|
|
401
399
|
* Save the base64 image to a file
|
|
@@ -404,30 +402,32 @@ export async function saveBase64Image(base64Image, filePath) {
|
|
|
404
402
|
await fsPromises.mkdir(dirname(filePath), { recursive: true });
|
|
405
403
|
await fsPromises.writeFile(filePath, Buffer.from(base64Image, 'base64'));
|
|
406
404
|
}
|
|
405
|
+
export async function savePngBuffer(buffer, filePath) {
|
|
406
|
+
await fsPromises.mkdir(dirname(filePath), { recursive: true });
|
|
407
|
+
await fsPromises.writeFile(filePath, buffer);
|
|
408
|
+
}
|
|
407
409
|
/**
|
|
408
410
|
* Create a canvas with the ignore boxes if they are present
|
|
409
411
|
*/
|
|
410
412
|
export async function addBlockOuts(screenshot, ignoredBoxes) {
|
|
411
|
-
const image =
|
|
413
|
+
const image = decodeImage(Buffer.from(screenshot, 'base64'));
|
|
412
414
|
// Loop over all ignored areas and add them to the current canvas
|
|
413
415
|
for (const ignoredBox of ignoredBoxes) {
|
|
414
416
|
const { right: ignoredBoxWidth, bottom: ignoredBoxHeight, left: x, top: y } = ignoredBox;
|
|
415
|
-
const ignoreCanvas =
|
|
416
|
-
ignoreCanvas
|
|
417
|
-
image
|
|
417
|
+
const ignoreCanvas = createCanvas(ignoredBoxWidth - x, ignoredBoxHeight - y, 57, 170, 86, 255);
|
|
418
|
+
setOpacity(ignoreCanvas, 0.5);
|
|
419
|
+
compositeImage(image, ignoreCanvas, x, y);
|
|
418
420
|
}
|
|
419
|
-
|
|
420
|
-
return base64ImageWithBlockOuts.replace(/^data:image\/png;base64,/, '');
|
|
421
|
+
return toBase64Png(image);
|
|
421
422
|
}
|
|
422
423
|
/**
|
|
423
424
|
* Rotate a base64 image
|
|
424
425
|
* Tnx to https://gist.github.com/Zyndoras/6897abdf53adbedf02564808aaab94db
|
|
425
426
|
*/
|
|
426
|
-
export
|
|
427
|
-
const image =
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
return base64RotatedImage.replace(/^data:image\/png;base64,/, '');
|
|
427
|
+
export function rotateBase64Image({ base64Image, degrees }) {
|
|
428
|
+
const image = decodeImage(Buffer.from(base64Image, 'base64'));
|
|
429
|
+
const rotated = degrees === 180 ? rotate180(image) : degrees === 270 ? rotate90CCW(image) : rotate90CW(image);
|
|
430
|
+
return toBase64Png(rotated);
|
|
431
431
|
}
|
|
432
432
|
/**
|
|
433
433
|
* Take a based64 screenshot of an element and resize it
|