@testomatio/reporter 0.8.0-beta.3 → 0.8.0-beta.31

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.
@@ -1,23 +1,27 @@
1
+ const debug = require('debug')('@testomatio/reporter:pipe:testomatio');
1
2
  const chalk = require('chalk');
2
3
  const axios = require('axios');
3
4
  const JsonCycle = require('json-cycle');
5
+
4
6
  const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_RUN } = process.env;
5
7
  const { APP_PREFIX } = require('../constants');
6
8
  const { isValidUrl } = require('../util');
7
9
 
10
+
8
11
  if (TESTOMATIO_RUN) {
9
12
  process.env.runId = TESTOMATIO_RUN;
10
13
  }
11
14
 
12
15
  class TestomatioPipe {
13
- isEnabled = false;
14
-
15
- constructor(params, store = {}) {
16
+ constructor(params, store = {}) {
17
+ this.isEnabled = false;
16
18
  this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
17
19
  this.apiKey = params.apiKey || process.env.TESTOMATIO;
20
+ debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
18
21
  if (!this.apiKey) {
19
22
  return;
20
23
  }
24
+ debug('Testomatio Pipe: Enabled');
21
25
  this.store = store;
22
26
  this.title = params.title || process.env.TESTOMATIO_TITLE;
23
27
  this.axios = axios.create();
@@ -26,14 +30,9 @@ class TestomatioPipe {
26
30
  this.runId = params.runId || process.env.runId;
27
31
  this.createNewTests = !!process.env.TESTOMATIO_CREATE;
28
32
 
29
-
30
33
  if (!isValidUrl(this.url.trim())) {
31
34
  this.isEnabled = false;
32
- console.log(
33
- APP_PREFIX,
34
- chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`),
35
- );
36
- return;
35
+ console.error(APP_PREFIX, chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`));
37
36
  }
38
37
  }
39
38
 
@@ -42,54 +41,53 @@ class TestomatioPipe {
42
41
 
43
42
  runParams.api_key = this.apiKey.trim();
44
43
  runParams.group_title = TESTOMATIO_RUNGROUP_TITLE;
45
-
44
+
46
45
  if (this.runId) {
47
- return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams)
46
+ return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams);
48
47
  }
49
-
48
+
50
49
  try {
51
50
  const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
52
51
  maxContentLength: Infinity,
53
52
  maxBodyLength: Infinity,
54
- })
53
+ });
55
54
  this.runId = resp.data.uid;
56
55
  this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
57
56
  this.store.runUrl = this.runUrl;
58
- this.store.runId = this.runId;
59
- console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId, chalk.gray(`v${this.version}`));
57
+ this.store.runId = this.runId;
58
+ console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
60
59
  process.env.runId = this.runId;
61
60
  } catch (err) {
62
- console.log(
61
+ console.error(
63
62
  APP_PREFIX,
64
63
  'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
65
64
  );
66
65
  }
67
66
  }
68
67
 
69
- async addTest(data) {
68
+ addTest(data) {
70
69
  if (!this.isEnabled) return;
71
70
  if (!this.runId) return;
72
71
  data.api_key = this.apiKey;
73
72
  data.create = this.createNewTests;
74
73
  const json = JsonCycle.stringify(data);
75
74
 
76
- try {
77
- return await this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
78
- maxContentLength: Infinity,
79
- maxBodyLength: Infinity,
80
- headers: {
81
- // Overwrite Axios's automatically set Content-Type
82
- 'Content-Type': 'application/json',
83
- },
84
- });
85
- } catch (err) {
75
+ return this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
76
+ maxContentLength: Infinity,
77
+ maxBodyLength: Infinity,
78
+ headers: {
79
+ // Overwrite Axios's automatically set Content-Type
80
+ 'Content-Type': 'application/json',
81
+ },
82
+ })
83
+ .catch((err) => {
86
84
  if (err.response) {
87
85
  if (err.response.status >= 400) {
88
- const data = err.response.data || { message: '' };
86
+ const responseData = err.response.data || { message: '' };
89
87
  console.log(
90
88
  APP_PREFIX,
91
89
  chalk.blue(this.title),
92
- `Report couldn't be processed: (${err.response.status}) ${data.message}`,
90
+ `Report couldn't be processed: (${err.response.status}) ${responseData.message}`,
93
91
  );
94
92
  return;
95
93
  }
@@ -97,8 +95,7 @@ class TestomatioPipe {
97
95
  } else {
98
96
  console.log(APP_PREFIX, chalk.blue(this.title), "Report couldn't be processed", err);
99
97
  }
100
- }
101
-
98
+ });
102
99
  }
103
100
 
