@testomatio/reporter 0.7.3 → 0.8.0-beta.2

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/Changelog.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # 0.7.3
2
2
 
3
+ * CodeceptJS: Upload all traces and videos from artifacts
3
4
  * Fixed reporting skipped test in XML
4
5
  * added `--timelimit` option to `report-xml` command line
5
6
 
@@ -148,7 +148,11 @@ function CodeceptReporter(config) {
148
148
  const files = [];
149
149
  if (artifacts.screenshot) files.push({ path: artifacts.screenshot, type: 'image/png' });
150
150
  // todo: video must be uploaded later....
151
- if (artifacts.video) videos.push({ testId: id, title, path: artifacts.video, type: 'video/webm' });
151
+
152
+ for (const aid in artifacts) {
153
+ if (aid.startsWith('video')) videos.push({ testId: id, title, path: artifacts[aid], type: 'video/webm' });
154
+ if (aid.startsWith('trace')) files.push({ testId: id, title, path: artifacts[aid], type: 'application/zip' });
155
+ }
152
156
 
153
157
  client.addTestRun(testId, TRConstants.FAILED, {
154
158
  ...stripExampleFromTitle(title),
package/lib/client.js CHANGED
@@ -1,19 +1,13 @@
1
1
  const axios = require('axios');
2
- const JsonCycle = require('json-cycle');
3
2
  const createCallsiteRecord = require('callsite-record');
4
3
  const { sep, join } = require('path');
5
4
  const fs = require('fs');
6
5
  const chalk = require('chalk');
7
6
  const upload = require('./fileUploader');
8
- const { isValidUrl } = require('./util');
9
7
  const { PASSED, FAILED, FINISHED, APP_PREFIX } = require('./constants');
8
+ const pipesFactory = require('./pipe');
9
+ const { TESTOMATIO_ENV } = process.env;
10
10
 
11
- const TESTOMAT_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
12
- const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
13
-
14
- if (TESTOMATIO_RUN) {
15
- process.env.runId = TESTOMATIO_RUN;
16
- }
17
11
 
18
12
  class TestomatClient {
19
13
  /**
@@ -24,11 +18,10 @@ class TestomatClient {
24
18
  constructor(params) {
25
19
  this.apiKey = params.apiKey || process.env.TESTOMATIO;
26
20
  this.title = params.title || process.env.TESTOMATIO_TITLE;
27
- this.createNewTests = !!process.env.TESTOMATIO_CREATE;
28
- this.proceed = process.env.TESTOMATIO_PROCEED;
29
21
  this.env = TESTOMATIO_ENV;
30
22
  this.parallel = params.parallel;
31
- this.runId = process.env.runId;
23
+ const store = {}
24
+ this.pipes = pipesFactory(params, store);
32
25
  this.queue = Promise.resolve();
33
26
  this.axios = axios.create();
34
27
  this.totalUploaded = 0;
@@ -42,46 +35,15 @@ class TestomatClient {
42
35
  */
43
36
  createRun() {
44
37
  const { runId } = process.env;
45
- if (!this.apiKey) throw new Error("No API key is set, can't create run");
46
38
  const runParams = {
47
- api_key: this.apiKey.trim(),
48
39
  title: this.title,
49
40
  parallel: this.parallel,
50
41
  env: this.env,
51
- group_title: TESTOMATIO_RUNGROUP_TITLE,
52
42
  };
53
43
 
54
44
  global.testomatioArtifacts = [];
55
45
 
56
- if (!isValidUrl(TESTOMAT_URL.trim())) {
57
- console.log(
58
- APP_PREFIX,
59
- chalk.red(`Error creating report on Testomat.io, report url '${TESTOMAT_URL}' is invalid`),
60
- );
61
- return;
62
- }
63
-
64
- if (runId) {
65
- this.runId = runId;
66
- this.queue = this.queue.then(() => axios.put(`${TESTOMAT_URL.trim()}/api/reporter/${runId}`, runParams));
67
- return Promise.resolve(runId);
68
- }
69
-
70
- this.queue = this.queue
71
- .then(() =>
72
- this.axios.post(`${TESTOMAT_URL.trim()}/api/reporter`, runParams).then(resp => {
73
- this.runId = resp.data.uid;
74
- this.runUrl = `${TESTOMAT_URL}/${resp.data.url.split('/').splice(3).join('/')}`;
75
- console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId, chalk.gray(`v${this.version}`));
76
- process.env.runId = this.runId;
77
- }),
78
- )
79
- .catch(() => {
80
- console.log(
81
- APP_PREFIX,
82
- 'Error creating report Testomat.io, please check if your API key is valid. Skipping report',
83
- );
84
- });
46
+ this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))));
85
47
 
