@testomatio/reporter 0.8.0-beta.9 → 0.8.2-beta.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/lib/client.js CHANGED
@@ -1,29 +1,30 @@
1
- const axios = require('axios');
1
+ const debug = require('debug')('@testomatio/reporter:client');
2
2
  const createCallsiteRecord = require('callsite-record');
3
3
  const { sep, join } = require('path');
4
4
  const fs = require('fs');
5
5
  const chalk = require('chalk');
6
+ const { randomUUID } = require('crypto');
6
7
  const upload = require('./fileUploader');
7
- const { PASSED, FAILED, FINISHED, APP_PREFIX } = require('./constants');
8
+ const { APP_PREFIX } = require('./constants');
8
9
  const pipesFactory = require('./pipe');
9
10
 
10
- const { TESTOMATIO_ENV } = process.env;
11
+ /**
12
+ * @typedef {import('../types').TestData} TestData
13
+ * @typedef {import('../types').RunStatus} RunStatus
14
+ */
11
15
 
12
-
13
- class TestomatClient {
16
+ class Client {
14
17
  /**
15
18
  * Create a Testomat client instance
16
19
  *
17
20
  * @param {*} params
18
21
  */
19
22
  constructor(params = {}) {
20
- this.title = params.title || process.env.TESTOMATIO_TITLE;
21
- this.env = TESTOMATIO_ENV;
22
23
  this.parallel = params.parallel;
23
- const store = {}
24
+ const store = {};
25
+ this.uuid = randomUUID();
24
26
  this.pipes = pipesFactory(params, store);
25
27
  this.queue = Promise.resolve();
26
- this.axios = axios.create();
27
28
  this.totalUploaded = 0;
28
29
  this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
29
30
  console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
@@ -32,11 +33,11 @@ class TestomatClient {
32
33
  /**
33
34
  * Used to create a new Test run
34
35
  *
35
- * @returns {Promise} - resolves to Run id which should be used to update / add test
36
+ * @returns {Promise<void>} - resolves to Run id which should be used to update / add test
36
37
  */
37
38
  createRun() {
38
39
  // all pipes disabled, skipping
39
- if (!this.pipes.filter(p => p.isEnabled).length) return;
40
+ if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
40
41
 
41
42
  const runParams = {
42
43
  title: this.title,
@@ -46,27 +47,39 @@ class TestomatClient {
46
47
 
47
48
  global.testomatioArtifacts = [];
48
49
 
49
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))));
50
-
50
+ this.queue = this.queue
51
+ .then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
52
+ .catch(err => console.log(APP_PREFIX, err))
53
+ .then(() => undefined); // fixes return type
54
+ debug('Run', this.queue);
51
55
  return this.queue;
52
56
  }
53
57
 
54
58
  /**
55
- * Used to add a new test to Run instance
56
- *
57
- * @returns {Promise}
59
+ * Updates test status and its data
60
+ *
61
+ * @param {string|undefined} status
62
+ * @param {TestData} [testData]
63
+ * @param {string[]} [storeArtifacts]
64
+ * @returns {Promise<void>}
58
65
  */
59
- async addTestRun(testId, status, testData = {}) {
66
+ async addTestRun(status, testData, storeArtifacts = []) {
60
67
  // all pipes disabled, skipping
61
68
  if (!this.pipes.filter(p => p.isEnabled).length) return;
62
69
 
70
+ if (!testData) testData = {
71
+ title: 'Unknown test',
72
+ suite_title: 'Unknown suite',
73
+ }
74
+
63
75
  const {
64
- error = '',
76
+ error = null,
65
77
  time = '',
66
78
  example = null,
67
79
  files = [],
68
80
  filesBuffers = [],
69
81
  steps,
82
+ code = null,
70
83
  title,
71
84
  suite_title,
72
85
  suite_id,
@@ -76,33 +89,37 @@ class TestomatClient {
76
89
 
77
90
  const uploadedFiles = [];
78
91
 
79
- if (testId) testData.test_id = testId;
80
-
81
92
  let stack = '';
82
93
 
83
94
  if (error) {
84
- stack = this.formatError(error);
85
- message = error.message;
95
+ stack = this.formatError(error) || '';
96
+ message = error?.message;
86
97
  }
87
98
  if (steps) {
88
99
  stack = this.formatSteps(stack, steps);
89
100
  }
90
101
 
102
+ if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
103
+ debug("CLIENT storeArtifact", storeArtifacts);
104
+ files.push(...storeArtifacts);
105
+ }
106
+
91
107
  if (Array.isArray(global.testomatioArtifacts)) {
108
+ debug("CLIENT global[testomatioArtifacts]", global.testomatioArtifacts);
92
109
  files.push(...global.testomatioArtifacts);
93
110
  global.testomatioArtifacts = [];
94
111
  }
95
112
 
96
113
  for (const file of files) {
97
- uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
114
+ uploadedFiles.push(upload.uploadFileByPath(file, this.uuid));
98
115
  }
99
116
 
100
117
  for (const [idx, buffer] of filesBuffers.entries()) {
101
118
  const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
102
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
119
+ uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.uuid));
103
120
  }
104
121
 
105
- const artifacts = await Promise.all(uploadedFiles)
122
+ const artifacts = await Promise.all(uploadedFiles);
106
123
 
107
124
  global.testomatioArtifacts = [];
108
125
 
@@ -114,7 +131,8 @@ class TestomatClient {
114
131
  status,
115
132
  stack,
116
133
  example,
117
- title,
134
+ code,
135
+ title,
118
136
  suite_title,
119
137
  suite_id,
120
138
  test_id,
@@ -123,36 +141,42 @@ class TestomatClient {
123
141
  artifacts,
124
142
  };
125
143
 
126
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.addTest(data))));
144
+ this.queue = this.queue
145
+ .then(() => Promise.all(this.pipes.map(p => p.addTest(data))))
146
+ .catch(err => console.log(APP_PREFIX, err))
147
+ .then(() => undefined);
127
148
  return this.queue;
128
149
  }