104
101
  async finishRun(params) {
@@ -109,6 +106,7 @@ class TestomatioPipe {
109
106
  api_key: this.apiKey,
110
107
  status_event: params.statusEvent,
111
108
  status: params.status,
109
+ tests: params.tests,
112
110
  });
113
111
  if (this.runUrl) {
114
112
  console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
@@ -122,7 +120,11 @@ class TestomatioPipe {
122
120
  } catch (err) {
123
121
  console.log(APP_PREFIX, 'Error updating status, skipping...', err);
124
122
  }
125
- }
123
+ }
124
+
125
+ toString() {
126
+ return 'Testomatio Reporter';
127
+ }
126
128
  }
127
129
 
128
- module.exports = TestomatioPipe;
130
+ module.exports = TestomatioPipe;
package/lib/reporter.js CHANGED
@@ -1,7 +1,9 @@
1
1
  const TestomatClient = require('./client');
2
2
  const TRConstants = require('./constants');
3
+ const TRArtifacts = require('./ArtifactStorage');
3
4
 
4
5
  module.exports = {
5
6
  TestomatClient,
6
7
  TRConstants,
8
+ TRArtifacts
7
9
  };
package/lib/util.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { URL } = require('url');
2
- const { sep } = require('path');
2
+ const { sep, basename } = require('path');
3
3
  const chalk = require('chalk');
4
4
  const fs = require('fs');
5
5
  const isValid = require('is-valid-path');
@@ -79,6 +79,8 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
79
79
  const prepend = 3;
80
80
  const source = fetchSourceCode(fs.readFileSync(file).toString(), { line, prepend, limit: 7 })
81
81
 
82
+ if (!source) return '';
83
+
82
84
  return source.split('\n')
