@testomatio/reporter 1.6.0-beta-6-artifacts → 1.6.0-beta-artifacts

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.
@@ -24,7 +24,6 @@ function MochaReporter(runner, opts) {
24
24
  let passes = 0;
25
25
  let failures = 0;
26
26
  let skipped = 0;
27
- // let artifactStore;
28
27
 
29
28
  const apiKey = opts?.reporterOptions?.apiKey || config.TESTOMATIO;
30
29
 
@@ -33,6 +32,7 @@ function MochaReporter(runner, opts) {
33
32
  runner.on(EVENT_RUN_BEGIN, () => {
34
33
  client.createRun();
35
34
 
35
+ // clear dir with artifacts/logs
36
36
  fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
37
37
  });
38
38
 
@@ -104,6 +104,7 @@ class PlaywrightReporter {
104
104
  if (!this.client) return;
105
105
 
106
106
  await Promise.all(reportTestPromises);
107
+ await this.client.updateRunStatus(checkStatus(result.status));
107
108
 
108
109
  if (!this.uploads.length) return;
109
110
 
@@ -135,8 +136,6 @@ class PlaywrightReporter {
135
136
  );
136
137
  }
137
138
  await Promise.all(promises);
138
-
139
- await this.client.updateRunStatus(checkStatus(result.status));
140
139
  }
141
140
  }
142
141
 
@@ -1,7 +1,9 @@
1
1
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
2
2
  const WDIOReporter = require('@wdio/reporter').default;
3
3
  const TestomatClient = require('../client');
