@testrevolution/bugbug-cli 7.9.0 → 7.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,7 +34,7 @@ bugbug remote <option>
34
34
 
35
35
  options:
36
36
  * list [test|suite|profile] [--no-wait] [--no-progress] [--debug]
37
- * run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable]
37
+ * run [test|suite] <string:testId|suiteId> [--no-wait] [--no-progress] [--debug] [--with-details] [--profile] [--variable] [--reporter] [--output-path]
38
38
  * status [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug]
39
39
  * result [test|suite] <string:testRunId|suiteRunId> [--no-progress] [--debug] [--with-details]
40
40
 
@@ -43,8 +43,10 @@ optional flags:
43
43
  * --no-progress - don't show progress spinner
44
44
  * --no-wait - exit immediately, don't wait for result
45
45
  * --profile <string:"profile name"> - run with specific profile
46
- * --variable <string:"varName=varValue"> - overrider variable during single run
46
+ * --variable <string:"varName=varValue"> - override variable during single run
47
47
  * --with-details - show result with details
48
+ * --reporter <"inline"|"junit"> - the name of the reporter to use (default: "inline")
49
+ * --output-path - the path to save the test report; relative to the current working directory
48
50
  ```
49
51
 
50
52
  Run test:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@testrevolution/bugbug-cli",
3
3
  "description": "BugBug CLI",
4
- "version": "7.9.0",
4
+ "version": "7.10.1",
5
5
  "keywords": [
6
6
  "automation",
7
7
  "cli",
@@ -22,13 +22,14 @@
22
22
  "axios": "^1.6.2",
23
23
  "console-table-printer": "^2.11.2",
24
24
  "dotenv": "^16.3.1",
25
+ "junit-xml": "^1.2.0",
25
26
  "minimist": "^1.2.8",
26
27
  "ora": "^5.4.1",
27
28
  "path": "^0.12.7",
28
29
  "validator": "^13.11.0"
29
30
  },
30
31
  "scripts": {
31
- "start": "NODE_ENV=development node ./bin/bugbug",
32
+ "start": "cross-env NODE_ENV=development node ./bin/bugbug",
32
33
  "test": "jest --coverage",
33
34
  "lint": "eslint --report-unused-disable-directives ./src/**/*.js",
34
35
  "lint:fix": "eslint ./src/**/*.js --fix",
@@ -42,6 +43,7 @@
42
43
  "@babel/eslint-parser": "^7.23.3",
43
44
  "@babel/plugin-proposal-throw-expressions": "^7.23.3",
44
45
  "babel-plugin-rewire": "^1.2.0",
46
+ "cross-env": "^7.0.3",
45
47
  "eslint": "^8.53.0",
46
48
  "eslint-config-airbnb-base": "^15.0.0",
47
49
  "eslint-plugin-import": "^2.29.0",
@@ -179,6 +179,8 @@ describe('commands module', () => {
179
179
  noprogress: true,
180
180
  nowait: false,
181
181
  withDetails: false,
182
+ reporter: 'inline',
183
+ outputPath: 'test-report.xml',
182
184
  });
183
185
  });
184
186
 
@@ -196,6 +198,8 @@ describe('commands module', () => {
196
198
  noprogress: true,
197
199
  nowait: false,
198
200
  withDetails: false,
201
+ reporter: 'inline',
202
+ outputPath: 'test-report.xml',
199
203
  });
200
204
  });
201
205
 
@@ -213,6 +217,8 @@ describe('commands module', () => {
213
217
  noprogress: true,
214
218
  nowait: false,
215
219
  withDetails: false,
220
+ reporter: 'inline',
221
+ outputPath: 'test-report.xml',
216
222
  });
217
223
  });
218
224
 
@@ -230,6 +236,8 @@ describe('commands module', () => {
230
236
  noprogress: true,
231
237
  nowait: false,
232
238
  withDetails: false,
239
+ reporter: 'inline',
240
+ outputPath: 'test-report.xml',
233
241
  });
234
242
  });
235
243
 
@@ -247,6 +255,8 @@ describe('commands module', () => {
247
255
  noprogress: true,
248
256
  nowait: false,
249
257
  withDetails: false,
258
+ reporter: 'inline',
259
+ outputPath: 'test-report.xml',
250
260
  });
251
261
  });
252
262
 
@@ -266,6 +276,8 @@ describe('commands module', () => {
266
276
  noprogress: true,
267
277
  nowait: false,
268
278
  withDetails: false,
279
+ reporter: 'inline',
280
+ outputPath: 'test-report.xml',
269
281
  });
270
282
  });
271
283
 
@@ -284,6 +296,8 @@ describe('commands module', () => {
284
296
  noprogress: true,
285
297
  nowait: false,
286
298
  withDetails: false,
299
+ reporter: 'inline',
300
+ outputPath: 'test-report.xml',
287
301
  });
288
302
  });
289
303
 
@@ -302,6 +316,8 @@ describe('commands module', () => {
302
316
  noprogress: true,
303
317
  nowait: true,
304
318
  withDetails: false,
319
+ reporter: 'inline',
320
+ outputPath: 'test-report.xml',
305
321
  });
306
322
  });
307
323
 
@@ -320,6 +336,8 @@ describe('commands module', () => {
320
336
  noprogress: true,
321
337
  nowait: true,
322
338
  withDetails: false,
339
+ reporter: 'inline',
340
+ outputPath: 'test-report.xml',
323
341
  });
324
342
  });
325
343
 
@@ -338,6 +356,8 @@ describe('commands module', () => {
338
356
  noprogress: true,
339
357
  nowait: true,
340
358
  withDetails: false,
359
+ reporter: 'inline',
360
+ outputPath: 'test-report.xml',
341
361
  });
342
362
  });
343
363
 
@@ -355,6 +375,8 @@ describe('commands module', () => {
355
375
  noprogress: false,
356
376
  nowait: false,
357
377
  withDetails: true,
378
+ reporter: 'inline',
379
+ outputPath: 'test-report.xml',
358
380
  });
359
381
  });
360
382
 
@@ -372,6 +394,8 @@ describe('commands module', () => {
372
394
  noprogress: true,
373
395
  nowait: false,
374
396
  withDetails: false,
397
+ reporter: 'inline',
398
+ outputPath: 'test-report.xml',
375
399
  });
376
400
  });
377
401
 
@@ -390,6 +414,8 @@ describe('commands module', () => {
390
414
  noprogress: true,
391
415
  nowait: false,
392
416
  withDetails: false,
417
+ reporter: 'inline',
418
+ outputPath: 'test-report.xml',
393
419
  });
394
420
  });
395
421
 
@@ -408,6 +434,8 @@ describe('commands module', () => {
408
434
  noprogress: true,
409
435
  nowait: true,
410
436
  withDetails: false,
437
+ reporter: 'inline',
438
+ outputPath: 'test-report.xml',
411
439
  });
412
440
  });
413
441
 
@@ -425,6 +453,8 @@ describe('commands module', () => {
425
453
  noprogress: true,
426
454
  nowait: false,
427
455
  withDetails: false,
456
+ reporter: 'inline',
457
+ outputPath: 'test-report.xml',
428
458
  });
429
459
  });
430
460
 
@@ -442,6 +472,8 @@ describe('commands module', () => {
442
472
  noprogress: true,
443
473
  nowait: false,
444
474
  withDetails: false,
475
+ reporter: 'inline',
476
+ outputPath: 'test-report.xml',
445
477
  });
446
478
  });
447
479
 
@@ -459,6 +491,8 @@ describe('commands module', () => {
459
491
  noprogress: true,
460
492
  nowait: false,
461
493
  withDetails: false,
494
+ reporter: 'inline',
495
+ outputPath: 'test-report.xml',
462
496
  });
463
497
  });
464
498
 
@@ -476,6 +510,8 @@ describe('commands module', () => {
476
510
  noprogress: true,
477
511
  nowait: false,
478
512
  withDetails: false,
513
+ reporter: 'inline',
514
+ outputPath: 'test-report.xml',
479
515
  });
480
516
  });
481
517
 
@@ -14,6 +14,9 @@ const {
14
14
  printTestRunInfo,
15
15
  printSuiteRunInfo,
16
16
  } = require('../utils/print');
17
+ const {
18
+ generateJunitReport,
19
+ } = require('../utils/testReports');
17
20
  const help = require('./help');
18
21
 
19
22
  const OBJECT_TYPES = [settings.TYPE_TEST, settings.TYPE_SUITE, settings.TYPE_PROFILE];
@@ -37,7 +40,10 @@ const getList = async (type, query, extraParams) => {
37
40
  };
38
41
 
39
42
  const getResult = async (type, id, extraParams) => {
40
- const { noprogress, withDetails } = extraParams;
43
+ const {
44
+ noprogress, withDetails, reporter, outputPath,
45
+ } = extraParams;
46
+
41
47
  const spinner = getSpinner(noprogress);
42
48
  spinner.start('Waiting for result...');
43
49
  const route = settings.API_ROUTING[`${type}Result`];
@@ -45,10 +51,19 @@ const getResult = async (type, id, extraParams) => {
45
51
  try {
46
52
  const result = await apiCall(path, route.method, {}, {});
47
53
  if (result) {
48
- if (type === settings.TYPE_TEST) {
49
- printTestRunInfo(spinner, result.data, withDetails);
50
- } else {
51
- printSuiteRunInfo(spinner, result.data, withDetails);
54
+ switch (reporter) {
55
+ case settings.REPORTER_TYPE.junit: {
56
+ await generateJunitReport(type, result.data, outputPath);
57
+ break;
58
+ }
59
+ default: {
60
+ if (type === settings.TYPE_TEST) {
61
+ printTestRunInfo(spinner, result.data, withDetails);
62
+ } else {
63
+ printSuiteRunInfo(spinner, result.data, withDetails);
64
+ }
65
+ break;
66
+ }
52
67
  }
53
68
  return getExitCode(result.data.status);
54
69
  }
@@ -60,7 +75,9 @@ const getResult = async (type, id, extraParams) => {
60
75
  };
61
76
 
62
77
  const checkStatus = async (type, id, extraParams) => {
63
- const { nowait, noprogress, withDetails } = extraParams;
78
+ const {
79
+ nowait, noprogress, withDetails, reporter,
80
+ } = extraParams;
64
81
  const spinner = getSpinner(noprogress);
65
82
  spinner.start('Waiting for result...');
66
83
  const route = settings.API_ROUTING[`${type}Status`];
@@ -73,7 +90,11 @@ const checkStatus = async (type, id, extraParams) => {
73
90
  } else {
74
91
  response = await apiCallPoll(path, route.method, {}, spinner, 0);
75
92
  }
76
- if (withDetails || settings.FAILED_STATUS.includes(response.data.status)) {
93
+ if (
94
+ reporter !== settings.REPORTER_TYPE.inline
95
+ || withDetails
96
+ || settings.FAILED_STATUS.includes(response.data.status)
97
+ ) {
77
98
  await getResult(type, id, extraParams);
78
99
  } else {
79
100
  printStatus(spinner, response.data.status);
@@ -138,6 +159,8 @@ const parseArgs = async (args) => {
138
159
  nowait: args.wait === false || args.nowait || false,
139
160
  noprogress: args.progress === false || args.noprogress || false,
140
161
  withDetails: args['with-details'] || false,
162
+ reporter: args.reporter || settings.REPORTER_TYPE.inline,
163
+ outputPath: args['output-path'] || 'test-report.xml',
141
164
  };
142
165
 
143
166
  return {
@@ -152,7 +175,7 @@ const validateArgs = async (args) => {
152
175
  return false;
153
176
  }
154
177
 
155
- const knownKeys = ['_', 'wait', 'nowait', 'noprogress', 'progress', 'with-details', 'debug', 'profile', 'variable', 'result-timeout'];
178
+ const knownKeys = ['_', 'wait', 'nowait', 'noprogress', 'progress', 'with-details', 'debug', 'profile', 'variable', 'result-timeout', 'reporter', 'output-path'];
156
179
  const unknownKeys = await getUnknownOptions(args, knownKeys);
157
180
  if (unknownKeys.length > 0) {
158
181
  console.error(`Unknown options: ${unknownKeys.join(', ')}`);
package/src/settings.js CHANGED
@@ -11,6 +11,7 @@ const ENV_FILE_PATH = path.resolve(__dirname, '..', `.env.${NODE_ENV}`);
11
11
  const USER_AGENT = `BugBug CLI ${VERSION}`;
12
12
  dotenv.config({ path: ENV_FILE_PATH });
13
13
 
14
+ // TODO: Fix this path due to Mac OS restrictions
14
15
  const CONFIG_DIR_PATH = path.join(os.homedir(), '.bugbug');
15
16
  const CONFIG_FILE_PATH = path.join(CONFIG_DIR_PATH, 'settings.json');
16
17
 
@@ -68,6 +69,11 @@ const API_ERROR_SUBSCRIPTION_EXCEPTION = 'subscriptionException';
68
69
  const API_ERROR_RUNNING_QUEUE_IS_FULL = 'runningQueueIsFullError';
69
70
  const API_ERROR_QUOTA_EXCEEDED = 'quotaExceeded';
70
71
 
72
+ const REPORTER_TYPE = {
73
+ junit: 'junit',
74
+ inline: 'inline',
75
+ };
76
+
71
77
  module.exports = {
72
78
  ACTION_HELP,
73
79
  ACTION_LIST,
@@ -95,4 +101,7 @@ module.exports = {
95
101
  TYPE_TEST,
96
102
  USER_AGENT,
97
103
  VERSION,
104
+ STATUS_ERROR,
105
+ STATUS_FAILED,
106
+ REPORTER_TYPE,
98
107
  };
@@ -43,10 +43,30 @@ const overrideSettings = async (args) => {
43
43
  }
44
44
  };
45
45
 
46
+ const parseTimeFromDuration = (duration) => {
47
+ const [hours, minutes = '0', secondsWithMs = '0.0'] = (duration || '').split(':');
48
+ const [seconds, milliSeconds] = secondsWithMs.split('.');
49
+ return {
50
+ hours: hours || '00', minutes, secondsWithMs, seconds, milliSeconds,
51
+ };
52
+ };
53
+
54
+ const getMillisecondsFromDuration = (duration) => {
55
+ const {
56
+ hours, minutes, seconds, milliSeconds,
57
+ } = parseTimeFromDuration(duration);
58
+ return (
59
+ parseInt(hours, 10) * 3600
60
+ + parseInt(minutes, 10) * 60
61
+ + parseInt(seconds, 10)
62
+ ) * 1000 + parseInt(milliSeconds, 10);
63
+ };
64
+
46
65
  module.exports = {
47
66
  getExitCode,
48
67
  getUnknownOptions,
49
68
  overrideSettings,
50
69
  parseVariables,
51
70
  printErrorResponse,
71
+ getMillisecondsFromDuration,
52
72
  };
@@ -0,0 +1,57 @@
1
+ const { getJunitXml } = require('junit-xml');
2
+ const fs = require('node:fs');
3
+ const path = require('node:path');
4
+
5
+ const settings = require('../settings');
6
+ const { getMillisecondsFromDuration } = require('./helper');
7
+
8
+ const getTestInJunitFormat = (testRun) => {
9
+ const errorDetails = testRun.errorCode ? {
10
+ message: `Error ${testRun.errorCode} occurred in step: ${testRun.details[0].step.type} (${testRun.details[0].step.id})`,
11
+ stack: testRun.errorCode,
12
+ } : undefined;
13
+
14
+ return {
15
+ id: testRun.id,
16
+ name: testRun.name,
17
+ time: getMillisecondsFromDuration(testRun.duration),
18
+ classname: null,
19
+ errors: testRun.status === settings.STATUS_ERROR ? [errorDetails] : [],
20
+ failures: testRun.status === settings.STATUS_FAILED ? [errorDetails] : [],
21
+ };
22
+ };
23
+
24
+ const getSuiteInJunitFormat = (suiteRun) => ({
25
+ id: suiteRun.id,
26
+ name: suiteRun.name,
27
+ time: getMillisecondsFromDuration(suiteRun.duration),
28
+ testCases: suiteRun.details.map((testRun) => getTestInJunitFormat(testRun)),
29
+ });
30
+
31
+ const generateJunitReport = async (type, result, outputPath) => {
32
+ const testReport = {};
33
+ testReport.name = 'BugBug Report';
34
+ testReport.time = getMillisecondsFromDuration(result.duration);
35
+ testReport.suites = [];
36
+
37
+ if (type === settings.TYPE_TEST) {
38
+ testReport.suites.push(
39
+ getSuiteInJunitFormat({
40
+ name: 'Single Test Run',
41
+ duration: result.duration,
42
+ details: [result],
43
+ }),
44
+ );
45
+ }
46
+
47
+ if (type === settings.TYPE_SUITE) {
48
+ testReport.suites.push(getSuiteInJunitFormat(result));
49
+ }
50
+
51
+ const junitReport = getJunitXml(testReport);
52
+ fs.writeFileSync(path.join(process.cwd(), outputPath), junitReport, { flag: 'w+' });
53
+ };
54
+
55
+ module.exports = {
56
+ generateJunitReport,
57
+ };