comparadise-utils 1.15.2 → 1.16.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.
@@ -0,0 +1,12 @@
1
+ import { MatchScreenshotArgs } from './match-screenshot.js';
2
+
3
+ declare function setupVisualTests(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions;
4
+ declare global {
5
+ namespace Cypress {
6
+ interface Chainable {
7
+ matchScreenshot(args?: MatchScreenshotArgs): Chainable;
8
+ }
9
+ }
10
+ }
11
+
12
+ export { setupVisualTests };
package/dist/index.js CHANGED
@@ -1,17 +1,203 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setupVisualTests = void 0;
4
- const screenshots_1 = require("./screenshots");
5
- function setupVisualTests(on, config) {
6
- on('after:screenshot', screenshots_1.onAfterScreenshot);
7
- on('task', {
8
- baseExists: screenshots_1.baseExists,
9
- compareScreenshots: screenshots_1.compareScreenshots,
10
- log: (message) => {
11
- console.log(message);
12
- return null;
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // index.ts
31
+ var comparadise_utils_exports = {};
32
+ __export(comparadise_utils_exports, {
33
+ setupVisualTests: () => setupVisualTests
34
+ });
35
+ module.exports = __toCommonJS(comparadise_utils_exports);
36
+
37
+ // screenshots.ts
38
+ var fs2 = __toESM(require("fs"));
39
+ var import_pngjs2 = require("pngjs");
40
+
41
+ // images.ts
42
+ var fs = __toESM(require("fs"));
43
+ var import_pngjs = require("pngjs");
44
+ var import_pixelmatch = __toESM(require("pixelmatch"));
45
+ var PIXELMATCH_OPTIONS = {
46
+ threshold: 0.3
47
+ };
48
+ var createImageResizer = (width, height) => (source) => {
49
+ const resized = new import_pngjs.PNG({ width, height, fill: true });
50
+ import_pngjs.PNG.bitblt(source, resized, 0, 0, source.width, source.height, 0, 0);
51
+ return resized;
52
+ };
53
+ var fillSizeDifference = (width, height) => (image) => {
54
+ const inArea = (x, y) => y > height || x > width;
55
+ for (let y = 0; y < image.height; y++) {
56
+ for (let x = 0; x < image.width; x++) {
57
+ if (inArea(x, y)) {
58
+ const idx = image.width * y + x << 2;
59
+ image.data[idx] = 0;
60
+ image.data[idx + 1] = 0;
61
+ image.data[idx + 2] = 0;
62
+ image.data[idx + 3] = 64;
63
+ }
64
+ }
65
+ }
66
+ return image;
67
+ };
68
+ function alignImagesToSameSize(firstImage, secondImage) {
69
+ const firstImageWidth = firstImage.width;
70
+ const firstImageHeight = firstImage.height;
71
+ const secondImageWidth = secondImage.width;
72
+ const secondImageHeight = secondImage.height;
73
+ const resizeToSameSize = createImageResizer(
74
+ Math.max(firstImageWidth, secondImageWidth),
75
+ Math.max(firstImageHeight, secondImageHeight)
76
+ );
77
+ const resizedFirst = resizeToSameSize(firstImage);
78
+ const resizedSecond = resizeToSameSize(secondImage);
79
+ return [
80
+ fillSizeDifference(firstImageWidth, firstImageHeight)(resizedFirst),
81
+ fillSizeDifference(secondImageWidth, secondImageHeight)(resizedSecond)
82
+ ];
83
+ }
84
+ function getDiffPixels(basePath, actualPath) {
85
+ const rawBase = import_pngjs.PNG.sync.read(fs.readFileSync(basePath));
86
+ const rawActual = import_pngjs.PNG.sync.read(fs.readFileSync(actualPath));
87
+ const hasSizeMismatch = rawBase.height !== rawActual.height || rawBase.width !== rawActual.width;
88
+ const [base, actual] = hasSizeMismatch ? alignImagesToSameSize(rawBase, rawActual) : [rawBase, rawActual];
89
+ const diff = new import_pngjs.PNG({ width: base.width, height: base.height });
90
+ const diffPixels = (0, import_pixelmatch.default)(
91
+ actual.data,
92
+ base.data,
93
+ diff.data,
94
+ diff.width,
95
+ diff.height,
96
+ PIXELMATCH_OPTIONS
97
+ );
98
+ return { diffPixels, diff };
99
+ }
100
+
101
+ // files.ts
102
+ function createImageFileName(path, type) {
103
+ let newPath = path;
104
+ if (path.startsWith("/")) {
105
+ newPath = `.${path}`;
106
+ }
107
+ return `${newPath}/${type}.png`;
108
+ }
109
+
110
+ // screenshots.ts
111
+ function baseExists(path) {
112
+ const fileName = createImageFileName(path, "base");
113
+ const exists = fs2.existsSync(fileName);
114
+ if (!exists) {
115
+ console.log(
116
+ "Base image does not exist. This means a new one will be created. If your base should exist, something went wrong."
117
+ );
118
+ }
119
+ return exists;
120
+ }
121
+ function compareScreenshots(screenshotFolder) {
122
+ const basePath = createImageFileName(screenshotFolder, "base");
123
+ const actualPath = createImageFileName(screenshotFolder, "new");
124
+ const { diffPixels, diff } = getDiffPixels(basePath, actualPath);
125
+ if (diffPixels) {
126
+ fs2.writeFile(
127
+ createImageFileName(screenshotFolder, "diff"),
128
+ import_pngjs2.PNG.sync.write(diff),
129
+ (err) => {
130
+ if (err) {
131
+ console.error("\u274C Diff exists but unable to create diff.png", err);
13
132
  }
133
+ }
134
+ );
135
+ } else {
136
+ fs2.unlink(actualPath, (err) => {
137
+ if (err) {
138
+ console.error("\u274C No diff but unable to delete actualPath}", err);
139
+ }
14
140
  });
15
- return config;
141
+ }
142
+ return diffPixels;
143
+ }
144
+ var trimPostfix = (path) => {
145
+ const imgExt = ".png";
146
+ const attemptPostfixRegex = new RegExp(`\\s\\(attempt \\d+\\)${imgExt}$`);
147
+ if (attemptPostfixRegex.test(path)) {
148
+ return path.replace(attemptPostfixRegex, imgExt);
149
+ }
150
+ return path;
151
+ };
152
+ var getNewPath = (path) => {
153
+ let newPath;
154
+ if (path.includes("___")) {
155
+ newPath = path.slice(path.lastIndexOf("___") + 3);
156
+ if (newPath.startsWith("/")) {
157
+ newPath = `.${newPath}`;
158
+ }
159
+ console.log(newPath);
160
+ } else {
161
+ newPath = path;
162
+ }
163
+ return trimPostfix(newPath);
164
+ };
165
+ function onAfterScreenshot(details) {
166
+ console.log("\u{1F9F8} Screenshot was saved to:", details.path);
167
+ if (!details.path.match("cypress")) {
168
+ return Promise.resolve({});
169
+ }
170
+ const newPath = getNewPath(details.path);
171
+ const newPathDir = newPath.substring(0, newPath.lastIndexOf("/"));
172
+ try {
173
+ fs2.mkdirSync(newPathDir, { recursive: true });
174
+ } catch (err) {
175
+ console.error("\u274C Error creating new screenshot folder:", newPathDir, err);
176
+ }
177
+ return new Promise((resolve, reject) => {
178
+ fs2.rename(details.path, newPath, (err) => {
179
+ if (err) {
180
+ reject(err);
181
+ }
182
+ resolve({ path: newPath });
183
+ });
184
+ });
185
+ }
186
+
187
+ // index.ts
188
+ function setupVisualTests(on, config) {
189
+ on("after:screenshot", onAfterScreenshot);
190
+ on("task", {
191
+ baseExists,
192
+ compareScreenshots,
193
+ log: (message) => {
194
+ console.log(message);
195
+ return null;
196
+ }
197
+ });
198
+ return config;
16
199
  }
17
- exports.setupVisualTests = setupVisualTests;
200
+ // Annotate the CommonJS export names for ESM import in node:
201
+ 0 && (module.exports = {
202
+ setupVisualTests
203
+ });
@@ -1,10 +1,8 @@
1
- /// <reference types="cypress" />
2
- /// <reference types="cypress" />
3
- export type MatchScreenshotArgs = {
1
+ type MatchScreenshotArgs = {
4
2
  rawName?: string;
5
3
  options?: Partial<Cypress.ScreenshotOptions>;
6
4
  };
7
- export declare function matchScreenshot(subject: Cypress.JQueryWithSelector | Window | Document | void, args?: MatchScreenshotArgs): void;
5
+ declare function matchScreenshot(subject: Cypress.JQueryWithSelector | Window | Document | void, args?: MatchScreenshotArgs): void;
8
6
  interface ExtendedCurrentRunnable extends Mocha.Runnable {
9
7
  currentRunnable?: {
10
8
  order?: unknown;
@@ -19,4 +17,5 @@ declare global {
19
17
  }
20
18
  }
21
19
  }
22
- export {};
20
+
21
+ export { type MatchScreenshotArgs, matchScreenshot };
@@ -1,81 +1,120 @@
1
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';
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // match-screenshot.ts
21
+ var match_screenshot_exports = {};
22
+ __export(match_screenshot_exports, {
23
+ matchScreenshot: () => matchScreenshot
24
+ });
25
+ module.exports = __toCommonJS(match_screenshot_exports);
26
+ var PREFIX_DIFFERENTIATOR = "___";
27
+ var SUFFIX_TEST_IDENTIFIER = ".spec.ts";
28
+ var SCREENSHOTS_FOLDER_NAME = "screenshots";
7
29
  function forceFont() {
8
- const iframe = window.parent.document.querySelector('iframe');
9
- const contentDocument = iframe && iframe.contentDocument;
10
- if (contentDocument) {
11
- const style = contentDocument.createElement('style');
12
- style.type = 'text/css';
13
- style.appendChild(contentDocument.createTextNode('* { font-family: Arial !important; }'));
14
- contentDocument.head.appendChild(style);
15
- return style;
16
- }
17
- return false;
30
+ const iframe = window.parent.document.querySelector("iframe");
31
+ const contentDocument = iframe && iframe.contentDocument;
32
+ if (contentDocument) {
33
+ const style = contentDocument.createElement("style");
34
+ style.type = "text/css";
35
+ style.appendChild(
36
+ contentDocument.createTextNode("* { font-family: Arial !important; }")
37
+ );
38
+ contentDocument.head.appendChild(style);
39
+ return style;
40
+ }
41
+ return false;
18
42
  }
19
43
  function getTestFolderPathFromScripts(rawName) {
20
- const relativeTestPath = Cypress.spec.relative;
21
- if (!relativeTestPath) {
22
- throw new Error('❌ Could not find matching script in the Cypress DOM to infer the test folder path');
23
- }
24
- const currentTestNumber = Cypress.mocha.getRunner().currentRunnable?.order;
25
- if (!rawName &&
26
- typeof currentTestNumber === 'number' &&
27
- currentTestNumber > 1) {
28
- throw new Error('❌ The rawName argument was not provided to matchScreenshot and is required for test files containing multiple tests!');
29
- }
30
- const testName = relativeTestPath.substring(relativeTestPath.lastIndexOf('/') + 1, relativeTestPath.lastIndexOf(SUFFIX_TEST_IDENTIFIER));
31
- const name = rawName || testName;
32
- const screenshotsFolder = `${SCREENSHOTS_FOLDER_NAME}/${relativeTestPath.substring(0, relativeTestPath.lastIndexOf(testName))}${name}`;
33
- return {
34
- name,
35
- screenshotsFolder
36
- };
44
+ const relativeTestPath = Cypress.spec.relative;
45
+ if (!relativeTestPath) {
46
+ throw new Error(
47
+ "\u274C Could not find matching script in the Cypress DOM to infer the test folder path"
48
+ );
49
+ }
50
+ const currentTestNumber = Cypress.mocha.getRunner().currentRunnable?.order;
51
+ if (!rawName && typeof currentTestNumber === "number" && currentTestNumber > 1) {
52
+ throw new Error(
53
+ "\u274C The rawName argument was not provided to matchScreenshot and is required for test files containing multiple tests!"
54
+ );
55
+ }
56
+ const testName = relativeTestPath.substring(
57
+ relativeTestPath.lastIndexOf("/") + 1,
58
+ relativeTestPath.lastIndexOf(SUFFIX_TEST_IDENTIFIER)
59
+ );
60
+ const name = rawName || testName;
61
+ const screenshotsFolder = `${SCREENSHOTS_FOLDER_NAME}/${relativeTestPath.substring(
62
+ 0,
63
+ relativeTestPath.lastIndexOf(testName)
64
+ )}${name}`;
65
+ return {
66
+ name,
67
+ screenshotsFolder
68
+ };
37
69
  }
38
70
  function verifyImages() {
39
- if (Cypress.$('img:visible').length > 0) {
40
- cy.document()
41
- .its('body')
42
- .find('img')
43
- .filter(':visible')
44
- .then(images => {
45
- if (images) {
46
- cy.wrap(images).each($img => {
47
- cy.wrap($img).should('exist').and('have.prop', 'naturalWidth');
48
- });
49
- }
71
+ if (Cypress.$("img:visible").length > 0) {
72
+ cy.document().its("body").find("img").filter(":visible").then((images) => {
73
+ if (images) {
74
+ cy.wrap(images).each(($img) => {
75
+ cy.wrap($img).should("exist").and("have.prop", "naturalWidth");
50
76
  });
51
- }
77
+ }
78
+ });
79
+ }
52
80
  }
53
81
  function matchScreenshot(subject, args) {
54
- const { rawName, options = {} } = args || {};
55
- // Set up screen
56
- forceFont();
57
- // Making sure each image is visible before taking screenshots
58
- verifyImages();
59
- const { name, screenshotsFolder } = getTestFolderPathFromScripts(rawName);
60
- cy.task('baseExists', screenshotsFolder).then(hasBase => {
61
- const target = subject ? cy.wrap(subject) : cy;
62
- // For easy slicing of path ignoring the root screenshot folder
63
- target.screenshot(`${PREFIX_DIFFERENTIATOR}${screenshotsFolder}/new`, options);
64
- if (!hasBase) {
65
- cy.task('log', `✅ A new base image was created for ${name}. Create this as a new base image via Comparadise!`);
66
- return null;
67
- }
68
- cy.task('compareScreenshots', screenshotsFolder).then(diffPixels => {
69
- if (diffPixels === 0) {
70
- cy.log(`✅ Actual image of ${name} was the same as base`);
71
- }
72
- else {
73
- cy.task('log', `❌ Actual image of ${name} differed by ${diffPixels} pixels.`);
74
- }
75
- return null;
76
- });
77
- return null;
82
+ const { rawName, options = {} } = args || {};
83
+ forceFont();
84
+ verifyImages();
85
+ const { name, screenshotsFolder } = getTestFolderPathFromScripts(rawName);
86
+ cy.task("baseExists", screenshotsFolder).then((hasBase) => {
87
+ const target = subject ? cy.wrap(subject) : cy;
88
+ target.screenshot(
89
+ `${PREFIX_DIFFERENTIATOR}${screenshotsFolder}/new`,
90
+ options
91
+ );
92
+ if (!hasBase) {
93
+ cy.task(
94
+ "log",
95
+ `\u2705 A new base image was created for ${name}. Create this as a new base image via Comparadise!`
96
+ );
97
+ return null;
98
+ }
99
+ cy.task("compareScreenshots", screenshotsFolder).then((diffPixels) => {
100
+ if (diffPixels === 0) {
101
+ cy.log(`\u2705 Actual image of ${name} was the same as base`);
102
+ } else {
103
+ throw new Error(
104
+ `\u274C Actual image of ${name} differed by ${diffPixels} pixels.`
105
+ );
106
+ }
107
+ return null;
78
108
  });
109
+ return null;
110
+ });
79
111
  }
80
- exports.matchScreenshot = matchScreenshot;
81
- Cypress.Commands.add('matchScreenshot', { prevSubject: ['optional', 'element', 'window', 'document'] }, matchScreenshot);
112
+ Cypress.Commands.add(
113
+ "matchScreenshot",
114
+ { prevSubject: ["optional", "element", "window", "document"] },
115
+ matchScreenshot
116
+ );
117
+ // Annotate the CommonJS export names for ESM import in node:
118
+ 0 && (module.exports = {
119
+ matchScreenshot
120
+ });
package/package.json CHANGED
@@ -22,8 +22,9 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@types/pixelmatch": "5.2.6",
25
- "@types/pngjs": "6.0.4",
26
- "cypress": "13.8.0"
25
+ "@types/pngjs": "6.0.5",
26
+ "cypress": "13.8.1",
27
+ "tsup": "8.0.2"
27
28
  },
28
29
  "peerDependencies": {
29
30
  "cypress": ">=12"
@@ -32,13 +33,12 @@
32
33
  "dist",
33
34
  "types"
34
35
  ],
35
- "version": "1.15.2",
36
+ "version": "1.16.1",
36
37
  "scripts": {
37
- "build": "tsc && tsc --project tsconfig.types.json",
38
+ "build": "tsup",
38
39
  "bump-version": "pnpm version $NEW_VERSION",
39
40
  "lint": "eslint . --max-warnings=0",
40
- "postbuild": "echo \"require('./dist/match-screenshot');\" > commands.js && echo \"export {};\" > types/index.js",
41
- "prebuild": "rm -rf dist types",
41
+ "postbuild": "echo \"require('./dist/match-screenshot');\" > commands.js",
42
42
  "test": "exit 0"
43
43
  }
44
44
  }
package/dist/files.js DELETED
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createImageFileName = void 0;
4
- /**
5
- * Creates a file path that is relative to the root
6
- * @param {string} type - Value to append to fileName
7
- * @param {string} path - (optional) FileName which corresponds to a module Id
8
- * returns a string of just the prefix or the fileName prepended by the prefix
9
- */
10
- function createImageFileName(path, type) {
11
- let newPath = path;
12
- if (path.startsWith('/')) {
13
- newPath = `.${path}`;
14
- }
15
- return `${newPath}/${type}.png`;
16
- }
17
- exports.createImageFileName = createImageFileName;
package/dist/images.js DELETED
@@ -1,102 +0,0 @@
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
- /**
37
- * Helper function to create reusable image resizer
38
- */
39
- const createImageResizer = (width, height) => (source) => {
40
- const resized = new pngjs_1.PNG({ width, height, fill: true });
41
- pngjs_1.PNG.bitblt(source, resized, 0, 0, source.width, source.height, 0, 0);
42
- return resized;
43
- };
44
- /**
45
- * Fills new area added after resize with transparent black color.
46
- * I like idea of checker board pattern, but it seems to be too complicated
47
- * to implement considering how low-level pngjs API is.
48
- */
49
- const fillSizeDifference = (width, height) => (image) => {
50
- const inArea = (x, y) => y > height || x > width;
51
- for (let y = 0; y < image.height; y++) {
52
- for (let x = 0; x < image.width; x++) {
53
- if (inArea(x, y)) {
54
- const idx = (image.width * y + x) << 2;
55
- image.data[idx] = 0;
56
- image.data[idx + 1] = 0;
57
- image.data[idx + 2] = 0;
58
- image.data[idx + 3] = 64;
59
- }
60
- }
61
- }
62
- return image;
63
- };
64
- /**
65
- * Aligns images sizes to biggest common value
66
- * and fills new pixels with transparent pixels
67
- */
68
- function alignImagesToSameSize(firstImage, secondImage) {
69
- // Keep original sizes to fill extended area later
70
- const firstImageWidth = firstImage.width;
71
- const firstImageHeight = firstImage.height;
72
- const secondImageWidth = secondImage.width;
73
- const secondImageHeight = secondImage.height;
74
- // Calculate biggest common values
75
- const resizeToSameSize = createImageResizer(Math.max(firstImageWidth, secondImageWidth), Math.max(firstImageHeight, secondImageHeight));
76
- // Resize both images
77
- const resizedFirst = resizeToSameSize(firstImage);
78
- const resizedSecond = resizeToSameSize(secondImage);
79
- // Fill resized area with black transparent pixels
80
- return [
81
- fillSizeDifference(firstImageWidth, firstImageHeight)(resizedFirst),
82
- fillSizeDifference(secondImageWidth, secondImageHeight)(resizedSecond)
83
- ];
84
- }
85
- /**
86
- * Compares a base and new image and returns the pixel difference
87
- * and diff PNG for writing the diff image to.
88
- * @param {string} basePath - Full file path to base image
89
- * @param {string} actualPath - Full file path to new image
90
- */
91
- function getDiffPixels(basePath, actualPath) {
92
- const rawBase = pngjs_1.PNG.sync.read(fs.readFileSync(basePath));
93
- const rawActual = pngjs_1.PNG.sync.read(fs.readFileSync(actualPath));
94
- const hasSizeMismatch = rawBase.height !== rawActual.height || rawBase.width !== rawActual.width;
95
- const [base, actual] = hasSizeMismatch
96
- ? alignImagesToSameSize(rawBase, rawActual)
97
- : [rawBase, rawActual];
98
- const diff = new pngjs_1.PNG({ width: base.width, height: base.height });
99
- const diffPixels = (0, pixelmatch_1.default)(actual.data, base.data, diff.data, diff.width, diff.height, PIXELMATCH_OPTIONS);
100
- return { diffPixels, diff };
101
- }
102
- exports.getDiffPixels = getDiffPixels;
@@ -1,124 +0,0 @@
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 = exports.compareScreenshots = 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
- /**
32
- * Checks if a base image exists
33
- * @param path - Folder path where you can find the base.png image
34
- * @returns true if path/base.png exists, false if not.
35
- */
36
- function baseExists(path) {
37
- const fileName = (0, files_1.createImageFileName)(path, 'base');
38
- const exists = fs.existsSync(fileName);
39
- if (!exists) {
40
- console.log('Base image does not exist. This means a new one will be created. If your base should exist, something went wrong.');
41
- }
42
- return exists;
43
- }
44
- exports.baseExists = baseExists;
45
- /**
46
- * Runs a visual regression test.
47
- * @param screenshotFolder - Full screenshots folder where the base/new/diff
48
- * images will be compared and written to.
49
- */
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 delete actualPath}', err);
67
- }
68
- });
69
- }
70
- return diffPixels;
71
- }
72
- exports.compareScreenshots = compareScreenshots;
73
- const trimPostfix = (path) => {
74
- const imgExt = '.png';
75
- const attemptPostfixRegex = new RegExp(`\\s\\(attempt \\d+\\)${imgExt}$`);
76
- if (attemptPostfixRegex.test(path)) {
77
- return path.replace(attemptPostfixRegex, imgExt);
78
- }
79
- return path;
80
- };
81
- const getNewPath = (path) => {
82
- let newPath;
83
- if (path.includes('___')) {
84
- newPath = path.slice(path.lastIndexOf('___') + 3);
85
- if (newPath.startsWith('/')) {
86
- newPath = `.${newPath}`;
87
- }
88
- console.log(newPath);
89
- }
90
- else {
91
- newPath = path;
92
- }
93
- return trimPostfix(newPath);
94
- };
95
- /**
96
- * Renames all root cypress screenshots to where the test was actually run.
97
- * Should NOT be used standalone. Works with the matchScreenshot task.
98
- * @param {Cypress.ScreenshotDetails} details
99
- */
100
- function onAfterScreenshot(details) {
101
- console.log('🧸 Screenshot was saved to:', details.path);
102
- if (!details.path.match('cypress')) {
103
- return Promise.resolve({});
104
- }
105
- const newPath = getNewPath(details.path);
106
- const newPathDir = newPath.substring(0, newPath.lastIndexOf('/'));
107
- try {
108
- fs.mkdirSync(newPathDir, { recursive: true });
109
- }
110
- catch (err) {
111
- console.error('❌ Error creating new screenshot folder:', newPathDir, err);
112
- }
113
- return new Promise((resolve, reject) => {
114
- fs.rename(details.path, newPath, err => {
115
- if (err) {
116
- reject(err);
117
- }
118
- // because we renamed/moved the image, resolve with the new path
119
- // so it is accurate in the test results
120
- resolve({ path: newPath });
121
- });
122
- });
123
- }
124
- exports.onAfterScreenshot = onAfterScreenshot;
package/types/files.d.ts DELETED
@@ -1,7 +0,0 @@
1
- /**
2
- * Creates a file path that is relative to the root
3
- * @param {string} type - Value to append to fileName
4
- * @param {string} path - (optional) FileName which corresponds to a module Id
5
- * returns a string of just the prefix or the fileName prepended by the prefix
6
- */
7
- export declare function createImageFileName(path: string, type: string): string;
package/types/images.d.ts DELETED
@@ -1,11 +0,0 @@
1
- import { PNG } from 'pngjs';
2
- /**
3
- * Compares a base and new image and returns the pixel difference
4
- * and diff PNG for writing the diff image to.
5
- * @param {string} basePath - Full file path to base image
6
- * @param {string} actualPath - Full file path to new image
7
- */
8
- export declare function getDiffPixels(basePath: string, actualPath: string): {
9
- diffPixels: number;
10
- diff: PNG;
11
- };
package/types/index.d.ts DELETED
@@ -1,10 +0,0 @@
1
- /// <reference types="cypress" />
2
- import { MatchScreenshotArgs } from './match-screenshot';
3
- export declare function setupVisualTests(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions;
4
- declare global {
5
- namespace Cypress {
6
- interface Chainable {
7
- matchScreenshot(args?: MatchScreenshotArgs): Chainable;
8
- }
9
- }
10
- }
package/types/index.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,19 +0,0 @@
1
- /// <reference types="cypress" />
2
- /**
3
- * Checks if a base image exists
4
- * @param path - Folder path where you can find the base.png image
5
- * @returns true if path/base.png exists, false if not.
6
- */
7
- export declare function baseExists(path: string): boolean;
8
- /**
9
- * Runs a visual regression test.
10
- * @param screenshotFolder - Full screenshots folder where the base/new/diff
11
- * images will be compared and written to.
12
- */
13
- export declare function compareScreenshots(screenshotFolder: string): number;
14
- /**
15
- * Renames all root cypress screenshots to where the test was actually run.
16
- * Should NOT be used standalone. Works with the matchScreenshot task.
17
- * @param {Cypress.ScreenshotDetails} details
18
- */
19
- export declare function onAfterScreenshot(details: Cypress.ScreenshotDetails): Promise<Cypress.AfterScreenshotReturnObject>;