86
48
  return this.queue;
87
49
  }
@@ -120,73 +82,42 @@ class TestomatClient {
120
82
  stack = this.formatSteps(stack, steps);
121
83
  }
122
84
 
123
- if (this.runId) {
124
- if (Array.isArray(global.testomatioArtifacts)) {
125
- files.push(...global.testomatioArtifacts);
126
- global.testomatioArtifacts = [];
127
- }
128
-
129
- for (const file of files) {
130
- uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
131
- }
85
+ if (Array.isArray(global.testomatioArtifacts)) {
86
+ files.push(...global.testomatioArtifacts);
87
+ global.testomatioArtifacts = [];
88
+ }
132
89
 
133
- for (const [idx, buffer] of filesBuffers.entries()) {
134
- const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
90
+ for (const file of files) {
91
+ uploadedFiles.push(upload.uploadFileByPath(file, this.runId));
92
+ }
135
93
 
136
- uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
137
- }
94
+ for (const [idx, buffer] of filesBuffers.entries()) {
95
+ const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
96
+ uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
138
97
  }
139
98
 
140
- this.queue = this.queue
141
- .then(async () => {
142
- if (!this.runId) return;
99
+ const artifacts = await Promise.all(uploadedFiles)
143
100
 
144
- global.testomatioArtifacts = [];
101
+ global.testomatioArtifacts = [];
145
102
 
146
- this.totalUploaded += uploadedFiles.filter(n => n).length;
103
+ this.totalUploaded += uploadedFiles.filter(n => n).length;
147
104
 
148
- const json = JsonCycle.stringify({
149
- api_key: this.apiKey,
150
- create: this.createNewTests,
151
- files,
152
- steps,
153
- status,
154
- stack,
155
- example,
156
- title,
157
- suite_title,
158
- suite_id,
159
- test_id,
160
- message,
161
- run_time: time,
162
- artifacts: await Promise.all(uploadedFiles),
163
- });
164
- return this.axios.post(`${TESTOMAT_URL}/api/reporter/${this.runId}/testrun`, json, {
165
- maxContentLength: Infinity,
166
- maxBodyLength: Infinity,
167
- headers: {
168
- // Overwrite Axios's automatically set Content-Type
169
- 'Content-Type': 'application/json',
170
- },
171
- });
172
- })
173
- .catch(err => {
174
- if (err.response) {
175
- if (err.response.status >= 400) {
176
- const data = err.response.data || { message: '' };
177
- console.log(
178
- APP_PREFIX,
179
- chalk.blue(title),
180
- `Report couldn't be processed: (${err.response.status}) ${data.message}`,
181
- );
182
- return;
183
- }
184
- console.log(APP_PREFIX, chalk.blue(title), `Report couldn't be processed: ${err.response.data.message}`);
185
- } else {
186
- console.log(APP_PREFIX, chalk.blue(title), "Report couldn't be processed", err);
187
- }
188
- });
105
+ const data = {
106
+ files,
107
+ steps,
108
+ status,
109
+ stack,
110
+ example,
111
+ title,
112
+ suite_title,
113
+ suite_id,
114
+ test_id,
115
+ message,
116
+ run_time: parseFloat(time),
117
+ artifacts,
118
+ };
189
119
 
120
+ this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.addTest(data))));
190
121
  return this.queue;
