@testomatio/reporter 1.4.1-beta-1 → 1.4.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.
Files changed (45) hide show
  1. package/README.md +60 -57
  2. package/lib/adapter/codecept.js +86 -27
  3. package/lib/adapter/cucumber/current.js +17 -10
  4. package/lib/adapter/cucumber/legacy.js +5 -4
  5. package/lib/adapter/cucumber.js +2 -2
  6. package/lib/adapter/cypress-plugin/index.js +53 -26
  7. package/lib/adapter/jasmine.js +2 -2
  8. package/lib/adapter/jest.js +48 -10
  9. package/lib/adapter/mocha.js +90 -10
  10. package/lib/adapter/playwright.js +68 -18
  11. package/lib/adapter/webdriver.js +47 -2
  12. package/lib/bin/reportXml.js +21 -16
  13. package/lib/bin/startTest.js +12 -12
  14. package/lib/client.js +112 -52
  15. package/lib/config.js +34 -0
  16. package/lib/constants.js +28 -1
  17. package/lib/data-storage.js +203 -0
  18. package/lib/fileUploader.js +66 -53
  19. package/lib/junit-adapter/adapter.js +0 -2
  20. package/lib/junit-adapter/csharp.js +3 -4
  21. package/lib/junit-adapter/index.js +3 -3
  22. package/lib/junit-adapter/java.js +11 -11
  23. package/lib/junit-adapter/javascript.js +1 -2
  24. package/lib/junit-adapter/python.js +12 -14
  25. package/lib/junit-adapter/ruby.js +1 -2
  26. package/lib/pipe/csv.js +35 -32
  27. package/lib/pipe/github.js +4 -3
  28. package/lib/pipe/gitlab.js +17 -10
  29. package/lib/pipe/html.js +361 -0
  30. package/lib/pipe/index.js +3 -1
  31. package/lib/pipe/testomatio.js +230 -51
  32. package/lib/reporter-functions.js +12 -9
  33. package/lib/reporter.js +8 -12
  34. package/lib/services/artifacts.js +57 -0
  35. package/lib/services/index.js +13 -0
  36. package/lib/{storages/key-value-storage.js → services/key-values.js} +19 -19
  37. package/lib/{storages → services}/logger.js +58 -37
  38. package/lib/template/emptyData.svg +23 -0
  39. package/lib/template/testomatio.hbs +1421 -0
  40. package/lib/utils/pipe_utils.js +73 -79
  41. package/lib/utils/utils.js +53 -40
  42. package/lib/xmlReader.js +113 -105
  43. package/package.json +13 -7
  44. package/lib/storages/artifact-storage.js +0 -70
  45. package/lib/storages/data-storage.js +0 -307
@@ -11,7 +11,6 @@ const readFile = util.promisify(fs.readFile);
11
11
  const stat = util.promisify(fs.stat);
12
12
  const chalk = require('chalk');
13
13
  const { randomUUID } = require('crypto');
14
- const memoize = require('lodash.memoize');
15
14
 
16
15
  const { APP_PREFIX } = require('./constants');
17
16
 
@@ -21,6 +20,7 @@ const keys = [
21
20
  'S3_BUCKET',
22
21
  'S3_ACCESS_KEY_ID',
23
22
  'S3_SECRET_ACCESS_KEY',
23
+ 'S3_SESSION_TOKEN',
24
24
  'TESTOMATIO_DISABLE_ARTIFACTS',
25
25
  'TESTOMATIO_PRIVATE_ARTIFACTS',
26
26
  'S3_FORCE_PATH_STYLE',
@@ -43,8 +43,12 @@ function getConfig() {
43
43
  }
44
44
 
45
45
  function getMaskedConfig() {
46
- return Object.fromEntries(Object.entries(getConfig())
47
- .map(([key, value]) => [key, key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value]));
46
+ return Object.fromEntries(
47
+ Object.entries(getConfig()).map(([key, value]) => [
48
+ key,
49
+ key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
50
+ ]),
51
+ );
48
52
  }
49
53
 
50
54
  let isEnabled;
@@ -71,7 +75,8 @@ const _getFileExtBase64 = str => {
71
75
  };
72
76
 
73
77
  const _getS3Config = () => {
74
- const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = getConfig();
78
+ const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
79
+ getConfig();
75
80
 
76
81
  const cfg = {
77
82
  region: S3_REGION,
@@ -79,35 +84,35 @@ const _getS3Config = () => {
79
84
  accessKeyId: S3_ACCESS_KEY_ID,
80
85
  secretAccessKey: S3_SECRET_ACCESS_KEY,
81
86
  s3ForcePathStyle: S3_FORCE_PATH_STYLE,
82
- }
87
+ },
83
88
  };
84
89
 
90
+ if (S3_SESSION_TOKEN) {
91
+ cfg.credentials.sessionToken = S3_SESSION_TOKEN;
92
+ }
93
+
85
94
  if (S3_ENDPOINT) {
86
95
  cfg.endpoint = S3_ENDPOINT;
87
96
  }
88
97
 
89
98
  return cfg;
90
- }
99
+ };
91
100
 
