@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/helpers/options.d.ts +1 -4
  3. package/dist/helpers/options.d.ts.map +1 -1
  4. package/dist/helpers/options.js +2 -11
  5. package/dist/methods/compareReport.interfaces.d.ts +1 -1
  6. package/dist/methods/compareReport.interfaces.d.ts.map +1 -1
  7. package/dist/methods/createCompareReport.test.js +4 -2
  8. package/dist/methods/images.d.ts +2 -1
  9. package/dist/methods/images.d.ts.map +1 -1
  10. package/dist/methods/images.executeImageCompare.test.js +39 -44
  11. package/dist/methods/images.js +31 -31
  12. package/dist/methods/images.test.js +80 -220
  13. package/dist/methods/processDiffPixels.d.ts +1 -1
  14. package/dist/methods/processDiffPixels.d.ts.map +1 -1
  15. package/dist/methods/processDiffPixels.js +11 -2
  16. package/dist/methods/rectangles.d.ts.map +1 -1
  17. package/dist/methods/rectangles.interfaces.d.ts +1 -1
  18. package/dist/methods/rectangles.interfaces.d.ts.map +1 -1
  19. package/dist/methods/rectangles.js +12 -9
  20. package/dist/pixelmatch/compare.interfaces.d.ts +43 -0
  21. package/dist/pixelmatch/compare.interfaces.d.ts.map +1 -0
  22. package/dist/pixelmatch/compareImages.d.ts.map +1 -0
  23. package/dist/pixelmatch/compareImages.js +173 -0
  24. package/dist/pixelmatch/compareImages.test.d.ts +2 -0
  25. package/dist/pixelmatch/compareImages.test.d.ts.map +1 -0
  26. package/dist/pixelmatch/compareImages.test.js +262 -0
  27. package/dist/utils/imageUtils.d.ts +17 -0
  28. package/dist/utils/imageUtils.d.ts.map +1 -0
  29. package/dist/utils/imageUtils.js +189 -0
  30. package/dist/utils/imageUtils.test.d.ts +2 -0
  31. package/dist/utils/imageUtils.test.d.ts.map +1 -0
  32. package/dist/utils/imageUtils.test.js +277 -0
  33. package/package.json +6 -5
  34. package/dist/resemble/compare.interfaces.d.ts +0 -136
  35. package/dist/resemble/compare.interfaces.d.ts.map +0 -1
  36. package/dist/resemble/compareImages.d.ts.map +0 -1
  37. package/dist/resemble/compareImages.js +0 -21
  38. package/dist/resemble/resemble.jimp.cjs +0 -981
  39. package/dist/resemble/resemble.jimp.d.cts +0 -46
  40. package/dist/resemble/resemble.jimp.d.cts.map +0 -1
  41. /package/dist/{resemble → pixelmatch}/compare.interfaces.js +0 -0
  42. /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 '../resemble/compare.interfaces.js';
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,mCAAmC,CAAA;AAS/E;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,cAAc,CA6DpE;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;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,mBAAmB,EAAE,gCAAgC,GAAG,sBAAsB,EAAE,CAQpH"}
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"}
@@ -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 resembleIgnoreDefaults = ['alpha', 'antialiasing', 'colors', 'less', 'nothing'];
186
- return resembleIgnoreDefaults.filter((option) => Object.keys(imageCompareOptions).find((key) => key.toLowerCase().includes(option) && imageCompareOptions[key]));
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/resemble/compare.interfaces.js';
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":"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"}
1
+ {"version":3,"file":"compareReport.interfaces.d.ts","sourceRoot":"","sources":["../../src/methods/compareReport.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAA;AACvE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAChF,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEpE,MAAM,MAAM,WAAW,GAAG;IACtB,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE;QACV,OAAO,EAAE;YACL,8BAA8B;YAC9B,IAAI,EAAE,MAAM,CAAA;YACZ,iCAAiC;YACjC,OAAO,EAAE,MAAM,CAAA;SAClB,CAAA;QACD,6BAA6B;QAC7B,UAAU,EAAE,MAAM,CAAA;QAClB,QAAQ,EAAE;YACN,+BAA+B;YAC/B,IAAI,EAAE,MAAM,CAAA;YACZ,kCAAkC;YAClC,OAAO,EAAE,MAAM,CAAA;SAClB,CAAA;QACD,iCAAiC;QACjC,GAAG,EAAE,MAAM,CAAA;QACX,mCAAmC;QACnC,QAAQ,EAAE,OAAO,CAAA;QACjB,oCAAoC;QACpC,SAAS,EAAE,OAAO,CAAA;QAClB,gCAAgC;QAChC,KAAK,EAAE,OAAO,CAAA;KACjB,CAAA;CACJ,CAAA;AAED,MAAM,MAAM,8BAA8B,GAAG;IACzC,gDAAgD;IAChD,aAAa,EAAE,aAAa,CAAC;IAC7B,wDAAwD;IACxD,IAAI,EAAE,WAAW,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,WAAW,EAAE,WAAW,CAAC;CAC5B,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG,8BAA8B,GAAG;IACtE,yDAAyD;IACzD,OAAO,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,IAAI,EAAE,eAAe,CAAC;CACzB,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG,8BAA8B,GAAG;IAC3E,oDAAoD;IACpD,SAAS,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,4CAA4C;IAC5C,gBAAgB,EAAE,MAAM,CAAC;IACzB,6CAA6C;IAC7C,mBAAmB,EAAE,sBAAsB,CAAC;IAC5C,mCAAmC;IACnC,UAAU,EAAE,OAAO,CAAC;CACvB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,qCAAqC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,YAAY,EAAE;QACV,6BAA6B;QAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,+BAA+B;QAC/B,OAAO,CAAC,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAC5C,kBAAkB;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,gCAAgC;QAChC,QAAQ,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;KAC/C,CAAC;IACF,gCAAgC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,gDAAgD;IAChD,aAAa,EAAE,aAAa,CAAC;IAC7B,0CAA0C;IAC1C,QAAQ,EAAE,SAAS,GAAG;QAClB,uBAAuB;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,kCAAkC;QAClC,IAAI,EAAE,eAAe,CAAC;KACzB,CAAC;IACF,8CAA8C;IAC9C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wCAAwC;IACxC,qBAAqB,EAAE,MAAM,CAAC;CACjC,CAAA"}
@@ -13,7 +13,8 @@ describe('createCompareReport', () => {
13
13
  const createMockData = (misMatchPercentage = 0) => ({
14
14
  misMatchPercentage,
15
15
  rawMisMatchPercentage: misMatchPercentage,
16
- getBuffer: () => Buffer.from(''),
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
- getBuffer: () => Buffer.from(''),
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: [],
@@ -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): Promise<string>;
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":"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"}
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 compareImages from '../resemble/compareImages.js';
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('../resemble/compareImages.js', () => ({
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
- vi.mocked(compareImages.default).mockResolvedValue({
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(compareImages.default).toHaveBeenCalledWith(Buffer.from('mock-image-data'), Buffer.from('mock-image-data'), {
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).toHaveBeenCalledWith(expect.any(Buffer), Buffer.from(base64Image, 'base64'), expect.any(Object));
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
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(compareImages.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
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(compareImages.default).toHaveBeenCalledWith(expect.any(Buffer), expect.any(Buffer), {
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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(compareImages.default).mockResolvedValue({
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,
@@ -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 { Jimp, JimpMime } from 'jimp';
4
+ import { decodeImage, toBase64Png, createCanvas, cropImage, compositeImage, setOpacity, rotate90CW, rotate90CCW, rotate180 } from '../utils/imageUtils.js';
5
5
  import logger from '@wdio/logger';
6
- import compareImages from '../resemble/compareImages.js';
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 ? await rotateBase64Image({ base64Image, degrees: 90 }) : base64Image;
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 ? await rotateBase64Image({ base64Image: topImage, degrees: 90 }) : topImage;
154
- const bottomBase64Image = isLandscape ? await rotateBase64Image({ base64Image: bottomImage, degrees: 90 }) : bottomImage;
155
- image.composite(await Jimp.read(Buffer.from(topBase64Image, 'base64')), 0, 0);
156
- image.composite(await Jimp.read(Buffer.from(bottomBase64Image, 'base64')), isLandscape ? width - getBase64ScreenshotSize(bottomImage).height : 0, isLandscape ? 0 : height - getBase64ScreenshotSize(bottomImage).height);
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 = await Jimp.read(Buffer.from(base64Image, 'base64'));
182
- const croppedImage = image.crop({ x: sourceX, y: sourceY, w: width, h: height });
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
- const base64CroppedImage = await croppedImage.getBase64(JimpMime.png);
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 compareImages(readFileSync(baselineFilePath), actualImageBuffer, compareOptions);
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 = await new Jimp({ width: canvasWidth, height: canvasHeight });
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 ? await rotateBase64Image({ base64Image: currentScreenshot, degrees: 90 }) : currentScreenshot;
380
+ const newBase64Image = isRotated ? rotateBase64Image({ base64Image: currentScreenshot, degrees: 90 }) : currentScreenshot;
382
381
  const { canvasYPosition, imageHeight, imageXPosition, imageYPosition } = screenshotsData.data[i];
383
- const image = await Jimp.read(Buffer.from(newBase64Image, 'base64'));
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.bitmap.width;
387
- const actualImageHeight = image.bitmap.height;
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.composite(image.crop({ x: clampedCropX, y: clampedCropY, w: clampedCropWidth, h: clampedCropHeight }), 0, canvasYPosition);
394
+ compositeImage(canvas, cropImage(image, clampedCropX, clampedCropY, clampedCropWidth, clampedCropHeight), 0, canvasYPosition);
396
395
  }
397
- const base64FullPageImage = await canvas.getBase64(JimpMime.png);
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 = await Jimp.read(Buffer.from(screenshot, 'base64'));
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 = new Jimp({ width: ignoredBoxWidth - x, height: ignoredBoxHeight - y, color: '#39aa56' });
416
- ignoreCanvas.opacity(0.5);
417
- image.composite(ignoreCanvas, x, y);
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
- const base64ImageWithBlockOuts = await image.getBase64(JimpMime.png);
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 async function rotateBase64Image({ base64Image, degrees }) {
427
- const image = await Jimp.read(Buffer.from(base64Image, 'base64'));
428
- const rotatedImage = image.rotate(degrees);
429
- const base64RotatedImage = await rotatedImage.getBase64(JimpMime.png);
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