4
- const { getTestomatIdFromTestTitle } = require('../utils/utils');
4
+ const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
5
+ const { services } = require('../services');
6
+ const { TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
5
7
 
6
8
  class WebdriverReporter extends WDIOReporter {
7
9
  constructor(options) {
@@ -15,6 +17,15 @@ class WebdriverReporter extends WDIOReporter {
15
17
  this._isSynchronising = false;
16
18
  }
17
19
 
20
+ onRunnerStart() {
21
+ // clear dir with artifacts/logs
22
+ fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
23
+ }
24
+
25
+ onTestStart(test) {
26
+ services.setContext(test.fullTitle);
27
+ }
28
+
18
29
  get isSynchronised() {
19
30
  return this._isSynchronising === false;
20
31
  }
@@ -28,15 +39,18 @@ class WebdriverReporter extends WDIOReporter {
28
39
  }
29
40
 
30
41
  onTestEnd(test) {
42
+ test.suite = test.parent;
43
+ const logs = getTestLogs(test.fullTitle);
44
+ // TODO: FIX: artifacts for some reason leads to empty report on Testomat.io
45
+ // const artifacts = services.artifacts.get(test.fullTitle);
46
+ // const keyValues = services.keyValues.get(test.fullTitle);
47
+ test.logs = logs;
48
+ // test.artifacts = artifacts;
49
+ // test.meta = keyValues;
31
50
  this._addTestPromises.push(this.addTest(test));
32
51
  }
33
52
 
34
53
  // wdio-cucumber does not trigger onTestEnd hook, thus, using this one
35
- /**
36
- *
37
- * @param {} scerario
38
- * @returns
39
- */
40
54
  onSuiteEnd(scerario) {
41
55
  if (scerario.type === 'scenario') {
42
56
  this._addTestPromises.push(this.addBddScenario(scerario));
@@ -56,7 +70,10 @@ class WebdriverReporter extends WDIOReporter {
56
70
  .map(el => Buffer.from(el.result.value, 'base64'));
57
71
 
58
72
  await this.client.addTestRun(state, {
73
+ // manuallyAttachedArtifacts: test.artifacts,
59
74
  error,
75
+ logs: test.logs,
76
+ // meta: test.meta,
60
77
  title,
61
78
  test_id: testId,
62
79
  time: duration,
@@ -98,4 +115,16 @@ class WebdriverReporter extends WDIOReporter {
98
115
  }
99
116
  }
100
117
 
118
+ /**
119
+ *
120
+ * @param {*} fullTestTitle
121
+ * @returns string
122
+ */
123
+ function getTestLogs(fullTestTitle) {
124
+ const logsArr = services.logger.getLogs(fullTestTitle);
125
+ // remove duplicates (for some reason, logs are duplicated several times)
126
+ const logs = logsArr ? Array.from(new Set(logsArr)).join('\n').trim() : '';
127
+ return logs;
128
+ }
129
+
101
130
  module.exports = WebdriverReporter;
package/lib/bin/cli.js CHANGED
@@ -185,7 +185,7 @@ program
185
185
  isBatchEnabled: false,
186
186
  });
187
187
 
188
- let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
188
+ let testruns = client.uploader.readUploadedFiles(runId);
189
189
  const numTotalArtifacts = testruns.length;
190
190
 
191
191
  debug('Found testruns:', testruns);
@@ -98,6 +98,7 @@ class TestomatioPipe {
98
98
  this.runId = params.runId || process.env.runId;
99
99
  this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
100
100
  this.hasUnmatchedTests = false;
101
+ this.requestFailures = 0;
101
102
 
102
103
  if (!isValidUrl(this.url.trim())) {
103
104
  this.isEnabled = false;
@@ -217,10 +218,11 @@ class TestomatioPipe {
217
218
  process.env.runId = this.runId;
218
219
  debug('Run created', this.runId);
219
220
  } catch (err) {
221
+ const errorInfo = `${err?.response?.statusText} ${chalk.red(err?.status)} ${err.message}`;
220
222
  console.error(
221
223
  APP_PREFIX,
222
- 'Error creating Testomat.io report, please check if your API key is valid. Skipping report | ',
223
- err?.response?.statusText || err?.status || err.message,
224
+ 'Error creating Testomat.io report, please check if your API key is valid. Skipping report\n',
225
+ errorInfo || JSON.stringify(err),
224
226
  );
225
227
  printCreateIssue(err);
226
228
  }
@@ -229,37 +231,25 @@ class TestomatioPipe {
229
231
 
230
232
  /**
231
233
  * Decides whether to skip test reporting in case of too many request failures
232
- * @param {TestData} testData
233
234
  * @returns {boolean}
234
235
  */
235
- #cancelTestReportingInCaseOfTooManyReqFailures(testData) {
236
- if (this.reportingCanceledDueToReqFailures) return true;
237
-
238
- const retriesCountWithinTime = this.retriesTimestamps.filter(
239
- timestamp => Date.now() - timestamp < REPORTER_REQUEST_RETRIES.withinTimeSeconds * 1000,
240
- ).length;
241
- debug(`${retriesCountWithinTime} failed requests within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s`);
242
-
243
- if (retriesCountWithinTime > REPORTER_REQUEST_RETRIES.maxTotalRetries) {
244
- const errorMessage = chalk.yellow(
245
- `${retriesCountWithinTime} requests were failed within ${REPORTER_REQUEST_RETRIES.withinTimeSeconds}s,\
246
- reporting for test "${testData.title}" to Testomat is skipped`,
247
- );
248
- console.warn(`${APP_PREFIX} ${errorMessage}`);
236
+ #cancelTestReportingInCaseOfTooManyReqFailures() {
237
+ if (!process.env.TESTOMATIO_MAX_REQUEST_FAILURES) return;
249
238
 
239
+ const cancelReporting = this.requestFailures >= parseInt(process.env.TESTOMATIO_MAX_REQUEST_FAILURES, 10);
240
+ if (cancelReporting) {
250
241
  this.reportingCanceledDueToReqFailures = true;
251
- this.notReportedTestsCount++;
252
-
253
- return true;
242
+ const errorMessage =
243
+ `⚠️ ${process.env.TESTOMATIO_MAX_REQUEST_FAILURES} requests were failed, reporting to Testomat aborted.`;
244
+ console.warn(`${APP_PREFIX} ${chalk.yellow(errorMessage)}`);
254
245
  }
255
-
256
- return false;
246
+ return cancelReporting;
257
247
  }
258
248
 
259
249
  #uploadSingleTest = async data => {
260
250
  if (!this.isEnabled) return;
261
251
  if (!this.runId) return;
262
- if (this.#cancelTestReportingInCaseOfTooManyReqFailures(data)) return;
252
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
263
253
 
264
254
  data.api_key = this.apiKey;
265
255
  data.create = this.createNewTests;
@@ -275,6 +265,8 @@ class TestomatioPipe {
275
265
  return this.axios
276
266
  .post(`/api/reporter/${this.runId}/testrun`, json, axiosAddTestrunRequestConfig)
277
267
  .catch(err => {
268
+ this.requestFailures++;
269
+ this.notReportedTestsCount++;
278
270
  if (err.response) {
279
271
  if (err.response.status >= 400) {
280
272
  const responseData = err.response.data || { message: '' };
@@ -308,6 +300,7 @@ class TestomatioPipe {
308
300
  this.batch.batchIndex++;
309
301
  if (!this.batch.isEnabled) return;
310
302
  if (!this.batch.tests.length) return;
303
+ if (this.#cancelTestReportingInCaseOfTooManyReqFailures()) return;
311
304
 
312
305
  // get tests from batch and clear batch
313
306
  const testsToSend = this.batch.tests.splice(0);
@@ -322,6 +315,8 @@ class TestomatioPipe {
322
315
  axiosAddTestrunRequestConfig,
323
316
  )
324
317
  .catch(err => {
318
+ this.requestFailures++;
319
+ this.notReportedTestsCount += testsToSend.length;
325
320
  if (err.response) {
326
321
  if (err.response.status >= 400) {
327
322
  const responseData = err.response.data || { message: '' };
@@ -399,6 +394,7 @@ class TestomatioPipe {
399
394
  await this.axios.put(`/api/reporter/${this.runId}`, {
400
395
  api_key: this.apiKey,
401
396
  status_event,
397
+ duration: params.duration,
402
398
  tests: params.tests,
403
399
  });
404
400
  if (this.runUrl) {
package/lib/reporter.js CHANGED
@@ -4,7 +4,7 @@ const { services } = require('./services');
4
4
 
5
5
  const reporterFunctions = require('./reporter-functions');
6
6
 
7
- module.exports = {
7
+ const testomat = {
8
8
  // TODO: deprecate in future; use log or testomat.log
9
9
  testomatioLogger: services.logger,
10
10
 
@@ -17,3 +17,14 @@ module.exports = {
17
17
  TestomatClient,
18
18
  TRConstants,
19
19
  };
20
+
21
+ module.exports = testomat;
22
+
23
+ module.exports.testomatioLogger = testomat;
24
+ module.exports.artifact = reporterFunctions.artifact;
25
+ module.exports.log = reporterFunctions.log;
26
+ module.exports.logger = services.logger;
27
+ module.exports.meta = reporterFunctions.keyValue;
28
+ module.exports.step = reporterFunctions.step;
29
+ module.exports.TestomatClient = TestomatClient;
30
+ module.exports.TRConstants = TRConstants;
package/lib/uploader.js CHANGED
@@ -79,7 +79,7 @@ class S3Uploader {
79
79
  this.storeEnabled = false;
80
80
  }
81
81
 
82
- async uploadToS3(Body, Key) {
82
+ async #uploadToS3(Body, Key) {
83
83
  const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
84
84
  const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
85
85
 
@@ -94,7 +94,7 @@ class S3Uploader {
94
94
 
95
95
  debug('Uploading to S3:', Key);
96
96
 
97
- const s3 = new S3(this.getS3Config());
97
+ const s3 = new S3(this.#getS3Config());
98
98
 
99
99
  try {
100
100
  const upload = new Upload({
@@ -118,8 +118,13 @@ class S3Uploader {
118
118
  }
119
119
  }
120
120
 
121
+ /**
122
+ * Returns an array of uploaded files list
123
+ *
124
+ * @returns {{rid: string, file: string, uploaded: boolean}[]}
125
+ */
121
126
  readUploadedFiles(runId) {
122
- const tempFilePath = this.#getUploadFilePath(runId);
127
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
123
128
 
124
129
  debug('Reading file', tempFilePath);
125
130
 
@@ -146,9 +151,9 @@ class S3Uploader {
146
151
  return lines.map(line => JSON.parse(line));
147
152
  }
148
153
 
149
- #getUploadFilePath(runId, forceCreate = false) {
150
- const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.jsonl`);
151
- if (!fs.existsSync(tempFilePath) || forceCreate) {
154
+ #getFilePathWithUploadsList(runId) {
155
+ const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.json`);
156
+ if (!fs.existsSync(tempFilePath)) {
152
157
  debug('Creating artifacts file:', tempFilePath);
153
158
  fs.writeFileSync(tempFilePath, '');
154
159
  }
@@ -160,7 +165,11 @@ class S3Uploader {
160
165
 
161
166
  if (!filePath || !runId || !rid) return;
162
167
 
163
- const tempFilePath = this.#getUploadFilePath(runId);
168
+ const tempFilePath = this.#getFilePathWithUploadsList(runId);
169
+
170
+ if (typeof filePath === 'object') {
171
+ filePath = filePath.path;
172
+ }
164
173
 
165
174
  if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
166
175
  filePath = path.join(process.cwd(), filePath);
@@ -168,14 +177,15 @@ class S3Uploader {
168
177
 
169
178
  const data = { rid, file: filePath, uploaded };
170
179
  const jsonLine = `${JSON.stringify(data)}\n`;
171
-
172
180
  fs.appendFileSync(tempFilePath, jsonLine);
173
181
  }
174
182
 
175
- getskippedUpload() {
176
- return this.skippedUploadsCount;
177
- }
178
-
183
+ /**
184
+ *
185
+ * @param {*} filePath
186
+ * @param {*} pathInS3 contains runId, rid and filename
187
+ * @returns
188
+ */
179
189
  async uploadFileByPath(filePath, pathInS3) {
180
190
  const [runId, rid] = pathInS3;
181
191
 
@@ -191,7 +201,7 @@ class S3Uploader {
191
201
 
192
202
  debug('Started upload', filePath, 'to', S3_BUCKET);
193
203
 
194
- const isFileExist = await this.checkFileExists(filePath, 20, 500);
204
+ const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
195
205
 
196
206
  if (!isFileExist) {
197
207
  console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
@@ -211,7 +221,7 @@ class S3Uploader {
211
221
  const fileStream = fs.createReadStream(filePath);
212
222
  const Key = pathInS3.join('/');
213
223
 
214
- const link = await this.uploadToS3(fileStream, Key);
224
+ const link = await this.#uploadToS3(fileStream, Key);
215
225
 
216
226
  this.storeUploadedFile(filePath, runId, rid, !!link);
217
227
 
@@ -228,10 +238,10 @@ class S3Uploader {
228
238
  Key = `${Key}.${ext}`;
229
239
  }
230
240
 
231
- return this.uploadToS3(buffer, Key);
241
+ return this.#uploadToS3(buffer, Key);
232
242
  }
233
243
 
234
- async checkFileExists(filePath, attempts = 5, intervalMs = 500) {
244
+ async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
235
245
  return promiseRetry(
236
246
  async (retry, number) => {
237
247
  try {
@@ -286,7 +296,7 @@ class S3Uploader {
286
296
  );
287
297
  }
288
298
 
289
- getS3Config() {
299
+ #getS3Config() {
290
300
  const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
291
301
  this.getConfig();
292
302
 
@@ -321,6 +321,7 @@ const testRunnerHelper = {
321
321
  };
322
322
 
323
323
  function storeRunId(runId) {
324
+ if (!runId || runId === 'undefined') return;
324
325
  const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
325
326
  fs.writeFileSync(filePath, runId);
326
327
  }
package/lib/xmlReader.js CHANGED
@@ -107,7 +107,7 @@ class XmlReader {
107
107
  }
108
108
 
109
109
  processJUnit(jsonSuite) {
110
- const { testsuite, name, tests, failures, errors } = jsonSuite;
110
+ const { testsuite, name, tests, failures, errors, time } = jsonSuite;
111
111
 
112
112
  reduceOptions.preferClassname = this.stats.language === 'python';
113
113
  const resultTests = processTestSuite(testsuite);
@@ -115,12 +115,18 @@ class XmlReader {
115
115
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
116
116
  const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
117
117
 
118
+ if (time) {
119
+ if (!this.stats.duration) this.stats.duration = 0;
120
+ this.stats.duration += parseFloat(time);
121
+ }
122
+
118
123
  this.tests = this.tests.concat(resultTests);
119
124
 
120
125
  return {
121
126
  status,
122
127
  create_tests: true,
123
128
  name,
129
+ duration: parseFloat(time),
124
130
  tests_count: parseInt(tests, 10),
125
131
  passed_count: parseInt(tests, 10) - parseInt(failures, 10),
126
132
  failed_count: parseInt(failures, 10),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.6.0-beta-6-artifacts",
3
+ "version": "1.6.0-beta-artifacts",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",