comparadise-utils 0.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/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/src/files.d.ts +1 -0
- package/dist/src/files.js +12 -0
- package/dist/src/files.js.map +1 -0
- package/dist/src/images.d.ts +5 -0
- package/dist/src/images.js +83 -0
- package/dist/src/images.js.map +1 -0
- package/dist/src/match-screenshot.d.ts +6 -0
- package/dist/src/match-screenshot.js +73 -0
- package/dist/src/match-screenshot.js.map +1 -0
- package/dist/src/on-after-screenshot.d.ts +2 -0
- package/dist/src/on-after-screenshot.js +62 -0
- package/dist/src/on-after-screenshot.js.map +1 -0
- package/dist/src/screenshots.d.ts +3 -0
- package/dist/src/screenshots.js +73 -0
- package/dist/src/screenshots.js.map +1 -0
- package/dist/src/setup-visual-tests.d.ts +2 -0
- package/dist/src/setup-visual-tests.js +20 -0
- package/dist/src/setup-visual-tests.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MatchScreenshotArgs } from './src/match-screenshot';
|
|
2
|
+
export { setupVisualTests } from './src/setup-visual-tests';
|
|
3
|
+
export {};
|
|
4
|
+
declare global {
|
|
5
|
+
namespace Cypress {
|
|
6
|
+
interface Chainable {
|
|
7
|
+
matchScreenshot(args?: MatchScreenshotArgs): Chainable;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupVisualTests = void 0;
|
|
4
|
+
const match_screenshot_1 = require("./src/match-screenshot");
|
|
5
|
+
var setup_visual_tests_1 = require("./src/setup-visual-tests");
|
|
6
|
+
Object.defineProperty(exports, "setupVisualTests", { enumerable: true, get: function () { return setup_visual_tests_1.setupVisualTests; } });
|
|
7
|
+
Cypress.Commands.add('matchScreenshot', { prevSubject: ['optional', 'element', 'window', 'document'] }, match_screenshot_1.matchScreenshot);
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;AAAA,6DAA8E;AAC9E,+DAA4D;AAAnD,sHAAA,gBAAgB,OAAA;AAWzB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,iBAAiB,EAAE,EAAE,WAAW,EAAE,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,EAAE,kCAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createImageFileName(path: string, type: string): string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createImageFileName = void 0;
|
|
4
|
+
function createImageFileName(path, type) {
|
|
5
|
+
let newPath = path;
|
|
6
|
+
if (path.startsWith('/')) {
|
|
7
|
+
newPath = `.${path}`;
|
|
8
|
+
}
|
|
9
|
+
return `${newPath}/${type}.png`;
|
|
10
|
+
}
|
|
11
|
+
exports.createImageFileName = createImageFileName;
|
|
12
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/files.ts"],"names":[],"mappings":";;;AAAA,SAAgB,mBAAmB,CAAC,IAAY,EAAE,IAAY;IAC5D,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;QACxB,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;KACtB;IACD,OAAO,GAAG,OAAO,IAAI,IAAI,MAAM,CAAC;AAClC,CAAC;AAND,kDAMC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.getDiffPixels = void 0;
|
|
30
|
+
const fs = __importStar(require("fs"));
|
|
31
|
+
const pngjs_1 = require("pngjs");
|
|
32
|
+
const pixelmatch_1 = __importDefault(require("pixelmatch"));
|
|
33
|
+
const PIXELMATCH_OPTIONS = {
|
|
34
|
+
threshold: 0.3
|
|
35
|
+
};
|
|
36
|
+
const createImageResizer = (width, height) => (source) => {
|
|
37
|
+
const resized = new pngjs_1.PNG({ width, height, fill: true });
|
|
38
|
+
pngjs_1.PNG.bitblt(source, resized, 0, 0, source.width, source.height, 0, 0);
|
|
39
|
+
return resized;
|
|
40
|
+
};
|
|
41
|
+
const fillSizeDifference = (width, height) => (image) => {
|
|
42
|
+
const inArea = (x, y) => y > height || x > width;
|
|
43
|
+
for (let y = 0; y < image.height; y++) {
|
|
44
|
+
for (let x = 0; x < image.width; x++) {
|
|
45
|
+
if (inArea(x, y)) {
|
|
46
|
+
const idx = (image.width * y + x) << 2;
|
|
47
|
+
image.data[idx] = 0;
|
|
48
|
+
image.data[idx + 1] = 0;
|
|
49
|
+
image.data[idx + 2] = 0;
|
|
50
|
+
image.data[idx + 3] = 64;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return image;
|
|
55
|
+
};
|
|
56
|
+
function alignImagesToSameSize(firstImage, secondImage) {
|
|
57
|
+
// Keep original sizes to fill extended area later
|
|
58
|
+
const firstImageWidth = firstImage.width;
|
|
59
|
+
const firstImageHeight = firstImage.height;
|
|
60
|
+
const secondImageWidth = secondImage.width;
|
|
61
|
+
const secondImageHeight = secondImage.height;
|
|
62
|
+
// Calculate biggest common values
|
|
63
|
+
const resizeToSameSize = createImageResizer(Math.max(firstImageWidth, secondImageWidth), Math.max(firstImageHeight, secondImageHeight));
|
|
64
|
+
// Resize both images
|
|
65
|
+
const resizedFirst = resizeToSameSize(firstImage);
|
|
66
|
+
const resizedSecond = resizeToSameSize(secondImage);
|
|
67
|
+
// Fill resized area with black transparent pixels
|
|
68
|
+
return [
|
|
69
|
+
fillSizeDifference(firstImageWidth, firstImageHeight)(resizedFirst),
|
|
70
|
+
fillSizeDifference(secondImageWidth, secondImageHeight)(resizedSecond)
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
function getDiffPixels(basePath, actualPath) {
|
|
74
|
+
const rawBase = pngjs_1.PNG.sync.read(fs.readFileSync(basePath));
|
|
75
|
+
const rawActual = pngjs_1.PNG.sync.read(fs.readFileSync(actualPath));
|
|
76
|
+
const hasSizeMismatch = rawBase.height !== rawActual.height || rawBase.width !== rawActual.width;
|
|
77
|
+
const [base, actual] = hasSizeMismatch ? alignImagesToSameSize(rawBase, rawActual) : [rawBase, rawActual];
|
|
78
|
+
const diff = new pngjs_1.PNG({ width: base.width, height: base.height });
|
|
79
|
+
const diffPixels = (0, pixelmatch_1.default)(actual.data, base.data, diff.data, diff.width, diff.height, PIXELMATCH_OPTIONS);
|
|
80
|
+
return { diffPixels, diff };
|
|
81
|
+
}
|
|
82
|
+
exports.getDiffPixels = getDiffPixels;
|
|
83
|
+
//# sourceMappingURL=images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/images.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
export type MatchScreenshotArgs = {
|
|
3
|
+
rawName?: string;
|
|
4
|
+
options?: Partial<Cypress.ScreenshotOptions>;
|
|
5
|
+
};
|
|
6
|
+
export declare function matchScreenshot(subject: Cypress.JQueryWithSelector | Window | Document | void, args?: MatchScreenshotArgs): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchScreenshot = void 0;
|
|
4
|
+
const PREFIX_DIFFERENTIATOR = '___';
|
|
5
|
+
const SUFFIX_TEST_IDENTIFIER = '.spec.ts';
|
|
6
|
+
const SCREENSHOTS_FOLDER_NAME = 'screenshots';
|
|
7
|
+
function getTestFolderPathFromScripts(rawName) {
|
|
8
|
+
const relativeTestPath = Cypress.spec.relative;
|
|
9
|
+
if (!relativeTestPath) {
|
|
10
|
+
throw new Error('❌ Could not find matching script in the Cypress DOM to infer the test folder path');
|
|
11
|
+
}
|
|
12
|
+
const testName = relativeTestPath.substring(relativeTestPath.lastIndexOf('/') + 1, relativeTestPath.lastIndexOf(SUFFIX_TEST_IDENTIFIER));
|
|
13
|
+
const name = rawName || testName;
|
|
14
|
+
const screenshotsFolder = `${SCREENSHOTS_FOLDER_NAME}/${relativeTestPath.substring(0, relativeTestPath.lastIndexOf(testName))}${name}`;
|
|
15
|
+
return {
|
|
16
|
+
name,
|
|
17
|
+
screenshotsFolder
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function verifyImages() {
|
|
21
|
+
if (Cypress.$('img:visible').length > 0) {
|
|
22
|
+
cy.document()
|
|
23
|
+
.its('body')
|
|
24
|
+
.find('img')
|
|
25
|
+
.filter(':visible')
|
|
26
|
+
.then(images => {
|
|
27
|
+
if (images) {
|
|
28
|
+
cy.wrap(images).each($img => {
|
|
29
|
+
cy.wrap($img).should('exist').and('have.prop', 'naturalWidth');
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function forceFont() {
|
|
36
|
+
const iframe = window.parent.document.querySelector('iframe');
|
|
37
|
+
const contentDocument = iframe?.contentDocument;
|
|
38
|
+
if (contentDocument) {
|
|
39
|
+
const style = contentDocument.createElement('style');
|
|
40
|
+
style.type = 'text/css';
|
|
41
|
+
style.appendChild(contentDocument.createTextNode('* { font-family: Arial !important; }'));
|
|
42
|
+
contentDocument.head.appendChild(style);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function matchScreenshot(subject, args) {
|
|
46
|
+
const { rawName, options = {} } = args ?? {};
|
|
47
|
+
forceFont();
|
|
48
|
+
verifyImages();
|
|
49
|
+
const { name, screenshotsFolder } = getTestFolderPathFromScripts(rawName);
|
|
50
|
+
cy.task('baseExists', screenshotsFolder).then(hasBase => {
|
|
51
|
+
const type = 'new';
|
|
52
|
+
const target = subject ? cy.wrap(subject) : cy;
|
|
53
|
+
// For easy slicing of path ignoring the root screenshot folder
|
|
54
|
+
target.screenshot(`${PREFIX_DIFFERENTIATOR}${screenshotsFolder}/${type}`, options);
|
|
55
|
+
if (!hasBase) {
|
|
56
|
+
cy.task('createNewScreenshot', screenshotsFolder).then(() => {
|
|
57
|
+
cy.task('log', `❌A new base image was created for ${name}. Create this as a new base image via Comparadise!`);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
cy.task('compareScreenshots', screenshotsFolder).then(diffPixels => {
|
|
61
|
+
if (diffPixels === 0) {
|
|
62
|
+
cy.log(`✅Actual image of ${name} was the same as base`);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const screenshotUrl = Cypress.env('BUILD_URL') ? `${Cypress.env('BUILD_URL')}artifact/${screenshotsFolder}` : screenshotsFolder;
|
|
66
|
+
throw new Error(`❌Actual image of ${name} differed by ${diffPixels} pixels.
|
|
67
|
+
See the diff image for more details >> ${screenshotUrl}/diff.png`);
|
|
68
|
+
});
|
|
69
|
+
return null;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
exports.matchScreenshot = matchScreenshot;
|
|
73
|
+
//# sourceMappingURL=match-screenshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match-screenshot.js","sourceRoot":"","sources":["../../src/match-screenshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.onAfterScreenshot = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
function onAfterScreenshot(details) {
|
|
29
|
+
console.log('🧸 Screenshot was saved to:', details.path);
|
|
30
|
+
if (!details.specName.includes('visual')) {
|
|
31
|
+
return Promise.resolve({});
|
|
32
|
+
}
|
|
33
|
+
const getNewPath = (path) => {
|
|
34
|
+
let newPath = path.slice(path.lastIndexOf('___') + 3);
|
|
35
|
+
console.log(newPath);
|
|
36
|
+
if (newPath.startsWith('/')) {
|
|
37
|
+
newPath = `.${newPath}`;
|
|
38
|
+
}
|
|
39
|
+
return newPath;
|
|
40
|
+
};
|
|
41
|
+
const newPath = getNewPath(details.path);
|
|
42
|
+
const newPathDir = newPath.substring(0, newPath.lastIndexOf('/'));
|
|
43
|
+
try {
|
|
44
|
+
fs.mkdirSync(newPathDir, { recursive: true });
|
|
45
|
+
console.log('🧸 No screenshot folder found in the package. Created new screenshot folder:', newPathDir);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
console.error('❌ Error creating new screenshot folder:', newPathDir, err);
|
|
49
|
+
}
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
fs.rename(details.path, newPath, err => {
|
|
52
|
+
if (err) {
|
|
53
|
+
reject(err);
|
|
54
|
+
}
|
|
55
|
+
// because we renamed/moved the image, resolve with the new path
|
|
56
|
+
// so it is accurate in the test results
|
|
57
|
+
resolve({ path: newPath });
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
exports.onAfterScreenshot = onAfterScreenshot;
|
|
62
|
+
//# sourceMappingURL=on-after-screenshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"on-after-screenshot.js","sourceRoot":"","sources":["../../src/on-after-screenshot.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AAEzB,SAAgB,iBAAiB,CAAC,OAAkC;IAClE,OAAO,CAAC,GAAG,CAAC,6BAA6B,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QACxC,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;KAC5B;IAED,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,EAAE;QAClC,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAErB,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YAC3B,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;SACzB;QAED,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IAElE,IAAI;QACF,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,8EAA8E,EAAE,UAAU,CAAC,CAAC;KACzG;IAAC,OAAO,GAAG,EAAE;QACZ,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;KAC3E;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE;YACrC,IAAI,GAAG,EAAE;gBACP,MAAM,CAAC,GAAG,CAAC,CAAC;aACb;YAED,gEAAgE;YAChE,wCAAwC;YACxC,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAtCD,8CAsCC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.compareScreenshots = exports.createNewScreenshot = exports.baseExists = void 0;
|
|
27
|
+
const fs = __importStar(require("fs"));
|
|
28
|
+
const pngjs_1 = require("pngjs");
|
|
29
|
+
const images_1 = require("./images");
|
|
30
|
+
const files_1 = require("./files");
|
|
31
|
+
function baseExists(path) {
|
|
32
|
+
const fileName = (0, files_1.createImageFileName)(path, 'base');
|
|
33
|
+
const exists = fs.existsSync(fileName);
|
|
34
|
+
if (!exists) {
|
|
35
|
+
console.log('Base image does not exist. This means a new one will be created. If your base should exist, something went wrong.');
|
|
36
|
+
}
|
|
37
|
+
return exists;
|
|
38
|
+
}
|
|
39
|
+
exports.baseExists = baseExists;
|
|
40
|
+
function createNewScreenshot(screenshotFolder) {
|
|
41
|
+
const newImage = pngjs_1.PNG.sync.read(fs.readFileSync((0, files_1.createImageFileName)(screenshotFolder, 'new')));
|
|
42
|
+
fs.writeFile((0, files_1.createImageFileName)(screenshotFolder, 'new'), pngjs_1.PNG.sync.write(newImage), err => {
|
|
43
|
+
if (err) {
|
|
44
|
+
console.error('❌Unable to create new.png', err);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
exports.createNewScreenshot = createNewScreenshot;
|
|
50
|
+
function compareScreenshots(screenshotFolder) {
|
|
51
|
+
const basePath = (0, files_1.createImageFileName)(screenshotFolder, 'base');
|
|
52
|
+
const actualPath = (0, files_1.createImageFileName)(screenshotFolder, 'new');
|
|
53
|
+
const { diffPixels, diff } = (0, images_1.getDiffPixels)(basePath, actualPath);
|
|
54
|
+
if (diffPixels) {
|
|
55
|
+
// Create diff.png next to base and new for review
|
|
56
|
+
fs.writeFile((0, files_1.createImageFileName)(screenshotFolder, 'diff'), pngjs_1.PNG.sync.write(diff), err => {
|
|
57
|
+
if (err) {
|
|
58
|
+
console.error('❌Diff exists but unable to create diff.png', err);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Delete created new.png. Not needed if there's no diff
|
|
64
|
+
fs.unlink(actualPath, err => {
|
|
65
|
+
if (err) {
|
|
66
|
+
console.error('❌No diff but unable to deleteactualPath}', err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return diffPixels;
|
|
71
|
+
}
|
|
72
|
+
exports.compareScreenshots = compareScreenshots;
|
|
73
|
+
//# sourceMappingURL=screenshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshots.js","sourceRoot":"","sources":["../../src/screenshots.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAyB;AACzB,iCAA4B;AAC5B,qCAAyC;AACzC,mCAA8C;AAE9C,SAAgB,UAAU,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,IAAA,2BAAmB,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEvC,IAAI,CAAC,MAAM,EAAE;QACX,OAAO,CAAC,GAAG,CAAC,mHAAmH,CAAC,CAAC;KAClI;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AARD,gCAQC;AAED,SAAgB,mBAAmB,CAAC,gBAAwB;IAC1D,MAAM,QAAQ,GAAG,WAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,IAAA,2BAAmB,EAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC9F,EAAE,CAAC,SAAS,CAAC,IAAA,2BAAmB,EAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,WAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,EAAE;QACzF,IAAI,GAAG,EAAE;YACP,OAAO,CAAC,KAAK,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAC;SACjD;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AACd,CAAC;AATD,kDASC;AAED,SAAgB,kBAAkB,CAAC,gBAAwB;IACzD,MAAM,QAAQ,GAAG,IAAA,2BAAmB,EAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;IAC/D,MAAM,UAAU,GAAG,IAAA,2BAAmB,EAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;IAChE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,IAAA,sBAAa,EAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAEjE,IAAI,UAAU,EAAE;QACd,kDAAkD;QAClD,EAAE,CAAC,SAAS,CAAC,IAAA,2BAAmB,EAAC,gBAAgB,EAAE,MAAM,CAAC,EAAE,WAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE;YACtF,IAAI,GAAG,EAAE;gBACP,OAAO,CAAC,KAAK,CAAC,4CAA4C,EAAE,GAAG,CAAC,CAAC;aAClE;QACH,CAAC,CAAC,CAAC;KACJ;SAAM;QACL,wDAAwD;QACxD,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE;YAC1B,IAAI,GAAG,EAAE;gBACP,OAAO,CAAC,KAAK,CAAC,0CAA0C,EAAE,GAAG,CAAC,CAAC;aAChE;QACH,CAAC,CAAC,CAAC;KACJ;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAtBD,gDAsBC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupVisualTests = void 0;
|
|
4
|
+
const on_after_screenshot_1 = require("./on-after-screenshot");
|
|
5
|
+
const screenshots_1 = require("./screenshots");
|
|
6
|
+
function setupVisualTests(on, config) {
|
|
7
|
+
on('after:screenshot', on_after_screenshot_1.onAfterScreenshot);
|
|
8
|
+
on('task', {
|
|
9
|
+
baseExists: screenshots_1.baseExists,
|
|
10
|
+
compareScreenshots: screenshots_1.compareScreenshots,
|
|
11
|
+
createNewScreenshot: screenshots_1.createNewScreenshot,
|
|
12
|
+
log: (message) => {
|
|
13
|
+
console.log(message);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
exports.setupVisualTests = setupVisualTests;
|
|
20
|
+
//# sourceMappingURL=setup-visual-tests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-visual-tests.js","sourceRoot":"","sources":["../../src/setup-visual-tests.ts"],"names":[],"mappings":";;;AAAA,+DAA0D;AAC1D,+CAAoF;AAEpF,SAAgB,gBAAgB,CAAC,EAAwB,EAAE,MAAmC;IAC5F,EAAE,CAAC,kBAAkB,EAAE,uCAAiB,CAAC,CAAC;IAC1C,EAAE,CAAC,MAAM,EAAE;QACT,UAAU,EAAV,wBAAU;QACV,kBAAkB,EAAlB,gCAAkB;QAClB,mBAAmB,EAAnB,iCAAmB;QACnB,GAAG,EAAE,CAAC,OAAe,EAAE,EAAE;YACvB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC;AAbD,4CAaC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "comparadise-utils",
|
|
3
|
+
"main": "dist/index.js",
|
|
4
|
+
"types": "dist/index.d.ts",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/ExpediaGroup/comparadise.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/ExpediaGroup/comparadise/issues"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"pixelmatch": "5.3.0",
|
|
18
|
+
"pngjs": "7.0.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"cypress": ">=12"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/pixelmatch": "5.2.4",
|
|
25
|
+
"@types/pngjs": "6.0.1"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc"
|
|
29
|
+
},
|
|
30
|
+
"version": "0.0.1"
|
|
31
|
+
}
|