129
150
 
130
151
  /**
131
- * Update run status
132
152
  *
133
- * @returns {Promise}
153
+ * Updates the status of the current test run and finishes the run.
154
+ * @param {RunStatus} status - The status of the current test run. Must be one of "passed", "failed", or "finished"
155
+ * @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
156
+ * @returns {Promise<void>} - A Promise that resolves when finishes the run.
134
157
  */
135
- updateRunStatus(status, isParallel) {
158
+ updateRunStatus(status, isParallel = false) {
136
159
  // all pipes disabled, skipping
137
- if (!this.pipes.filter(p => p.isEnabled).length) return;
160
+ if (!this.pipes.filter(p => p.isEnabled).length) return Promise.resolve();
161
+
162
+ const runParams = { status, parallel: isParallel };
163
+
164
+ this.queue = this.queue
165
+ .then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
166
+ .then(() => {
167
+ debug("TOTAL uploaded files", this.totalUploaded);
168
+
169
+ if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
170
+ console.log(
171
+ APP_PREFIX,
172
+ `🗄️ Total ${this.totalUploaded} artifacts ${
173
+ process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
174
+ } uploaded to S3 bucket `,
175
+ );
176
+ }
177
+ })
178
+ .catch(err => console.log(APP_PREFIX, err));
138
179
 
139
- let statusEvent;
140
- if (status === FINISHED) statusEvent = 'finish';
141
- if (status === PASSED) statusEvent = 'pass';
142
- if (status === FAILED) statusEvent = 'fail';
143
- if (isParallel) statusEvent += '_parallel';
144
- const runParams = { status, statusEvent }
145
-
146
- this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))));
147
-
148
- if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
149
- console.log(
150
- APP_PREFIX,
151
- `🗄️ Total ${this.totalUploaded} artifacts ${
152
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
153
- } uploaded to S3 bucket `,
154
- );
155
- }
156
180
  return this.queue;
157
181
  }
158
182
 
@@ -193,4 +217,4 @@ class TestomatClient {
193
217
  }
194
218
  }
195
219
 
196
- module.exports = TestomatClient;
220
+ module.exports = Client;
package/lib/constants.js CHANGED
@@ -1,11 +1,26 @@
1
1
  const chalk = require('chalk');
2
2
 
3
3
  const APP_PREFIX = chalk.gray('[TESTOMATIO]');
4
+ const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
4
5
 
5
- module.exports = Object.freeze({
6
+ const CSV_HEADERS = [
7
+ { id: 'suite_title', title: 'Suite_title' },
8
+ { id: 'title', title: 'Title' },
9
+ { id: 'status', title: 'Status' },
10
+ { id: 'message', title: 'Message' },
11
+ { id: 'stack', title: 'Stack' },
12
+ ];
13
+
14
+ const STATUS = {
6
15
  PASSED: 'passed',
7
16
  FAILED: 'failed',
8
17
  SKIPPED: 'skipped',
9
18
  FINISHED: 'finished',
19
+ };
20
+
21
+ module.exports = {
10
22
  APP_PREFIX,
11
- });
23
+ TESTOMAT_ARTIFACT_SUFFIX,
24
+ CSV_HEADERS,
25
+ STATUS,
26
+ }
@@ -1,4 +1,7 @@
1
- const AWS = require('aws-sdk');
1
+ const debug = require('debug')('@testomatio/reporter:file-uploader');
2
+ const { S3 } = require('@aws-sdk/client-s3');
3
+ const { Upload } = require('@aws-sdk/lib-storage');
4
+
2
5
  const fs = require('fs');
