@pixsame/cypress-plugin-visual-regression-diff 0.0.0-canary-20261007-tvtlvt3u

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