chore: add blink-diff third party library (#10984)
This commit is contained in:
parent
b6aad54b9f
commit
8f98074fc8
|
|
@ -24,6 +24,8 @@ import pixelmatch from 'pixelmatch';
|
|||
import { diff_match_patch, DIFF_INSERT, DIFF_DELETE, DIFF_EQUAL } from '../third_party/diff_match_patch';
|
||||
import { TestInfoImpl, UpdateSnapshots } from '../types';
|
||||
import { addSuffixToFilePath } from '../util';
|
||||
import BlinkDiff from '../third_party/blink-diff';
|
||||
import PNGImage from '../third_party/png-js';
|
||||
|
||||
// Note: we require the pngjs version of pixelmatch to avoid version mismatches.
|
||||
const { PNG } = require(require.resolve('pngjs', { paths: [require.resolve('pixelmatch')] })) as typeof import('pngjs');
|
||||
|
|
@ -66,7 +68,16 @@ function compareImages(actualBuffer: Buffer | string, expectedBuffer: Buffer, mi
|
|||
};
|
||||
}
|
||||
const diff = new PNG({ width: expected.width, height: expected.height });
|
||||
const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, { threshold: 0.2, ...options });
|
||||
const thresholdOptions = { threshold: 0.2, ...options };
|
||||
if (process.env.PW_USE_BLINK_DIFF && mimeType === 'image/png') {
|
||||
const diff = new BlinkDiff({
|
||||
imageA: new PNGImage(expected as any),
|
||||
imageB: new PNGImage(actual as any),
|
||||
});
|
||||
const result = diff.runSync();
|
||||
return result.code !== BlinkDiff.RESULT_IDENTICAL ? { diff: PNG.sync.write(diff._imageOutput.getImage()) } : null;
|
||||
}
|
||||
const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, thresholdOptions);
|
||||
return count > 0 ? { diff: PNG.sync.write(diff) } : null;
|
||||
}
|
||||
|
||||
|
|
|
|||
1046
packages/playwright-test/src/third_party/blink-diff.js
vendored
Normal file
1046
packages/playwright-test/src/third_party/blink-diff.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
466
packages/playwright-test/src/third_party/png-js.js
vendored
Normal file
466
packages/playwright-test/src/third_party/png-js.js
vendored
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2014-2015 Yahoo! Inc.
|
||||
* Modifications copyright (c) Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef {number} int
|
||||
* @typedef {number} float
|
||||
* @typedef {{red: int, green: int, blue: int, alpha: int | undefined, opacity: float}} RGBAOColor
|
||||
*/
|
||||
|
||||
const { PNG } = require('pngjs');
|
||||
const fs = require('fs');
|
||||
|
||||
class PNGImage {
|
||||
/**
|
||||
* Creates an image by dimensions
|
||||
*
|
||||
* @static
|
||||
* @method createImage
|
||||
* @param {int} width
|
||||
* @param {int} height
|
||||
* @return {PNGImage}
|
||||
*/
|
||||
static createImage = function (width, height) {
|
||||
var image = new PNG({
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
return new PNGImage(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies an already existing image
|
||||
*
|
||||
* @static
|
||||
* @method copyImage
|
||||
* @param {PNGImage} image
|
||||
* @return {PNGImage}
|
||||
*/
|
||||
static copyImage(image) {
|
||||
var newImage = this.createImage(image.getWidth(), image.getHeight());
|
||||
PNG.bitblt(image.getImage(), newImage.getImage(), 0, 0, image.getWidth(), image.getHeight(), 0, 0);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} buffer
|
||||
* @returns {PNGImage}
|
||||
*/
|
||||
static loadImageSync(buffer) {
|
||||
return new PNGImage(PNG.sync.read(buffer));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {PNGImage}
|
||||
*/
|
||||
static readImageSync(path) {
|
||||
return this.loadImageSync(fs.readFileSync(path));
|
||||
}
|
||||
|
||||
/** @param {PNG} image */
|
||||
constructor(image) {
|
||||
/** @type {PNG} */
|
||||
this._image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original png-js object
|
||||
*
|
||||
* @method getImage
|
||||
* @return {PNG}
|
||||
*/
|
||||
getImage() {
|
||||
return this._image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the image
|
||||
*
|
||||
* @method getWidth
|
||||
* @return {int}
|
||||
*/
|
||||
getWidth() {
|
||||
return this._image.width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the height of the image
|
||||
*
|
||||
* @method getHeight
|
||||
* @return {int}
|
||||
*/
|
||||
getHeight() {
|
||||
return this._image.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clips the current image by modifying it in-place
|
||||
*
|
||||
* @method clip
|
||||
* @param {int} x Starting x-coordinate
|
||||
* @param {int} y Starting y-coordinate
|
||||
* @param {int} width Width of area relative to starting coordinate
|
||||
* @param {int} height Height of area relative to starting coordinate
|
||||
*/
|
||||
clip(x, y, width, height) {
|
||||
|
||||
var image;
|
||||
|
||||
width = Math.min(width, this.getWidth() - x);
|
||||
height = Math.min(height, this.getHeight() - y);
|
||||
|
||||
if ((width < 0) || (height < 0)) {
|
||||
throw new Error('Width and height cannot be negative.');
|
||||
}
|
||||
|
||||
image = new PNG({
|
||||
width: width, height: height
|
||||
});
|
||||
|
||||
this._image.bitblt(image, x, y, width, height, 0, 0);
|
||||
this._image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills an area with the specified color
|
||||
*
|
||||
* @method fillRect
|
||||
* @param {int} x Starting x-coordinate
|
||||
* @param {int} y Starting y-coordinate
|
||||
* @param {int} width Width of area relative to starting coordinate
|
||||
* @param {int} height Height of area relative to starting coordinate
|
||||
* @param {RGBAOColor} color
|
||||
*/
|
||||
fillRect(x, y, width, height, color) {
|
||||
var i,
|
||||
iLen = x + width,
|
||||
j,
|
||||
jLen = y + height,
|
||||
index;
|
||||
|
||||
for (i = x; i < iLen; i++) {
|
||||
for (j = y; j < jLen; j++) {
|
||||
index = this.getIndex(i, j);
|
||||
this.setAtIndex(index, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets index of a specific coordinate
|
||||
*
|
||||
* @method getIndex
|
||||
* @param {int} x X-coordinate of pixel
|
||||
* @param {int} y Y-coordinate of pixel
|
||||
* @return {int} Index of pixel
|
||||
*/
|
||||
getIndex(x, y) {
|
||||
return (this.getWidth() * y) + x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the image to the filesystem
|
||||
*
|
||||
* @method writeImage
|
||||
* @param {string} filename Path to file
|
||||
* @param {(error: Error | undefined, image: PNGImage) => void} fn Callback
|
||||
*/
|
||||
writeImage(filename, fn) {
|
||||
fn = fn || (() => {});
|
||||
this._image.pack().pipe(fs.createWriteStream(filename)).once('close', () => {
|
||||
this._image.removeListener('error', fn);
|
||||
fn(undefined, this);
|
||||
}).once('error', (err) => {
|
||||
this._image.removeListener('close', fn);
|
||||
fn(err, this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filename
|
||||
*/
|
||||
writeImageSync(filename) {
|
||||
fs.writeFileSync(filename, PNG.sync.write(this._image));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a pixel at a specific index
|
||||
*
|
||||
* @method getPixel
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int} Color
|
||||
*/
|
||||
getAtIndex(idx) {
|
||||
return this.getColorAtIndex(idx) | (this.getAlpha(idx) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a pixel at a specific coordinate
|
||||
*
|
||||
* @method getAt
|
||||
* @param {int} x X-coordinate of pixel
|
||||
* @param {int} y Y-coordinate of pixel
|
||||
* @return {int} Color
|
||||
*/
|
||||
getAt(x, y) {
|
||||
var idx = this.getIndex(x, y);
|
||||
return this.getAtIndex(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a pixel at a specific coordinate
|
||||
* Alias for getAt
|
||||
*
|
||||
* @method getPixel
|
||||
* @param {int} x X-coordinate of pixel
|
||||
* @param {int} y Y-coordinate of pixel
|
||||
* @return {int} Color
|
||||
*/
|
||||
getPixel(x, y) {
|
||||
return this.getAt(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of a pixel at a specific index
|
||||
*
|
||||
* @method setAtIndex
|
||||
* @param {int} idx Index of pixel
|
||||
* @param {RGBAOColor|int} color
|
||||
*/
|
||||
setAtIndex(idx, color) {
|
||||
if (typeof color === 'object') {
|
||||
if (color.red !== undefined) this.setRed(idx, color.red, color.opacity);
|
||||
if (color.green !== undefined) this.setGreen(idx, color.green, color.opacity);
|
||||
if (color.blue !== undefined) this.setBlue(idx, color.blue, color.opacity);
|
||||
if (color.alpha !== undefined) this.setAlpha(idx, color.alpha, color.opacity);
|
||||
} else {
|
||||
this.setRed(idx, color & 0xff);
|
||||
this.setGreen(idx, (color & 0xff00) >> 8);
|
||||
this.setBlue(idx, (color & 0xff0000) >> 16);
|
||||
this.setAlpha(idx, (color & 0xff000000) >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of a pixel at a specific coordinate
|
||||
*
|
||||
* @method setAt
|
||||
* @param {int} x X-coordinate for pixel
|
||||
* @param {int} y Y-coordinate for pixel
|
||||
* @param {RGBAOColor} color
|
||||
*/
|
||||
setAt(x, y, color) {
|
||||
var idx = this.getIndex(x, y);
|
||||
this.setAtIndex(idx, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color of a pixel at a specific coordinate
|
||||
* Alias for setAt
|
||||
*
|
||||
* @method setPixel
|
||||
* @param {int} x X-coordinate for pixel
|
||||
* @param {int} y Y-coordinate for pixel
|
||||
* @param {RGBAOColor} color
|
||||
*/
|
||||
setPixel(x, y, color) {
|
||||
this.setAt(x, y, color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a pixel at a specific index
|
||||
*
|
||||
* @method getColorAtIndex
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int} Color
|
||||
*/
|
||||
getColorAtIndex(idx) {
|
||||
return this.getRed(idx) | (this.getGreen(idx) << 8) | (this.getBlue(idx) << 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the color of a pixel at a specific coordinate
|
||||
*
|
||||
* @method getColor
|
||||
* @param {int} x X-coordinate of pixel
|
||||
* @param {int} y Y-coordinate of pixel
|
||||
* @return {int} Color
|
||||
*/
|
||||
getColor(x, y) {
|
||||
var idx = this.getIndex(x, y);
|
||||
return this.getColorAtIndex(idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the final color value for opacity
|
||||
*
|
||||
* @method _calculateColorValue
|
||||
* @param {int} originalValue
|
||||
* @param {int} paintValue
|
||||
* @param {number} [opacity]
|
||||
* @return {int}
|
||||
* @private
|
||||
*/
|
||||
_calculateColorValue(originalValue, paintValue, opacity) {
|
||||
|
||||
var originalPart, paintPart;
|
||||
|
||||
if (opacity === undefined) {
|
||||
return paintValue;
|
||||
} else {
|
||||
originalPart = originalValue * (1 - opacity);
|
||||
paintPart = (paintValue * opacity);
|
||||
|
||||
return Math.floor(originalPart + paintPart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the red value of a pixel
|
||||
*
|
||||
* @method getRed
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int}
|
||||
*/
|
||||
getRed(idx) {
|
||||
return this._getValue(idx, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the red value of a pixel
|
||||
*
|
||||
* @method setRed
|
||||
* @param {int} idx Index of pixel
|
||||
* @param {int} value Value for pixel
|
||||
* @param {number} [opacity] Opacity of value set
|
||||
*/
|
||||
setRed(idx, value, opacity) {
|
||||
this._setValue(idx, 0, value, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the green value of a pixel
|
||||
*
|
||||
* @method getGreen
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int}
|
||||
*/
|
||||
getGreen(idx) {
|
||||
return this._getValue(idx, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the green value of a pixel
|
||||
*
|
||||
* @method setGreen
|
||||
* @param {int} idx Index of pixel
|
||||
* @param {int} value Value for pixel
|
||||
* @param {number} [opacity] Opacity of value set
|
||||
*/
|
||||
setGreen(idx, value, opacity) {
|
||||
this._setValue(idx, 1, value, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blue value of a pixel
|
||||
*
|
||||
* @method getBlue
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int}
|
||||
*/
|
||||
getBlue(idx) {
|
||||
return this._getValue(idx, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blue value of a pixel
|
||||
*
|
||||
* @method setBlue
|
||||
* @param {int} idx Index of pixel
|
||||
* @param {int} value Value for pixel
|
||||
* @param {number} [opacity] Opacity of value set
|
||||
*/
|
||||
setBlue(idx, value, opacity) {
|
||||
this._setValue(idx, 2, value, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the alpha value of a pixel
|
||||
*
|
||||
* @method getAlpha
|
||||
* @param {int} idx Index of pixel
|
||||
* @return {int}
|
||||
*/
|
||||
getAlpha(idx) {
|
||||
return this._getValue(idx, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the alpha value of a pixel
|
||||
*
|
||||
* @method setAlpha
|
||||
* @param {int} idx Index of pixel
|
||||
* @param {int} value Value for pixel
|
||||
* @param {number} [opacity] Opacity of value set
|
||||
*/
|
||||
setAlpha(idx, value, opacity) {
|
||||
this._setValue(idx, 3, value, opacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a pixel
|
||||
*
|
||||
* @method _getValue
|
||||
* @param {int} offset Offset of a value
|
||||
* @param {int} colorOffset Offset of a color
|
||||
* @return {int}
|
||||
* @private
|
||||
*/
|
||||
_getValue(offset, colorOffset) {
|
||||
var localOffset = offset << 2;
|
||||
return this._image.data[localOffset + colorOffset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of a pixel
|
||||
*
|
||||
* @method _setValue
|
||||
* @param {int} offset Offset of a value
|
||||
* @param {int} colorOffset Offset of a color
|
||||
* @param {int} value Value for pixel
|
||||
* @param {number} [opacity] Opacity of value set
|
||||
* @private
|
||||
*/
|
||||
_setValue(offset, colorOffset, value, opacity) {
|
||||
var previousValue = this._getValue(offset, colorOffset),
|
||||
localOffset = offset << 2;
|
||||
|
||||
this._image.data[localOffset + colorOffset] = this._calculateColorValue(previousValue, value, opacity);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PNGImage;
|
||||
BIN
tests/playwright-test/assets/test.png
Normal file
BIN
tests/playwright-test/assets/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 B |
846
tests/playwright-test/blink-diff.spec.ts
Normal file
846
tests/playwright-test/blink-diff.spec.ts
Normal file
|
|
@ -0,0 +1,846 @@
|
|||
/**
|
||||
* The MIT License (MIT)
|
||||
* Copyright (c) 2014-2015 Yahoo! Inc.
|
||||
* Modifications copyright (c) Microsoft Corporation
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
import { test as it, expect } from './playwright-test-fixtures';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import BlinkDiff from '../../packages/playwright-test/lib/third_party/blink-diff';
|
||||
import PNGImage from '../../packages/playwright-test/lib/third_party/png-js';
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @returns {PNGImage}
|
||||
*/
|
||||
function generateImage(type) {
|
||||
let image;
|
||||
|
||||
switch (type) {
|
||||
case 'small-1':
|
||||
image = PNGImage.createImage(2, 2);
|
||||
image.setAt(0, 0, { red: 10, green: 20, blue: 30, alpha: 40 });
|
||||
image.setAt(0, 1, { red: 50, green: 60, blue: 70, alpha: 80 });
|
||||
image.setAt(1, 0, { red: 90, green: 100, blue: 110, alpha: 120 });
|
||||
image.setAt(1, 1, { red: 130, green: 140, blue: 150, alpha: 160 });
|
||||
break;
|
||||
|
||||
case 'small-2':
|
||||
image = PNGImage.createImage(2, 2);
|
||||
image.setAt(0, 0, { red: 210, green: 220, blue: 230, alpha: 240 });
|
||||
image.setAt(0, 1, { red: 10, green: 20, blue: 30, alpha: 40 });
|
||||
image.setAt(1, 0, { red: 50, green: 60, blue: 70, alpha: 80 });
|
||||
image.setAt(1, 1, { red: 15, green: 25, blue: 35, alpha: 45 });
|
||||
break;
|
||||
|
||||
case 'small-3':
|
||||
image = PNGImage.createImage(2, 2);
|
||||
break;
|
||||
|
||||
case 'medium-1':
|
||||
image = PNGImage.createImage(3, 3);
|
||||
image.setAt(0, 0, { red: 130, green: 140, blue: 150, alpha: 160 });
|
||||
image.setAt(0, 1, { red: 170, green: 180, blue: 190, alpha: 200 });
|
||||
image.setAt(0, 2, { red: 210, green: 220, blue: 230, alpha: 240 });
|
||||
image.setAt(1, 0, { red: 15, green: 25, blue: 35, alpha: 45 });
|
||||
image.setAt(1, 1, { red: 55, green: 65, blue: 75, alpha: 85 });
|
||||
image.setAt(1, 2, { red: 95, green: 105, blue: 115, alpha: 125 });
|
||||
image.setAt(2, 0, { red: 10, green: 20, blue: 30, alpha: 40 });
|
||||
image.setAt(2, 1, { red: 50, green: 60, blue: 70, alpha: 80 });
|
||||
image.setAt(2, 2, { red: 90, green: 100, blue: 110, alpha: 120 });
|
||||
break;
|
||||
|
||||
case 'medium-2':
|
||||
image = PNGImage.createImage(3, 3);
|
||||
image.setAt(0, 0, { red: 95, green: 15, blue: 165, alpha: 26 });
|
||||
image.setAt(0, 1, { red: 15, green: 225, blue: 135, alpha: 144 });
|
||||
image.setAt(0, 2, { red: 170, green: 80, blue: 210, alpha: 2 });
|
||||
image.setAt(1, 0, { red: 50, green: 66, blue: 23, alpha: 188 });
|
||||
image.setAt(1, 1, { red: 110, green: 120, blue: 63, alpha: 147 });
|
||||
image.setAt(1, 2, { red: 30, green: 110, blue: 10, alpha: 61 });
|
||||
image.setAt(2, 0, { red: 190, green: 130, blue: 180, alpha: 29 });
|
||||
image.setAt(2, 1, { red: 10, green: 120, blue: 31, alpha: 143 });
|
||||
image.setAt(2, 2, { red: 155, green: 165, blue: 15, alpha: 185 });
|
||||
break;
|
||||
|
||||
case 'slim-1':
|
||||
image = PNGImage.createImage(1, 3);
|
||||
image.setAt(0, 0, { red: 15, green: 225, blue: 135, alpha: 144 });
|
||||
image.setAt(0, 1, { red: 170, green: 80, blue: 210, alpha: 2 });
|
||||
image.setAt(0, 2, { red: 50, green: 66, blue: 23, alpha: 188 });
|
||||
break;
|
||||
|
||||
case 'slim-2':
|
||||
image = PNGImage.createImage(3, 1);
|
||||
image.setAt(0, 0, { red: 15, green: 225, blue: 135, alpha: 144 });
|
||||
image.setAt(1, 0, { red: 170, green: 80, blue: 210, alpha: 2 });
|
||||
image.setAt(2, 0, { red: 50, green: 66, blue: 23, alpha: 188 });
|
||||
break;
|
||||
}
|
||||
|
||||
return /** @type {PNGImage} */ (image);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Buffer} buf1
|
||||
* @param {Buffer} buf2
|
||||
* @returns boolean
|
||||
*/
|
||||
function compareBuffer(buf1, buf2) {
|
||||
|
||||
if (buf1.length !== buf2.length)
|
||||
return false;
|
||||
|
||||
for (let i = 0, len = buf1.length; i < len; i++) {
|
||||
if (buf1[i] !== buf2[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
it.describe('Blink-Diff', () => {
|
||||
|
||||
it.describe('Default values', () => {
|
||||
|
||||
/** @type {BlinkDiff} */
|
||||
let instance;
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance = new BlinkDiff({
|
||||
imageA: 'image-a' as any, imageAPath: 'path to image-a', imageB: 'image-b' as any, imageBPath: 'path to image-b',
|
||||
|
||||
composition: false
|
||||
});
|
||||
});
|
||||
|
||||
it('should have the right values for imageA', () => {
|
||||
expect(instance._imageA).toBe('image-a');
|
||||
});
|
||||
|
||||
it('should have the right values for imageAPath', () => {
|
||||
expect(instance._imageAPath).toBe('path to image-a');
|
||||
});
|
||||
|
||||
it('should have the right values for imageB', () => {
|
||||
expect(instance._imageB).toBe('image-b');
|
||||
});
|
||||
|
||||
it('should have the right values for imageBPath', () => {
|
||||
expect(instance._imageBPath).toBe('path to image-b');
|
||||
});
|
||||
|
||||
it('should not have a value for imageOutputPath', () => {
|
||||
expect(instance._imageOutputPath).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not have a value for thresholdType', () => {
|
||||
expect(instance._thresholdType).toBe('pixel');
|
||||
});
|
||||
|
||||
it('should not have a value for threshold', () => {
|
||||
expect(instance._threshold).toBe(500);
|
||||
});
|
||||
|
||||
it('should not have a value for delta', () => {
|
||||
expect(instance._delta).toBe(20);
|
||||
});
|
||||
|
||||
it('should not have a value for outputMaskRed', () => {
|
||||
expect(instance._outputMaskRed).toBe(255);
|
||||
});
|
||||
|
||||
it('should not have a value for outputMaskGreen', () => {
|
||||
expect(instance._outputMaskGreen).toBe(0);
|
||||
});
|
||||
|
||||
it('should not have a value for outputMaskBlue', () => {
|
||||
expect(instance._outputMaskBlue).toBe(0);
|
||||
});
|
||||
|
||||
it('should not have a value for outputMaskAlpha', () => {
|
||||
expect(instance._outputMaskAlpha).toBe(255);
|
||||
});
|
||||
|
||||
it('should not have a value for outputMaskOpacity', () => {
|
||||
expect(instance._outputMaskOpacity).toBe(0.7);
|
||||
});
|
||||
|
||||
it('should not have a value for outputBackgroundRed', () => {
|
||||
expect(instance._outputBackgroundRed).toBe(0);
|
||||
});
|
||||
|
||||
it('should not have a value for outputBackgroundGreen', () => {
|
||||
expect(instance._outputBackgroundGreen).toBe(0);
|
||||
});
|
||||
|
||||
it('should not have a value for outputBackgroundBlue', () => {
|
||||
expect(instance._outputBackgroundBlue).toBe(0);
|
||||
});
|
||||
|
||||
it('should not have a value for outputBackgroundAlpha', () => {
|
||||
expect(instance._outputBackgroundAlpha).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not have a value for outputBackgroundOpacity', () => {
|
||||
expect(instance._outputBackgroundOpacity).toBe(0.6);
|
||||
});
|
||||
|
||||
it('should not have a value for copyImageAToOutput', () => {
|
||||
expect(instance._copyImageAToOutput).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not have a value for copyImageBToOutput', () => {
|
||||
expect(instance._copyImageBToOutput).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not have a value for filter', () => {
|
||||
expect(instance._filter).toEqual([]);
|
||||
});
|
||||
|
||||
it('should not have a value for debug', () => {
|
||||
expect(instance._debug).toBeFalsy();
|
||||
});
|
||||
|
||||
it.describe('Special cases', () => {
|
||||
|
||||
/** @type {BlinkDiff} */
|
||||
let instance;
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance = new BlinkDiff({
|
||||
imageA: 'image-a' as any, imageB: 'image-b' as any
|
||||
});
|
||||
});
|
||||
|
||||
it('should have the images', () => {
|
||||
expect(instance._imageA).toBe('image-a');
|
||||
expect(instance._imageB).toBe('image-b');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Methods', () => {
|
||||
|
||||
/** @type {BlinkDiff} */
|
||||
let instance;
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance = new BlinkDiff({
|
||||
imageA: 'image-a' as any, imageAPath: 'path to image-a', imageB: 'image-b' as any, imageBPath: 'path to image-b'
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('hasPassed', () => {
|
||||
|
||||
it('should pass when identical', () => {
|
||||
expect(instance.hasPassed(BlinkDiff.RESULT_IDENTICAL)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should pass when similar', () => {
|
||||
expect(instance.hasPassed(BlinkDiff.RESULT_SIMILAR)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not pass when unknown', () => {
|
||||
expect(instance.hasPassed(BlinkDiff.RESULT_UNKNOWN)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should not pass when different', () => {
|
||||
expect(instance.hasPassed(BlinkDiff.RESULT_DIFFERENT)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_colorDelta', () => {
|
||||
it('should calculate the delta', () => {
|
||||
const color1 = {
|
||||
c1: 23, c2: 87, c3: 89, c4: 234
|
||||
}, color2 = {
|
||||
c1: 84, c2: 92, c3: 50, c4: 21
|
||||
};
|
||||
|
||||
expect(instance._colorDelta(color1, color2)).toBeGreaterThanOrEqual(225.02);
|
||||
expect(instance._colorDelta(color1, color2)).toBeLessThanOrEqual(225.03);
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_loadImage', () => {
|
||||
|
||||
/** @type {PNGImage} */
|
||||
let image;
|
||||
|
||||
it.beforeEach(() => {
|
||||
image = generateImage('medium-2');
|
||||
});
|
||||
|
||||
it.describe('from Image', () => {
|
||||
|
||||
it('should use already loaded image', () => {
|
||||
const result = instance._loadImageSync('pathToFile', image);
|
||||
|
||||
expect(result).toBeInstanceOf(PNGImage);
|
||||
expect(result).toBe(image);
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('from Path', () => {
|
||||
|
||||
it('should load image when only path given', async () => {
|
||||
const image = await instance._loadImageSync(path.join(__dirname, 'assets', 'test.png'));
|
||||
const compare = compareBuffer(image.getImage().data, image.getImage().data);
|
||||
expect(compare).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('from Buffer', () => {
|
||||
|
||||
/** @type {Buffer} */
|
||||
let buffer;
|
||||
|
||||
it.beforeEach(() => {
|
||||
buffer = fs.readFileSync(path.join(__dirname, 'assets', 'test.png'));
|
||||
});
|
||||
|
||||
it('should load image from buffer if given', async () => {
|
||||
const image = await instance._loadImageSync('pathToFile', buffer);
|
||||
const compare = compareBuffer(image.getImage().data, image.getImage().data);
|
||||
expect(compare).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_copyImage', () => {
|
||||
|
||||
it('should copy the image', () => {
|
||||
const image1 = generateImage('small-1');
|
||||
const image2 = generateImage('small-2');
|
||||
|
||||
instance._copyImage(image1, image2);
|
||||
|
||||
expect(image1.getAt(0, 0)).toBe(image2.getAt(0, 0));
|
||||
expect(image1.getAt(0, 1)).toBe(image2.getAt(0, 1));
|
||||
expect(image1.getAt(1, 0)).toBe(image2.getAt(1, 0));
|
||||
expect(image1.getAt(1, 1)).toBe(image2.getAt(1, 1));
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_correctDimensions', () => {
|
||||
|
||||
it.describe('Negative Values', () => {
|
||||
|
||||
it('should correct negative x values', () => {
|
||||
const rect = { x: -10, y: 23, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(0);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct negative y values', () => {
|
||||
const rect = { x: 10, y: -23, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct negative width values', () => {
|
||||
const rect = { x: 10, y: 23, width: -42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(0);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct negative height values', () => {
|
||||
const rect = { x: 10, y: 23, width: 42, height: -57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(0);
|
||||
});
|
||||
|
||||
it('should correct all negative values', () => {
|
||||
const rect = { x: -10, y: -23, width: -42, height: -57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(0);
|
||||
expect(rect.y).toBe(0);
|
||||
expect(rect.width).toBe(0);
|
||||
expect(rect.height).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Dimensions', () => {
|
||||
|
||||
it('should correct too big x values', () => {
|
||||
const rect = { x: 1000, y: 23, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(299);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(1);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct too big y values', () => {
|
||||
const rect = { x: 10, y: 2300, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(199);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(1);
|
||||
});
|
||||
|
||||
it('should correct too big width values', () => {
|
||||
const rect = { x: 11, y: 23, width: 4200, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(11);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(289);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct too big height values', () => {
|
||||
const rect = { x: 11, y: 23, width: 42, height: 5700 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(11);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(177);
|
||||
});
|
||||
|
||||
it('should correct too big width and height values', () => {
|
||||
const rect = { x: 11, y: 23, width: 420, height: 570 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(11);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(289);
|
||||
expect(rect.height).toBe(177);
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Border Dimensions', () => {
|
||||
|
||||
it('should correct too big x values', () => {
|
||||
const rect = { x: 300, y: 23, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(299);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(1);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct too big y values', () => {
|
||||
const rect = { x: 10, y: 200, width: 42, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(10);
|
||||
expect(rect.y).toBe(199);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(1);
|
||||
});
|
||||
|
||||
it('should correct too big width values', () => {
|
||||
const rect = { x: 11, y: 23, width: 289, height: 57 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(11);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(289);
|
||||
expect(rect.height).toBe(57);
|
||||
});
|
||||
|
||||
it('should correct too big height values', () => {
|
||||
const rect = { x: 11, y: 23, width: 42, height: 177 };
|
||||
|
||||
instance._correctDimensions(300, 200, rect);
|
||||
|
||||
expect(rect.x).toBe(11);
|
||||
expect(rect.y).toBe(23);
|
||||
expect(rect.width).toBe(42);
|
||||
expect(rect.height).toBe(177);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_crop', () => {
|
||||
|
||||
/** @type {PNGImage} */
|
||||
let croppedImage;
|
||||
/** @type {PNGImage} */
|
||||
let expectedImage;
|
||||
it.beforeEach(() => {
|
||||
croppedImage = generateImage('medium-1');
|
||||
expectedImage = generateImage('medium-1');
|
||||
});
|
||||
|
||||
it('should crop image', () => {
|
||||
instance._crop('Medium-1', croppedImage, { x: 1, y: 2, width: 2, height: 1 });
|
||||
|
||||
expect(croppedImage.getWidth()).toBe(2);
|
||||
expect(croppedImage.getHeight()).toBe(1);
|
||||
|
||||
expect(croppedImage.getAt(0, 0)).toBe(expectedImage.getAt(1, 2));
|
||||
expect(croppedImage.getAt(1, 0)).toBe(expectedImage.getAt(2, 2));
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_clip', () => {
|
||||
|
||||
it('should clip the image small and medium', () => {
|
||||
const image1 = generateImage('small-1'), image2 = generateImage('medium-2');
|
||||
|
||||
instance._clip(image1, image2);
|
||||
|
||||
expect(image1.getWidth()).toBe(image2.getWidth());
|
||||
expect(image1.getHeight()).toBe(image2.getHeight());
|
||||
});
|
||||
|
||||
it('should clip the image medium and small', () => {
|
||||
const image1 = generateImage('medium-1'), image2 = generateImage('small-2');
|
||||
|
||||
instance._clip(image1, image2);
|
||||
|
||||
expect(image1.getWidth()).toBe(image2.getWidth());
|
||||
expect(image1.getHeight()).toBe(image2.getHeight());
|
||||
});
|
||||
|
||||
it('should clip the image slim-1 and medium', () => {
|
||||
const image1 = generateImage('slim-1'), image2 = generateImage('medium-1');
|
||||
|
||||
instance._clip(image1, image2);
|
||||
|
||||
expect(image1.getWidth()).toBe(image2.getWidth());
|
||||
expect(image1.getHeight()).toBe(image2.getHeight());
|
||||
});
|
||||
|
||||
it('should clip the image slim-2 and medium', () => {
|
||||
const image1 = generateImage('slim-2'), image2 = generateImage('medium-1');
|
||||
|
||||
instance._clip(image1, image2);
|
||||
|
||||
expect(image1.getWidth()).toBe(image2.getWidth());
|
||||
expect(image1.getHeight()).toBe(image2.getHeight());
|
||||
});
|
||||
|
||||
it('should clip the image small and small', () => {
|
||||
const image1 = generateImage('small-2'), image2 = generateImage('small-1');
|
||||
|
||||
instance._clip(image1, image2);
|
||||
|
||||
expect(image1.getWidth()).toBe(image2.getWidth());
|
||||
expect(image1.getHeight()).toBe(image2.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('isAboveThreshold', () => {
|
||||
|
||||
it.describe('Pixel threshold', () => {
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL;
|
||||
instance._threshold = 50;
|
||||
});
|
||||
|
||||
it('should be below threshold', () => {
|
||||
expect(instance.isAboveThreshold(49)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should be above threshold on border', () => {
|
||||
expect(instance.isAboveThreshold(50)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be above threshold', () => {
|
||||
expect(instance.isAboveThreshold(51)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Percent threshold', () => {
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance._thresholdType = BlinkDiff.THRESHOLD_PERCENT;
|
||||
instance._threshold = 0.1;
|
||||
});
|
||||
|
||||
it('should be below threshold', () => {
|
||||
expect(instance.isAboveThreshold(9, 100)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should be above threshold on border', () => {
|
||||
expect(instance.isAboveThreshold(10, 100)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should be above threshold', () => {
|
||||
expect(instance.isAboveThreshold(11, 100)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Comparison', () => {
|
||||
/** @type {PNGImage} */
|
||||
let image1;
|
||||
/** @type {PNGImage} */
|
||||
let image2;
|
||||
/** @type {PNGImage} */
|
||||
let image3;
|
||||
/** @type {PNGImage} */
|
||||
let image4;
|
||||
/** @type {{ red: number, green: number, blue: number, alpha: number }} */
|
||||
let maskColor;
|
||||
/** @type {{ red: number, green: number, blue: number, alpha: number }} */
|
||||
let shiftColor;
|
||||
/** @type {{ red: number, green: number, blue: number, alpha: number }} */
|
||||
let backgroundMaskColor;
|
||||
|
||||
|
||||
it.beforeEach(() => {
|
||||
image1 = generateImage('small-1');
|
||||
image2 = generateImage('small-2');
|
||||
image3 = generateImage('small-3');
|
||||
image4 = generateImage('small-1');
|
||||
maskColor = {
|
||||
red: 123, green: 124, blue: 125, alpha: 126
|
||||
};
|
||||
shiftColor = {
|
||||
red: 200, green: 100, blue: 0, alpha: 113
|
||||
};
|
||||
backgroundMaskColor = {
|
||||
red: 31, green: 33, blue: 35, alpha: 37
|
||||
};
|
||||
});
|
||||
|
||||
it.describe('_pixelCompare', () => {
|
||||
|
||||
it('should have no differences with a zero dimension', () => {
|
||||
const deltaThreshold = 10, width = 0, height = 0, hShift = 0, vShift = 0;
|
||||
const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toBe(0);
|
||||
});
|
||||
|
||||
it('should have all differences', () => {
|
||||
const deltaThreshold = 10, width = 2, height = 2, hShift = 0, vShift = 0;
|
||||
const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toBe(4);
|
||||
});
|
||||
|
||||
it('should have some differences', () => {
|
||||
const deltaThreshold = 100, width = 2, height = 2, hShift = 0, vShift = 0;
|
||||
const result = instance._pixelCompare(image1, image2, image3, deltaThreshold, width, height, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('_compare', () => {
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL;
|
||||
instance._threshold = 3;
|
||||
});
|
||||
|
||||
it('should be different', () => {
|
||||
const deltaThreshold = 10, hShift = 0, vShift = 0;
|
||||
const result = instance._compare(image1, image2, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toEqual({
|
||||
code: BlinkDiff.RESULT_DIFFERENT, differences: 4, dimension: 4, width: 2, height: 2
|
||||
});
|
||||
});
|
||||
|
||||
it('should be similar', () => {
|
||||
const deltaThreshold = 100, hShift = 0, vShift = 0;
|
||||
const result = instance._compare(image1, image2, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toEqual({
|
||||
code: BlinkDiff.RESULT_SIMILAR, differences: 2, dimension: 4, width: 2, height: 2
|
||||
});
|
||||
});
|
||||
|
||||
it('should be identical', () => {
|
||||
const deltaThreshold = 10, hShift = 0, vShift = 0;
|
||||
const result = instance._compare(image1, image4, image3, deltaThreshold, maskColor, shiftColor, backgroundMaskColor, hShift, vShift);
|
||||
|
||||
expect(result).toEqual({
|
||||
code: BlinkDiff.RESULT_IDENTICAL, differences: 0, dimension: 4, width: 2, height: 2
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Run', () => {
|
||||
|
||||
it.beforeEach(() => {
|
||||
instance._imageA = generateImage('small-1');
|
||||
instance._imageB = generateImage('medium-1');
|
||||
|
||||
instance._thresholdType = BlinkDiff.THRESHOLD_PIXEL;
|
||||
instance._threshold = 3;
|
||||
|
||||
instance._composition = false;
|
||||
});
|
||||
|
||||
it('should crop image-a', async () => {
|
||||
instance._cropImageA = { width: 1, height: 2 };
|
||||
console.log('A');
|
||||
const result = instance.runSync();
|
||||
expect(result.dimension).toBe(2);
|
||||
});
|
||||
|
||||
it('should crop image-b', async () => {
|
||||
instance._cropImageB = { width: 1, height: 1 };
|
||||
const result = instance.runSync();
|
||||
expect(result.dimension).toBe(1);
|
||||
});
|
||||
|
||||
it('should clip image-b', async () => {
|
||||
const result = instance.runSync();
|
||||
expect(result.dimension).toBe(4);
|
||||
});
|
||||
|
||||
it('should crop and clip images', async () => {
|
||||
instance._cropImageA = { width: 1, height: 2 };
|
||||
instance._cropImageB = { width: 1, height: 1 };
|
||||
const result = instance.runSync();
|
||||
expect(result.dimension).toBe(1);
|
||||
});
|
||||
|
||||
it('should write output file', async ({}, testInfo) => {
|
||||
instance._imageOutputPath = testInfo.outputPath('tmp.png');
|
||||
instance.runSync();
|
||||
if (!fs.existsSync(testInfo.outputPath('tmp.png')))
|
||||
throw new Error('Could not write file.');
|
||||
});
|
||||
|
||||
it('should compare image-a to image-b', async () => {
|
||||
const result = instance.runSync();
|
||||
expect(result.code).toBe(BlinkDiff.RESULT_DIFFERENT);
|
||||
});
|
||||
|
||||
it('should be black', async () => {
|
||||
instance._delta = 1000;
|
||||
instance._copyImageAToOutput = false;
|
||||
instance._copyImageBToOutput = false;
|
||||
instance._outputBackgroundRed = 0;
|
||||
instance._outputBackgroundGreen = 0;
|
||||
instance._outputBackgroundBlue = 0;
|
||||
instance._outputBackgroundAlpha = 0;
|
||||
instance._outputBackgroundOpacity = undefined;
|
||||
|
||||
instance.runSync();
|
||||
expect(instance._imageOutput.getAt(0, 0)).toBe(0);
|
||||
});
|
||||
|
||||
it('should copy image-a to output by default', async () => {
|
||||
instance._delta = 1000;
|
||||
instance._outputBackgroundRed = undefined;
|
||||
instance._outputBackgroundGreen = undefined;
|
||||
instance._outputBackgroundBlue = undefined;
|
||||
instance._outputBackgroundAlpha = undefined;
|
||||
instance._outputBackgroundOpacity = undefined;
|
||||
|
||||
instance.runSync();
|
||||
expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageA.getAt(0, 0));
|
||||
});
|
||||
|
||||
it('should copy image-a to output', async () => {
|
||||
instance._delta = 1000;
|
||||
instance._copyImageAToOutput = true;
|
||||
instance._copyImageBToOutput = false;
|
||||
instance._outputBackgroundRed = undefined;
|
||||
instance._outputBackgroundGreen = undefined;
|
||||
instance._outputBackgroundBlue = undefined;
|
||||
instance._outputBackgroundAlpha = undefined;
|
||||
instance._outputBackgroundOpacity = undefined;
|
||||
|
||||
instance.runSync();
|
||||
expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageA.getAt(0, 0));
|
||||
});
|
||||
|
||||
it('should copy image-b to output', async () => {
|
||||
instance._delta = 1000;
|
||||
instance._copyImageAToOutput = false;
|
||||
instance._copyImageBToOutput = true;
|
||||
instance._outputBackgroundRed = undefined;
|
||||
instance._outputBackgroundGreen = undefined;
|
||||
instance._outputBackgroundBlue = undefined;
|
||||
instance._outputBackgroundAlpha = undefined;
|
||||
instance._outputBackgroundOpacity = undefined;
|
||||
|
||||
instance.runSync();
|
||||
expect(instance._imageOutput.getAt(0, 0)).toBe(instance._imageB.getAt(0, 0));
|
||||
});
|
||||
});
|
||||
|
||||
it.describe('Color-Conversion', () => {
|
||||
|
||||
it('should convert RGB to XYZ', () => {
|
||||
const color = /** @type {any} */ (instance._convertRgbToXyz({ c1: 92 / 255, c2: 255 / 255, c3: 162 / 255, c4: 1 }));
|
||||
|
||||
expect(color.c1).toBeCloseTo(0.6144431682352941, 0.0001);
|
||||
expect(color.c2).toBeCloseTo(0.8834245847058824, 0.0001);
|
||||
expect(color.c3).toBeCloseTo(0.6390158682352941, 0.0001);
|
||||
expect(color.c4).toBeCloseTo(1, 0.0001);
|
||||
});
|
||||
|
||||
it('should convert Xyz to CIELab', () => {
|
||||
const color = /** @type {any} */ (instance._convertXyzToCieLab({
|
||||
c1: 0.6144431682352941, c2: 0.8834245847058824, c3: 0.6390158682352941, c4: 1
|
||||
}));
|
||||
|
||||
expect(color.c1).toBeCloseTo(95.30495102757038, 0.0001);
|
||||
expect(color.c2).toBeCloseTo(-54.68933740774734, 0.0001);
|
||||
expect(color.c3).toBeCloseTo(19.63870174748623, 0.0001);
|
||||
expect(color.c4).toBeCloseTo(1, 0.0001);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -421,6 +421,7 @@ test('should compare different PNG images', async ({ runInlineTest }, testInfo)
|
|||
});
|
||||
|
||||
test('should respect threshold', async ({ runInlineTest }) => {
|
||||
test.skip(!!process.env.PW_USE_BLINK_DIFF);
|
||||
const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
|
||||
const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
|
||||
const result = await runInlineTest({
|
||||
|
|
@ -441,6 +442,7 @@ test('should respect threshold', async ({ runInlineTest }) => {
|
|||
});
|
||||
|
||||
test('should respect project threshold', async ({ runInlineTest }) => {
|
||||
test.skip(!!process.env.PW_USE_BLINK_DIFF);
|
||||
const expected = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-expected.png'));
|
||||
const actual = fs.readFileSync(path.join(__dirname, 'assets/screenshot-canvas-actual.png'));
|
||||
const result = await runInlineTest({
|
||||
|
|
|
|||
Loading…
Reference in a new issue