@testomatio/reporter 1.0.12 → 1.0.14-beta

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.
@@ -23,6 +23,7 @@ program
23
23
  }
24
24
  let { javaTests, lang } = opts;
25
25
  if (opts.envFile) {
26
+ console.log(APP_PREFIX, 'Loading env file:', opts.envFile);
26
27
  debug('Loading env file: %s', opts.envFile)
27
28
  require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
28
29
  }
package/lib/client.js CHANGED
@@ -36,6 +36,7 @@ class Client {
36
36
  * @returns {Promise<any>} - resolves to Run id which should be used to update / add test
37
37
  */
38
38
  createRun() {
39
+ debug('Creating run...');
39
40
  // all pipes disabled, skipping
40
41
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
41
42
 
@@ -59,6 +60,7 @@ class Client {
59
60
  * @returns {Promise<PipeResult[]>}
60
61
  */
61
62
  async addTestRun(status, testData, storeArtifacts = []) {
63
+ debug('Adding test run for test', testData?.test_id || 'unknown test');
62
64
  // all pipes disabled, skipping
63
65
  if (!this.pipes?.filter(p => p.isEnabled).length) return [];
64
66
 
@@ -170,6 +172,7 @@ class Client {
170
172
  * @returns {Promise<any>} - A Promise that resolves when finishes the run.
171
173
  */
172
174
  updateRunStatus(status, isParallel = false) {
175
+ debug('Updating run status...');
173
176
  // all pipes disabled, skipping
174
177
  if (!this.pipes?.filter(p => p.isEnabled).length) return Promise.resolve();
175
178
 
@@ -33,7 +33,9 @@ class TestomatioPipe {
33
33
  this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
34
34
  this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
35
35
  this.env = process.env.TESTOMATIO_ENV;
36
- this.axios = axios.create();
36
+ this.axios = axios.create({
37
+ timeout: 30000,
38
+ });
37
39
  this.isEnabled = true;
38
40
  // do not finish this run (for parallel testing)
39
41
  this.proceed = process.env.TESTOMATIO_PROCEED;
@@ -51,6 +53,7 @@ class TestomatioPipe {
51
53
  * @returns Promise<void>
52
54
  */
53
55
  async createRun() {
56
+ debug('Creating run...');
54
57
  if (!this.isEnabled) return;
55
58
 
56
59
  let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
@@ -58,7 +61,7 @@ class TestomatioPipe {
58
61
  // GitHub Actions Url
59
62
  if (!buildUrl && process.env.GITHUB_RUN_ID) {
60
63
  // eslint-disable-next-line max-len
61
- buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
64
+ buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
62
65
  }
63
66
 
64
67
  // Azure DevOps Url
@@ -83,7 +86,7 @@ class TestomatioPipe {
83
86
  env: this.env,
84
87
  title: this.title,
85
88
  shared_run: this.sharedRun,
86
- }).filter(([, value]) => !!value)
89
+ }).filter(([, value]) => !!value),
87
90
  );
88
91
 
89
92
  if (this.runId) {
@@ -110,57 +113,65 @@ class TestomatioPipe {
110
113
  console.error(
111
114
  APP_PREFIX,
112
115
  'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
116
+ err,
113
117
  );
114
118
  }
119
+ debug('Run created');
115
120
  }
116
121
 
117
122
  /**
118
- *
119
- * @param testData data
120
- * @returns
123
+ *
124
+ * @param testData data
125
+ * @returns
121
126
  */
122
127
  addTest(data) {
128
+ debug('Adding test...');
123
129
  if (!this.isEnabled) return;
124
130
  if (!this.runId) return;
125
131
  data.api_key = this.apiKey;
126
132
  data.create = this.createNewTests;
127
133
  const json = JsonCycle.stringify(data);
128
134
 
129
- return this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
130
- maxContentLength: Infinity,
131
- maxBodyLength: Infinity,
132
- headers: {
133
- // Overwrite Axios's automatically set Content-Type
134
- 'Content-Type': 'application/json',
135
- },
136
- })
137
- .catch((err) => {
138
- if (err.response) {
139
- if (err.response.status >= 400) {
140
- const responseData = err.response.data || { message: '' };
135
+ return this.axios
136
+ .post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
137
+ maxContentLength: Infinity,
138
+ maxBodyLength: Infinity,
139
+ headers: {
140
+ // Overwrite Axios's automatically set Content-Type
141
+ 'Content-Type': 'application/json',
142
+ },
143
+ })
144
+ .catch(err => {
145
+ if (err.response) {
146
+ if (err.response.status >= 400) {
147
+ const responseData = err.response.data || { message: '' };
148
+ console.log(
149
+ APP_PREFIX,
150
+ chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
151
+ chalk.grey(data?.title || ''),
152
+ );
153
+ if (err.response.data.message.includes('could not be matched')) {
154
+ this.hasUnmatchedTests = true;
155
+ }
156
+ return;
157
+ }
141
158
  console.log(
142
159
  APP_PREFIX,
143
- chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
144
- chalk.grey(data?.title || ''),
160
+ chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
161
+ `Report couldn't be processed: ${err?.response?.data?.message}`,
145
162
  );
146
- if (err.response.data.message.includes('could not be matched')) {
147
- this.hasUnmatchedTests = true;
148
- }
149
- return;
163
+ } else {
164
+ console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
150
165
  }
151
- // eslint-disable-next-line max-len
152
- console.log(APP_PREFIX, chalk.blue(data?.title || ''), `Report couldn't be processed: ${err.response.data.message}`);
153
- } else {
154
- console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
155
- }
156
- });
166
+ });
157
167
  }
158
168
 
159
169
  /**
160
- * @param {import('../../types').RunData} params
161
- * @returns
170
+ * @param {import('../../types').RunData} params
171
+ * @returns
162
172
  */
163
173
  async finishRun(params) {
174
+ debug('Finishing run...');
164
175
  if (!this.isEnabled) return;
165
176
 
166
177
  const { status, parallel } = params;
@@ -185,7 +196,6 @@ class TestomatioPipe {
185
196
  if (this.runPublicUrl) {
186
197
  console.log(APP_PREFIX, '🌟 Public URL:', chalk.magenta(this.runPublicUrl));
187
198
  }
188
-
189
199
  }
190
200
  if (this.runUrl && this.proceed) {
191
201
  const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
@@ -197,19 +207,30 @@ class TestomatioPipe {
197
207
  // eslint-disable-next-line max-len
198
208
  console.log(APP_PREFIX, chalk.yellow.bold('⚠️ Some reported tests were not found in Testomat.io project'));
199
209
  // eslint-disable-next-line max-len
200
- console.log(APP_PREFIX, `If you use Testomat.io as a reporter only, please re-run tests using ${chalk.bold('TESTOMATIO_CREATE=1')}`);
210
+ console.log(
211
+ APP_PREFIX,
212
+ `If you use Testomat.io as a reporter only, please re-run tests using ${chalk.bold('TESTOMATIO_CREATE=1')}`,
213
+ );
201
214
  // eslint-disable-next-line max-len
202
- console.log(APP_PREFIX, `But to keep your tests consistent it is recommended to ${chalk.bold('import tests first')}`);
215
+ console.log(
216
+ APP_PREFIX,
217
+ `But to keep your tests consistent it is recommended to ${chalk.bold('import tests first')}`,
218
+ );
203
219
  console.log(APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
204
220
  console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
205
221
  console.log(APP_PREFIX, chalk.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
206
222
  console.log(APP_PREFIX, 'or for Cucumber:');
207
223
  // eslint-disable-next-line max-len
208
- console.log(APP_PREFIX, chalk.bold('npx check-cucumber ... --update-ids'), 'See: https://bit.ly/bdd-update-ids');
224
+ console.log(
225
+ APP_PREFIX,
226
+ chalk.bold('npx check-cucumber ... --update-ids'),
227
+ 'See: https://bit.ly/bdd-update-ids',
228
+ );
209
229
  }
210
230
  } catch (err) {
211
231
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
212
232
  }
233
+ debug('Run finished');
213
234
  }
214
235
 
215
236
  toString() {
@@ -219,17 +240,16 @@ class TestomatioPipe {
219
240
 
220
241
  module.exports = TestomatioPipe;
221
242
 
222
-
223
- function setS3Credentials(artifacts) {
243
+ function setS3Credentials(artifacts) {
224
244
  if (!Object.keys(artifacts).length) return;
225
245
 
226
246
  console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
227
247
 
228
- if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
229
- if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
230
- if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
231
- if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
232
- if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
233
- if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
248
+ if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
249
+ if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
250
+ if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
251
+ if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
252
+ if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
253
+ if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
234
254
  resetConfig();
235
255
  }
package/lib/util.js CHANGED
@@ -55,11 +55,21 @@ const isValidUrl = s => {
55
55
  }
56
56
  };
57
57
 
58
+ const fileMatchRegex = /file:(\/\/?[^:\s]+?\.(png|avi|webm|jpg|html|txt))/ig;
59
+
58
60
  const fetchFilesFromStackTrace = (stack = '') => {
59
- const files = stack.matchAll(/file:?\/(\/.*?\.(png|avi|webm|jpg|html|txt))/g);
60
- return Array.from(files)
61
- .map(f => f[1])
62
- .filter(f => fs.existsSync(f));
61
+
62
+ const files = Array.from(stack.matchAll(fileMatchRegex))
63
+ .map(f => f[1].trim())
64
+ .map(f => f.startsWith('//') ? f.substring(1) : f )
65
+
66
+ debug('Found files in stack trace: ', files);
67
+
68
+ return files.filter(f => {
69
+ const isFile = fs.existsSync(f);
70
+ if (!isFile) debug('File %s could not be found and uploaded as artifact', f);
71
+ return isFile;
72
+ });
63
73
  };
64
74
 
65
75
  const fetchSourceCodeFromStackTrace = (stack = '') => {
@@ -96,6 +106,29 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
96
106
  .join('\n');
97
107
  };
98
108
 
109
+ const TEST_ID_REGEX = /@T([\w\d]{8})/;
110
+
111
+ const fetchIdFromCode = (code, opts = {}) => {
112
+ const comments = code.split('\n').map(l => l.trim()).filter(l => {
113
+ switch (opts.lang) {
114
+ case 'ruby':
115
+ case 'python':
116
+ return l.startsWith('# ');
117
+ default:
118
+ return l.startsWith('// ');
119
+ }
120
+ });
121
+
122
+ return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
123
+ }
124
+
125
+
126
+ const fetchIdFromOutput = (output) => {
127
+ const lines = output.split('\n').map(l => l.trim()).filter(l => l.startsWith('tid://'));
128
+
129
+ return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
130
+ }
131
+
99
132
  const fetchSourceCode = (contents, opts = {}) => {
100
133
  if (!opts.title && !opts.line) return '';
101
134
 
@@ -252,6 +285,8 @@ module.exports = {
252
285
  isSameTest,
253
286
  fetchSourceCode,
254
287
  fetchSourceCodeFromStackTrace,
288
+ fetchIdFromCode,
289
+ fetchIdFromOutput,
255
290
  fetchFilesFromStackTrace,
256
291
  fileSystem,
257
292
  getCurrentDateTime,
package/lib/xmlReader.js CHANGED
@@ -4,7 +4,12 @@ const chalk = require('chalk');
4
4
  const fs = require("fs");
5
5
  const { XMLParser } = require("fast-xml-parser");
6
6
  const { APP_PREFIX, STATUS } = require('./constants');
7
- const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace } = require('./util');
7
+ const { fetchFilesFromStackTrace,
8
+ fetchIdFromOutput,
9
+ fetchSourceCode,
10
+ fetchSourceCodeFromStackTrace,
11
+ fetchIdFromCode
12
+ } = require('./util');
8
13
  const upload = require('./fileUploader');
9
14
  const pipesFactory = require('./pipe');
10
15
  const adapterFactory = require('./junit-adapter');
@@ -25,7 +30,7 @@ const options = {
25
30
  const reduceOptions = {};
26
31
 
27
32
  class XmlReader {
28
-
33
+
29
34
  constructor(opts = {}) {
30
35
  this.requestParams = {
31
36
  apiKey: opts.apiKey || TESTOMATIO,
@@ -39,8 +44,8 @@ class XmlReader {
39
44
  if (!this.adapter) throw new Error('XML adapter for this format not found');
40
45
 
41
46
  this.opts = opts || {};
42
- const store = {}
43
- this.pipes = pipesFactory(opts, store);
47
+ this.store = {}
48
+ this.pipes = pipesFactory(opts, this.store);
44
49
 
45
50
  this.parser = new XMLParser(options);
46
51
  this.tests = []
@@ -49,7 +54,7 @@ class XmlReader {
49
54
  this.filesToUpload = {}
50
55
 
51
56
  this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
52
- console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
57
+ console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
53
58
  }
54
59
 
55
60
  connectAdapter() {
@@ -61,21 +66,21 @@ class XmlReader {
61
66
  return this.adapter;
62
67
  }
63
68
 
64
- parse(fileName) {
69
+ parse(fileName) {
65
70
  const xmlData = fs.readFileSync(path.resolve(fileName));
66
71
  const jsonResult = this.parser.parse(xmlData);
67
72
  let jsonSuite;
68
-
73
+
69
74
  if (jsonResult.testsuites) {
70
75
  jsonSuite = jsonResult.testsuites;
71
76
  } else if (jsonResult.testsuite) {
72
77
  jsonSuite = jsonResult;
73
78
  } else if (jsonResult.TestRun) {
74
- return this.processTRX(jsonResult);
79
+ return this.processTRX(jsonResult);
75
80
  } else if (jsonResult['test-run']) {
76
81
  return this.processNUnit(jsonResult['test-run']);
77
82
  } else if (jsonResult.assemblies) {
78
- return this.processXUnit(jsonResult.assemblies);
83
+ return this.processXUnit(jsonResult.assemblies);
79
84
  } else {
80
85
  console.log(jsonResult)
81
86
  throw new Error("Format can't be parsed")
@@ -89,9 +94,9 @@ class XmlReader {
89
94
 
90
95
  reduceOptions.preferClassname = this.stats.language === 'python';
91
96
  const resultTests = processTestSuite(testsuite);
92
-
97
+
93
98
  const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
94
- const status = ( failures > 0 || errors > 0 || hasFailures) ? 'failed' : 'passed';
99
+ const status = (failures > 0 || errors > 0 || hasFailures) ? 'failed' : 'passed';
95
100
 
96
101
  this.tests = this.tests.concat(resultTests);
97
102
 
@@ -124,7 +129,7 @@ class XmlReader {
124
129
  skipped_count: parseInt(inconclusive + skipped, 10),
125
130
  tests: resultTests,
126
131
  };
127
- }
132
+ }
128
133
 
129
134
  processTRX(jsonSuite) {
130
135
  let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
@@ -133,10 +138,10 @@ class XmlReader {
133
138
  const tests = defs.map(td => {
134
139
  const title = td.name.replace(/\(.*?\)/, '').trim();
135
140
  let example = td.name.match(/\((.*?)\)/);
136
- if (example) example = { ...example[1].split(',')};
141
+ if (example) example = { ...example[1].split(',') };
137
142
  const suite = td.TestMethod.className.split(', ')[0].split('.');
138
143
  const suite_title = suite.pop();
139
- return {
144
+ return {
140
145
  title,
141
146
  example,
142
147
  file: suite.join('/'),
@@ -149,18 +154,18 @@ class XmlReader {
149
154
  let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
150
155
  if (!Array.isArray(result)) result = [result].filter(d => !!d);
151
156
 
152
- const results = result.map(td => ({
157
+ const results = result.map(td => ({
153
158
  id: td.executionId,
154
159
  // seconds are used in junit reports, but ms are used by testomatio
155
160
  run_time: parseFloat(td.duration) * 1000,
156
- status: td.outcome,
161
+ status: td.outcome,
157
162
  stack: td.Output.StdOut,
158
163
  files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
159
164
  }));
160
165
 
161
-
166
+
162
167
  results.forEach(r => {
163
- const test = tests.find(t => t.id === r.id) || { };
168
+ const test = tests.find(t => t.id === r.id) || {};
164
169
  r.suite_title = test.suite_title;
165
170
  r.title = test.title?.trim();
166
171
  if (test.code) r.code = test.code;
@@ -175,7 +180,7 @@ class XmlReader {
175
180
  });
176
181
 
177
182
  debug(results);
178
-
183
+
179
184
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
180
185
 
181
186
  const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
@@ -193,7 +198,7 @@ class XmlReader {
193
198
  skipped_count: parseInt(counters.notExecuted, 10),
194
199
  failed_count,
195
200
  tests: results,
196
- };
201
+ };
197
202
  }
198
203
 
199
204
  processXUnit(assemblies) {
@@ -227,7 +232,7 @@ class XmlReader {
227
232
  let status = STATUS.PASSED;
228
233
  if (result === 'Pass') status = STATUS.PASSED;
229
234
  if (result === 'Fail') status = STATUS.FAILED;
230
- if (result === 'Skip') status = STATUS.SKIPPED;
235
+ if (result === 'Skip') status = STATUS.SKIPPED;
231
236
 
232
237
  const pathParts = type.split('.');
233
238
  const suite_title = pathParts[pathParts.length - 1];
@@ -300,12 +305,19 @@ class XmlReader {
300
305
  if (file.endsWith('.py')) this.stats.language = 'python';
301
306
  if (file.endsWith('.java')) this.stats.language = 'java';
302
307
  if (file.endsWith('.rb')) this.stats.language = 'ruby';
303
- if (file.endsWith('.js')) this.stats.language = 'js';
308
+ if (file.endsWith('.js')) this.stats.language = 'js';
304
309
  if (file.endsWith('.ts')) this.stats.language = 'ts';
305
310
  }
306
311
 
312
+ if (!fs.existsSync(file)) {
313
+ debug('Failed to open file with the source code', file)
314
+ return;
315
+ }
307
316
  const contents = fs.readFileSync(file).toString();
308
317
  t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
318
+ if (t.code) debug('Fetched code for test %s', t.title);
319
+ t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
320
+ if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
309
321
  } catch (err) {
310
322
  debug(err)
311
323
  }
@@ -349,8 +361,12 @@ class XmlReader {
349
361
  let files = [];
350
362
  if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
351
363
  files = [...files, ...fetchFilesFromStackTrace(test.stack)];
352
- debug('Uploading files', files)
353
- test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, this.runId)));
364
+
365
+ if (!files.length) continue;
366
+
367
+ const runId = this.runId || this.store.runId || Date.now().toString();
368
+ test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
369
+ console.log(APP_PREFIX, `🗄️ Uploaded ${chalk.bold(`${files.length} artifacts`)} for test ${test.title}`);
354
370
  }
355
371
  }
356
372
 
@@ -360,7 +376,7 @@ class XmlReader {
360
376
  title: this.requestParams.title,
361
377
  env: this.requestParams.env,
362
378
  group_title: this.requestParams.group_title,
363
- };
379
+ };
364
380
 
365
381
  debug("Run", runParams);
366
382
 
@@ -378,9 +394,9 @@ class XmlReader {
378
394
  debug(
379
395
  'Uploading data',
380
396
  {
381
- ...this.stats,
382
- tests: this.tests,
383
- })
397
+ ...this.stats,
398
+ tests: this.tests,
399
+ })
384
400
 
385
401
  const dataString = {
386
402
  ...this.stats,
@@ -402,6 +418,8 @@ function reduceTestCases(prev, item) {
402
418
  if (!Array.isArray(testCases)) {
403
419
  testCases = [testCases]
404
420
  }
421
+ const suiteOutput = item['system-out'] || item.output || item.log || '';
422
+ const suiteErr = item['system-err'] || item.output || item.log || '';
405
423
  testCases.filter(t => !!t).forEach(testCaseItem => {
406
424
  const file = testCaseItem.file || item.filepath || '';
407
425
 
@@ -417,8 +435,9 @@ function reduceTestCases(prev, item) {
417
435
  if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
418
436
  if (!message) message = stack.trim().split('\n')[0];
419
437
 
420
- // prepend system output
421
- stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}`.trim()
438
+ // eslint-disable-next-line
439
+ stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim()
440
+ const testId = fetchIdFromOutput(stack);
422
441
 
423
442
  let status = STATUS.PASSED.toString();
424
443
  if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
@@ -428,6 +447,7 @@ function reduceTestCases(prev, item) {
428
447
  create: true,
429
448
  file,
430
449
  stack,
450
+ test_id: testId,
431
451
  message,
432
452
  line: testCaseItem.lineno,
433
453
  // seconds are used in junit reports, but ms are used by testomatio
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "1.0.12",
3
+ "version": "1.0.14-beta",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "typings": "typings/index.d.ts",