@pixsame/cypress-plugin-visual-regression-diff 0.0.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,575 @@
1
+ var fs = require('fs');
2
+ var pngjs = require('pngjs');
3
+ var pixelmatch = require('pixelmatch');
4
+ var moveFile = require('move-file');
5
+ var path = require('path');
6
+ var sharp = require('sharp');
7
+ var metaPngPkg = require('meta-png');
8
+ var glob = require('glob');
9
+
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
13
+ var pixelmatch__default = /*#__PURE__*/_interopDefaultLegacy(pixelmatch);
14
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
15
+ var sharp__default = /*#__PURE__*/_interopDefaultLegacy(sharp);
16
+ var metaPngPkg__default = /*#__PURE__*/_interopDefaultLegacy(metaPngPkg);
17
+
18
+ const PLUGIN_NAME = 'cp-visual-regression-diff';
19
+ const IMAGE_SNAPSHOT_PREFIX = `__${PLUGIN_NAME}_snapshots__`;
20
+ var FILE_SUFFIX;
21
+ (function (FILE_SUFFIX) {
22
+ FILE_SUFFIX["diff"] = ".diff";
23
+ FILE_SUFFIX["actual"] = ".actual";
24
+ })(FILE_SUFFIX || (FILE_SUFFIX = {}));
25
+ const TASK = {
26
+ getScreenshotPathInfo: `${PLUGIN_NAME}-getScreenshotPathInfo`,
27
+ compareImages: `${PLUGIN_NAME}-compareImages`,
28
+ approveImage: `${PLUGIN_NAME}-approveImage`,
29
+ cleanupImages: `${PLUGIN_NAME}-cleanupImages`,
30
+ doesFileExist: `${PLUGIN_NAME}-doesFileExist`,
31
+ processImgPath: `${PLUGIN_NAME}-processImgPath`,
32
+ recordPendingDiff: `${PLUGIN_NAME}-recordPendingDiff`,
33
+ getPendingDiffs: `${PLUGIN_NAME}-getPendingDiffs`,
34
+ clearPendingDiffs: `${PLUGIN_NAME}-clearPendingDiffs`
35
+ /* c8 ignore next */
36
+ };
37
+ const PATH_VARIABLES = {
38
+ specPath: '{spec_path}',
39
+ unixSystemRootPath: '{unix_system_root_path}',
40
+ winSystemRootPath: '{win_system_root_path}'
41
+ };
42
+ const WINDOWS_LIKE_DRIVE_REGEX = /^[A-Z]:$/;
43
+ const METADATA_KEY = 'FRSOURCE_CPVRD_V';
44
+
45
+ const supportsExpose = version => {
46
+ const [major, minor] = version.split('.').map(Number);
47
+ return major > 15 || major === 15 && minor >= 10;
48
+ };
49
+ const getPluginConfig = (config, key) => {
50
+ var _config$version, _config$env;
51
+ if (supportsExpose((_config$version = config.version) != null ? _config$version : '')) {
52
+ var _config$expose;
53
+ return (_config$expose = config.expose) == null ? void 0 : _config$expose[key];
54
+ }
55
+ return (_config$env = config.env) == null ? void 0 : _config$env[key];
56
+ };
57
+
58
+ function isHighSurrogate(codePoint) {
59
+ return codePoint >= 0xd800 && codePoint <= 0xdbff;
60
+ }
61
+
62
+ function isLowSurrogate(codePoint) {
63
+ return codePoint >= 0xdc00 && codePoint <= 0xdfff;
64
+ }
65
+
66
+ // Truncate string by size in bytes
67
+ var truncate = function truncate(getLength, string, byteLength) {
68
+ if (typeof string !== "string") {
69
+ throw new Error("Input must be string");
70
+ }
71
+
72
+ var charLength = string.length;
73
+ var curByteLength = 0;
74
+ var codePoint;
75
+ var segment;
76
+
77
+ for (var i = 0; i < charLength; i += 1) {
78
+ codePoint = string.charCodeAt(i);
79
+ segment = string[i];
80
+
81
+ if (isHighSurrogate(codePoint) && isLowSurrogate(string.charCodeAt(i + 1))) {
82
+ i += 1;
83
+ segment += string[i];
84
+ }
85
+
86
+ curByteLength += getLength(segment);
87
+
88
+ if (curByteLength === byteLength) {
89
+ return string.slice(0, i + 1);
90
+ }
91
+ else if (curByteLength > byteLength) {
92
+ return string.slice(0, i - segment.length + 1);
93
+ }
94
+ }
95
+
96
+ return string;
97
+ };
98
+
99
+ var getLength = Buffer.byteLength.bind(Buffer);
100
+ var truncateUtf8Bytes = truncate.bind(null, getLength);
101
+
102
+ /*jshint node:true*/
103
+
104
+ /**
105
+ * Replaces characters in strings that are illegal/unsafe for filenames.
106
+ * Unsafe characters are either removed or replaced by a substitute set
107
+ * in the optional `options` object.
108
+ *
109
+ * Illegal Characters on Various Operating Systems
110
+ * / ? < > \ : * | "
111
+ * https://kb.acronis.com/content/39790
112
+ *
113
+ * Unicode Control codes
114
+ * C0 0x00-0x1f & C1 (0x80-0x9f)
115
+ * http://en.wikipedia.org/wiki/C0_and_C1_control_codes
116
+ *
117
+ * Reserved filenames on Unix-based systems (".", "..")
118
+ * Reserved filenames in Windows ("CON", "PRN", "AUX", "NUL", "COM1",
119
+ * "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
120
+ * "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", and
121
+ * "LPT9") case-insesitively and with or without filename extensions.
122
+ *
123
+ * Capped at 255 characters in length.
124
+ * http://unix.stackexchange.com/questions/32795/what-is-the-maximum-allowed-filename-and-folder-size-with-ecryptfs
125
+ *
126
+ * @param {String} input Original filename
127
+ * @param {Object} options {replacement: String | Function }
128
+ * @return {String} Sanitized filename
129
+ */
130
+
131
+
132
+
133
+ var illegalRe = /[\/\?<>\\:\*\|"]/g;
134
+ var controlRe = /[\x00-\x1f\x80-\x9f]/g;
135
+ var reservedRe = /^\.+$/;
136
+ var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
137
+
138
+ /**
139
+ * Strip trailing spaces and dots, which are not allowed on some Windows file
140
+ * systems. Does not use a regex to avoid a quadratic ReDoS vulnerability
141
+ * (CWE-1333).
142
+ */
143
+ function replaceTrailingDotsAndSpaces(str, replacement) {
144
+ var end = str.length;
145
+ while (end > 0 && (str[end - 1] === '.' || str[end - 1] === ' ')) end--;
146
+ return end < str.length ? str.slice(0, end) + replacement : str;
147
+ }
148
+
149
+ function sanitize(input, replacement) {
150
+ if (typeof input !== 'string') {
151
+ throw new Error('Input must be string');
152
+ }
153
+ var sanitized = input
154
+ .replace(illegalRe, replacement)
155
+ .replace(controlRe, replacement)
156
+ .replace(reservedRe, replacement)
157
+ .replace(windowsReservedRe, replacement);
158
+ sanitized = replaceTrailingDotsAndSpaces(sanitized, replacement);
159
+ return truncateUtf8Bytes(sanitized, 255);
160
+ }
161
+
162
+ var sanitizeFilename = function (input, options) {
163
+ var replacement = (options && options.replacement) || '';
164
+ var output = sanitize(input, replacement);
165
+ if (replacement === '') {
166
+ return output;
167
+ }
168
+ return sanitize(output, '');
169
+ };
170
+
171
+ // highest `_#n` index handed out per screenshot path during the current spec
172
+ const nameCacheCounter = {};
173
+ // counters as they were before the current test attempt started, for every
174
+ // path the attempt touched - a retry restores them so the regenerated
175
+ // screenshots get the same names again
176
+ const countersBeforeAttempt = {};
177
+ let currentAttempt;
178
+ const resetMap = map => {
179
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
180
+ for (const key in map) delete map[key];
181
+ };
182
+ const parsePathPartVariables = (specPath, pathPart, i) => {
183
+ if (pathPart === PATH_VARIABLES.specPath) {
184
+ return path__default["default"].dirname(specPath);
185
+ } else if (i === 0 && !pathPart) {
186
+ // when unix-like absolute path
187
+ return PATH_VARIABLES.unixSystemRootPath;
188
+ } else if (i === 0 && WINDOWS_LIKE_DRIVE_REGEX.test(pathPart)) {
189
+ // when win-like absolute path
190
+ return path__default["default"].join(PATH_VARIABLES.winSystemRootPath, pathPart[0]);
191
+ }
192
+ return pathPart;
193
+ };
194
+ const startAttempt = (testId, retry) => {
195
+ var _currentAttempt;
196
+ const isSameTest = ((_currentAttempt = currentAttempt) == null ? void 0 : _currentAttempt.testId) === testId;
197
+ if (isSameTest && currentAttempt && retry > currentAttempt.retry) {
198
+ for (const screenshotPath in countersBeforeAttempt) {
199
+ nameCacheCounter[screenshotPath] = countersBeforeAttempt[screenshotPath];
200
+ }
201
+ }
202
+ if (!isSameTest || currentAttempt && retry !== currentAttempt.retry) {
203
+ resetMap(countersBeforeAttempt);
204
+ }
205
+ currentAttempt = {
206
+ testId,
207
+ retry
208
+ };
209
+ };
210
+ const generateScreenshotPath = ({
211
+ titleFromOptions,
212
+ imagesPath,
213
+ specPath,
214
+ currentRetryNumber,
215
+ testId
216
+ }) => {
217
+ const screenshotPath = path__default["default"].join(...imagesPath.split('/').map(parsePathPartVariables.bind(void 0, specPath)), sanitizeFilename(titleFromOptions));
218
+ startAttempt(testId, currentRetryNumber);
219
+ if (typeof nameCacheCounter[screenshotPath] === 'undefined') {
220
+ nameCacheCounter[screenshotPath] = -1;
221
+ }
222
+ if (!(screenshotPath in countersBeforeAttempt)) {
223
+ countersBeforeAttempt[screenshotPath] = nameCacheCounter[screenshotPath];
224
+ }
225
+ return path__default["default"].join(IMAGE_SNAPSHOT_PREFIX, `${screenshotPath}_#${++nameCacheCounter[screenshotPath]}${FILE_SUFFIX.actual}.png`);
226
+ };
227
+ const screenshotPathRegex = new RegExp(`^([\\s\\S]+?)_#([0-9]+)(?:(?:\\${FILE_SUFFIX.diff})|(?:\\${FILE_SUFFIX.actual}))?\\.(?:png|PNG)$`);
228
+ const wasScreenshotUsed = imagePath => {
229
+ const matched = imagePath.match(screenshotPathRegex);
230
+ /* c8 ignore next */
231
+ if (!matched) return false;
232
+ const [, screenshotPath, numString] = matched;
233
+ const num = parseInt(numString);
234
+ /* c8 ignore next */
235
+ if (!screenshotPath || isNaN(num)) return false;
236
+ return screenshotPath in nameCacheCounter && num <= nameCacheCounter[screenshotPath];
237
+ };
238
+ const resetScreenshotNameCache = () => {
239
+ currentAttempt = undefined;
240
+ resetMap(nameCacheCounter);
241
+ resetMap(countersBeforeAttempt);
242
+ };
243
+
244
+ const {
245
+ addMetadata,
246
+ getMetadata
247
+ } = metaPngPkg__default["default"];
248
+ // version of images generated by this plugin
249
+ // bump only when there is a breaking change to:
250
+ // image comparison or image generation algorithms
251
+ const DIFF_IMAGES_VERSION = '1';
252
+ const addPNGMetadata = (config, png) => addMetadata(new Uint8Array(png), METADATA_KEY, JSON.stringify({
253
+ version: DIFF_IMAGES_VERSION,
254
+ testingType: config.testingType || 'e2e'
255
+ }) /* c8 ignore next */);
256
+ const getPNGMetadata = png => {
257
+ const metadataString = getMetadata(new Uint8Array(png), METADATA_KEY /* c8 ignore next */);
258
+ if (metadataString === undefined) return;
259
+ try {
260
+ return JSON.parse(metadataString);
261
+ } catch /* v8 ignore next 2 */{
262
+ return {
263
+ version: metadataString
264
+ };
265
+ }
266
+ };
267
+ const isImageCurrentVersion = png => {
268
+ var _getPNGMetadata;
269
+ return ((_getPNGMetadata = getPNGMetadata(png)) == null ? void 0 : _getPNGMetadata.version) === DIFF_IMAGES_VERSION;
270
+ };
271
+ const isImageGeneratedByPlugin = png => !!getPNGMetadata(png /* c8 ignore next */);
272
+ const isImageOfTestType = (png, testingType) => {
273
+ var _getPNGMetadata2;
274
+ if (!isImageGeneratedByPlugin(png)) return false;
275
+ const imageTestingType = (_getPNGMetadata2 = getPNGMetadata(png /* c8 ignore next */)) == null ? void 0 : _getPNGMetadata2.testingType;
276
+ return imageTestingType === testingType || testingType === undefined;
277
+ };
278
+ const writePNG = (config, name, png) => fs__default["default"].writeFileSync(name, addPNGMetadata(config, png instanceof pngjs.PNG ? pngjs.PNG.sync.write(png) : png));
279
+ const inArea = (x, y, height, width) => y > height || x > width;
280
+ const fillSizeDifference = (image, width, height) => {
281
+ for (let y = 0; y < image.height; y++) {
282
+ for (let x = 0; x < image.width; x++) {
283
+ if (inArea(x, y, height, width)) {
284
+ const idx = image.width * y + x << 2;
285
+ image.data[idx] = 0;
286
+ image.data[idx + 1] = 0;
287
+ image.data[idx + 2] = 0;
288
+ image.data[idx + 3] = 64;
289
+ }
290
+ }
291
+ }
292
+ return image;
293
+ /* c8 ignore next */
294
+ };
295
+ const createImageResizer = (width, height) => source => {
296
+ const resized = new pngjs.PNG({
297
+ width,
298
+ height,
299
+ fill: true
300
+ });
301
+ pngjs.PNG.bitblt(source, resized, 0, 0, source.width, source.height, 0, 0);
302
+ return resized;
303
+ /* c8 ignore next */
304
+ };
305
+ const scaleImageAndWrite = async ({
306
+ scaleFactor,
307
+ path
308
+ }) => {
309
+ const imgBuffer = fs__default["default"].readFileSync(path);
310
+ if (scaleFactor === 1) return imgBuffer;
311
+ const rawImgNew = pngjs.PNG.sync.read(imgBuffer);
312
+ const newImageWidth = Math.ceil(rawImgNew.width * scaleFactor);
313
+ const newImageHeight = Math.ceil(rawImgNew.height * scaleFactor);
314
+ await sharp__default["default"](imgBuffer).resize(newImageWidth, newImageHeight).toFile(path);
315
+ return fs__default["default"].readFileSync(path);
316
+ };
317
+ const alignImagesToSameSize = (firstImage, secondImage) => {
318
+ const firstImageWidth = firstImage.width;
319
+ const firstImageHeight = firstImage.height;
320
+ const secondImageWidth = secondImage.width;
321
+ const secondImageHeight = secondImage.height;
322
+ const resizeToSameSize = createImageResizer(Math.max(firstImageWidth, secondImageWidth), Math.max(firstImageHeight, secondImageHeight));
323
+ const resizedFirst = resizeToSameSize(firstImage);
324
+ const resizedSecond = resizeToSameSize(secondImage);
325
+ return [fillSizeDifference(resizedFirst, firstImageWidth, firstImageHeight), fillSizeDifference(resizedSecond, secondImageWidth, secondImageHeight)];
326
+ };
327
+ const cleanupUnused = config => {
328
+ glob.globSync('**/*.png', {
329
+ cwd: config.projectRoot,
330
+ ignore: 'node_modules/**/*'
331
+ }).forEach(pngPath => {
332
+ const absolutePath = path__default["default"].join(config.projectRoot, pngPath);
333
+ if (!wasScreenshotUsed(pngPath) && isImageOfTestType(fs__default["default"].readFileSync(absolutePath), config.testingType)) {
334
+ fs__default["default"].unlinkSync(absolutePath);
335
+ }
336
+ });
337
+ };
338
+
339
+ let pendingDiffs = [];
340
+ const round = n => Math.ceil(n * 1000) / 1000;
341
+ const unlinkSyncSafe = path => fs__default["default"].existsSync(path) && fs__default["default"].unlinkSync(path);
342
+ // `.diff.png` sibling of an `.actual.png`; null when the name has no `.actual`
343
+ // suffix (only happens with hand-picked paths), so nothing else gets deleted
344
+ const diffPathFor = actualPath => {
345
+ const diffPath = actualPath.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff);
346
+ return diffPath === actualPath ? null : diffPath;
347
+ };
348
+ const removeStaleDiff = actualPath => {
349
+ const diffPath = diffPathFor(actualPath);
350
+ if (diffPath) unlinkSyncSafe(diffPath);
351
+ };
352
+ const getScreenshotPathInfoTask = cfg => {
353
+ const screenshotPath = generateScreenshotPath(cfg);
354
+ return {
355
+ screenshotPath,
356
+ title: path__default["default"].basename(screenshotPath, '.png')
357
+ };
358
+ };
359
+ const cleanupImagesTask = config => {
360
+ if (getPluginConfig(config, 'pluginVisualRegressionCleanupUnusedImages')) {
361
+ cleanupUnused(config);
362
+ }
363
+ resetScreenshotNameCache();
364
+ return null;
365
+ };
366
+ const approveImageTask = async ({
367
+ img,
368
+ imgOld
369
+ }) => {
370
+ const oldImg = imgOld != null ? imgOld : img.replace(FILE_SUFFIX.actual, '');
371
+ unlinkSyncSafe(oldImg);
372
+ const diffImg = img.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff);
373
+ unlinkSyncSafe(diffImg);
374
+ await moveFile.moveFile(img, oldImg);
375
+ return null;
376
+ };
377
+ const compareImagesTask = async (cypressConfig, cfg) => {
378
+ const messages = [];
379
+ // Stamp the screenshot with plugin metadata exactly once, so that every file
380
+ // derived from it (baseline via moveFile, kept .actual.png, manual rename)
381
+ // carries FRSOURCE_CPVRD_V and won't be silently rewritten on the next run.
382
+ const stampedImgNew = addPNGMetadata(cypressConfig, await scaleImageAndWrite({
383
+ scaleFactor: cfg.scaleFactor,
384
+ path: cfg.imgNew
385
+ }));
386
+ fs__default["default"].writeFileSync(cfg.imgNew, stampedImgNew);
387
+ const rawImgNewBuffer = Buffer.from(stampedImgNew);
388
+ let imgDiff;
389
+ let imgNewBase64, imgOldBase64, imgDiffBase64;
390
+ let error = false;
391
+ if (fs__default["default"].existsSync(cfg.imgOld) && cfg.updateImages !== true) {
392
+ const rawImgNew = pngjs.PNG.sync.read(rawImgNewBuffer);
393
+ const rawImgOldBuffer = fs__default["default"].readFileSync(cfg.imgOld);
394
+ const rawImgOld = pngjs.PNG.sync.read(rawImgOldBuffer);
395
+ const isImgSizeDifferent = rawImgNew.height !== rawImgOld.height || rawImgNew.width !== rawImgOld.width;
396
+ const [imgNew, imgOld] = isImgSizeDifferent ? alignImagesToSameSize(rawImgNew, rawImgOld) : [rawImgNew, rawImgOld];
397
+ const {
398
+ width,
399
+ height
400
+ } = imgNew;
401
+ const diff = new pngjs.PNG({
402
+ width,
403
+ height
404
+ });
405
+ const diffConfig = Object.assign({
406
+ includeAA: true
407
+ }, cfg.diffConfig);
408
+ const diffPixels = pixelmatch__default["default"](new Uint8Array(imgNew.data), new Uint8Array(imgOld.data), diff.data, width, height, diffConfig);
409
+ imgDiff = diffPixels / (width * height);
410
+ if (isImgSizeDifferent) {
411
+ messages.push(`Warning: Images size mismatch - new screenshot is ${rawImgNew.width}px by ${rawImgNew.height}px while old one is ${rawImgOld.width}px by ${rawImgOld.height} (width x height).`);
412
+ }
413
+ if (imgDiff > cfg.maxDiffThreshold) {
414
+ messages.unshift(`Image diff factor (${round(imgDiff * 100)}%) is bigger than maximum threshold option ${round(cfg.maxDiffThreshold * 100)}%.`);
415
+ error = true;
416
+ }
417
+ const diffBuffer = pngjs.PNG.sync.write(diff);
418
+ imgNewBase64 = pngjs.PNG.sync.write(imgNew).toString('base64');
419
+ imgDiffBase64 = diffBuffer.toString('base64');
420
+ imgOldBase64 = pngjs.PNG.sync.write(imgOld).toString('base64');
421
+ if (error && cfg.updateImages === 'failures-only') {
422
+ await moveFile.moveFile(cfg.imgNew, cfg.imgOld);
423
+ // a diff image left by an earlier failed attempt is stale now
424
+ removeStaleDiff(cfg.imgNew);
425
+ error = false;
426
+ messages[0] = messages[0].replace('is bigger than maximum threshold option', 'was bigger than maximum threshold option (baseline updated):');
427
+ } else if (error) {
428
+ writePNG(cypressConfig, cfg.imgNew.replace(FILE_SUFFIX.actual, FILE_SUFFIX.diff), diffBuffer);
429
+ } else {
430
+ removeStaleDiff(cfg.imgNew);
431
+ if (rawImgOld && !isImageCurrentVersion(rawImgOldBuffer)) {
432
+ await moveFile.moveFile(cfg.imgNew, cfg.imgOld);
433
+ } else {
434
+ // don't overwrite file if it's the same (imgDiff < cfg.maxDiffThreshold && !isImgSizeDifferent)
435
+ fs__default["default"].unlinkSync(cfg.imgNew);
436
+ }
437
+ }
438
+ } else {
439
+ // there is no "old screenshot" or screenshots should be immediately updated
440
+ imgDiff = 0;
441
+ imgNewBase64 = '';
442
+ imgDiffBase64 = '';
443
+ imgOldBase64 = '';
444
+ if (cfg.createMissingImages) {
445
+ await moveFile.moveFile(cfg.imgNew, cfg.imgOld);
446
+ removeStaleDiff(cfg.imgNew);
447
+ } else {
448
+ error = true;
449
+ messages.unshift(`Baseline image is missing at path: "${cfg.imgOld}". Provide a baseline image or enable "createMissingImages" option in plugin configuration.`);
450
+ }
451
+ }
452
+ if (typeof imgDiff !== 'undefined') {
453
+ if (!error) {
454
+ messages.unshift(`Image diff factor (${round(imgDiff * 100)}%) is within boundaries of maximum threshold option ${round(cfg.maxDiffThreshold * 100)}%.`);
455
+ }
456
+ return {
457
+ error,
458
+ message: messages.join('\n'),
459
+ imgDiff,
460
+ imgNewBase64,
461
+ imgDiffBase64,
462
+ imgOldBase64,
463
+ maxDiffThreshold: cfg.maxDiffThreshold
464
+ };
465
+ }
466
+ /* c8 ignore next */
467
+ return null;
468
+ };
469
+ const doesFileExistTask = ({
470
+ path
471
+ }) => fs__default["default"].existsSync(path);
472
+ const processImgPathTask = ({
473
+ path
474
+ }) => path;
475
+ const recordPendingDiffTask = record => {
476
+ pendingDiffs.push(record);
477
+ return pendingDiffs.filter(d => !d.passed).length;
478
+ };
479
+ const getPendingDiffsTask = () => [...pendingDiffs];
480
+ const clearPendingDiffsTask = () => {
481
+ pendingDiffs = [];
482
+ return null;
483
+ };
484
+ /* c8 ignore start */
485
+ const initTaskHook = config => ({
486
+ [TASK.getScreenshotPathInfo]: getScreenshotPathInfoTask,
487
+ [TASK.cleanupImages]: cleanupImagesTask.bind(undefined, config),
488
+ [TASK.doesFileExist]: doesFileExistTask,
489
+ [TASK.approveImage]: approveImageTask,
490
+ [TASK.compareImages]: compareImagesTask.bind(undefined, config),
491
+ [TASK.processImgPath]: processImgPathTask,
492
+ [TASK.recordPendingDiff]: recordPendingDiffTask,
493
+ [TASK.getPendingDiffs]: getPendingDiffsTask,
494
+ [TASK.clearPendingDiffs]: clearPendingDiffsTask
495
+ });
496
+ /* c8 ignore stop */
497
+
498
+ const MIMIC_ROOT_WIN_REGEX = new RegExp(`^${PATH_VARIABLES.winSystemRootPath}\\${path__default["default"].sep}([A-Z])\\${path__default["default"].sep}`);
499
+ const MIMIC_ROOT_UNIX_REGEX = new RegExp(`^${PATH_VARIABLES.unixSystemRootPath}\\${path__default["default"].sep}`);
500
+ const getConfigVariableOrThrow = (config, name) => {
501
+ if (config[name]) {
502
+ return config[name];
503
+ }
504
+ /* c8 ignore start */
505
+ throw `[@pixsame/cypress-plugin-visual-regression-diff] CypressConfig.${name} cannot be missing or \`false\`!`;
506
+ };
507
+ /* c8 ignore stop */
508
+ const parseAbsolutePath = ({
509
+ screenshotPath,
510
+ projectRoot
511
+ }) => {
512
+ let newAbsolutePath;
513
+ const matchedMimicingWinRoot = screenshotPath.match(MIMIC_ROOT_WIN_REGEX);
514
+ const matchedMimicingUnixRoot = screenshotPath.match(MIMIC_ROOT_UNIX_REGEX);
515
+ if (matchedMimicingWinRoot && matchedMimicingWinRoot[1]) {
516
+ const driveLetter = matchedMimicingWinRoot[1];
517
+ newAbsolutePath = path__default["default"].join(`${driveLetter}:\\`, screenshotPath.substring(matchedMimicingWinRoot[0].length));
518
+ } else if (matchedMimicingUnixRoot) {
519
+ newAbsolutePath = path__default["default"].sep + screenshotPath.substring(matchedMimicingUnixRoot[0].length);
520
+ } else {
521
+ newAbsolutePath = path__default["default"].join(projectRoot, screenshotPath);
522
+ }
523
+ return path__default["default"].normalize(newAbsolutePath);
524
+ };
525
+ const initAfterScreenshotHook = config => details => {
526
+ var _details$name;
527
+ // it's not a screenshot generated by FRSOURCE Cypress Plugin Visual Regression Diff
528
+ /* c8 ignore start */
529
+ if (((_details$name = details.name) == null ? void 0 : _details$name.indexOf(IMAGE_SNAPSHOT_PREFIX)) !== 0) return;
530
+ /* c8 ignore stop */
531
+ const screenshotsFolder = getConfigVariableOrThrow(config, 'screenshotsFolder');
532
+ const screenshotPath = details.name.substring(IMAGE_SNAPSHOT_PREFIX.length + path__default["default"].sep.length);
533
+ const newAbsolutePath = parseAbsolutePath({
534
+ screenshotPath,
535
+ projectRoot: config.projectRoot
536
+ });
537
+ return (async () => {
538
+ await moveFile.moveFile(details.path, newAbsolutePath);
539
+ await fs.promises.rm(path__default["default"].join(screenshotsFolder, IMAGE_SNAPSHOT_PREFIX), {
540
+ recursive: true,
541
+ force: true
542
+ });
543
+ return {
544
+ path: newAbsolutePath
545
+ };
546
+ })();
547
+ };
548
+
549
+ /* c8 ignore start */
550
+ const initForceDeviceScaleFactor = on => {
551
+ // based on https://github.com/cypress-io/cypress/issues/2102#issuecomment-521299946
552
+ on('before:browser:launch', (browser, launchOptions) => {
553
+ if (browser.name === 'chrome' || browser.name === 'chromium') {
554
+ launchOptions.args.push('--force-device-scale-factor=1');
555
+ launchOptions.args.push('--high-dpi-support=1');
556
+ return launchOptions;
557
+ } else if (browser.name === 'electron' && browser.isHeaded) {
558
+ // eslint-disable-next-line no-console
559
+ console.log("There isn't currently a way of setting the device scale factor in Cypress when running headed electron so we disable the image regression commands.");
560
+ }
561
+ });
562
+ };
563
+ /* c8 ignore stop */
564
+ const initPlugin = (on, config) => {
565
+ /* c8 ignore start */
566
+ if (getPluginConfig(config, 'pluginVisualRegressionForceDeviceScaleFactor') !== false) {
567
+ initForceDeviceScaleFactor(on);
568
+ }
569
+ /* c8 ignore stop */
570
+ on('task', initTaskHook(config));
571
+ on('after:screenshot', initAfterScreenshotHook(config));
572
+ };
573
+
574
+ exports.initPlugin = initPlugin;
575
+ //# sourceMappingURL=plugins.js.map