92
101
  const uploadUsingS3 = async (filePath, runId) => {
93
102
  let ContentType;
94
103
  let Key;
95
- let s3FileLocation;
96
104
 
97
105
  if (typeof filePath === 'object') {
98
- ContentType = filePath.type;
99
- filePath = filePath.path;
100
- Key = filePath.name;
106
+ ContentType = filePath?.type;
107
+ filePath = filePath?.path;
108
+ Key = filePath?.name;
101
109
  }
102
110
 
103
- const {
104
- TESTOMATIO_PRIVATE_ARTIFACTS,
105
- S3_BUCKET
106
- } = getConfig();
111
+ const { TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
107
112
 
108
113
  try {
109
114
  debug('S3 config', getMaskedConfig());
110
- debug('Try upload', filePath, 'to', S3_BUCKET);
115
+ debug('Started upload', filePath, 'to ', S3_BUCKET);
111
116
 
112
117
  // Verification that the file was actually created: 20 attempts of 0.5 second => 10sec
113
118
  const isFileExist = await checkFileExists(filePath, 20, 500);
@@ -117,7 +122,7 @@ const uploadUsingS3 = async (filePath, runId) => {
117
122
  return;
118
123
  }
119
124
 
120
- debug('Yes, ', filePath, 'is exist.');
125
+ debug('File: ', filePath, ' exists');
121
126
 
122
127
  const fileData = await readFile(filePath);
123
128
 
@@ -128,7 +133,9 @@ const uploadUsingS3 = async (filePath, runId) => {
128
133
  if (!S3_BUCKET || !fileData) {
129
134
  console.log(
130
135
  APP_PREFIX,
131
- chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
136
+ chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
137
+ getMaskedConfig(),
138
+ );
132
139
  return;
133
140
  }
134
141
 
@@ -144,20 +151,16 @@ const uploadUsingS3 = async (filePath, runId) => {
144
151
 
145
152
  const out = new Upload({
146
153
  client: s3,
147
- params
154
+ params,
148
155
  });
149
156
 
150
- const result = await out.done();
157
+ const link = await getS3LocationLink(out);
151
158
 
152
- s3FileLocation = result?.Location;
153
- debug('Uploaded location', s3FileLocation);
159
+ debug(`Succesfully uploaded ${filePath} => ${S3_BUCKET}/${Key} | URL: ${link}`);
154
160
 
155
- return s3FileLocation || console.log(
156
- chalk.bold.red(`Problems getting the S3 artifact's link , contact the ${APP_PREFIX} team!`)
157
- );
158
- }
159
- catch (e) {
160
- console.log(e);
161
+ return link;
162
+ } catch (e) {
163
+ debug('S3 file uploading error: ', e);
161
164
 
162
165
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
163
166
 
@@ -174,11 +177,8 @@ const uploadUsingS3 = async (filePath, runId) => {
174
177
  };
175
178
 
176
179
  const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
177
- let s3BufferLocation;
178
-
179
- const {
180
- S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
181
- } = getConfig();
180
+ const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } =
181
+ getConfig();
182
182
 
183
183
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
184
184
 
@@ -208,19 +208,12 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
208
208
  Key,
209
209
  Body: buffer,
210
210
  ACL,
211
- }
211
+ },
212
212
  });
213
- const result = await out.done();
214
-
215
- s3BufferLocation = result?.Location;
216
- debug('Uploaded location', s3BufferLocation);
217
213
 
218
- return s3BufferLocation || console.log(
219
- chalk.bold.red(`Problems getting the S3 artifact's link , contact the ${APP_PREFIX} team!`)
220
- );
221
- }
222
- catch (e) {
223
- console.log(e);
214
+ return await getS3LocationLink(out);
215
+ } catch (e) {
216
+ debug('S3 buffer uploading error: ', e);
224
217
 
225
218
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
226
219
 
@@ -242,7 +235,9 @@ const uploadFileByPath = async (filePath, runId) => {
242
235
  return uploadUsingS3(filePath, runId);
243
236
  }
244
237
  } catch (e) {
245
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
238
+ debug(e);
239
+
240
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
246
241
  }