191
122
  }
192
123
 
@@ -196,41 +127,23 @@ class TestomatClient {
196
127
  * @returns {Promise}
197
128
  */
198
129
  updateRunStatus(status, isParallel) {
199
- this.queue = this.queue
200
- .then(async () => {
201
- if (this.runId && !this.proceed) {
202
- let statusEvent;
203
- if (status === FINISHED) statusEvent = 'finish';
204
- if (status === PASSED) statusEvent = 'pass';
205
- if (status === FAILED) statusEvent = 'fail';
206
- if (isParallel) statusEvent += '_parallel';
207
- await this.axios.put(`${TESTOMAT_URL}/api/reporter/${this.runId}`, {
208
- api_key: this.apiKey,
209
- status_event: statusEvent,
210
- status,
211
- });
212
- if (this.runUrl) {
213
- console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
214
- }
130
+ let statusEvent;
131
+ if (status === FINISHED) statusEvent = 'finish';
132
+ if (status === PASSED) statusEvent = 'pass';
133
+ if (status === FAILED) statusEvent = 'fail';
134
+ if (isParallel) statusEvent += '_parallel';
135
+ const runParams = { status, statusEvent }
215
136
 
216
- if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
217
- console.log(
218
- APP_PREFIX,
219
- `🗄️ Total ${this.totalUploaded} artifacts ${
220
- process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
221
- } uploaded to S3 bucket `,
222
- );
223
- }
224
- }
225
- if (this.runUrl && this.proceed) {
226
- const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
227
- console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
228
- console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
229
- }
230
- })
231
- .catch(err => {
232
- console.log(APP_PREFIX, 'Error updating status, skipping...', err);
233
- });
137
+ this.queue = this.queue.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))));
138
+
139
+ if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
140
+ console.log(
141
+ APP_PREFIX,
142
+ `🗄️ Total ${this.totalUploaded} artifacts ${
143
+ process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
144
+ } uploaded to S3 bucket `,
145
+ );
146
+ }
234
147
  return this.queue;
235
148
  }
236
149
 