83
85
  .map((l, i) => {
84
86
  if (i === prepend) return `${line} > ${chalk.bold(l)}`;
@@ -137,10 +139,37 @@ const fetchSourceCode = (contents, opts = {}) => {
137
139
  }
138
140
  }
139
141
 
140
- const isSameTest = (test, t) => {
141
- return t.title == test.title && t.suite_title == test.suite_title && Object.values(t.example) == Object.values(test.example) && t.test_id == test.test_id;
142
+ const isSameTest = (test, t) => (typeof t === 'object')
143
+ && (typeof test === 'object')
144
+ && t.title === test.title
145
+ && t.suite_title === test.suite_title
146
+ && Object.values(t.example || {}) === Object.values(test.example || {})
147
+ && t.test_id === test.test_id;
148
+
149
+ const getCurrentDateTime = () => {
150
+ const today = new Date();
151
+
152
+ return `${today.getFullYear() }_${ today.getMonth() + 1 }_${ today.getDate() }_${
153
+ today.getHours() }_${ today.getMinutes() }_${ today.getSeconds()}`;
142
154
  }
143
155
 
156
+ /**
157
+ * @param {String} test - Test adapter object
158
+ *
159
+ * @returns {String} testInfo as one string
160
+ */
161
+ const specificTestInfo = test => {
162
+ //TODO: afterEach has another context.... need to add specific handler, maybe...
163
+ if (test?.title && test?.file) {
164
+
165
+ return basename(test.file).split(".").join("#")
166
+ + "#"
167
+ + test.title.split(" ").join("#");
168
+ }
169
+
170
+ return null;
171
+ };
172
+
144
173
  module.exports = {
145
174
  parseTest,
146
175
  parseSuite,
@@ -150,4 +179,6 @@ module.exports = {
150
179
  fetchSourceCode,
151
180
  fetchSourceCodeFromStackTrace,
152
181
  fetchFilesFromStackTrace,
182
+ getCurrentDateTime,
183
+ specificTestInfo
153
184
  };
package/lib/xmlReader.js CHANGED
@@ -1,10 +1,11 @@
1
+ const debug = require('debug')('@testomatio/reporter:xml');
1
2
  const path = require("path");
2
3
  const chalk = require('chalk');
3
4
  const fs = require("fs");
4
5
 
5
6
  // const util = require("util"); // you can see a result
6
7
  const { XMLParser } = require("fast-xml-parser");
7
- const { PASSED, FAILED, SKIPPED } = require('./constants');
8
+ const { APP_PREFIX, PASSED, FAILED, SKIPPED } = require('./constants');
8
9
  const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace } = require('./util');
9
10
  const upload = require('./fileUploader');
10
11
  const pipesFactory = require('./pipe');
@@ -45,6 +46,9 @@ class XmlReader {
45
46
  this.stats = {}
46
47
  this.stats.language = opts.lang?.toLowerCase();
47
48
  this.filesToUpload = {}
49
+
50
+ this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
51
+ console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
48
52
  }
49
53
 
50
54
  connectAdapter() {
@@ -116,32 +120,43 @@ class XmlReader {
116
120
  }
117
121
 
118
122
  processTRX(jsonSuite) {
119
- const tests = jsonSuite?.TestRun?.TestDefinitions?.UnitTest?.map(td => {
123
+ let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
124
+ if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
125
+
126
+ const tests = defs.map(td => {
120
127
  const title = td.name.replace(/\(.*?\)/, '').trim();
121
128
  let example = td.name.match(/\((.*?)\)/);
122
129
  if (example) example = { ...example[1].split(',')};
123
- const suite = td.TestMethod.className.split('.');
130
+ const suite = td.TestMethod.className.split(', ')[0].split('.');
124
131
  const suite_title = suite.pop();
125
132
  return {
126
133
  title,
127
134
  example,
128
135
  file: suite.join('/'),
136
+ description: td.Description,
129
137
  suite_title,
130
138
  id: td.Execution.id,
131
139
  }
132
140
  }) || [];
133
141
 
134
- const results = jsonSuite?.TestRun?.Results?.UnitTestResult?.map(td => ({
142
+ let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
143
+ if (!Array.isArray(result)) result = [result].filter(d => !!d);
144
+
145
+ const results = result.map(td => ({
135
146
  id: td.executionId,
136
147
  run_time: parseFloat(td.duration),
137
148
  status: td.outcome,
138
- stack: td.Output.StdOut
149
+ stack: td.Output.StdOut,
150
+ files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
139
151
  }));
140
152
 
153
+
141
154
  results.forEach(r => {
142
- const test = tests.find(t => t.id === r.id) || {};
155
+ const test = tests.find(t => t.id === r.id) || { };
143
156
  r.suite_title = test.suite_title;
144
157
  r.title = test.title?.trim();
158
+ if (test.code) r.code = test.code;
159
+ if (test.description) r.description = test.description;
145
160
  if (test.example) r.example = test.example;
146
161
  if (test.file) r.file = test.file;
147
162
  r.create = true;
@@ -150,6 +165,8 @@ class XmlReader {
150
165
  if (r.status === 'Skipped') r.status = SKIPPED;
151
166
  delete r.id;
152
167
  });
168
+
169
+ debug(results);
153
170
 
154
171
  const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
155
172
 
@@ -158,7 +175,7 @@ class XmlReader {
158
175
  let status = PASSED;
159
176
  if (failed_count > 0) status = FAILED;
160
177
 
161
- this.tests = results;
178
+ this.tests = results.filter(t => !!t.title);
162
179
 
163
180
  return {
164
181
  status,
@@ -209,9 +226,7 @@ class XmlReader {
209
226
  const contents = fs.readFileSync(file).toString();
210
227
  t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
211
228
  } catch (err) {
212
- if (process.env.DEBUG) {
213
- console.log(err)
214
- }
229
+ debug(err)
215
230
  }
216
231
  });
217
232
  }
@@ -261,7 +276,8 @@ class XmlReader {
261
276
 
262
277
  async uploadArtifacts() {
263
278
  for (const test of this.tests.filter(t => !!t.stack)) {
264
- const files = fetchFilesFromStackTrace(test.stack);
279
+ const files = [...test.files.map(f => path.join(process.cwd(), f)), ...fetchFilesFromStackTrace(test.stack)];
280
+ debug('Uploading files', files)
265
281
  test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, this.runId)));
266
282
  }
267
283
  }
@@ -274,7 +290,7 @@ class XmlReader {
274
290
  group_title: this.requestParams.group_title,
275
291
  };
276
292
 
277
- if (process.env.DEBUG) console.log("Run", runParams);
293
+ debug("Run", runParams);
278
294
 
279
295
  return Promise.all(this.pipes.map(p => p.createRun(runParams)));
280
296
  }
@@ -287,12 +303,12 @@ class XmlReader {
287
303
  this.formatErrors();
288
304
  this.formatTests();
289
305
 
290
- if (process.env.DEBUG) {
291
- console.log({
292
- ...this.stats,
293
- tests: this.tests,
294
- })
295
- }
306
+ debug(
307
+ 'Uploading data',
308
+ {
309
+ ...this.stats,
310
+ tests: this.tests,
311
+ })
296
312
 
297
313
  const dataString = {
298
314
  ...this.stats,
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "0.8.0-beta.3",
3
+ "version": "0.8.0-beta.31",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "main": "./lib/reporter.js",
6
6
  "repository": "git@github.com:testomatio/reporter.git",
7
7
  "author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
8
8
  "license": "MIT",
9
9
  "dependencies": {
10
+ "@aws-sdk/client-s3": "^3.279.0",
11
+ "@aws-sdk/lib-storage": "^3.279.0",
10
12
  "@octokit/rest": "^19.0.5",
11
13
  "aws-sdk": "^2.1072.0",
12
14
  "axios": "^0.25.0",
@@ -17,10 +19,13 @@
17
19
  "fast-xml-parser": "^4.0.8",
18
20
  "glob": "^8.0.3",
19
21
  "has-flag": "^5.0.1",
22
+ "humanize-duration": "^3.27.3",
20
23
  "is-valid-path": "^0.1.1",
21
24
  "json-cycle": "^1.3.0",
22
25
  "lodash.memoize": "^4.1.2",
23
- "lodash.merge": "^4.6.2"
26
+ "lodash.merge": "^4.6.2",
27
+ "csv-writer": "^1.6.0",
28
+ "uuid": "^9.0.0"
24
29
  },
25
30
  "files": [
26
31
  "bin",
@@ -28,20 +33,21 @@
28
33
  "testcafe"
29
34
  ],
30
35
  "scripts": {
36
+ "clear-exportdir": "rm -rf export/",
31
37
  "pretty": "prettier --write .",
32
38
  "lint": "eslint lib",
33
39
  "lint:fix": "eslint lib --fix",
34
- "test": "mocha tests/** -R ./lib/adapter/mocha.js",
40
+ "test": "mocha tests/**",
35
41
  "init": "cd ./tests/adapter/examples/cucumber && npm i",
36
42
  "test:unit": "mocha tests",
37
43
  "test:adapter": "mocha './tests/adapter/index.test.js'",
44
+ "test:pipes": "mocha './tests/pipes/*_test.js'",
38
45
  "test:adapter:jest:example": "jest './tests/adapter/examples/jest/index.test.js' --config='./tests/adapter/examples/jest/jest.config.js'",
39
46
  "test:adapter:mocha:example": "mocha './tests/adapter/examples/mocha/index.test.js' --config='./tests/adapter/examples/mocha/mocha.config.js'",
40
47
  "test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
41
48
  "test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
42
49
  "test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
43
50
  },
44
- "peerDependencies": {},
45
51
  "devDependencies": {
46
52
  "@cucumber/cucumber": "^8.6.0",
47
53
  "@wdio/reporter": "^7.16.13",
package/testcafe/index.js DELETED
@@ -1,61 +0,0 @@
1
- const TestomatClient = require('@testomatio/reporter/lib/client');
2
- const TRConstants = require('@testomatio/reporter/lib/constants');
3
- const util = require('@testomatio/reporter/lib/util');
4
-
5
- module.exports = () => {
6
- const apiKey = process.env.TESTOMATIO;
7
- let failed = false;
8
-
9
- if (apiKey === '' || apiKey === undefined) {
10
- throw new Error('Testomat.io API key cannot be empty');
11
- }
12
- const client = new TestomatClient({ apiKey });
13
-
14
- return {
15
- reportTaskStart(startTime, userAgents) {
16
- console.log('TestCafe started with: ', userAgents);
17
- client.createRun();
18
- },
19
-
20
- reportFixtureStart(name) {
21
- console.log(`Suite : ${name}`);
22
- },
23
-
24
- reportTestDone(name, testRunInfo) {
25
- let status = TRConstants.PASSED;
26
- let message = '';
27
-
28
- if (testRunInfo.skipped) {
29
- status = TRConstants.SKIPPED;
30
- }
31
- if (testRunInfo.errs.length) {
32
- status = TRConstants.FAILED;
33
- message = this.renderErrors(testRunInfo.errs);
34
- failed = true;
35
- }
36
- console.log(` - ${name} : ${status}`);
37
- client.addTestRun(util.parseTest(name), status, {
38
- error: testRunInfo.errs.length ? testRunInfo.errs[0] : null,
39
- message,
40
- title: name,
41
- time: testRunInfo.durationMs,
42
- });
43
- },
44
-
45
- renderErrors(errors) {
46
- let errorMessage = '';
47
- errors.forEach((error, id) => {
48
- errorMessage = `${errorMessage}${this.formatError(error, `${id + 1} `)}\n`;
49
- });
50
-
51
- console.log(errorMessage);
52
- return errorMessage.replace(util.ansiRegExp(), '');
53
- },
54
-
55
- reportTaskDone() {
56
- const status = failed ? TRConstants.FAILED : TRConstants.PASSED;
57
- console.log(`Status : ${status}`);
58
- client.updateRunStatus(status);
59
- },
60
- };
61
- };