-
Notifications
You must be signed in to change notification settings - Fork 2
/
plugin.js
582 lines (529 loc) · 30.9 KB
/
plugin.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
const axios = require('axios').default;
const {v4: uuidv4} = require('uuid');
const fs = require("fs-extra");
const package_json = require('./package.json');
const cwd = process.cwd();
const path = require("path");
const chalk = require('chalk');
const sharp = require("sharp");
const os = require('os');
const pino = require('pino');
const semver = require("semver");
require('dotenv').config();
const targetArray = [{target: 'pino-pretty', level: 'warn'}]; //to log below warn uncomment two lines below
let logger = pino(pino.transport({targets: targetArray}));
// logger.level = 'trace' // uncomment if you want to log below 'info'
let usersCypress, env, host, webUrl, cdnUrl, debugFolderPath;
const sessionId = uuidv4();
try {
const packageFile = fs.readFileSync(path.resolve(path.dirname(require.resolve('cypress', {paths: [cwd]})), 'package.json'));
usersCypress = JSON.parse(packageFile.toString());
if (!usersCypress.version) {
usersCypress.version = "10.0.0.failure";
logger.warn('failed to find cypress assuming it is v10+');
}
} catch (err) {
logger.warn("catch");
usersCypress.version = "10.0.0";
console.log(err);
logger.warn(err.message);
}
let setEnv = (projectToken) => {
if (projectToken && projectToken.split('_')[1] && projectToken.includes("/")) {
//check if the projectToken is environmented, and not to throw any errors here if user messes up, error later
env = projectToken.split('_')[1].toLowerCase();
}
if (env) {
logger.warn(`overwritten env is: ${env}`);
host = `https://api.${env}.visualtest.io`;
webUrl = `https://app.${env}.visualtest.io`;
cdnUrl = `https://cdn.${env}.visualtest.io/browser-toolkit`;
} else {
host = "https://api.visualtest.io";
webUrl = "https://app.visualtest.io";
cdnUrl = "https://cdn.visualtest.io/browser-toolkit";
}
};
let getDebugFolderPath = () => {
const currentDate = new Date();
const month = (currentDate.getMonth() + 1).toString().padStart(2, '0'); // Add 1 to the month since it is zero-based
const day = currentDate.getDate().toString().padStart(2, '0');
const hours = currentDate.getHours().toString().padStart(2, '0');
const minutes = currentDate.getMinutes().toString().padStart(2, '0');
const seconds = currentDate.getSeconds().toString().padStart(2, '0');
const formattedString = `${month}-${day}_${hours}-${minutes}-${seconds}`;
return `sbvt_debug/${formattedString}_${sessionId}`;
};
let configFile = (() => {
try {
let config = {};
const fileName = 'visualTest.config.js';
const fullPath = `${process.cwd()}/${fileName}`;
if (fs.existsSync(fullPath)) {
logger.trace(fileName + ' has been found');
config = {...require(fullPath)}; //write the VT config file into config object
if (config.debug) {
debugFolderPath = getDebugFolderPath();
config.debug = debugFolderPath; //overwrite 'true' to the folder path for passing to commands.js
fs.mkdirSync(debugFolderPath, {recursive: true});
targetArray.push({
target: './debug-pino-transport.js',
level: 'trace',
options: {destination: `${debugFolderPath}/debug.log`}
});
logger = pino(pino.transport({targets: targetArray}));
logger.level = 'trace'; //required to overwrite default 'info'
logger.info('"debug: true" found in visualtest.config.js');
}
if (config.projectToken) {
//dont throw error if missing projectToken in visualtest.config.js——default to prod
setEnv(config.projectToken);
}
config.cdnUrl = cdnUrl || "https://cdn.visualtest.io/browser-toolkit";
return config;
} else {
config.fail = true;
logger.fatal('The path ' + fullPath + ' was not found');
return config;
}
} catch (e) {
console.log(e);
}
})();
const apiRequest = async (method, url, body, headers) => {
return await axios({
method: method, url: url, headers: headers, data: body
}).catch((err) => {
console.log(`Full error is: %o`, err.response.data);
logger.info(`Full error is: %o`, err.response.data);
return err.response;
});
};
const checkConnectionToBackend = (async (projectToken) => {
let env = projectToken.includes('_') && projectToken.split('_').length === 2 ? projectToken.split('_')[1].toLowerCase() : null;
if (env) {
host = `https://api.${env}.visualtest.io`;
} else {
host = "https://api.visualtest.io";
}
const response = await apiRequest('get', host);
if (response.error) {
logger.trace(response)
throw new Error(`The VisualTest SDK is unable to communicate with our server. This is usually due to one of the following reasons:\n\
1) Firewall is blocking the domain: Solution is to whitelist the domain: "*.visualtest.io"\n\
2) Internet access requires a proxy server: Talk to your network admin\n\
\n\
Error:\n\
${response.error}`);
} else {
logger.info(`Got initial connection response: ${response.body}`);
}
});
const isValidProjectToken = (async (projectToken) => {
const response = await apiRequest('get', `${host}/api/v1/projects/${projectToken.split('/')[0]}`, null, {Authorization: `Bearer ${projectToken}`});
if (response.data.status) {
logger.trace(response)
throw new Error(`Error checking projectToken: ${response.data.message}`);
} else {
logger.info(`ProjectToken is correct.`)
}
return null
})
const checkUsersVersion = (async () => {
const userVersion = package_json.version
const response = await apiRequest('get', 'https://registry.npmjs.org/@smartbear/visualtest-cypress')
const {latest: latestVersion} = response.data["dist-tags"]
if (semver.eq(userVersion, latestVersion)) {
// console.log(chalk.blue('The user has the latest version.'));
} else {
console.log(chalk.yellow('Please upgrade to the latest VisualTest Cypress Plugin version.'));
console.log(chalk.grey('\tnpm install @smartbear/visualtest-cypress@latest'));
}
return null
})();
let getDomCapture = (async () => {
try {
const res = await apiRequest('get', `${configFile.cdnUrl}/dom-capture.min.js`);
return res.data;
} catch (error) {
configFile.fail = true;
logger.fatal(`Error with grabbing domCapture: %o`, error.message);
}
})();
let getUserAgent = (async () => {
try {
const res = await apiRequest('get', `${configFile.cdnUrl}/user-agent.min.js`);
return res.data;
} catch (error) {
configFile.fail = true;
logger.fatal(`Error with grabbing userAgent: %o`, error.message);
}
})();
let getFreezePage = (async () => {
try {
const res = await apiRequest('get', `${configFile.cdnUrl}/freeze-page.min.js`);
return res.data;
} catch (error) {
configFile.fail = true;
logger.fatal(`Error with grabbing freezePage: %o`, error.message);
}
})();
let domToolKit = null;
Promise.all([getDomCapture, getUserAgent, getFreezePage]).then((values) => {
const data = {};
data.domCapture = values[0];
data.userAgent = values[1];
data.freezePage = values[2];
domToolKit = data;
});
function makeGlobalRunHooks() {
return {
'task': {
async postTestRunId(fromCommands) { //cy.task('postTestRunId') to run this code
if (!configFile.testRunId && !configFile.fail) {//all this only needs to run once
try {
//Create file for BitBar to grab sessionId
fs.writeFileSync('./node_modules/@smartbear/visualtest-cypress/sessionId.txt', sessionId);
} catch (error) {
//In case of an error do not want to throw an error
logger.info("FOR BitBar——issue creating the sessionId file: %o", error);
}
if (fromCommands.envFromCypress.debug || process.env.DEBUG) {
logger.warn(`debug flag found on ${fromCommands.envFromCypress.debug ? `CLI ENV` : `process.env`}`);
if (debugFolderPath) {
logger.warn(`debug ALREADY set true, path: ${debugFolderPath}`);
} else {
debugFolderPath = getDebugFolderPath();
logger.info(`debug logs started: ${debugFolderPath}`);
configFile.debug = debugFolderPath; //overwrite 'true' to the folder path for passing to commands.js
fs.mkdirSync(debugFolderPath, {recursive: true});
targetArray.push({
target: './debug-pino-transport.js',
level: 'trace',
options: {destination: `${debugFolderPath}/debug.log`}
});
logger = pino(pino.transport({targets: targetArray}));
logger.level = 'trace'; //required to overwrite default 'info'
}
}
if (fromCommands.envFromCypress.projectToken) {
logger.info('PROJECT_TOKEN found in env flag from Cypress');
setEnv(fromCommands.envFromCypress.projectToken);
configFile.projectToken = fromCommands.envFromCypress.projectToken;
} else if (process.env.PROJECT_TOKEN) {
logger.info('PROJECT_TOKEN found in env variable');
setEnv(process.env.PROJECT_TOKEN);
configFile.projectToken = process.env.PROJECT_TOKEN;
} else {
if (!configFile.projectToken) { //check to make sure user added a projectToken
const message = `Please add **module.exports = { projectToken: 'PROJECT_TOKEN' }** to your visualTest.config.js file`
configFile.fail = message;
logger.fatal(message);
return configFile;
}
if (configFile.projectToken.includes("PROJECT_TOKEN")) { //check to make sure the user changed it from the default
const message = `Please insert your projectToken. If you don't have an account, start a free trial: https://try.smartbear.com/visualtest`
configFile.fail = message;
logger.fatal(message);
return configFile;
}
}
if (!configFile.projectToken.split(/\/(.*)/s)[1]) { //check to make sure user added the auth part(~second-half) of projectToken
const message = `Please add your full projectToken for example -> ** projectToken: 'xxxxxxxx/xxxxxxxxxxxx' **`
configFile.fail = message;
logger.fatal(message);
return configFile;
}
await isValidProjectToken(configFile.projectToken);
await checkConnectionToBackend(configFile.projectToken);
logger.trace('config.projectToken: ' + configFile.projectToken);
configFile.projectId = configFile.projectToken.split('/')[0]; //take the first ~half to get the projectId
logger.trace('config.projectId: ' + configFile.projectId);
axios.defaults.headers.common['Authorization'] = `Bearer ${configFile.projectToken}`;
axios.defaults.headers.common['sbvt-client'] = 'sdk';
axios.defaults.headers.common['sbvt-sdk'] = `cypress`;
axios.defaults.headers.common['sbvt-sdk-version'] = package_json.version;
logger.trace(`axios.defaults.headers.common['Authorization']: ` + axios.defaults.headers.common['Authorization']);
configFile.sessionId = sessionId;
logger.trace('config.sessionId: ' + configFile.sessionId);
if (fromCommands.envFromCypress.testRunName) {
logger.info('TEST_RUN_NAME found in env flag from Cypress');
configFile.testRunName = fromCommands.envFromCypress.testRunName;
} else if (process.env.TEST_RUN_NAME) {
logger.info('TEST_RUN_NAME found in env variable');
configFile.testRunName = process.env.TEST_RUN_NAME;
} else if (!configFile.testRunName) {
//if testRunName not defined---use device / browser
let osPrettyName;
if (fromCommands.userAgentData.osName === 'macos') {
osPrettyName = 'macOS';
} else {
const str = fromCommands.userAgentData.osName;
osPrettyName = str.charAt(0).toUpperCase() + str.slice(1);
}
const str = fromCommands.userAgentData.browserName;
const browserPrettyName = str.charAt(0).toUpperCase() + str.slice(1);
const browserMajorVersion = fromCommands.userAgentData.browserVersion.split('.');
configFile.testRunName = `${osPrettyName} / ${browserPrettyName} ${browserMajorVersion[0]}`;
}
logger.trace('config.testRunName: ' + configFile.testRunName);
if (configFile.testRunName.length > 100) {
throw new Error(`The maximum size of testRunName is 100 characters. Received: ${configFile.testRunName.length} characters.`)
}
configFile.url = host;
configFile.websiteUrl = webUrl;
configFile.cypressVersion = usersCypress.version;
try {
const postResponse = await apiRequest('post', `${configFile.url}/api/v1/projects/${configFile.projectId}/testruns`, {
testRunName: configFile.testRunName,
sdk: 'cypress',
sdkVersion: `${package_json.version}/c${usersCypress.version}`,
...(!!process.env.SBVT_TEST_GROUP_ID ? {testGroupId: process.env.SBVT_TEST_GROUP_ID} : !!configFile.testGroupName ? {testGroupId: await getCreateTestGroupId(getUsersTestGroupName(configFile.testGroupName), configFile.projectToken)} : {}),
...(!!process.env.SBVT_SCM_COMMIT_ID ? {scmCommitId: process.env.SBVT_SCM_COMMIT_ID} : {}),
...(!!process.env.SBVT_SCM_BRANCH ? {scmBranch: process.env.SBVT_SCM_BRANCH} : {}),
});
configFile.testRunId = postResponse.data.testRunId;
logger.debug('config.testRunId: ' + configFile.testRunId);
} catch (error) {
const message = `Error with creating testRun: %o`
configFile.fail = message + error.message;
logger.fatal(message + error.message);
logger.trace(`Full error with creating testRun: %o`, error);
return configFile;
}
configFile.fail = false; //no errors in generating testRunId
logger.trace('—————————————————Successfully created a testRunId—————————————————');
}
return configFile;
}, async apiRequest({method, url, body, headers}) {
const response = {};
try {
const res = await apiRequest(method, url, body, headers);
response.data = res.data;
return response; // have to return the res.data or JSON issues
} catch (err) {
response.error = err;
return response;
}
}, async stitchImages({imageName, imagesPath, pageHeight, viewportWidth, viewportHeight}) {
const folderPath = imagesPath.substring(0, imagesPath.lastIndexOf(path.sep));
if (configFile.debug) {
// copy all the contents of tmp/folderPath to the configFile.debug folder
await fs.ensureDir(configFile.debug);
await fs.copy(folderPath, (configFile.debug + path.sep + imageName + "-fullPage"), {
overwrite: true,
});
}
let files = fs.readdirSync(folderPath);
const firstImage = await sharp(`${folderPath}/0.png`);
const metadata = await getMetaData(firstImage);
const pixelRatio = metadata.width / viewportWidth;
logger.debug(`pixelRatio (firstImage.metadata().width/viewportWidth): ${pixelRatio}, firstImage.metadata().width: ${metadata.width}, viewportWidth: ${viewportWidth}`);
if (pixelRatio !== 1) {
pageHeight = pageHeight * pixelRatio;
viewportWidth = viewportWidth * pixelRatio;
viewportHeight = viewportHeight * pixelRatio;
}
logger.info(`inside stitchImages()——pixelRatio: ${pixelRatio}, imageName: ${imageName}, pageHeight: ${pageHeight}, viewportWidth: ${viewportWidth}, viewportHeight: ${viewportHeight}, ${files.length} images.`);
//crop the last image
const toBeCropped = (files.length * (viewportHeight)) - (pageHeight);
if ((viewportHeight) - toBeCropped < 0) { //error handling in commands.js should prevent this from ever reaching
logger.warn(`imagesPath: ${imagesPath}`);
logger.warn(`pixelRatio: ${pixelRatio}, imageName: ${imageName}, imagesPath: ${imagesPath}, pageHeight: ${pageHeight}, viewportWidth: ${viewportWidth}, viewportHeight: ${viewportHeight}`);
logger.warn(`toBeCropped:${toBeCropped}, viewportHeight-toBeCropped:${viewportHeight - toBeCropped}`);
return "error";
}
logger.debug(`files.length:${files.length}, viewportHeight:${viewportHeight}, pageHeight:${pageHeight}, toBeCropped:${(files.length * viewportHeight) - pageHeight} ((files.length*viewportHeight)-pageHeight)`);
logger.debug(`calculations of what last image should be - viewportWidth:${viewportWidth} x height:${viewportHeight - toBeCropped} (viewportHeight-toBeCropped)`);
const bottomImagePath = `${folderPath}/${files.length - 1}.png`;
const bottomImage = await sharp(bottomImagePath);
if (viewportHeight - toBeCropped !== 0) {
// cropping last image
if (configFile.debug) {
//copy last image before cropping or deletion
const lastImageFileName = path.parse(path.basename(bottomImagePath)).name; //get the last image name without extension
await fs.copy(bottomImagePath, `${debugFolderPath}/${imageName}-fullPage/${lastImageFileName}-before-cropped-bottom.png`);
}
await bottomImage.extract({
top: 0, left: 0, width: viewportWidth, height: viewportHeight - toBeCropped,
})
if (configFile.debug) await bottomImage.toFile(`${debugFolderPath}/${imageName}-fullPage/${files.length - 1}.png`);
const bottomImageCroppedMetaData = await getMetaData(bottomImage);
logger.debug(`cropped last image width:${bottomImageCroppedMetaData.width} x height:${bottomImageCroppedMetaData.height}`);
} else {
//deleting last image (it is just a blank white image most likely)
logger.info(`stopped the cropping because: viewportHeight-toBeCropped = 0, removing the image at: ${bottomImagePath}`);
fs.unlinkSync(bottomImagePath);
files = fs.readdirSync(folderPath); //reading this folder again since an image has been deleted
}
//create the new blank fullpage image
const newImage = await sharp({
create: {
width: metadata.width,
height: pageHeight,
channels: metadata.channels,
background: {r: 0, g: 0, b: 0, alpha: 0}
}
})
const newImageData = [];
for (let i = 0; i < files.length; i++) {
// put all the viewports image path & top in an array for the sharp.composite funciton below
logger.trace(`adding ${i + 1}/${files.length} to newImageData`);
newImageData.push({input: `${folderPath}/${i}.png`, top: viewportHeight * i, left: 0});
}
const tmpFolderFilePath = `${folderPath.substring(0, folderPath.lastIndexOf(path.sep))}`;
const userPath = `${tmpFolderFilePath.substring(0, tmpFolderFilePath.lastIndexOf(path.sep))}${path.sep}${imageName}.png`;
// combine the images and write the new image to the users screenshot folder
await newImage.composite(newImageData).toFile(userPath);
logger.debug(`new stitched image has been written at: ${userPath}`);
if (configFile.debug) await fs.copy(userPath, `${debugFolderPath}/${imageName}-fullPage/${imageName}.png`); //copy the final image to debug folder if debug is true
await tmpFileCleanUp(tmpFolderFilePath)
const fullPageImageMetaData = await getMetaData(newImage);
return {
height: fullPageImageMetaData.height, width: fullPageImageMetaData.width, path: userPath
};
}, async lowerImageResolution({image, viewportWidth, tmpPath}) {
const imageToBeReducedPath = tmpPath.substring(0, tmpPath.lastIndexOf(path.sep));
const tmpFolderPath = path.dirname(imageToBeReducedPath)
logger.info(`lowerImageResolution() viewportWidth: ${viewportWidth}, imagePath: ${image}`);
const sharp = require('sharp');
try {
const buffer = await sharp(image)
.resize(viewportWidth)
.toBuffer();
await fs.ensureDir(imageToBeReducedPath)
const newFilePath = imageToBeReducedPath + '-final-reduced.png'
await sharp(image)
.resize(viewportWidth)
.toFile(newFilePath);
const metaData = await getMetaData(sharp(buffer));
return {
deletePath: tmpFolderPath, path: newFilePath, height: metaData.height, width: metaData.width,
};
} catch (error) {
console.error(error);
throw new Error(`Error with lowering image resolution: ${error}`);
}
}, async copy({path, imageName, imageType, reduced = false}) {
if (configFile.debug) await fs.copy(path, `${debugFolderPath}/${imageName}-${imageType}/${reduced ? `${imageName}-reduced` : imageName}.png`); //copy the final image to debug folder
return null;
}, async deleteImage({path}) {
if (configFile.debug) {
logger.info(`deleting: ${path}`);
}
fs.unlinkSync(path);
return null;
}, async deleteTmpFolder(path) {
await tmpFileCleanUp(path)
return null;
}, async logger({type, message}) { //this task is for printing logs to node console from the custom command
//todo this still isnt waiting to print the logger before returning
type === 'fatal' ? await logger.fatal(message) : type === 'error' ? await logger.error(message) : type === 'warn' ? await logger.warn(message) : type === 'info' ? await logger.info(message) : type === 'debug' ? await logger.debug(message) : type === 'trace' ? await logger.trace(message) : await logger.warn('error with the logger task');
return null;
}, async log({message}) {
console.log(message);
return null;
}, async getOsVersion() {
return os.release();
}, getToolkit() {
return domToolKit;
}, async getTestRunResult(timeoutMinutes = 3) {
const response = {};
try {
if (!configFile.url) {
response.error = "Cannot run this without first taking a sbvtCapture()";
return response;
}
let testRunUrl = `${configFile.url}/api/v1/projects/${configFile.projectId}/testruns/${configFile.testRunId}?expand=comparison-totals`;
let comparisonResponse = await apiRequest('get', testRunUrl);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
let i = 0;
while (comparisonResponse.data.comparisons.pending > 0 && i < (timeoutMinutes * 60) * 4) {
//default timeout after 3 minutes
comparisonResponse = await apiRequest('get', testRunUrl);
await sleep(250);
i++;
}
if (comparisonResponse.data.comparisons.pending) console.log(chalk.magenta('\tComparison results are still in pending state, get up to date results on VisualTest website.'));
response.data = comparisonResponse.data.comparisons;
return response;
} catch (error) {
logger.info(error.code);
logger.trace(error);
return null;
}
}, printReport(comparisonResponse) {
try {
process.stdout.write(`View your ${comparisonResponse.aggregate.failed + comparisonResponse.aggregate.passed} ${(comparisonResponse.aggregate.failed + comparisonResponse.aggregate.passed === 1 ? 'capture' : 'captures')} here: `);
console.log(chalk.blue(`${configFile.websiteUrl}/projects/${configFile.projectId}/testruns/${configFile.testRunId}/comparisons`));
if (comparisonResponse.status.new_image) console.log(chalk.yellow(`\t${comparisonResponse.status.new_image} new base ${comparisonResponse.status.new_image === 1 ? 'image' : 'images'}`));
if (comparisonResponse.status.unreviewed) console.log(chalk.red(`\t${comparisonResponse.status.unreviewed} image comparison ${comparisonResponse.status.unreviewed === 1 ? 'failure' : 'failures'} to review`));
if (comparisonResponse.status.passed) console.log(chalk.green(`\t${comparisonResponse.status.passed} image ${comparisonResponse.status.passed === 1 ? 'comparison' : 'comparisons'} passed`));
// return comparisonResponse; // no need to return data on this call
return null;
} catch (error) {
logger.warn(`Issue with printing report: ${error}`);
return null;
}
}
}
};
}
function makePluginExport() {
let setupNodeEventState = false;
return function pluginExport(pluginModule) {
if (pluginModule.exports.e2e && pluginModule.exports.e2e.setupNodeEvents) {
setupNodeEventState = true; //for not throwing an error if the user doesn't have setupNodeEvents SBVT-335
}
const pluginModuleExports = pluginModule.exports.e2e ? pluginModule.exports.e2e.setupNodeEvents : pluginModule.exports;
const setupNodeEvents = async function (...args) {
const globalHooks = makeGlobalRunHooks();
const [origOn] = args;
for (const [eventName, eventHandler] of Object.entries(globalHooks)) { // for-loop to add all functions on this file
origOn.call(this, eventName, eventHandler);
}
if (setupNodeEventState) {
//Needed for grabbing functions in cypress.config.js file
await pluginModuleExports(grabUserFunctions);
}
function grabUserFunctions(eventName, handler) {//Needed for grabbing functions in cypress.config.js file
return origOn.call(this, eventName, handler);
}
};
if (pluginModule.exports.e2e) {
logger.info(`in pluginModule.exports.e2e - most likely newer version of Cypress (+10) `);
pluginModule.exports.e2e.setupNodeEvents = setupNodeEvents;
} else if (pluginModule.exports.default && pluginModule.exports.default.e2e) {
logger.info(`in pluginModule.exports.default.e2e, due to cypress.config having 'export default defineConfig' - most likely TS `);
pluginModule.exports.default.e2e.setupNodeEvents = setupNodeEvents;
} else {
logger.info(`in pluginModule.exports - most likely older version of Cypress (-10) `);
pluginModule.exports = setupNodeEvents;
}
};
}
module.exports = makePluginExport({});
const getMetaData = async (image) => {
return await image.metadata();
}
const tmpFileCleanUp = async (folderPath) => {
fs.rmSync(folderPath, {recursive: true, force: true}); // comment this out to check viewports before stitched together, can be sync
logger.debug(`tmpFileCleanUp() removed the folder at: ${folderPath}`);
}
const getCreateTestGroupId = async (testGroupName, projectToken) => {
const projectId = projectToken?.split('/')[0];
const testGroupResponse = await apiRequest('post', `${host}/api/v1/projects/${projectId}/testgroups`, {
testGroupName,
},);
return testGroupResponse.data.testGroupId
}
const getUsersTestGroupName = (configFileTestGroupName) => {
const testGroupName = configFileTestGroupName ? configFileTestGroupName : !!process.env.SBVT_TEST_GROUP_NAME ? process.env.SBVT_TEST_GROUP_NAME : null
if (!testGroupName) return null; else {
if (typeof testGroupName !== 'string') throw new Error('testGroupName must be a string')
if (testGroupName.length > 100) throw new Error(`The maximum size of testGroupName is 100 characters. ${testGroupName.length} characters given.`)
return testGroupName
}
}