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