@testomatio/reporter 0.8.0-beta.3 → 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.
@@ -0,0 +1,119 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const csvWriter = require('csv-writer');
4
+ const chalk = require('chalk');
5
+ const merge = require('lodash.merge');
6
+ const { isSameTest, getCurrentDateTime } = require('../util');
7
+ const { CSV_HEADERS } = require('../constants');
8
+
9
+ class CsvPipe {
10
+ constructor(params, store = {}) {
11
+ this.store = store;
12
+ this.title = params.title || process.env.TESTOMATIO_TITLE;
13
+ this.isEnabled = true;
14
+ this.results = [];
15
+
16
+ this.outputDir = 'export';
17
+ this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
18
+ this.isCsvSave = false;
19
+
20
+ if (this.csvFilename !== undefined && this.csvFilename.split('.').length > 0) {
21
+ this.isCsvSave = true;
22
+
23
+ if (this.csvFilename.split('.')[0] === 'report') {
24
+ this.outputFile = path.resolve(process.cwd(), this.outputDir, 'report.csv');
25
+ } else {
26
+ this.outputFile = path.resolve(
27
+ process.cwd(),
28
+ this.outputDir,
29
+ `${getCurrentDateTime()}_${this.csvFilename.split('.')[0]}.csv`,
30
+ );
31
+ }
32
+ }
33
+ }
34
+
35
+ async createRun() {
36
+ // empty
37
+ }
38
+
39
+ updateRun() {}
40
+
41
+ /**
42
+ * Create a folder that will contain the exported files
43
+ */
44
+ checkExportDir() {
45
+ if (!fs.existsSync(this.outputDir)) {
46
+ return fs.mkdirSync(this.outputDir);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Save data to the csv file.
52
+ * @param {Object} data - data that will be added to the CSV file.
53
+ * Example: [{suite_title: "Suite #1", test: "Test-case-1", message: "Test msg"}]
54
+ * @param {Object} headers - csv file headers. Example: [{ id: 'suite_title', title: 'Suite_title' }]
55
+ */
56
+ async saveToCsv(data, headers) {
57
+ // First, we check whether the export directory exists: if yes - OK, no - create it.
58
+ this.checkExportDir();
59
+
60
+ console.log(chalk.yellow(`The test results will be added to the csv. It will take some time...`));
61
+ // Create csv writer object
62
+ const writer = csvWriter.createObjectCsvWriter({
63
+ path: this.outputFile,
64
+ header: headers,
65
+ });
66
+ // Save csv file based on the current data
67
+ try {
68
+ await writer.writeRecords(data);
69
+ console.log(chalk.green(`Recording completed! You can check the result in file = ${this.outputFile}`));
70
+ } catch (e) {
71
+ console.log('Unknown error: ', e);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Add test data to the result array for saving. As a result of this function, we get a result object to save.
77
+ * @param {Object} test - object which includes each test entry.
78
+ */
79
+ addTest(test) {
80
+ if (!this.isEnabled) return;
81
+
82
+ const index = this.results.findIndex(t => isSameTest(t, test));
83
+ // update if they were already added
84
+ if (index >= 0) {
85
+ this.results[index] = merge(this.results[index], test);
86
+ return;
87
+ }
88
+
89
+ const { suite_title, title, status, message, stack } = test;
90
+
91
+ this.results.push({
92
+ suite_title,
93
+ title,
94
+ status,
95
+ message,
96
+ stack,
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Save short tests data to the csv file.
102
+ * @param {Object} runParams - run params.
103
+ */
104
+ async finishRun(runParams) {
105
+ if (!this.isEnabled) return;
106
+
107
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
108
+ // Save results based on the default headers
109
+ if (this.isCsvSave) {
110
+ this.saveToCsv(this.results, CSV_HEADERS);
111
+ }
112
+ }
113
+
114
+ toString() {
115
+ return 'csv exporter';
116
+ }
117
+ }
118
+
119
+ module.exports = CsvPipe;
@@ -1,26 +1,37 @@
1
+ const path = require('path');
1
2
  const chalk = require('chalk');
2
- const { Octokit } = require("@octokit/rest");
3
+ const humanizeDuration = require('humanize-duration');
4
+ const merge = require('lodash.merge');
5
+ const { Octokit } = require('@octokit/rest');
3
6
  const { APP_PREFIX } = require('../constants');
4
7
  const { ansiRegExp, isSameTest } = require('../util');
5
- const merge = require('lodash.merge');
6
-
7
8
 
8
9
  class GitHubPipe {
9
-
10
- isEnabled = false;
11
-
12
10
  constructor(params, store = {}) {
11
+ this.isEnabled = false;
13
12
  this.store = store;
14
- this.tests = []
13
+ this.tests = [];
15
14
  this.token = params.GH_PAT || process.env.GH_PAT;
16
- this.ref = process.env.GITHUB_REF
17
- this.repo = process.env.GITHUB_ACTION_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 || ''} -->`;
18
+
19
+ if (process.env.DEBUG) {
20
+ console.log(APP_PREFIX, 'GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', this.ref, this.repo);
21
+ }
22
+
18
23
  if (!this.token || !this.ref || !this.repo) return;
19
24
  this.isEnabled = true;
20
- this.issue = this.ref.match(/refs\/pull\/(\d+)\/merge/)[1]
25
+ const matchedIssue = this.ref.match(/refs\/pull\/(\d+)\/merge/);
26
+ if (!matchedIssue) return;
27
+ this.issue = matchedIssue[1];
21
28
  this.start = new Date();
29
+
30
+ if (process.env.DEBUG) {
31
+ console.log(APP_PREFIX, 'GitHub Pipe: Enabled');
32
+ }
22
33
  }
23
-
34
+
24
35
  async createRun() {}
25
36
 
26
37
  updateRun() {}
@@ -28,21 +39,20 @@ class GitHubPipe {
28
39
  addTest(test) {
29
40
  if (!this.isEnabled) return;
30
41
 
31
- const index = this.tests.findIndex(t => isSameTest(t, test))
42
+ const index = this.tests.findIndex(t => isSameTest(t, test));
32
43
  // update if they were already added
33
44
  if (index >= 0) {
34
45
  this.tests[index] = merge(this.tests[index], test);
35
46
  return;
36
47
  }
37
48
 
38
- this.tests.push(test)
49
+ this.tests.push(test);
39
50
  }
40
51
 
41
52
  async finishRun(runParams) {
42
53
  if (!this.isEnabled) return;
43
54
 
44
55
  if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
45
-
46
56
 
47
57
  this.octokit = new Octokit({
48
58
  auth: this.token,
@@ -56,15 +66,17 @@ class GitHubPipe {
56
66
  const failedCount = this.tests.filter(t => t.status === 'failed').length;
57
67
  const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
58
68
 
59
- let summary = `
60
- ### Run Report
69
+ let summary = `${this.hiddenCommentData}
61
70
 
62
- | | ${statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
71
+ | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
72
+ statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
63
73
  | --- | --- |
64
74
  | 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
- `
75
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
76
+ 'passed',
77
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
78
+ | Duration | 🕐 **${humanizeDuration(parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)))}** |
79
+ `;
68
80
  if (this.store.runUrl) {
69
81
  summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
70
82
  }
@@ -81,44 +93,67 @@ class GitHubPipe {
81
93
  summary += `| Run Attempt | 🌒 \`${process.env.GITHUB_RUN_ATTEMPT}\` | `;
82
94
  }
83
95
  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} | `;
96
+ summary += `| Build Log | ✒️ ${process.env.GITHUB_SERVER_URL || 'https://github.com'}/${
97
+ this.repo
98
+ }/actions/runs/${process.env.GITHUB_RUN_ID} | `;
85
99
  }
86
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 += `![](${f})\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
+ });
87
132
 
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
133
  let body = summary;
106
134
 
107
135
  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*"
136
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
137
+ if (failures.length > 20) {
138
+ body += '\n> Notice\n> Only first 20 failures shown*';
139
+ }
140
+ body += '\n\n</details>';
113
141
  }
114
142
 
115
143
  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')
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>';
120
152
  }