3
6
  const path = require('path');
4
7
  const chalk = require('chalk');
@@ -6,18 +9,42 @@ const { randomUUID } = require('crypto');
6
9
  const memoize = require('lodash.memoize');
7
10
  const { APP_PREFIX } = require('./constants');
8
11
 
9
- const {
10
- S3_ENDPOINT,
11
- S3_REGION,
12
- S3_BUCKET,
13
- S3_ACCESS_KEY_ID,
14
- S3_SECRET_ACCESS_KEY,
15
- TESTOMATIO_DISABLE_ARTIFACTS,
16
- TESTOMATIO_PRIVATE_ARTIFACTS,
17
- S3_FORCE_PATH_STYLE,
18
- } = process.env;
19
-
20
- const isArtifactsEnabled = S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS;
12
+ const keys = [
13
+ 'S3_ENDPOINT',
14
+ 'S3_REGION',
15
+ 'S3_BUCKET',
16
+ 'S3_ACCESS_KEY_ID',
17
+ 'S3_SECRET_ACCESS_KEY',
18
+ 'TESTOMATIO_DISABLE_ARTIFACTS',
19
+ 'TESTOMATIO_PRIVATE_ARTIFACTS',
20
+ 'S3_FORCE_PATH_STYLE',
21
+ ];
22
+
23
+ let config;
24
+
25
+ function getConfig() {
26
+ if (config) return config;
27
+ config = keys.reduce((acc, key) => {
28
+ acc[key] = process.env[key];
29
+ return acc;
30
+ }, {});
31
+ return config;
32
+ }
33
+
34
+ function getMaskedConfig() {
35
+ const config = getConfig();
36
+ return Object.fromEntries(Object.entries(config).map(([key, value]) => [key, key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value]));
37
+ }
38
+
39
+ let isEnabled;
40
+
41
+ const isArtifactsEnabled = () => {
42
+ if (isEnabled !== undefined) return isEnabled;
43
+ const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = getConfig();
44
+ isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
45
+ debug(`Upload is ${isEnabled ? 'enabled' : 'disabled'}`);
46
+ return isEnabled;
47
+ };
21
48
 
22
49
  const _getFileExtBase64 = str => {
23
50
  const type = str.charAt(0);
@@ -32,6 +59,25 @@ const _getFileExtBase64 = str => {
32
59
  );
33
60
  };
34
61
 
62
+ const _getS3Config = () => {
63
+ const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = getConfig();
64
+
65
+ const cfg = {
66
+ region: S3_REGION,
67
+ credentials: {
68
+ accessKeyId: S3_ACCESS_KEY_ID,
69
+ secretAccessKey: S3_SECRET_ACCESS_KEY,
70
+ s3ForcePathStyle: S3_FORCE_PATH_STYLE,
71
+ }
72
+ };
73
+
74
+ if (S3_ENDPOINT) {
75
+ cfg.endpoint = S3_ENDPOINT;
76
+ }
77
+
78
+ return cfg;
79
+ }
80
+
35
81
  const uploadUsingS3 = async (filePath, runId) => {
36
82
  let ContentType;
37
83
  let Key;
@@ -42,49 +88,45 @@ const uploadUsingS3 = async (filePath, runId) => {
42
88
  Key = filePath.name;
43
89
  }
44
90
 
45
- const config = {
46
- region: S3_REGION,
47
- accessKeyId: S3_ACCESS_KEY_ID,
48
- secretAccessKey: S3_SECRET_ACCESS_KEY,
49
- s3ForcePathStyle: S3_FORCE_PATH_STYLE,
50
- };
51
-
52
- if (S3_ENDPOINT) {
53
- config.endpoint = new AWS.Endpoint(S3_ENDPOINT);
54
- }
55
-
56
- const s3 = new AWS.S3(config);
57
91
  if (!fs.existsSync(filePath)) {
58
92
  console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
59
93
  return;
60
94
  }
95
+
96
+ const {
97
+ TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
98
+ } = getConfig();
99
+
100
+ debug('S3 config', getMaskedConfig());
101
+ debug('Uploading', filePath, 'to', S3_BUCKET);
61
102
 
62
- const file = fs.readFileSync(filePath);
103
+ const fileData = fs.readFileSync(filePath);
63
104
 
64
105
  Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
65
106
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
66
107
 
108
+ const s3 = new S3(_getS3Config());
109
+
110
+ const params = {
111
+ Bucket: S3_BUCKET,
112
+ Key,
113
+ Body: fileData,
114
+ ACL,
115
+ };
116
+
67
117
  try {
68
- const out = await s3
69
- .upload({
70
- Bucket: S3_BUCKET,
71
- Key,
72
- Body: file,
73
- ContentType,
74
- ACL,
75
- })
76
- .promise();
118
+ const out = new Upload({
119
+ client: s3,
120
+ params
121
+ });
122
+
123
+ await out.done();
124
+ debug('Uploaded', out.singleUploadResult.Location)
77
125
 
78
- return out.Location;
126
+ return out.singleUploadResult.Location;
79
127
  } catch (e) {
80
- console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
81
- accessKeyId: S3_ACCESS_KEY_ID,
82
- secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
83
- region: S3_REGION,
84
- bucket: S3_BUCKET,
85
- acl: ACL,
86
- endpoint: S3_ENDPOINT,
87
- });
128
+ console.log(e);
129
+ console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
88
130
 