@@ -0,0 +1,27 @@
1
+ const Adapter = require('./adapter');
2
+ const JavaScriptAdapter = require('./javascript');
3
+ const JavaAdapter = require('./java');
4
+ const PythonAdapter = require('./python');
5
+ const RubyAdapter = require('./ruby');
6
+ const CSharpAdapter = require('./csharp');
7
+
8
+
9
+ module.exports = function (lang, opts) {
10
+ if (!lang) return new Adapter(opts);
11
+
12
+ if (lang === 'java') {
13
+ return new JavaAdapter(opts);
14
+ }
15
+ if (lang === 'js') {
16
+ return new JavaScriptAdapter(opts);
17
+ }
18
+ if (lang === 'python') {
19
+ return new PythonAdapter(opts);
20
+ }
21
+ if (lang === 'ruby') {
22
+ return new RubyAdapter(opts);
23
+ }
24
+ if (lang === 'c#' || lang === 'csharp') {
25
+ return new CSharpAdapter(opts);
26
+ }
27
+ }
@@ -0,0 +1,163 @@
1
+ const chalk = require('chalk');
2
+ const { Octokit } = require("@octokit/rest");
3
+ const { APP_PREFIX } = require('../constants');
4
+ const { ansiRegExp, isSameTest } = require('../util');
5
+ const merge = require('lodash.merge');
6
+
7
+
8
+ class GitHubPipe {
9
+
10
+ isEnabled = false;
11
+
12
+ constructor(params, store = {}) {
13
+ this.store = store;
14
+ this.tests = []
15
+ this.token = params.GH_PAT || process.env.GH_PAT;
16
+ this.ref = process.env.GITHUB_REF
17
+ this.repo = process.env.GITHUB_ACTION_REPOSITORY
18
+ if (!this.token || !this.ref || !this.repo) return;
19
+ this.isEnabled = true;
20
+ this.issue = this.ref.match(/refs\/pull\/(\d+)\/merge/)[1]
21
+ this.start = new Date();
22
+ }
23
+
24
+ async createRun() {}
25
+
26
+ updateRun() {}
27
+
28
+ addTest(test) {
29
+ if (!this.isEnabled) return;
30
+
31
+ const index = this.tests.findIndex(t => isSameTest(t, test))
32
+ // update if they were already added
33
+ if (index >= 0) {
34
+ this.tests[index] = merge(this.tests[index], test);
35
+ return;
36
+ }
37
+
38
+ this.tests.push(test)
39
+ }
40
+
41
+ async finishRun(runParams) {
42
+ if (!this.isEnabled) return;
43
+
44
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
45
+
46
+
47
+ this.octokit = new Octokit({
48
+ auth: this.token,
49
+ });
50
+
51
+ const [owner, repo] = this.repo.split('/');
52
+ if (!(owner || repo)) return;
53
+
54
+ // ... create a comment on GitHub
55
+ const passedCount = this.tests.filter(t => t.status === 'passed').length;
56
+ const failedCount = this.tests.filter(t => t.status === 'failed').length;
57
+ const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
58
+
59
+ let summary = `
60
+ ### Run Report
61
+
62
+ | | ${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
63
+ | --- | --- |
64
+ | Tests | ✔️ **${this.tests.length}** tests run |
65
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji('passed')} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
66
+ | Duration | 🕐 **${parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)).toFixed(2)}**ms |
67
+ `
68
+ if (this.store.runUrl) {
69
+ summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
70
+ }
71
+ if (process.env.GITHUB_WORKFLOW) {
72
+ summary += `| Workflow | 🗂️ ${process.env.GITHUB_WORKFLOW} | `;
73
+ }
74
+ if (process.env.RUNNER_OS) {
75
+ summary += `| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
76
+ }
77
+ if (process.env.GITHUB_HEAD_REF) {
78
+ summary += `| Branch | 🌳 \`${process.env.GITHUB_HEAD_REF}\` | `;
79
+ }
80
+ if (process.env.GITHUB_RUN_ATTEMPT) {
81
+ summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
82
+ }
83
+ if (process.env.GITHUB_RUN_ID) {
84
+ summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${this.repo}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
85
+ }
86
+
87
+
88
+ const failures = this.tests.filter(t => t.status === 'failed').slice(0, 20).map(t => {
89
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
90
+ text += "\n\n"
91
+ if (t.message) text += "> " + t.message.replace(/[^\x20-\x7E]/g, '').replace(ansiRegExp(), '').trim() + "\n"
92
+ if (t.stack) text += "```diff\n" + t.stack.replace(ansiRegExp(), '').trim() + "\n```\n";
93
+
94
+ if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
95
+ t.artifacts.filter(f => f.endsWith('.png')).forEach(f => {
96
+ if (f.endsWith('.png')) return text+= `![](${f})\n`
97
+ });
98
+ }
99
+
100
+ text += "\n---\n"
101
+
102
+ return text;
103
+ })
104
+
105
+ let body = summary;
106
+
107
+ if (failures.length) {
108
+ body += `<details>\n<summary>### 🟥 Failures (${failures.length})</summary>\n${failures.join('\n')} </details>`;
109
+ }
110
+
111
+ if (failures.length > 20) {
112
+ body += "\n> Notice\n> Only first 20 failures shown*"
113
+ }
114
+
115
+ if (this.tests.length > 0) {
116
+ body += "\n### 🐢 Slowest Tests\n\n"
117
+ body += this.tests.sort((a, b) => b?.run_time - a?.run_time).slice(0, 5).map(t => {
118
+ return `* ${fullName(t)} (${parseFloat(t.run_time).toFixed(2)}ms)`
119
+ }).join('\n')
120
+ }
121
+
122
+ try {
123
+ const resp = await this.octokit.rest.issues.createComment({
124
+ owner,
125
+ repo,
126
+ issue_number: this.issue,
127
+ body,
128
+ });
129
+
130
+ const url = resp.data?.html_url;
131
+ this.store.githubUrl = url;
132
+
133
+ console.log(
134
+ APP_PREFIX,
135
+ chalk.yellow('GitHub'),
136
+ `Report created: ${chalk.magenta(url)}`,
137
+ );
138
+ } catch (err) {
139
+ console.log(
140
+ APP_PREFIX,
141
+ chalk.yellow('GitHub'),
142
+ `Couldn't create GitHub report ${err}`,
143
+ );
144
+ }
145
+ }
146
+ }
147
+
148
+ function statusEmoji(status) {
149
+ if (status === 'passed') return '🟢';
150
+ if (status === 'failed') return '🔴';
151
+ if (status === 'skipped') return '🟡';
152
+ return ''
153
+ }
154
+
155
+ function fullName(t) {
156
+ let line = '';
157
+ if (t.suite_title) line = `${t.suite_title}: `;
158
+ line += `**${t.title}**`
159
+ if (t.example) line += ` \`[${Object.values(t.example)}]\``;
160
+ return line;
161
+ }
162
+
163
+ module.exports = GitHubPipe;
@@ -0,0 +1,45 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { APP_PREFIX } = require('../constants');
4
+ const TestomatioPipe = require('./testomatio');
5
+ const GitHubPipe = require('./github');
6
+
7
+ module.exports = function(params, opts) {
8
+ const extraPipes = [];
9
+
10
+ // Add extra pipes into package.json file:
11
+ // "testomatio": {
12
+ // "pipes": ["my-module-pipe", "./local/js/file/pipe"]
13
+ // }
14
+
15
+ const packageJsonFile = path.join(process.cwd(), 'package.json');
16
+ if (fs.existsSync(packageJsonFile)) {
17
+ const package = fs.readFileSync(packageJsonFile);
18
+ const pipeDefs = package?.testomatio?.pipes || [];
19
+
20
+ for (const pipeDef of pipeDefs) {
21
+ let pipeClass;
22
+ try {
23
+ pipeClass = require(pipeDef);
24
+ } catch (err) {
25
+ console.log(APP_PREFIX, `Can't load module Testomatio pipe module from ${pipeDef}`);
26
+ continue;
27
+ }
28
+
29
+ try {
30
+ extraPipes.push(new pipeClass(params, opts))
31
+ } catch (err) {
32
+ console.log(APP_PREFIX, `Can't instantiate Testomatio for ${pipeDef}`, err);
33
+ continue;
34
+ }
35
+ }
36
+ }
37
+
38
+
39
+ const defaultPipes = [
40
+ new TestomatioPipe(params, opts),
41
+ new GitHubPipe(params, opts),
42
+ ];
43
+
44
+ return [...defaultPipes, ...extraPipes];
45
+ }
@@ -0,0 +1,128 @@
1
+ const chalk = require('chalk');
2
+ const axios = require('axios');
3
+ const JsonCycle = require('json-cycle');
4
+ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_RUN } = process.env;
5
+ const { APP_PREFIX } = require('../constants');
6
+ const { isValidUrl } = require('../util');
7
+
8
+ if (TESTOMATIO_RUN) {
9
+ process.env.runId = TESTOMATIO_RUN;
10
+ }
11
+
12
+ class TestomatioPipe {
13
+ isEnabled = false;
14
+
15
+ constructor(params, store = {}) {
16
+ this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
17
+ this.apiKey = params.apiKey || process.env.TESTOMATIO;
18
+ if (!this.apiKey) {
19
+ return;
20
+ }
21
+ this.store = store;
22
+ this.title = params.title || process.env.TESTOMATIO_TITLE;
23
+ this.axios = axios.create();
24
+ this.isEnabled = true;
25
+ this.proceed = process.env.TESTOMATIO_PROCEED;
26
+ this.runId = params.runId || process.env.runId;
27
+ this.createNewTests = !!process.env.TESTOMATIO_CREATE;
28
+
29
+
30
+ if (!isValidUrl(this.url.trim())) {
31
+ 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;
37
+ }
38
+ }
39
+
40
+ async createRun(runParams) {
41
+ if (!this.isEnabled) return;
42
+
43
+ runParams.api_key = this.apiKey.trim();
44
+ runParams.group_title = TESTOMATIO_RUNGROUP_TITLE;
45
+
46
+ if (this.runId) {
47
+ return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams)
48
+ }
49
+
50
+ try {
51
+ const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
52
+ maxContentLength: Infinity,
53
+ maxBodyLength: Infinity,
54
+ })
55
+ this.runId = resp.data.uid;
56
+ this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
57
+ 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}`));
60
+ process.env.runId = this.runId;
61
+ } catch (err) {
62
+ console.log(
63
+ APP_PREFIX,
64
+ 'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
65
+ );
66
+ }
67
+ }
68
+
69
+ async addTest(data) {
70
+ if (!this.isEnabled) return;
71
+ if (!this.runId) return;
72
+ data.api_key = this.apiKey;
73
+ data.create = this.createNewTests;
74
+ const json = JsonCycle.stringify(data);
75
+
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) {
86
+ if (err.response) {
87
+ if (err.response.status >= 400) {
88
+ const data = err.response.data || { message: '' };
89
+ console.log(
90
+ APP_PREFIX,
91
+ chalk.blue(this.title),
92
+ `Report couldn't be processed: (${err.response.status}) ${data.message}`,
93
+ );
94
+ return;
95
+ }
96
+ console.log(APP_PREFIX, chalk.blue(this.title), `Report couldn't be processed: ${err.response.data.message}`);
97
+ } else {
98
+ console.log(APP_PREFIX, chalk.blue(this.title), "Report couldn't be processed", err);
99
+ }
100
+ }
101
+
102
+ }
103
+
104
+ async finishRun(params) {
105
+ if (!this.isEnabled) return;
106
+ try {
107
+ if (this.runId && !this.proceed) {
108
+ await this.axios.put(`${this.url}/api/reporter/${this.runId}`, {
109
+ api_key: this.apiKey,
110
+ status_event: params.statusEvent,
111
+ status: params.status,
112
+ });
113
+ if (this.runUrl) {
114
+ console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
115
+ }
116
+ }
117
+ if (this.runUrl && this.proceed) {
118
+ const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
119
+ console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
120
+ console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
121
+ }
122
+ } catch (err) {
123
+ console.log(APP_PREFIX, 'Error updating status, skipping...', err);
124
+ }
125
+ }
126
+ }
127
+
128
+ module.exports = TestomatioPipe;
package/lib/util.js CHANGED
@@ -135,13 +135,17 @@ const fetchSourceCode = (contents, opts = {}) => {
135
135
  }
