@testomatio/reporter 1.6.0-beta-2-artifacts → 2.0.0-beta-esm
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/lib/adapter/codecept.js +288 -330
- package/lib/adapter/cucumber/current.js +195 -203
- package/lib/adapter/cucumber/legacy.js +130 -155
- package/lib/adapter/cucumber.js +5 -16
- package/lib/adapter/cypress-plugin/index.js +91 -105
- package/lib/adapter/jasmine/jasmine.js +63 -0
- package/lib/adapter/jasmine.js +54 -53
- package/lib/adapter/jest.js +97 -99
- package/lib/adapter/mocha/mocha.js +125 -0
- package/lib/adapter/mocha.js +111 -140
- package/lib/adapter/playwright.js +168 -200
- package/lib/adapter/vitest.js +144 -143
- package/lib/adapter/webdriver.js +113 -97
- package/lib/bin/reportXml.js +49 -49
- package/lib/bin/startTest.js +80 -97
- package/lib/client.js +344 -385
- package/lib/config.js +16 -21
- package/lib/constants.js +49 -43
- package/lib/data-storage.js +206 -188
- package/lib/fileUploader.js +245 -0
- package/lib/junit-adapter/adapter.js +17 -20
- package/lib/junit-adapter/csharp.js +18 -14
- package/lib/junit-adapter/index.js +27 -25
- package/lib/junit-adapter/java.js +41 -53
- package/lib/junit-adapter/javascript.js +30 -27
- package/lib/junit-adapter/python.js +38 -37
- package/lib/junit-adapter/ruby.js +11 -8
- package/lib/output.js +44 -52
- package/lib/package.json +1 -0
- package/lib/pipe/bitbucket.js +208 -227
- package/lib/pipe/csv.js +111 -124
- package/lib/pipe/github.js +184 -211
- package/lib/pipe/gitlab.js +164 -205
- package/lib/pipe/html.js +253 -312
- package/lib/pipe/index.js +83 -63
- package/lib/pipe/testomatio.js +391 -454
- package/lib/reporter-functions.js +16 -20
- package/lib/reporter.js +47 -17
- package/lib/services/artifacts.js +55 -51
- package/lib/services/index.js +14 -12
- package/lib/services/key-values.js +56 -53
- package/lib/services/logger.js +227 -245
- package/lib/utils/chalk.js +10 -0
- package/lib/utils/pipe_utils.js +91 -84
- package/lib/utils/utils.js +289 -273
- package/lib/xmlReader.js +480 -519
- package/package.json +57 -19
- package/src/adapter/codecept.js +369 -0
- package/src/adapter/cucumber/current.js +228 -0
- package/src/adapter/cucumber/legacy.js +158 -0
- package/src/adapter/cucumber.js +4 -0
- package/src/adapter/cypress-plugin/index.js +110 -0
- package/src/adapter/jasmine.js +60 -0
- package/src/adapter/jest.js +107 -0
- package/src/adapter/mocha.cjs +2 -0
- package/src/adapter/mocha.js +156 -0
- package/src/adapter/playwright.js +222 -0
- package/src/adapter/vitest.js +183 -0
- package/src/adapter/webdriver.js +111 -0
- package/src/bin/reportXml.js +67 -0
- package/src/bin/startTest.js +119 -0
- package/src/client.js +423 -0
- package/src/config.js +30 -0
- package/src/constants.js +49 -0
- package/src/data-storage.js +204 -0
- package/src/fileUploader.js +307 -0
- package/src/junit-adapter/adapter.js +23 -0
- package/src/junit-adapter/csharp.js +16 -0
- package/src/junit-adapter/index.js +28 -0
- package/src/junit-adapter/java.js +58 -0
- package/src/junit-adapter/javascript.js +31 -0
- package/src/junit-adapter/python.js +42 -0
- package/src/junit-adapter/ruby.js +10 -0
- package/src/output.js +57 -0
- package/src/pipe/bitbucket.js +254 -0
- package/src/pipe/csv.js +140 -0
- package/src/pipe/github.js +234 -0
- package/src/pipe/gitlab.js +229 -0
- package/src/pipe/html.js +366 -0
- package/src/pipe/index.js +73 -0
- package/src/pipe/testomatio.js +498 -0
- package/src/reporter-functions.js +44 -0
- package/src/reporter.cjs +22 -0
- package/src/reporter.js +24 -0
- package/src/services/artifacts.js +59 -0
- package/src/services/index.js +13 -0
- package/src/services/key-values.js +59 -0
- package/src/services/logger.js +314 -0
- package/src/template/emptyData.svg +23 -0
- package/src/template/testomatio.hbs +1421 -0
- package/src/utils/chalk.js +13 -0
- package/src/utils/pipe_utils.js +127 -0
- package/src/utils/utils.js +341 -0
- package/src/xmlReader.js +551 -0
- package/lib/bin/cli.js +0 -216
- package/lib/bin/uploadArtifacts.js +0 -86
- package/lib/uploader.js +0 -312
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import createDebugMessages from 'debug';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import humanizeDuration from 'humanize-duration';
|
|
5
|
+
import merge from 'lodash.merge';
|
|
6
|
+
import { Octokit } from '@octokit/rest';
|
|
7
|
+
import { APP_PREFIX, testomatLogoURL } from '../constants.js';
|
|
8
|
+
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
9
|
+
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
10
|
+
|
|
11
|
+
const debug = createDebugMessages('@testomatio/reporter:pipe:github');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('../../types').Pipe} Pipe
|
|
16
|
+
* @typedef {import('../../types').TestData} TestData
|
|
17
|
+
* @class GitHubPipe
|
|
18
|
+
* @implements {Pipe}
|
|
19
|
+
*/
|
|
20
|
+
class GitHubPipe {
|
|
21
|
+
constructor(params, store = {}) {
|
|
22
|
+
this.isEnabled = false;
|
|
23
|
+
this.store = store;
|
|
24
|
+
this.tests = [];
|
|
25
|
+
this.token = params.GH_PAT || process.env.GH_PAT;
|
|
26
|
+
this.ref = process.env.GITHUB_REF;
|
|
27
|
+
this.repo = process.env.GITHUB_REPOSITORY;
|
|
28
|
+
this.jobKey = `${process.env.GITHUB_WORKFLOW || ''} / ${process.env.GITHUB_JOB || ''}`;
|
|
29
|
+
this.hiddenCommentData = `<!--- testomat.io report ${this.jobKey} -->`;
|
|
30
|
+
|
|
31
|
+
debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
|
|
32
|
+
|
|
33
|
+
if (!this.token || !this.ref || !this.repo) return;
|
|
34
|
+
this.isEnabled = true;
|
|
35
|
+
const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
|
|
36
|
+
if (!matchedIssue) return;
|
|
37
|
+
this.issue = parseInt(matchedIssue[1], 10);
|
|
38
|
+
|
|
39
|
+
this.start = new Date();
|
|
40
|
+
|
|
41
|
+
debug('GitHub Pipe: Enabled');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
45
|
+
async prepareRun() {}
|
|
46
|
+
|
|
47
|
+
async createRun() {}
|
|
48
|
+
|
|
49
|
+
addTest(test) {
|
|
50
|
+
if (!this.isEnabled) return;
|
|
51
|
+
debug('Adding test:', test);
|
|
52
|
+
|
|
53
|
+
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
54
|
+
// update if they were already added
|
|
55
|
+
if (index >= 0) {
|
|
56
|
+
this.tests[index] = merge(this.tests[index], test);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.tests.push(test);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async finishRun(runParams) {
|
|
64
|
+
if (!this.isEnabled) return;
|
|
65
|
+
if (!this.issue) return;
|
|
66
|
+
|
|
67
|
+
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
68
|
+
|
|
69
|
+
this.octokit = new Octokit({
|
|
70
|
+
auth: this.token,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const [owner, repo] = (this.repo || '').split('/');
|
|
74
|
+
if (!(owner || repo)) return;
|
|
75
|
+
|
|
76
|
+
// ... create a comment on GitHub
|
|
77
|
+
const passedCount = this.tests.filter(t => t.status === 'passed').length;
|
|
78
|
+
const failedCount = this.tests.filter(t => t.status === 'failed').length;
|
|
79
|
+
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
80
|
+
|
|
81
|
+
let summary = `${this.hiddenCommentData}
|
|
82
|
+
|
|
83
|
+
| [](https://testomat.io) | ${statusEmoji(
|
|
84
|
+
runParams.status,
|
|
85
|
+
)} ${`${process.env.GITHUB_JOB} ${runParams.status}`.toUpperCase()} |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
88
|
+
| Summary | ${failedCount ? `${statusEmoji('failed')} **${failedCount}** failed; ` : ''} ${statusEmoji(
|
|
89
|
+
'passed',
|
|
90
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
91
|
+
| Duration | 🕐 **${humanizeDuration(
|
|
92
|
+
parseInt(
|
|
93
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
94
|
+
10,
|
|
95
|
+
),
|
|
96
|
+
{
|
|
97
|
+
maxDecimalPoints: 0,
|
|
98
|
+
},
|
|
99
|
+
)}** |`;
|
|
100
|
+
|
|
101
|
+
if (this.store.runUrl) {
|
|
102
|
+
summary += `\n| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
103
|
+
}
|
|
104
|
+
if (process.env.GITHUB_WORKFLOW) {
|
|
105
|
+
summary += `\n| Job | 🗂️ [${this.jobKey}](${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
|
|
106
|
+
this.repo
|
|
107
|
+
}/actions/runs/${process.env.GITHUB_RUN_ID}) | `;
|
|
108
|
+
}
|
|
109
|
+
if (process.env.RUNNER_OS) {
|
|
110
|
+
summary += `\n| Operating System | 🖥️ \`${process.env.RUNNER_OS}\` ${process.env.RUNNER_ARCH || ''} | `;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const failures = this.tests
|
|
114
|
+
.filter(t => t.status === 'failed')
|
|
115
|
+
.slice(0, 20)
|
|
116
|
+
.map(t => {
|
|
117
|
+
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
118
|
+
text += '\n\n';
|
|
119
|
+
if (t.message)
|
|
120
|
+
text += `> ${t.message
|
|
121
|
+
.replace(/[^\x20-\x7E]/g, '')
|
|
122
|
+
.replace(ansiRegExp(), '')
|
|
123
|
+
.trim()}\n`;
|
|
124
|
+
if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
|
|
125
|
+
|
|
126
|
+
if (t.artifacts && t.artifacts.length && !process.env.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
127
|
+
t.artifacts
|
|
128
|
+
.filter(f => !!f)
|
|
129
|
+
.filter(f => f.endsWith('.png'))
|
|
130
|
+
.forEach(f => {
|
|
131
|
+
if (f.endsWith('.png')) {
|
|
132
|
+
text += `\n`;
|
|
133
|
+
return text;
|
|
134
|
+
}
|
|
135
|
+
text += `[📄 ${path.basename(f)}](${f})\n`;
|
|
136
|
+
return text;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
text += '\n---\n';
|
|
141
|
+
|
|
142
|
+
return text;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
let body = summary;
|
|
146
|
+
|
|
147
|
+
if (failures.length) {
|
|
148
|
+
body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
|
|
149
|
+
if (failures.length > 20) {
|
|
150
|
+
body += '\n> Notice\n> Only first 20 failures shown*';
|
|
151
|
+
}
|
|
152
|
+
body += '\n\n</details>';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (this.tests.length > 0) {
|
|
156
|
+
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
157
|
+
body += this.tests
|
|
158
|
+
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
159
|
+
.sort((a, b) => b?.run_time - a?.run_time)
|
|
160
|
+
.slice(0, 5)
|
|
161
|
+
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
|
162
|
+
.join('\n');
|
|
163
|
+
body += '\n</details>';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
|
|
167
|
+
|
|
168
|
+
// add report as comment
|
|
169
|
+
try {
|
|
170
|
+
debug('Adding comment\n', body);
|
|
171
|
+
const resp = await this.octokit.rest.issues.createComment({
|
|
172
|
+
owner,
|
|
173
|
+
repo,
|
|
174
|
+
issue_number: this.issue,
|
|
175
|
+
body,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const url = resp.data?.html_url;
|
|
179
|
+
debug('Comment URL:', url);
|
|
180
|
+
this.store.githubUrl = url;
|
|
181
|
+
|
|
182
|
+
console.log(APP_PREFIX, pc.yellow('GitHub'), `Report created: ${pc.magenta(url)}`);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.log(APP_PREFIX, pc.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
toString() {
|
|
189
|
+
return 'GitHub Reporter';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentData) {
|
|
194
|
+
if (process.env.GH_KEEP_OUTDATED_REPORTS) return;
|
|
195
|
+
|
|
196
|
+
// get comments
|
|
197
|
+
let comments = [];
|
|
198
|
+
try {
|
|
199
|
+
const response = await octokit.rest.issues.listComments({
|
|
200
|
+
owner,
|
|
201
|
+
repo,
|
|
202
|
+
issue_number: issue,
|
|
203
|
+
});
|
|
204
|
+
comments = response.data;
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.error('Error while attempt to retrieve comments on GitHub Pull Request:\n', e);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (!comments.length) return;
|
|
210
|
+
|
|
211
|
+
for (const comment of comments) {
|
|
212
|
+
// if comment was left by the same workflow
|
|
213
|
+
if (comment.body.includes(hiddenCommentData)) {
|
|
214
|
+
try {
|
|
215
|
+
// delete previous comment
|
|
216
|
+
await octokit.rest.issues.deleteComment({
|
|
217
|
+
owner,
|
|
218
|
+
repo,
|
|
219
|
+
issue_number: issue,
|
|
220
|
+
comment_id: comment.id,
|
|
221
|
+
});
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// pass next env var if need to clear all previous reports;
|
|
227
|
+
// only the last one is removed by default
|
|
228
|
+
if (!process.env.GITHUB_REMOVE_ALL_OUTDATED_REPORTS) break;
|
|
229
|
+
// TODO: in case of many reports should implement pagination
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export default GitHubPipe;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import createDebugMessages from 'debug';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import humanizeDuration from 'humanize-duration';
|
|
5
|
+
import merge from 'lodash.merge';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { APP_PREFIX, testomatLogoURL } from '../constants.js';
|
|
8
|
+
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
9
|
+
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
10
|
+
|
|
11
|
+
const debug = createDebugMessages('@testomatio/reporter:pipe:gitlab');
|
|
12
|
+
|
|
13
|
+
//! GITLAB_PAT environment variable is required for this functionality to work
|
|
14
|
+
//! and your pipeline trigger should be merge_request
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @class GitLabPipe
|
|
18
|
+
* @typedef {import('../../types').Pipe} Pipe
|
|
19
|
+
* @typedef {import('../../types').TestData} TestData
|
|
20
|
+
*/
|
|
21
|
+
class GitLabPipe {
|
|
22
|
+
constructor(params, store = {}) {
|
|
23
|
+
this.isEnabled = false;
|
|
24
|
+
this.ENV = process.env;
|
|
25
|
+
this.store = store;
|
|
26
|
+
this.tests = [];
|
|
27
|
+
// GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
|
|
28
|
+
this.token = params.GITLAB_PAT || process.env.GITLAB_PAT || this.ENV.GITLAB_PAT;
|
|
29
|
+
this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
|
|
30
|
+
|
|
31
|
+
debug(
|
|
32
|
+
pc.yellow('GitLab Pipe:'),
|
|
33
|
+
this.token ? 'TOKEN passed' : '*no token*',
|
|
34
|
+
`Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
|
|
38
|
+
debug(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!this.token) {
|
|
43
|
+
debug(`Hint: GitLab CI variables are unavailable for unprotected branches by default.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.isEnabled = true;
|
|
48
|
+
|
|
49
|
+
debug('GitLab Pipe: Enabled');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
53
|
+
async prepareRun() {}
|
|
54
|
+
|
|
55
|
+
async createRun() {}
|
|
56
|
+
|
|
57
|
+
addTest(test) {
|
|
58
|
+
if (!this.isEnabled) return;
|
|
59
|
+
|
|
60
|
+
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
61
|
+
// update if they were already added
|
|
62
|
+
if (index >= 0) {
|
|
63
|
+
this.tests[index] = merge(this.tests[index], test);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.tests.push(test);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async finishRun(runParams) {
|
|
71
|
+
if (!this.isEnabled) return;
|
|
72
|
+
|
|
73
|
+
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
74
|
+
|
|
75
|
+
// ... create a comment on GitLab
|
|
76
|
+
const passedCount = this.tests.filter(t => t.status === 'passed').length;
|
|
77
|
+
const failedCount = this.tests.filter(t => t.status === 'failed').length;
|
|
78
|
+
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
79
|
+
|
|
80
|
+
// constructing the table
|
|
81
|
+
let summary = `${this.hiddenCommentData}
|
|
82
|
+
|
|
83
|
+
| [](https://testomat.io) | ${statusEmoji(
|
|
84
|
+
runParams.status,
|
|
85
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
88
|
+
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
89
|
+
'passed',
|
|
90
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
91
|
+
| Duration | 🕐 **${humanizeDuration(
|
|
92
|
+
parseInt(
|
|
93
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
94
|
+
10,
|
|
95
|
+
),
|
|
96
|
+
{
|
|
97
|
+
maxDecimalPoints: 0,
|
|
98
|
+
},
|
|
99
|
+
)}** |
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
|
|
103
|
+
// eslint-disable-next-line max-len
|
|
104
|
+
summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const failures = this.tests
|
|
108
|
+
.filter(t => t.status === 'failed')
|
|
109
|
+
.slice(0, 20)
|
|
110
|
+
.map(t => {
|
|
111
|
+
let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
|
|
112
|
+
text += '\n\n';
|
|
113
|
+
if (t.message)
|
|
114
|
+
text += `> ${t.message
|
|
115
|
+
.replace(/[^\x20-\x7E]/g, '')
|
|
116
|
+
.replace(ansiRegExp(), '')
|
|
117
|
+
.trim()}\n`;
|
|
118
|
+
if (t.stack) text += `\`\`\`diff\n${t.stack.replace(ansiRegExp(), '').trim()}\n\`\`\`\n`;
|
|
119
|
+
|
|
120
|
+
if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
121
|
+
t.artifacts
|
|
122
|
+
.filter(f => !!f)
|
|
123
|
+
.filter(f => f.endsWith('.png'))
|
|
124
|
+
.forEach(f => {
|
|
125
|
+
if (f.endsWith('.png')) {
|
|
126
|
+
text += `\n`;
|
|
127
|
+
return text;
|
|
128
|
+
}
|
|
129
|
+
text += `[📄 ${path.basename(f)}](${f})\n`;
|
|
130
|
+
return text;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
text += '\n---\n';
|
|
135
|
+
|
|
136
|
+
return text;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
let body = summary;
|
|
140
|
+
|
|
141
|
+
if (failures.length) {
|
|
142
|
+
body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
|
|
143
|
+
if (failures.length > 20) {
|
|
144
|
+
body += '\n> Notice\n> Only first 20 failures shown*';
|
|
145
|
+
}
|
|
146
|
+
body += '\n\n</details>';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (this.tests.length > 0) {
|
|
150
|
+
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
151
|
+
body += this.tests
|
|
152
|
+
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
153
|
+
.sort((a, b) => b?.run_time - a?.run_time)
|
|
154
|
+
.slice(0, 5)
|
|
155
|
+
.map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
|
|
156
|
+
.join('\n');
|
|
157
|
+
body += '\n</details>';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// eslint-disable-next-line max-len
|
|
161
|
+
const commentsRequestURL = `https://gitlab.com/api/v4/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
|
|
162
|
+
|
|
163
|
+
// delete previous report
|
|
164
|
+
await deletePreviousReport(axios, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
165
|
+
|
|
166
|
+
// add current report
|
|
167
|
+
debug(`Adding comment via url: ${commentsRequestURL}`);
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const addCommentResponse = await axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
|
|
171
|
+
|
|
172
|
+
const commentID = addCommentResponse.data.id;
|
|
173
|
+
// eslint-disable-next-line max-len
|
|
174
|
+
const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
|
|
175
|
+
|
|
176
|
+
console.log(APP_PREFIX, pc.yellow('GitLab'), `Report created: ${pc.magenta(commentURL)}`);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.error(
|
|
179
|
+
APP_PREFIX,
|
|
180
|
+
pc.yellow('GitLab'),
|
|
181
|
+
`Couldn't create GitLab report\n${err}.
|
|
182
|
+
Request url: ${commentsRequestURL}
|
|
183
|
+
Request data: ${body}`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
toString() {
|
|
189
|
+
return 'GitLab Reporter';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
updateRun() {}
|
|
193
|
+
}
|
|
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.warn(`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
|
+
|
|
229
|
+
export default GitLabPipe;
|