89
131
  console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
90
132
  if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
@@ -96,39 +138,36 @@ const uploadUsingS3 = async (filePath, runId) => {
96
138
  );
97
139
  }
98
140
  console.log(APP_PREFIX, '---------------');
99
- }
141
+ }
100
142
  };
101
143
 
102
144
  const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
103
- const config = {
104
- region: S3_REGION,
105
- accessKeyId: S3_ACCESS_KEY_ID,
106
- secretAccessKey: S3_SECRET_ACCESS_KEY,
107
- s3ForcePathStyle: S3_FORCE_PATH_STYLE,
108
- };
109
145
 
110
- if (S3_ENDPOINT) {
111
- config.endpoint = new AWS.Endpoint(S3_ENDPOINT);
112
- }
113
-
114
- const s3 = new AWS.S3(config);
146
+ const {
147
+ S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
148
+ } = getConfig();
115
149
 
116
150
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
117
151
 
118
152
  const fileExtension = _getFileExtBase64(buffer.toString('base64'));
119
153
  const Key = `${runId}/${fileName}${fileExtension}`;
120
154
 
155
+ const s3 = new S3(_getS3Config());
156
+
121
157
  try {
122
- const out = await s3
123
- .upload({
158
+ const out = new Upload({
159
+ client: s3,
160
+
161
+ params: {
124
162
  Bucket: S3_BUCKET,
125
163
  Key,
126
164
  Body: buffer,
127
165
  ACL,
128
- })
129
- .promise();
166
+ }
167
+ });
168
+ await out.done();
130
169
 
131
- return out.Location;
170
+ return out.singleUploadResult.Location;
132
171
  } catch (e) {
133
172
  console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
134
173
  accessKeyId: S3_ACCESS_KEY_ID,
@@ -154,7 +193,7 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
154
193
 
155
194
  const uploadFileByPath = async (filePath, runId) => {
156
195
  try {
157
- if (isArtifactsEnabled) {
196
+ if (isArtifactsEnabled()) {
158
197
  return uploadUsingS3(filePath, runId);
159
198
  }
160
199
  } catch (e) {
@@ -164,7 +203,7 @@ const uploadFileByPath = async (filePath, runId) => {
164
203
 
165
204
  const uploadFileAsBuffer = async (buffer, fileName, runId) => {
166
205
  try {
167
- if (isArtifactsEnabled) {
206
+ if (isArtifactsEnabled()) {
168
207
  return uploadUsingS3AsBuffer(buffer, fileName, runId);
169
208
  }
170
209
  } catch (e) {
@@ -175,5 +214,5 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
175
214
  module.exports = {
176
215
  uploadFileByPath: memoize(uploadFileByPath),
177
216
  uploadFileAsBuffer: memoize(uploadFileAsBuffer),
178
- isArtifactsEnabled,
217
+ isArtifactsEnabled: memoize(isArtifactsEnabled),
179
218
  };
@@ -14,5 +14,4 @@ class CSharpAdapter extends Adapter {
14
14
  }
15
15
  }
16
16
 
17
-
18
17
  module.exports = CSharpAdapter;
@@ -5,8 +5,7 @@ const PythonAdapter = require('./python');
5
5
  const RubyAdapter = require('./ruby');
6
6
  const CSharpAdapter = require('./csharp');
7
7
 
8
-
9
- module.exports = function (lang, opts) {
8
+ function AdapterFactory(lang, opts) {
10
9
  if (!lang) return new Adapter(opts);
11
10
 
12
11
  if (lang === 'java') {
@@ -25,3 +24,5 @@ module.exports = function (lang, opts) {
25
24
  return new CSharpAdapter(opts);
26
25
  }
27
26
  }
27
+
28
+ module.exports = AdapterFactory;
@@ -0,0 +1,133 @@
1
+ const debug = require('debug')('@testomatio/reporter:pipe:csv');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const csvWriter = require('csv-writer');
5
+ const chalk = require('chalk');
6
+ const merge = require('lodash.merge');
7
+ const { isSameTest, getCurrentDateTime } = require('../util');
8
+ const { CSV_HEADERS } = require('../constants');
9
+
10
+ /**
11
+ * @typedef {import('../../types').Pipe} Pipe
12
+ * @typedef {import('../../types').TestData} TestData
13
+ * @class CsvPipe
14
+ * @implements {Pipe}
15
+ */
16
+ class CsvPipe {
17
+
18
+ constructor(params, store) {
19
+ this.store = store || {};
20
+ this.title = params.title || process.env.TESTOMATIO_TITLE;
21
+ this.isEnabled = true;
22
+ this.results = [];
23
+
24
+ this.outputDir = 'export';
25
+ this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
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
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ async createRun() {
44
+ // empty
45
+ }
46
+
47
+ updateRun() {}
48
+
49
+ /**
50
+ * Create a folder that will contain the exported files
51
+ */
52
+ checkExportDir() {
53
+ if (!fs.existsSync(this.outputDir)) {
54
+ return fs.mkdirSync(this.outputDir);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Save data to the csv file.
60
+ * @param {Object} data - data that will be added to the CSV file.
61
+ * Example: [{suite_title: "Suite #1", test: "Test-case-1", message: "Test msg"}]
62
+ * @param {Object} headers - csv file headers. Example: [{ id: 'suite_title', title: 'Suite_title' }]
63
+ */
64
+ async saveToCsv(data, headers) {
65
+ debug('Data', data);
66
+ // First, we check whether the export directory exists: if yes - OK, no - create it.
67
+ this.checkExportDir();
68
+
69
+ if (!this.outputFile) {
70
+ console.log(this, chalk.yellow(`CSV file is not set, ignoring`));
71
+ return;
72
+ }
73
+
74
+ console.log(this, chalk.yellow(`The test results will be added to the csv. It will take some time...`));
75
+ // Create csv writer object
76
+ const writer = csvWriter.createObjectCsvWriter({
77
+ path: this.outputFile,
78
+ header: headers,
79
+ });
80
+ // Save csv file based on the current data
81
+ try {
82
+ await writer.writeRecords(data);
83
+ console.log(this, chalk.green(`Recording completed! You can check the result in file = ${this.outputFile}`));
84
+ } catch (e) {
85
+ console.log('Unknown error: ', e);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Add test data to the result array for saving. As a result of this function, we get a result object to save.
91
+ * @param {Object} test - object which includes each test entry.
92
+ */
93
+ addTest(test) {
94
+ if (!this.isEnabled) return;
95
+
96
+ const index = this.results.findIndex(t => isSameTest(t, test));
97
+ // update if they were already added
98
+ if (index >= 0) {
99
+ this.results[index] = merge(this.results[index], test);
100
+ return;
101
+ }
102
+
103
+ const { suite_title, title, status, message, stack } = test;
104
+
105
+ this.results.push({
106
+ suite_title,
107
+ title,
108
+ status,
109
+ message,
110
+ stack,
111
+ });
112
+ }
113
+
114
+ /**
115
+ * @param {{ tests?: TestData[] }} runParams
116
+ * @returns {Promise<void>}
117
+ */
118
+ async finishRun(runParams) {
119
+ if (!this.isEnabled) return;
120
+
121
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
122
+ // Save results based on the default headers
123
+ if (this.isCsvSave) {
124
+ this.saveToCsv(this.results, CSV_HEADERS);
125
+ }
126
+ }
127
+
128
+ toString() {
129
+ return 'csv exporter';
130
+ }
131
+ }
132
+
133
+ module.exports = CsvPipe;