121
153
 
154
+ await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
155
+
156
+ // add report as comment
122
157
  try {
123
158
  const resp = await this.octokit.rest.issues.createComment({
124
159
  owner,
@@ -126,38 +161,75 @@ class GitHubPipe {
126
161
  issue_number: this.issue,
127
162
  body,
128
163
  });
129
-
164
+
130
165
  const url = resp.data?.html_url;
131
166
  this.store.githubUrl = url;
132
-
133
- console.log(
134
- APP_PREFIX,
135
- chalk.yellow('GitHub'),
136
- `Report created: ${chalk.magenta(url)}`,
137
- );
167
+
168
+ console.log(APP_PREFIX, chalk.yellow('GitHub'), `Report created: ${chalk.magenta(url)}`);
138
169
  } catch (err) {
139
- console.log(
140
- APP_PREFIX,
141
- chalk.yellow('GitHub'),
142
- `Couldn't create GitHub report ${err}`,
143
- );
170
+ console.log(APP_PREFIX, chalk.yellow('GitHub'), `Couldn't create GitHub report ${err}`);
144
171
  }
145
172
  }
173
+
174
+ toString() {
175
+ return 'GitHub Reporter';
176
+ }
146
177
  }
147
178
 
148
179
  function statusEmoji(status) {
149
180
  if (status === 'passed') return '🟢';
150
181
  if (status === 'failed') return '🔴';
151
182
  if (status === 'skipped') return '🟡';
152
- return ''
183
+ return '';
153
184
  }