247
242
  };
248
243
 
@@ -252,7 +247,9 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
252
247
  return uploadUsingS3AsBuffer(buffer, fileName, runId);
253
248
  }
254
249
  } catch (e) {
255
- console.error(chalk.red('Error occurred while uploading artifacts'), e);
250
+ debug(e);
251
+
252
+ console.error(chalk.red('Error occurred while uploading artifacts! '), e);
256
253
  }
257
254
  };
258
255
 
@@ -268,26 +265,42 @@ const checkFileExists = async (filePath, attempts = 5, intervalMs = 500) => {
268
265
 
269
266
  try {
270
267
  await promiseRetry(
271
- {
268
+ {
272
269
  retries: attempts,
273
- minTimeout: intervalMs
270
+ minTimeout: intervalMs,
274
271
  },
275
- checkFile
272
+ checkFile,
276
273
  );
277
274
 
278
275
  return true;
279
276
  } catch (err) {
280
- console.error(
281
- chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`)
282
- );
277
+ console.error(chalk.yellow(`File ${filePath} was not found or did not have time to be generated...`));
283
278
 
284
279
  return false;
285
280
  }
286
281
  };
287
282
 
283
+ const getS3LocationLink = async out => {
284
+ const response = await out.done();
285
+
286
+ let s3Location = response?.Location;
287
+
288
+ if (!s3Location) {
289
+ // TODO: out: a fallback case - remove after deeper testing
290
+ s3Location = out?.singleUploadResult?.Location;
291
+ debug('Uploaded singleUploadResult.Location', s3Location);
292
+
293
+ if (!s3Location) {
294
+ throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
295
+ }
296
+ }
297
+
298
+ return s3Location;
299
+ };
300
+
288
301
  module.exports = {
289
- uploadFileByPath: memoize(uploadFileByPath),
290
- uploadFileAsBuffer: memoize(uploadFileAsBuffer),
302
+ uploadFileByPath,
303
+ uploadFileAsBuffer,
291
304
  isArtifactsEnabled,
292
305
  resetConfig,
293
306
  };
@@ -1,5 +1,4 @@
1
1
  class Adapter {
2
-
3
2
  constructor(opts) {
4
3
  this.opts = opts;
5
4
  }
@@ -19,7 +18,6 @@ class Adapter {
19
18
  formatMessage(t) {
20
19
  return t.message;
21
20
  }
22
-
23
21
  }
24
22
 
25
23
  module.exports = Adapter;
@@ -1,12 +1,11 @@
1
1
  const Adapter = require('./adapter');
2
2
 
3
3
  class CSharpAdapter extends Adapter {
4
-
5
4
  formatTest(t) {
6
5
  const title = t.title.replace(/\(.*?\)/, '').trim();
7
6
  const example = t.title.match(/\((.*?)\)/);
8
- if (example) t.example = { ...example[1].split(',')};
9
- const suite = t.suite_title.split('.')
7
+ if (example) t.example = { ...example[1].split(',') };
8
+ const suite = t.suite_title.split('.');
10
9
  t.suite_title = suite.pop();
11
10
  t.file = suite.join('/');
12
11
  t.title = title.trim();
@@ -14,4 +13,4 @@ class CSharpAdapter extends Adapter {
14
13
  }
15
14
  }
16
15
 
17
- module.exports = CSharpAdapter;
16
+ module.exports = CSharpAdapter;
@@ -6,8 +6,6 @@ const RubyAdapter = require('./ruby');
6
6
  const CSharpAdapter = require('./csharp');
7
7
 
8
8
  function AdapterFactory(lang, opts) {
9
- if (!lang) return new Adapter(opts);
10
-
11
9
  if (lang === 'java') {
12
10
  return new JavaAdapter(opts);
13
11
  }
@@ -23,6 +21,8 @@ function AdapterFactory(lang, opts) {
23
21
  if (lang === 'c#' || lang === 'csharp') {
24
22
  return new CSharpAdapter(opts);
25
23
  }
24
+
25
+ return new Adapter(opts);
26
26
  }
27
27
 
28
- module.exports = AdapterFactory;
28
+ module.exports = AdapterFactory;
@@ -2,14 +2,13 @@ const path = require('path');
2
2
  const Adapter = require('./adapter');
3
3
 
4
4
  class JavaAdapter extends Adapter {
5
-
6
5
  getFilePath(t) {
7
- const fileName = namespaceToFileName(t.suite_title)
6
+ const fileName = namespaceToFileName(t.suite_title);
8
7
  return this.opts.javaTests + path.sep + fileName;
9
8
  }
10
9
 
11
10
  formatTest(t) {
12
- const fileParts = t.suite_title.split('.')
11
+ const fileParts = t.suite_title.split('.');
13
12
 
14
13
  t.file = namespaceToFileName(t.suite_title);
15
14
  t.title = t.title.split('(')[0];
@@ -21,19 +20,21 @@ class JavaAdapter extends Adapter {
21
20
  const params = paramMatches.map((_match, index) => `param${index + 1}`);
22
21
  if (params.length === 1) params[0] = 'param';
23
22
  let paramIndex = 0;
24
-
23
+
25
24
  t.title = t.title.replace(/: \[(.*?)\]/g, () => {
26
- if (params.length < 2) return `\${param}`
25
+ if (params.length < 2) return `\${param}`;
27
26
  const paramName = params[paramIndex] || `param${paramIndex + 1}`;
28
27
  paramIndex++;
29
28
  return `\${${paramName}}`;
30
29
  });
31
30
  const example = {};
32
- paramMatches.forEach((match, index) => { example[params[index]] = match.replace(/[[\]]/g, '') });
31
+ paramMatches.forEach((match, index) => {
32
+ example[params[index]] = match.replace(/[[\]]/g, '');
33
+ });
33
34
  t.example = example;
34
35
  }
35
36
 
36
- t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
37
+ t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
37
38
  return t;
38
39
  }
39
40
 
@@ -49,10 +50,9 @@ class JavaAdapter extends Adapter {
49
50
  }
50
51
 
51
52
  function namespaceToFileName(fileName) {
52
- const fileParts = fileName.split('.')
53
- fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '')
54
- return `${fileParts.join(path.sep) }.java`;
55
-
53
+ const fileParts = fileName.split('.');
54
+ fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
55
+ return `${fileParts.join(path.sep)}.java`;
56
56
  }
57
57
 
58
58
  module.exports = JavaAdapter;
@@ -3,12 +3,11 @@ const path = require('path');
3
3
  const Adapter = require('./adapter');
4
4
 
5
5
  class JavaScriptAdapter extends Adapter {
6
-
7
6
  formatStack(t) {
8
7
  let stack = super.formatStack(t);
9
8
 
10
9
  try {
11
- const error = new Error(stack.split('\n')[0])
10
+ const error = new Error(stack.split('\n')[0]);
12
11
  error.stack = stack;
13
12
  const record = createCallsiteRecord({
14
13
  forError: error,
@@ -3,7 +3,6 @@ const fs = require('fs');
3
3
  const Adapter = require('./adapter');
4
4
 
5
5
  class PythonAdapter extends Adapter {
6
-
7
6
  getFilePath(t) {
8
7
  let fileName = namespaceToFileName(t.suite_title, { checkFile: true });
9
8
  if (!fileName) fileName = namespaceToFileName(t.suite_title, { checkFile: false });
@@ -11,34 +10,33 @@ class PythonAdapter extends Adapter {
11
10
  }
12
11
 
13
12
  formatTest(t) {
14
- const fileParts = t.suite_title.split('.')
13
+ const fileParts = t.suite_title.split('.');
15
14
  const example = t.title.match(/\[(.*)\]/)?.[1];
16
- if (example) t.example = { "#": example }
15
+ if (example) t.example = { '#': example };
17
16
 
18
17
  t.file = namespaceToFileName(t.suite_title);
19
18
  t.title = t.title.split('[')[0];
20
- t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ')
19
+ t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
21
20
  return t;
22
21
  }
23
22
 
24
23
  formatMessage(t) {
25
24
  return t.message.split('&#10;')[0];
26
25
  }
27
-
28
26
  }
29
27
 
30
28
  function namespaceToFileName(fileName, opts = {}) {
31
- const fileParts = fileName.split('.')
29
+ const fileParts = fileName.split('.');
32
30
 
33
- while (fileParts.length > 0) {
34
- const file = `${fileParts.join(path.sep) }.py`;
35
- if (!opts.checkFile) return file;
36
- if (fs.existsSync(`${fileParts.join(path.sep) }.py`)) {
37
- return file;
38
- }
39
- fileParts.pop();
31
+ while (fileParts.length > 0) {
32
+ const file = `${fileParts.join(path.sep)}.py`;
33
+ if (!opts.checkFile) return file;
34
+ if (fs.existsSync(`${fileParts.join(path.sep)}.py`)) {
35
+ return file;
40
36
  }
41
- return null;
37
+ fileParts.pop();
38
+ }
39
+ return null;
42
40
  }
43
41
 
44
42
  module.exports = PythonAdapter;
@@ -1,10 +1,9 @@
1
1
  const Adapter = require('./adapter');
2
2
 
3
3
  class RubyAdapter extends Adapter {
4
-
5
4
  formatStack(t) {
6
5
  const stack = super.formatStack(t);
7
- return stack.replace(/\[(.*?:.\d*)\]/g, '\n$1')
6
+ return stack.replace(/\[(.*?:.\d*)\]/g, '\n$1');
8
7
  }
9
8
  }
10
9
 
package/lib/pipe/csv.js CHANGED
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const csvWriter = require('csv-writer');
5
5
  const chalk = require('chalk');
6
6
  const merge = require('lodash.merge');
7
- const { isSameTest, getCurrentDateTime } = require('../utils/utils');
7
+ const { isSameTest, getCurrentDateTime, ansiRegExp } = require('../utils/utils');
8
8
  const { CSV_HEADERS } = require('../constants');
9
9
 
10
10
  /**
@@ -14,35 +14,37 @@ const { CSV_HEADERS } = require('../constants');
14
14
  * @implements {Pipe}
15
15
  */
16
16
  class CsvPipe {
17
-
18
17
  constructor(params, store) {
19
18
  this.store = store || {};
20
19
  this.title = params.title || process.env.TESTOMATIO_TITLE;
21
20
  this.results = [];
22
21
 
23
22
  this.outputDir = 'export';
23
+ this.defaultReportName = 'report.csv';
24
24
  this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
25
- this.isEnabled = !!this.csvFilename
26
- this.isCsvSave = false;
27
-
28
- if (this.csvFilename !== undefined && this.csvFilename.split('.').length > 0) {
29
- this.isCsvSave = true;
30
-
31
- if (this.csvFilename.split('.')[0] === 'report') {
32
- this.outputFile = path.resolve(process.cwd(), this.outputDir, 'report.csv');
33
- } else {
34
- this.outputFile = path.resolve(
35
- process.cwd(),
36
- this.outputDir,
37
- `${getCurrentDateTime()}_${this.csvFilename.split('.')[0]}.csv`,
38
- );
25
+ this.isEnabled = false;
26
+
27
+ if (this.csvFilename) {
28
+ const filenameParts = this.csvFilename.split('.');
29
+
30
+ if (filenameParts.length > 0) {
31
+ this.isEnabled = true;
32
+ const baseFilename = filenameParts[0];
33
+ const defaultOutputFile = path.resolve(process.cwd(), this.outputDir, this.defaultReportName);
34
+
35
+ const outputFile =
36
+ baseFilename === this.defaultReportName.split('.')[0] // = 'report'
37
+ ? defaultOutputFile
38
+ : path.resolve(process.cwd(), this.outputDir, `${getCurrentDateTime()}_${baseFilename}.csv`);
39
+
40
+ this.outputFile = outputFile;
39
41
  }
40
42
  }
41
43
  }
42
44
 
43
45
  // TODO: to using SET opts as argument => prepareRun(opts)
44
46
  async prepareRun() {}
45
-
47
+
46
48
  async createRun() {
47
49
  // empty
48
50
  }
@@ -70,22 +72,22 @@ class CsvPipe {
70
72
  this.checkExportDir();
71
73
 
72
74
  if (!this.outputFile) {
73
- console.log(this, chalk.yellow(`CSV file is not set, ignoring`));
75
+ console.log(chalk.yellow(`⚠️ CSV file is not set, ignoring`));
74
76
  return;
75
77
  }
76
78
 
77
- console.log(this, chalk.yellow(`The test results will be added to the csv. It will take some time...`));
78
- // Create csv writer object
79
- const writer = csvWriter.createObjectCsvWriter({
80
- path: this.outputFile,
81
- header: headers,
82
- });
83
- // Save csv file based on the current data
79
+ console.log(chalk.yellow(`⏳ The test results will be added to the csv. It will take some time...`));
80
+
84
81
  try {
85
- await writer.writeRecords(data);
86
- console.log(this, chalk.green(`Recording completed! You can check the result in file = ${this.outputFile}`));
82
+ // Create csv writer object
83
+ const writer = csvWriter.createObjectCsvWriter({
84
+ path: this.outputFile,
85
+ header: headers,
86
+ });
87
+ // Save csv file based on the current data
88
+ return await writer.writeRecords(data);
87
89
  } catch (e) {
88
- console.log('Unknown error: ', e);
90
+ console.log('Unknown csv error: ', e);
89
91
  }
90
92
  }
91
93
 
@@ -109,8 +111,8 @@ class CsvPipe {
109
111
  suite_title,
110
112
  title,
111
113
  status,
112
- message,
113
- stack,
114
+ message: message.replace(ansiRegExp(), ''),
115
+ stack: stack.replace(ansiRegExp(), ''),
114
116
  });
115
117
  }
116
118
 
@@ -123,8 +125,9 @@ class CsvPipe {
123
125
 
124
126
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
125
127
  // Save results based on the default headers
126
- if (this.isCsvSave) {
127
- this.saveToCsv(this.results, CSV_HEADERS);
128
+ if (this.isEnabled) {
129
+ await this.saveToCsv(this.results, CSV_HEADERS);
130
+ console.log(chalk.green(`🗃️ Recording completed! You can check the result in file = ${this.outputFile}`));
128
131
  }
129
132
  }
130
133
 
@@ -4,7 +4,7 @@ const chalk = require('chalk');
4
4
  const humanizeDuration = require('humanize-duration');
5
5
  const merge = require('lodash.merge');
6
6
  const { Octokit } = require('@octokit/rest');
7
- const { APP_PREFIX } = require('../constants');
7
+ const { APP_PREFIX, testomatLogoURL } = require('../constants');
8
8
  const { ansiRegExp, isSameTest } = require('../utils/utils');
9
9
  const { statusEmoji, fullName } = require('../utils/pipe_utils');
10
10
 
@@ -77,8 +77,9 @@ class GitHubPipe {
77
77
 
78
78
  let summary = `${this.hiddenCommentData}
79
79
 
80
- | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
81
- statusEmoji(runParams.status,)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
80
+ | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
81
+ runParams.status,
82
+ )} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
82
83
  | --- | --- |
83
84
  | Tests | ✔️ **${this.tests.length}** tests run |
84
85
  | Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
@@ -4,7 +4,7 @@ const chalk = require('chalk');
4
4
  const humanizeDuration = require('humanize-duration');
5
5
  const merge = require('lodash.merge');
6
6
  const path = require('path');
7
- const { APP_PREFIX } = require('../constants');
7
+ const { APP_PREFIX, testomatLogoURL } = require('../constants');
8
8
  const { ansiRegExp, isSameTest } = require('../utils/utils');
9
9
  const { statusEmoji, fullName } = require('../utils/pipe_utils');
10
10
 
@@ -23,7 +23,7 @@ class GitLabPipe {
23
23
  this.store = store;
24
24
  this.tests = [];
25
25
  // GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
26
- this.token = params.GITLAB_PAT || this.ENV.GITLAB_PAT;
26
+ this.token = params.GITLAB_PAT || process.env.GITLAB_PAT || this.ENV.GITLAB_PAT;
27
27
  this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
28
28
 
29
29
  debug(
@@ -77,17 +77,24 @@ class GitLabPipe {
77
77
 
78
78
  // constructing the table
79
79
  let summary = `${this.hiddenCommentData}
80
-
81
- | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
82
- statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
80
+
81
+ | [![Testomat.io Report](${testomatLogoURL})](https://testomat.io) | ${statusEmoji(
82
+ runParams.status,
83
+ )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
83
84
  | --- | --- |
84
85
  | Tests | ✔️ **${this.tests.length}** tests run |
85
86
  | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
86
- 'passed',
87
- )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
88
- | Duration | 🕐 **${humanizeDuration(parseInt(this.tests.reduce((a, t) => a + (t.run_time || 0), 0), 10), {
89
- maxDecimalPoints: 0,
90
- })}** |
87
+ 'passed',
88
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
89
+ | Duration | 🕐 **${humanizeDuration(
90
+ parseInt(
91
+ this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
92
+ 10,
93
+ ),
94
+ {
95
+ maxDecimalPoints: 0,
96
+ },
97
+ )}** |
91
98
  `;
92
99
 
93
100
  if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {