@testomatio/reporter 0.8.0-beta.20 → 0.8.0-beta.30
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 +12 -0
- package/README.md +29 -1
- package/lib/adapter/codecept.js +1 -1
- package/lib/adapter/mocha.js +19 -4
- package/lib/adapter/playwright.js +1 -1
- package/lib/bin/reportXml.js +6 -2
- package/lib/client.js +17 -10
- package/lib/fileUploader.js +82 -49
- package/lib/pipe/csv.js +100 -92
- package/lib/pipe/github.js +117 -63
- package/lib/pipe/gitlab.js +75 -38
- package/lib/pipe/index.js +20 -12
- package/lib/pipe/testomatio.js +7 -5
- package/lib/util.js +9 -9
- package/lib/xmlReader.js +26 -16
- package/package.json +3 -2
- package/testcafe/index.js +0 -61
- package/testcafe/package-lock.json +0 -863
- package/testcafe/package.json +0 -14
package/lib/pipe/github.js
CHANGED
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
2
|
const chalk = require('chalk');
|
|
3
|
-
const humanizeDuration = require(
|
|
3
|
+
const humanizeDuration = require('humanize-duration');
|
|
4
4
|
const merge = require('lodash.merge');
|
|
5
|
-
const { Octokit } = require(
|
|
5
|
+
const { Octokit } = require('@octokit/rest');
|
|
6
6
|
const { APP_PREFIX } = require('../constants');
|
|
7
7
|
const { ansiRegExp, isSameTest } = require('../util');
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
class GitHubPipe {
|
|
11
|
-
|
|
12
|
-
isEnabled = false;
|
|
13
|
-
|
|
14
10
|
constructor(params, store = {}) {
|
|
11
|
+
this.isEnabled = false;
|
|
15
12
|
this.store = store;
|
|
16
|
-
this.tests = []
|
|
13
|
+
this.tests = [];
|
|
17
14
|
this.token = params.GH_PAT || process.env.GH_PAT;
|
|
18
|
-
this.ref = process.env.GITHUB_REF
|
|
19
|
-
this.repo = process.env.GITHUB_REPOSITORY
|
|
15
|
+
this.ref = process.env.GITHUB_REF;
|
|
16
|
+
this.repo = process.env.GITHUB_REPOSITORY;
|
|
17
|
+
this.hiddenCommentData = `<!--- testomat.io report ${process.env.GITHUB_WORKFLOW || ''} -->`;
|
|
20
18
|
|
|
21
19
|
if (process.env.DEBUG) {
|
|
22
|
-
console.log(APP_PREFIX, 'GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', this.ref, this.repo);
|
|
20
|
+
console.log(APP_PREFIX, 'GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', this.ref, this.repo);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
if (!this.token || !this.ref || !this.repo) return;
|
|
26
24
|
this.isEnabled = true;
|
|
27
25
|
const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
|
|
28
26
|
if (!matchedIssue) return;
|
|
29
|
-
this.issue = matchedIssue[1]
|
|
27
|
+
this.issue = matchedIssue[1];
|
|
30
28
|
this.start = new Date();
|
|
31
29
|
|
|
32
30
|
if (process.env.DEBUG) {
|
|
33
|
-
console.log(APP_PREFIX, 'GitHub Pipe: Enabled');
|
|
31
|
+
console.log(APP_PREFIX, 'GitHub Pipe: Enabled');
|
|
34
32
|
}
|
|
35
33
|
}
|
|
36
|
-
|
|
34
|
+
|
|
37
35
|
async createRun() {}
|
|
38
36
|
|
|
39
37
|
updateRun() {}
|
|
@@ -41,21 +39,20 @@ class GitHubPipe {
|
|
|
41
39
|
addTest(test) {
|
|
42
40
|
if (!this.isEnabled) return;
|
|
43
41
|
|
|
44
|
-
const index = this.tests.findIndex(t => isSameTest(t, test))
|
|
42
|
+
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
45
43
|
// update if they were already added
|
|
46
44
|
if (index >= 0) {
|
|
47
45
|
this.tests[index] = merge(this.tests[index], test);
|
|
48
46
|
return;
|
|
49
47
|
}
|
|
50
48
|
|
|
51
|
-
this.tests.push(test)
|
|
49
|
+
this.tests.push(test);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
async finishRun(runParams) {
|
|
55
53
|
if (!this.isEnabled) return;
|
|
56
54
|
|
|
57
55
|
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
58
|
-
|
|
59
56
|
|
|
60
57
|
this.octokit = new Octokit({
|
|
61
58
|
auth: this.token,
|
|
@@ -69,14 +66,17 @@ class GitHubPipe {
|
|
|
69
66
|
const failedCount = this.tests.filter(t => t.status === 'failed').length;
|
|
70
67
|
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
71
68
|
|
|
72
|
-
let summary =
|
|
69
|
+
let summary = `${this.hiddenCommentData}
|
|
73
70
|
|
|
74
|
-
| [](https://testomat.io) | ${
|
|
71
|
+
| [](https://testomat.io) | ${
|
|
72
|
+
statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
75
73
|
| --- | --- |
|
|
76
74
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
77
|
-
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
75
|
+
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
76
|
+
'passed',
|
|
77
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
78
78
|
| Duration | 🕐 **${humanizeDuration(parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)))}** |
|
|
79
|
-
|
|
79
|
+
`;
|
|
80
80
|
if (this.store.runUrl) {
|
|
81
81
|
summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
82
82
|
}
|
|
@@ -93,46 +93,67 @@ class GitHubPipe {
|
|
|
93
93
|
summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
|
|
94
94
|
}
|
|
95
95
|
if (process.env.GITHUB_RUN_ID) {
|
|
96
|
-
summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
96
|
+
summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
97
|
+
this.repo
|
|
98
|
+
}/actions/runs/${process.env.GITHUB_RUN_ID} | `;
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
const failures = this.tests
|
|
102
|
+
.filter(t => t.status === 'failed')
|
|
103
|
+
.slice(0, 20)
|
|
104
|
+
.map(t => {
|
|
105
|
+
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
106
|
+
text += '\n\n';
|
|
107
|
+
if (t.message)
|
|
108
|
+
text += `> ${t.message
|
|
109
|
+
.replace(/[^\x20-\x7E]/g, '')
|
|
110
|
+
.replace(ansiRegExp(), '')
|
|
111
|
+
.trim()}\n`;
|
|
112
|
+
if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
|
|
113
|
+
|
|
114
|
+
if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
115
|
+
t.artifacts
|
|
116
|
+
.filter(f => !!f)
|
|
117
|
+
.filter(f => f.endsWith('.png'))
|
|
118
|
+
.forEach(f => {
|
|
119
|
+
if (f.endsWith('.png')) {
|
|
120
|
+
text += `\n`;
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
text += `[📄 ${path.basename(f)}](${f})\n`;
|
|
124
|
+
return text;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
text += '\n---\n';
|
|
129
|
+
|
|
130
|
+
return text;
|
|
131
|
+
});
|
|
99
132
|
|
|
100
|
-
const failures = this.tests.filter(t => t.status === 'failed').slice(0, 20).map(t => {
|
|
101
|
-
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
102
|
-
text += "\n\n"
|
|
103
|
-
if (t.message) text += "> " + t.message.replace(/[^\x20-\x7E]/g, '').replace(ansiRegExp(), '').trim() + "\n"
|
|
104
|
-
if (t.stack) text += "```diff\n" + t.stack.replace(ansiRegExp(), '').trim() + "\n```\n";
|
|
105
|
-
|
|
106
|
-
if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
107
|
-
t.artifacts.filter(f => !!f).filter(f => f.endsWith('.png')).forEach(f => {
|
|
108
|
-
if (f.endsWith('.png')) return text+= `\n`
|
|
109
|
-
return text+= `[📄 ${path.basename(f)}](${f})\n`
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
text += "\n---\n"
|
|
114
|
-
|
|
115
|
-
return text;
|
|
116
|
-
})
|
|
117
|
-
|
|
118
133
|
let body = summary;
|
|
119
134
|
|
|
120
135
|
if (failures.length) {
|
|
121
136
|
body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
|
|
122
137
|
if (failures.length > 20) {
|
|
123
|
-
body +=
|
|
138
|
+
body += '\n> Notice\n> Only first 20 failures shown*';
|
|
124
139
|
}
|
|
125
|
-
body +=
|
|
140
|
+
body += '\n\n</details>';
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
if (this.tests.length > 0) {
|
|
129
|
-
body +=
|
|
130
|
-
body += this.tests
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
145
|
+
body += this.tests
|
|
146
|
+
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
147
|
+
.sort((a, b) => b?.run_time - a?.run_time)
|
|
148
|
+
.slice(0, 5)
|
|
149
|
+
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
|
150
|
+
.join('\n');
|
|
151
|
+
body += '\n</details>';
|
|
134
152
|
}
|
|
135
153
|
|
|
154
|
+
await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
|
|
155
|
+
|
|
156
|
+
// add report as comment
|
|
136
157
|
try {
|
|
137
158
|
const resp = await this.octokit.rest.issues.createComment({
|
|
138
159
|
owner,
|
|
@@ -140,26 +161,18 @@ class GitHubPipe {
|
|
|
140
161
|
issue_number: this.issue,
|
|
141
162
|
body,
|
|
142
163
|
});
|
|
143
|
-
|
|
164
|
+
|
|
144
165
|
const url = resp.data?.html_url;
|
|
145
166
|
this.store.githubUrl = url;
|
|
146
|
-
|
|
147
|
-
console.log(
|
|
148
|
-
APP_PREFIX,
|
|
149
|
-
chalk.yellow('GitHub'),
|
|
150
|
-
`Report created: ${chalk.magenta(url)}`,
|
|
151
|
-
);
|
|
167
|
+
|
|
168
|
+
console.log(APP_PREFIX, chalk.yellow('GitHub'), `Report created: ${chalk.magenta(url)}`);
|
|
152
169
|
} catch (err) {
|
|
153
|
-
console.log(
|
|
154
|
-
APP_PREFIX,
|
|
155
|
-
chalk.yellow('GitHub'),
|
|
156
|
-
`Couldn't create GitHub report ${err}`,
|
|
157
|
-
);
|
|
170
|
+
console.log(APP_PREFIX, chalk.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
|
|
158
171
|
}
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
toString() {
|
|
162
|
-
return 'GitHub Reporter'
|
|
175
|
+
return 'GitHub Reporter';
|
|
163
176
|
}
|
|
164
177
|
}
|
|
165
178
|
|
|
@@ -167,15 +180,56 @@ function statusEmoji(status) {
|
|
|
167
180
|
if (status === 'passed') return '🟢';
|
|
168
181
|
if (status === 'failed') return '🔴';
|
|
169
182
|
if (status === 'skipped') return '🟡';
|
|
170
|
-
return ''
|
|
183
|
+
return '';
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
function fullName(t) {
|
|
174
187
|
let line = '';
|
|
175
|
-
if (t.suite_title) line = `${t.suite_title}: `;
|
|
176
|
-
line += `**${t.title}
|
|
188
|
+
if (t.suite_title) line = `${t.suite_title}: `;
|
|
189
|
+
line += `**${t.title}**`;
|
|
177
190
|
if (t.example) line += ` \`[${Object.values(t.example)}]\``;
|
|
178
191
|
return line;
|
|
179
192
|
}
|
|
180
193
|
|
|
181
|
-
|
|
194
|
+
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
195
|
+
if (process.env.GITHUB_KEEP_OUTDATED_REPORTS) return;
|
|
196
|
+
|
|
197
|
+
// get comments
|
|
198
|
+
let comments = [];
|
|
199
|
+
try {
|
|
200
|
+
const response = await octokit.rest.issues.listComments({
|
|
201
|
+
owner,
|
|
202
|
+
repo,
|
|
203
|
+
issue_number: issue,
|
|
204
|
+
});
|
|
205
|
+
comments = response.data;
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.error('Error while attempt to retrieve comments on GitHub Pull Request:\n', e);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!comments.length) return;
|
|
211
|
+
|
|
212
|
+
for (const comment of comments) {
|
|
213
|
+
// if comment was left by the same workflow
|
|
214
|
+
if (comment.body.includes(hiddenCommentData)) {
|
|
215
|
+
try {
|
|
216
|
+
// delete previous comment
|
|
217
|
+
await octokit.rest.issues.deleteComment({
|
|
218
|
+
owner,
|
|
219
|
+
repo,
|
|
220
|
+
issue_number: issue,
|
|
221
|
+
comment_id: comment.id,
|
|
222
|
+
});
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// pass next env var if need to clear all previous reports;
|
|
228
|
+
// only the last one is removed by default
|
|
229
|
+
if (!process.env.GITHUB_REMOVE_ALL_OUTDATED_REPORTS) break;
|
|
230
|
+
// TODO: in case of many reports should implement pagination
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
module.exports = GitHubPipe;
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { APP_PREFIX } = require('../constants');
|
|
1
|
+
const axios = require('axios');
|
|
3
2
|
const chalk = require('chalk');
|
|
4
|
-
const got = require('got');
|
|
5
3
|
const humanizeDuration = require('humanize-duration');
|
|
6
4
|
const merge = require('lodash.merge');
|
|
7
5
|
const path = require('path');
|
|
6
|
+
const { APP_PREFIX } = require('../constants');
|
|
7
|
+
const { ansiRegExp, isSameTest } = require('../util');
|
|
8
8
|
|
|
9
|
-
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
9
|
+
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
10
|
+
//! and your pipeline trigger should be merge_request
|
|
10
11
|
|
|
11
12
|
class GitLabPipe {
|
|
12
|
-
isEnabled = false;
|
|
13
|
-
|
|
14
13
|
constructor(params, store = {}) {
|
|
14
|
+
this.isEnabled = false;
|
|
15
15
|
this.ENV = process.env;
|
|
16
16
|
this.store = store;
|
|
17
17
|
this.tests = [];
|
|
18
18
|
// GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
|
|
19
19
|
this.token = params.GITLAB_PAT || this.ENV.GITLAB_PAT;
|
|
20
|
+
this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
|
|
21
|
+
this.axios = axios.create();
|
|
20
22
|
|
|
21
23
|
if (this.ENV.DEBUG) {
|
|
22
24
|
console.log(
|
|
@@ -28,9 +30,7 @@ class GitLabPipe {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
|
|
31
|
-
console.warn(
|
|
32
|
-
`CI pipeline should be run in Merge Request to have ability to add the report comment.`,
|
|
33
|
-
);
|
|
33
|
+
// console.warn(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -72,10 +72,10 @@ class GitLabPipe {
|
|
|
72
72
|
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
73
73
|
|
|
74
74
|
// constructing the table
|
|
75
|
-
let summary =
|
|
75
|
+
let summary = `${this.hiddenCommentData}
|
|
76
76
|
|
|
77
|
-
| [](https://testomat.io) | ${
|
|
78
|
-
runParams.status,
|
|
77
|
+
| [](https://testomat.io) | ${
|
|
78
|
+
statusEmoji(runParams.status,
|
|
79
79
|
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
80
80
|
| --- | --- |
|
|
81
81
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
@@ -86,7 +86,8 @@ class GitLabPipe {
|
|
|
86
86
|
`;
|
|
87
87
|
|
|
88
88
|
if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
|
|
89
|
-
summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${
|
|
89
|
+
summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${
|
|
90
|
+
this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const failures = this.tests
|
|
@@ -96,22 +97,23 @@ class GitLabPipe {
|
|
|
96
97
|
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
97
98
|
text += '\n\n';
|
|
98
99
|
if (t.message)
|
|
99
|
-
text +=
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.trim() +
|
|
105
|
-
'\n';
|
|
106
|
-
if (t.stack) text += '```diff\n' + t.stack.replace(ansiRegExp(), '').trim() + '\n```\n';
|
|
100
|
+
text += `> ${t.message
|
|
101
|
+
.replace(/[^\x20-\x7E]/g, '')
|
|
102
|
+
.replace(ansiRegExp(), '')
|
|
103
|
+
.trim()}\n`;
|
|
104
|
+
if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
|
|
107
105
|
|
|
108
106
|
if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
109
107
|
t.artifacts
|
|
110
108
|
.filter(f => !!f)
|
|
111
109
|
.filter(f => f.endsWith('.png'))
|
|
112
110
|
.forEach(f => {
|
|
113
|
-
if (f.endsWith('.png'))
|
|
114
|
-
|
|
111
|
+
if (f.endsWith('.png')) {
|
|
112
|
+
text += `\n`;
|
|
113
|
+
return text;
|
|
114
|
+
}
|
|
115
|
+
text += `[📄 ${path.basename(f)}](${f})\n`;
|
|
116
|
+
return text;
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
|
|
@@ -133,26 +135,27 @@ class GitLabPipe {
|
|
|
133
135
|
if (this.tests.length > 0) {
|
|
134
136
|
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
135
137
|
body += this.tests
|
|
138
|
+
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
136
139
|
.sort((a, b) => b?.run_time - a?.run_time)
|
|
137
140
|
.slice(0, 5)
|
|
138
|
-
.map(t => {
|
|
139
|
-
return `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`;
|
|
140
|
-
})
|
|
141
|
+
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
|
141
142
|
.join('\n');
|
|
142
143
|
body += '\n</details>';
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
// eslint-disable-next-line max-len
|
|
147
|
+
const commentsRequestURL = `https://gitlab.com/api/v4/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
|
|
148
|
+
|
|
149
|
+
// delete previous report
|
|
150
|
+
await deletePreviousReport(this.axios, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
151
|
+
|
|
152
|
+
// add current report
|
|
153
|
+
console.info(chalk.grey(`Adding comment via url: ${commentsRequestURL}`));
|
|
145
154
|
try {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
body,
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const addCommentResponse = await got.post(addCommentRequestURL, options);
|
|
155
|
-
const commentID = JSON.parse(addCommentResponse.body).id;
|
|
155
|
+
const addCommentResponse = await this.axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
|
|
156
|
+
|
|
157
|
+
const commentID = addCommentResponse.data.id;
|
|
158
|
+
// eslint-disable-next-line max-len
|
|
156
159
|
const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
|
|
157
160
|
|
|
158
161
|
console.log(APP_PREFIX, chalk.yellow('GitLab'), `Report created: ${chalk.magenta(commentURL)}`);
|
|
@@ -161,8 +164,8 @@ class GitLabPipe {
|
|
|
161
164
|
APP_PREFIX,
|
|
162
165
|
chalk.yellow('GitLab'),
|
|
163
166
|
`Couldn't create GitLab report\n${err}.
|
|
164
|
-
|
|
165
|
-
Request data: ${
|
|
167
|
+
Request url: ${commentsRequestURL}
|
|
168
|
+
Request data: ${body}`,
|
|
166
169
|
);
|
|
167
170
|
}
|
|
168
171
|
}
|
|
@@ -189,4 +192,38 @@ function fullName(t) {
|
|
|
189
192
|
return line;
|
|
190
193
|
}
|
|
191
194
|
|
|
195
|
+
async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
|
|
196
|
+
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
|
|
197
|
+
|
|
198
|
+
// get comments
|
|
199
|
+
let comments = [];
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const response = await axiosInstance.get(`${commentsRequestURL}?access_token=${token}`);
|
|
203
|
+
comments = response.data;
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.error('Error while attempt to retrieve comments on GitLab Merge Request:\n', e);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!comments.length) return;
|
|
209
|
+
|
|
210
|
+
for (const comment of comments) {
|
|
211
|
+
// if comment was left by the same workflow
|
|
212
|
+
if (comment.body.includes(hiddenCommentData)) {
|
|
213
|
+
try {
|
|
214
|
+
// delete previous comment
|
|
215
|
+
const deleteCommentURL = `${commentsRequestURL}/${comment.id}?access_token=${token}`;
|
|
216
|
+
await axiosInstance.delete(deleteCommentURL);
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// pass next env var if need to clear all previous reports;
|
|
222
|
+
// only the last one is removed by default
|
|
223
|
+
if (!process.env.GITLAB_REMOVE_ALL_OUTDATED_REPORTS) break;
|
|
224
|
+
// TODO: in case of many reports should implement pagination
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
192
229
|
module.exports = GitLabPipe;
|
package/lib/pipe/index.js
CHANGED
|
@@ -7,47 +7,55 @@ const GitHubPipe = require('./github');
|
|
|
7
7
|
const GitLabPipe = require('./gitlab');
|
|
8
8
|
const CsvPipe = require('./csv');
|
|
9
9
|
|
|
10
|
-
module.exports = function(params, opts) {
|
|
10
|
+
module.exports = function (params, opts) {
|
|
11
11
|
const extraPipes = [];
|
|
12
12
|
|
|
13
13
|
// Add extra pipes into package.json file:
|
|
14
14
|
// "testomatio": {
|
|
15
|
-
// "pipes": ["my-module-pipe", "./local/js/file/pipe"]
|
|
15
|
+
// "pipes": ["my-module-pipe", "./local/js/file/pipe"]
|
|
16
16
|
// }
|
|
17
17
|
|
|
18
18
|
const packageJsonFile = path.join(process.cwd(), 'package.json');
|
|
19
19
|
if (fs.existsSync(packageJsonFile)) {
|
|
20
|
-
const
|
|
21
|
-
const pipeDefs =
|
|
22
|
-
|
|
20
|
+
const packageJson = fs.readFileSync(packageJsonFile);
|
|
21
|
+
const pipeDefs = packageJson?.testomatio?.pipes || [];
|
|
22
|
+
|
|
23
23
|
for (const pipeDef of pipeDefs) {
|
|
24
|
-
let
|
|
24
|
+
let PipeClass;
|
|
25
25
|
try {
|
|
26
|
-
|
|
26
|
+
PipeClass = require(pipeDef);
|
|
27
27
|
} catch (err) {
|
|
28
28
|
console.log(APP_PREFIX, `Can't load module Testomatio pipe module from ${pipeDef}`);
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
|
-
extraPipes.push(new
|
|
33
|
+
extraPipes.push(new PipeClass(params, opts));
|
|
34
34
|
} catch (err) {
|
|
35
35
|
console.log(APP_PREFIX, `Can't instantiate Testomatio for ${pipeDef}`, err);
|
|
36
36
|
continue;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
|
|
41
40
|
|
|
42
41
|
const pipes = [
|
|
43
42
|
new TestomatioPipe(params, opts),
|
|
44
43
|
new GitHubPipe(params, opts),
|
|
45
44
|
new GitLabPipe(params, opts),
|
|
46
45
|
new CsvPipe(params, opts),
|
|
47
|
-
...extraPipes
|
|
46
|
+
...extraPipes,
|
|
48
47
|
];
|
|
49
48
|
|
|
50
|
-
console.log(
|
|
49
|
+
console.log(
|
|
50
|
+
APP_PREFIX,
|
|
51
|
+
chalk.cyan('Pipes:'),
|
|
52
|
+
chalk.cyan(
|
|
53
|
+
pipes
|
|
54
|
+
.filter(p => p.isEnabled)
|
|
55
|
+
.map(p => p.toString())
|
|
56
|
+
.join(', ') || 'No pipes enabled',
|
|
57
|
+
),
|
|
58
|
+
);
|
|
51
59
|
|
|
52
60
|
return pipes;
|
|
53
|
-
}
|
|
61
|
+
};
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const axios = require('axios');
|
|
3
3
|
const JsonCycle = require('json-cycle');
|
|
4
|
+
|
|
4
5
|
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_RUN } = process.env;
|
|
5
6
|
const { APP_PREFIX } = require('../constants');
|
|
6
7
|
const { isValidUrl } = require('../util');
|
|
@@ -10,9 +11,9 @@ if (TESTOMATIO_RUN) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
class TestomatioPipe {
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
constructor(params, store = {}) {
|
|
16
|
+
this.isEnabled = false;
|
|
16
17
|
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
17
18
|
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
18
19
|
if (process.env.DEBUG) {
|
|
@@ -38,7 +39,7 @@ class TestomatioPipe {
|
|
|
38
39
|
APP_PREFIX,
|
|
39
40
|
chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`),
|
|
40
41
|
);
|
|
41
|
-
|
|
42
|
+
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
@@ -90,11 +91,11 @@ class TestomatioPipe {
|
|
|
90
91
|
} catch (err) {
|
|
91
92
|
if (err.response) {
|
|
92
93
|
if (err.response.status >= 400) {
|
|
93
|
-
const
|
|
94
|
+
const responseData = err.response.data || { message: '' };
|
|
94
95
|
console.log(
|
|
95
96
|
APP_PREFIX,
|
|
96
97
|
chalk.blue(this.title),
|
|
97
|
-
`Report couldn't be processed: (${err.response.status}) ${
|
|
98
|
+
`Report couldn't be processed: (${err.response.status}) ${responseData.message}`,
|
|
98
99
|
);
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
@@ -114,6 +115,7 @@ class TestomatioPipe {
|
|
|
114
115
|
api_key: this.apiKey,
|
|
115
116
|
status_event: params.statusEvent,
|
|
116
117
|
status: params.status,
|
|
118
|
+
tests: params.tests,
|
|
117
119
|
});
|
|
118
120
|
if (this.runUrl) {
|
|
119
121
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
|
package/lib/util.js
CHANGED
|
@@ -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,20 +139,18 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
137
139
|
}
|
|
138
140
|
}
|
|
139
141
|
|
|
140
|
-
const isSameTest = (test, t) =>
|
|
141
|
-
return (typeof t === 'object')
|
|
142
|
+
const isSameTest = (test, t) => (typeof t === 'object')
|
|
142
143
|
&& (typeof test === 'object')
|
|
143
|
-
&& t.title
|
|
144
|
-
&& t.suite_title
|
|
145
|
-
&& Object.values(t.example || {})
|
|
146
|
-
&& t.test_id
|
|
147
|
-
};
|
|
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
148
|
|
|
149
149
|
const getCurrentDateTime = () => {
|
|
150
150
|
const today = new Date();
|
|
151
151
|
|
|
152
|
-
return today.getFullYear()
|
|
153
|
-
today.getHours()
|
|
152
|
+
return `${today.getFullYear() }_${ today.getMonth() + 1 }_${ today.getDate() }_${
|
|
153
|
+
today.getHours() }_${ today.getMinutes() }_${ today.getSeconds()}`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
module.exports = {
|