136
136
  return result.join('\n');
137
137
  }
138
+ }
138
139
 
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;
139
142
  }
140
143
 
141
144
  module.exports = {
142
145
  parseTest,
143
146
  parseSuite,
144
147
  ansiRegExp,
148
+ isSameTest,
145
149
  isValidUrl,
146
150
  fetchSourceCode,
147
151
  fetchSourceCodeFromStackTrace,
package/lib/xmlReader.js CHANGED
@@ -1,19 +1,14 @@
1
1
  const path = require("path");
2
- const axios = require('axios');
3
2
  const chalk = require('chalk');
4
3
  const fs = require("fs");
5
4
 
6
5
  // const util = require("util"); // you can see a result
7
6
  const { XMLParser } = require("fast-xml-parser");
8
- const { APP_PREFIX, PASSED, FAILED, SKIPPED } = require('./constants');
9
- const { isValidUrl, fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace } = require('./util');
7
+ const { PASSED, FAILED, SKIPPED } = require('./constants');
8
+ const { fetchFilesFromStackTrace, fetchSourceCode, fetchSourceCodeFromStackTrace } = require('./util');
10
9
  const upload = require('./fileUploader');
11
- const Adapter = require('./junit-adapter/adapter');
12
- const JavaScriptAdapter = require('./junit-adapter/javascript');
13
- const JavaAdapter = require('./junit-adapter/java');
14
- const PythonAdapter = require('./junit-adapter/python');
15
- const RubyAdapter = require('./junit-adapter/ruby');
16
- const CSharpAdapter = require('./junit-adapter/csharp');
10
+ const pipesFactory = require('./pipe');
11
+ const adapterFactory = require('./junit-adapter');
17
12
 
18
13
  const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
19
14
  const TESTOMATIO = process.env.TESTOMATIO; // key?
@@ -40,9 +35,11 @@ class XmlReader {
40
35
  group_title: TESTOMATIO_RUNGROUP_TITLE,
41
36
  };
42
37
  this.runId = opts.runId || TESTOMATIO_RUN;
43
- this.adapter = new Adapter(opts)
38
+ this.adapter = adapterFactory(null, opts)
44
39
  this.opts = opts;
45
- this.axios = axios.create();
40
+ const store = {}
41
+ this.pipes = pipesFactory(opts, store);
42
+
46
43
  this.parser = new XMLParser(options);
47
44
  this.tests = []
48
45
  this.stats = {}
@@ -51,21 +48,8 @@ class XmlReader {
51
48
  }
52
49
 
53
50
  connectAdapter() {
54
- if (this.opts.javaTests || this.stats.language === 'java') {
55
- this.adapter = new JavaAdapter(this.opts);
56
- }
57
- if (this.stats.language === 'js') {
58
- this.adapter = new JavaScriptAdapter(this.opts);
59
- }
60
- if (this.stats.language === 'python') {
61
- this.adapter = new PythonAdapter(this.opts);
62
- }
63
- if (this.stats.language === 'ruby') {
64
- this.adapter = new RubyAdapter(this.opts);
65
- }
66
- if (this.stats.language === 'c#' || this.stats.language === 'csharp') {
67
- this.adapter = new CSharpAdapter(this.opts);
68
- }
51
+ if (this.opts.javaTests) return adapterFactory('java', this.opts);
52
+ return adapterFactory(this.stats.language, this.opts);
69
53
  }
70
54
 
71
55
  parse(fileName) {
@@ -149,7 +133,7 @@ class XmlReader {
149
133
 
150
134
  const results = jsonSuite?.TestRun?.Results?.UnitTestResult?.map(td => ({
151
135
  id: td.executionId,
152
- run_time: td.duration,
136
+ run_time: parseFloat(td.duration),
153
137
  status: td.outcome,
154
138
  stack: td.Output.StdOut
155
139
  }));
@@ -276,7 +260,6 @@ class XmlReader {
276
260
  }
277
261
 
278
262
  async uploadArtifacts() {
279
- if (!this.runId) return;
280
263
  for (const test of this.tests.filter(t => !!t.stack)) {
281
264
  const files = fetchFilesFromStackTrace(test.stack);
282
265
  test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, this.runId)));
@@ -284,8 +267,6 @@ class XmlReader {
284
267
  }
285
268
 
286
269
  async createRun() {
287
- if (this.runId) return;
288
-
289
270
  const runParams = {
290
271
  api_key: this.requestParams.apiKey,
291
272
  title: this.requestParams.title,
@@ -295,30 +276,7 @@ class XmlReader {
295
276
 
296
277
  if (process.env.DEBUG) console.log("Run", runParams);
297
278
 
298
- try {
299
- const resp = await this.axios.post(this.url, runParams, {
300
- maxContentLength: Infinity,
301
- maxBodyLength: Infinity,
302
- headers: {
303
- // Overwrite Axios's automatically set Content-Type
304
- 'Content-Type': 'application/json',
305
- }
306
- });
307
- if (resp.status >= 400) {
308
- const data = resp.data || { message: '' };
309
- console.log(
310
- APP_PREFIX,
311
- `Report couldn't be processed: (${resp.status}) ${data.message}`,
312
- );
313
- return;
314
- }
315
- this.runId = resp.data.uid;
316
- this.runUrl = `${TESTOMATIO_URL}/${resp.data.url.split('/').splice(3).join('/')}`;
317
- } catch(err) {
318
- if (process.env.DEBUG) console.log(err)
319
- const data = err?.response?.data || { message: '' };
320
- console.log(APP_PREFIX, 'Error creating run, skipping...', err?.response?.statusText, data);
321
- }
279
+ return Promise.all(this.pipes.map(p => p.createRun(runParams)));
322
280
  }
323
281
 
324
282
  async uploadData() {
@@ -336,56 +294,14 @@ class XmlReader {
336
294
  })
337
295
  }
338
296
 
339
- const dataString = JSON.stringify({
297
+ const dataString = {
340
298
  ...this.stats,
341
299
  api_key: this.requestParams.apiKey,
342
300
  status_event: 'finish',
343
301
  tests: this.tests,
344
- });
345
-
346
- try {
347
- const resp = await this.axios.put(`${this.url}/${this.runId}`, dataString, {
348
- maxContentLength: Infinity,
349
- maxBodyLength: Infinity,
350
- headers: {
351
- // Overwrite Axios's automatically set Content-Type
352
- 'Content-Type': 'application/json',
353
- }
354
- });
355
-
356
- if (resp.status >= 400) {
357
- const data = resp.data || { message: '' };
358
- console.log(
359
- APP_PREFIX,
360
- `Report couldn't be processed: (${resp.status}) ${data.message}`,
361
- );
362
- return;
363
- }
364
-
365
- if (this.runUrl) {
366
- console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
367
- }
368
- process.env.runId = this.runId;
369
- return resp;
370
- } catch (err) {
371
- // if (process.env.DEBUG) console.log(err.response)
372
- const data = err.response.data || { message: '' };
373
- console.log(APP_PREFIX, 'Error uploading, skipping...', err.response.statusText, data);
374
- return err.response;
375
- }
376
-
377
- }
378
-
379
- get url() {
380
- if (!isValidUrl(this.requestParams.url)) {
381
- console.log(
382
- APP_PREFIX,
383
- chalk.red(`Error creating report on Testomat.io, report url '${this.requestParams.url}' is invalid`),
384
- );
385
- return;
386
- }
302
+ };
387
303
 
388
- return `${this.requestParams.url}/api/reporter`;
304
+ return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
389
305
  }
390
306
  }
391
307
 
@@ -426,7 +342,7 @@ function reduceTestCases(prev, item) {
426
342
  stack,
427
343
  message,
428
344
  line: testCaseItem.lineno,
429
- run_time: testCaseItem.time || testCaseItem.duration,
345
+ run_time: parseFloat(testCaseItem.time || testCaseItem.duration),
430
346
  status,
431
347
  title: testCaseItem.name,
432
348
  suite_title: reduceOptions.preferClassname ? testCaseItem.classname : (item.name || testCaseItem.classname),
@@ -449,4 +365,3 @@ function processTestSuite(testsuite) {
449
365
 
450
366
  return res;
451
367
  }
452
-
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "0.7.3",
3
+ "version": "0.8.0-beta.2",
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
+ "@octokit/rest": "^19.0.5",
10
11
  "aws-sdk": "^2.1072.0",
11
12
  "axios": "^0.25.0",
12
13
  "callsite-record": "^4.1.4",