@wdio/image-comparison-core 1.3.0 → 2.0.1
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 +65 -3
- package/dist/base.interfaces.d.ts +2 -2
- package/dist/helpers/constants.js +1 -1
- package/dist/helpers/options.d.ts +1 -4
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.interfaces.d.ts +4 -2
- package/dist/helpers/options.interfaces.d.ts.map +1 -1
- package/dist/helpers/options.js +2 -11
- package/dist/helpers/options.test.js +16 -1
- 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 +60 -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 +178 -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 +267 -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 +3 -2
- 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/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
|