154
185
 
155
186
  function fullName(t) {
156
187
  let line = '';
157
- if (t.suite_title) line = `${t.suite_title}: `;
158
- line += `**${t.title}**`
188
+ if (t.suite_title) line = `${t.suite_title}: `;
189
+ line += `**${t.title}**`;
159
190
  if (t.example) line += ` \`[${Object.values(t.example)}]\``;
160
191
  return line;
161
192
  }
162
193
 
163
- module.exports = GitHubPipe;
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;
@@ -0,0 +1,229 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const humanizeDuration = require('humanize-duration');
4
+ const merge = require('lodash.merge');
5
+ const path = require('path');
6
+ const { APP_PREFIX } = require('../constants');
7
+ const { ansiRegExp, isSameTest } = require('../util');
8
+
9
+ //! GITLAB_PAT environment variable is required for this functionality to work
10
+ //! and your pipeline trigger should be merge_request
11
+
12
+ class GitLabPipe {
13
+ constructor(params, store = {}) {
14
+ this.isEnabled = false;
15
+ this.ENV = process.env;
16
+ this.store = store;
17
+ this.tests = [];
18
+ // GitLab PAT looks like glpat-nKGdja3jsG4850sGksh7
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();
22
+
23
+ if (this.ENV.DEBUG) {
24
+ console.log(
25
+ APP_PREFIX,
26
+ chalk.yellow('GitLab Pipe:'),
27
+ this.token ? 'TOKEN passed' : '*no token*',
28
+ `Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
29
+ );
30
+ }
31
+
32
+ if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
33
+ // console.warn(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
34
+ return;
35
+ }
36
+
37
+ if (!this.token) {
38
+ console.warn(chalk.grey(`Hint: GitLab CI variables are unavailable for unprotected branches by default.`));
39
+ return;
40
+ }
41
+
42
+ this.isEnabled = true;
43
+
44
+ if (this.ENV.DEBUG) {
45
+ console.log(APP_PREFIX, chalk.yellow('GitLab Pipe: Enabled'));
46
+ }
47
+ }
48
+
49
+ async createRun() {}
50
+
51
+ addTest(test) {
52
+ if (!this.isEnabled) return;
53
+
54
+ const index = this.tests.findIndex(t => isSameTest(t, test));
55
+ // update if they were already added
56
+ if (index >= 0) {
57
+ this.tests[index] = merge(this.tests[index], test);
58
+ return;
59
+ }
60
+
61
+ this.tests.push(test);
62
+ }
63
+
64
+ async finishRun(runParams) {
65
+ if (!this.isEnabled) return;
66
+
67
+ if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
68
+
69
+ // ... create a comment on GitLab
70
+ const passedCount = this.tests.filter(t => t.status === 'passed').length;
71
+ const failedCount = this.tests.filter(t => t.status === 'failed').length;
72
+ const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
73
+
74
+ // constructing the table
75
+ let summary = `${this.hiddenCommentData}
76
+
77
+ | [![Testomat.io Report](https://avatars.githubusercontent.com/u/59105116?s=36&v=4)](https://testomat.io) | ${
78
+ statusEmoji(runParams.status,
79
+ )} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
80
+ | --- | --- |
81
+ | Tests | ✔️ **${this.tests.length}** tests run |
82
+ | Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
83
+ 'passed',
84
+ )} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
85
+ | Duration | 🕐 **${humanizeDuration(parseFloat(this.tests.reduce((a, t) => a + (t.run_time || 0), 0)))}** |
86
+ `;
87
+
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: **${
90
+ this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
91
+ }
92
+
93
+ const failures = this.tests
94
+ .filter(t => t.status === 'failed')
95
+ .slice(0, 20)
96
+ .map(t => {
97
+ let text = `#### ${statusEmoji('failed')} ${fullName(t)} `;
98
+ text += '\n\n';
99
+ if (t.message)
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`;
105
+
106
+ if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
107
+ t.artifacts
108
+ .filter(f => !!f)
109
+ .filter(f => f.endsWith('.png'))
110
+ .forEach(f => {
111
+ if (f.endsWith('.png')) {
112
+ text += `![](${f})\n`;
113
+ return text;
114
+ }
115
+ text += `[📄 ${path.basename(f)}](${f})\n`;
116
+ return text;
117
+ });
118
+ }
119
+
120
+ text += '\n---\n';
121
+
122
+ return text;
123
+ });
124
+
125
+ let body = summary;
126
+
127
+ if (failures.length) {
128
+ body += `\n<details>\n<summary><h3>🟥 Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
129
+ if (failures.length > 20) {
130
+ body += '\n> Notice\n> Only first 20 failures shown*';
131
+ }
132
+ body += '\n\n</details>';
133
+ }
134
+
135
+ if (this.tests.length > 0) {
136
+ body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
137
+ body += this.tests
138
+ // eslint-disable-next-line no-unsafe-optional-chaining
139
+ .sort((a, b) => b?.run_time - a?.run_time)
140
+ .slice(0, 5)
141
+ .map(t => `* ${fullName(t)} (${humanizeDuration(parseFloat(t.run_time))})`)
142
+ .join('\n');
143
+ body += '\n</details>';
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}`));
154
+ try {
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
159
+ const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
160
+
161
+ console.log(APP_PREFIX, chalk.yellow('GitLab'), `Report created: ${chalk.magenta(commentURL)}`);
162
+ } catch (err) {
163
+ console.log(
164
+ APP_PREFIX,
165
+ chalk.yellow('GitLab'),
166
+ `Couldn't create GitLab report\n${err}.
167
+ Request url: ${commentsRequestURL}
168
+ Request data: ${body}`,
169
+ );
170
+ }
171
+ }
172
+
173
+ toString() {
174
+ return 'GitLab Reporter';
175
+ }
176
+
177
+ updateRun() {}
178
+ }
179
+
180
+ function statusEmoji(status) {
181
+ if (status === 'passed') return '🟢';
182
+ if (status === 'failed') return '🔴';
183
+ if (status === 'skipped') return '🟡';
184
+ return '';
185
+ }
186
+
187
+ function fullName(t) {
188
+ let line = '';
189
+ if (t.suite_title) line = `${t.suite_title}: `;
190
+ line += `**${t.title}**`;
191
+ if (t.example) line += ` \`[${Object.values(t.example)}]\``;
192
+ return line;
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.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
+
229
+ module.exports = GitLabPipe;