@testomatio/reporter 1.2.0-beta → 1.2.0-beta-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -9
- package/lib/_ArtifactStorageOld.js +1 -1
- package/lib/adapter/codecept.js +59 -39
- package/lib/adapter/cucumber/current.js +95 -59
- package/lib/adapter/cucumber/legacy.js +25 -11
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +18 -3
- package/lib/adapter/mocha.js +23 -23
- package/lib/adapter/playwright.js +18 -11
- package/lib/adapter/webdriver.js +1 -1
- package/lib/artifactStorage.js +2 -2
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +102 -34
- package/lib/constants.js +7 -0
- package/lib/dataStorage.js +132 -72
- package/lib/junit-adapter/java.js +27 -9
- package/lib/logger.js +182 -164
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +24 -37
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/html.js +262 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +138 -34
- package/lib/reporter.js +2 -1
- package/lib/template/real-data-example.js +47 -0
- package/lib/template/template.hbs +249 -0
- package/lib/template/testomatio.hbs +695 -0
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +116 -10
- package/lib/xmlReader.js +132 -44
- package/package.json +10 -6
package/lib/pipe/html.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:html');
|
|
2
|
+
const merge = require('lodash.merge');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const handlebars = require('handlebars');
|
|
7
|
+
|
|
8
|
+
const { fileSystem, isSameTest, ansiRegExp } = require('../utils/utils');
|
|
9
|
+
const { HTML_REPORT } = require('../constants');
|
|
10
|
+
|
|
11
|
+
class HtmlPipe {
|
|
12
|
+
constructor(params, store = {}) {
|
|
13
|
+
this.store = store || {};
|
|
14
|
+
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
15
|
+
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
16
|
+
this.isHtml = process.env.TESTOMATIO_HTML_REPORT_SAVE;
|
|
17
|
+
this.data = {};
|
|
18
|
+
|
|
19
|
+
debug('HTML Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
|
|
20
|
+
if (!this.apiKey) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.isEnabled = false;
|
|
25
|
+
this.htmlOutputPath = "";
|
|
26
|
+
this.tests = [];
|
|
27
|
+
this.data = {};
|
|
28
|
+
|
|
29
|
+
if (this.isHtml) {
|
|
30
|
+
this.isEnabled = true;
|
|
31
|
+
this.htmlReportDir = process.env.TESTOMATIO_HTML_REPORT_FOLDER || HTML_REPORT.FOLDER;
|
|
32
|
+
|
|
33
|
+
if (process.env.TESTOMATIO_HTML_FILENAME && process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
|
|
34
|
+
this.htmlReportName = process.env.TESTOMATIO_HTML_FILENAME
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.env.TESTOMATIO_HTML_FILENAME && !process.env.TESTOMATIO_HTML_FILENAME.endsWith(".html")) {
|
|
38
|
+
console.log(
|
|
39
|
+
chalk.blue(`The name must include the extension ".html". The default report name is used!`)
|
|
40
|
+
);
|
|
41
|
+
this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!process.env.TESTOMATIO_HTML_FILENAME) {
|
|
45
|
+
this.htmlReportName = HTML_REPORT.REPORT_DEFAULT_NAME;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.templateFolderPath = path.resolve(__dirname, '..', 'template');
|
|
49
|
+
this.templateHtmlPath = path.resolve(this.templateFolderPath, HTML_REPORT.TEMPLATE_NAME);
|
|
50
|
+
this.htmlOutputPath = path.join(this.htmlReportDir, this.htmlReportName);
|
|
51
|
+
// create a new folder for the HTML reports
|
|
52
|
+
fileSystem.createDir(this.htmlReportDir);
|
|
53
|
+
|
|
54
|
+
debug(
|
|
55
|
+
chalk.yellow('HTML Pipe:'),
|
|
56
|
+
`Save HTML report: ${this.isEnabled}`,
|
|
57
|
+
`HTML report folder: ${this.htmlReportDir}, report name: ${this.htmlReportName}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async createRun() {
|
|
63
|
+
// empty
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
updateRun() {
|
|
67
|
+
// empty
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Add test data to the result array for saving. As a result of this function, we get a result object to save.
|
|
72
|
+
* @param {Object} test - object which includes each test entry.
|
|
73
|
+
*/
|
|
74
|
+
addTest(test) {
|
|
75
|
+
if (!this.isEnabled) return;
|
|
76
|
+
|
|
77
|
+
if (!test.steps || !test.status) return;
|
|
78
|
+
|
|
79
|
+
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
80
|
+
// update if they were already added
|
|
81
|
+
if (index >= 0) {
|
|
82
|
+
this.tests[index] = merge(this.tests[index], test);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.tests.push(test);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async finishRun(runParams) {
|
|
90
|
+
if (!this.isEnabled) return;
|
|
91
|
+
|
|
92
|
+
if (this.isHtml) {
|
|
93
|
+
|
|
94
|
+
this.tests.forEach(test => {
|
|
95
|
+
|
|
96
|
+
if (!test.message || test.message.trim() === "") {
|
|
97
|
+
test.message = "This test has no 'message' code";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!test.suite_title || test.suite_title.trim() === "") {
|
|
101
|
+
test.suite_title = "Unknown suite";
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!test.title || test.title.trim() === "") {
|
|
105
|
+
test.title = "Unknown test title";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!test.files || test.files.length === 0) {
|
|
109
|
+
test.files = "This test has no files";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (test.steps) {
|
|
113
|
+
if (!test.steps || test.steps.trim() === "") {
|
|
114
|
+
test.steps = "This test has no 'steps' code";
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
test.steps = this.#removeAnsiColorCodes(test.steps);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// TODO: u can added an additional test values to this checks in the future
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
this.data = {
|
|
125
|
+
runId: this.store.runId,
|
|
126
|
+
status: runParams.status,
|
|
127
|
+
parallel: runParams.isParallel,
|
|
128
|
+
runUrl: this.store.runUrl,
|
|
129
|
+
executionTime: testExecutionSumTime(this.tests),
|
|
130
|
+
executionDate: getCurrentDateTimeFormatted(),
|
|
131
|
+
tests: this.tests
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// GENERATE HTML reports based on the results data
|
|
135
|
+
this.buildReport(this.data);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
buildReport(data) {
|
|
140
|
+
debug('HTML tests data:', data);
|
|
141
|
+
|
|
142
|
+
if (!this.htmlOutputPath && this.htmlOutputPath !== "") {
|
|
143
|
+
console.log(chalk.yellow(`HTML export path is not set, ignoring...`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.yellow(`The test results will be added to the HTML report. It will take some time...`));
|
|
148
|
+
// generate output HTML based on the template
|
|
149
|
+
const html = this.#generateHTMLReport(data);
|
|
150
|
+
|
|
151
|
+
fs.writeFileSync(this.htmlOutputPath, html, 'utf-8');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#generateHTMLReport(data) {
|
|
155
|
+
if (!this.templateHtmlPath) {
|
|
156
|
+
console.log(chalk.red(`HTML template not found. Report generation is impossible!`))
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const templateSource = fs.readFileSync(this.templateHtmlPath, 'utf8');
|
|
161
|
+
this.#loadReportHelpers();
|
|
162
|
+
try {
|
|
163
|
+
const template = handlebars.compile(templateSource);
|
|
164
|
+
|
|
165
|
+
console.log(chalk.green(`Generated HTML report saved in file: ${this.htmlOutputPath}`));
|
|
166
|
+
return template(data);
|
|
167
|
+
}
|
|
168
|
+
catch (e) {
|
|
169
|
+
console.log('Unknown HTML report generation error: ', e);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#loadReportHelpers() {
|
|
174
|
+
handlebars.registerHelper('getTestsByStatus', (tests, status) =>
|
|
175
|
+
tests.filter(test => test.status.toLowerCase() === status.toLowerCase()).length
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
handlebars.registerHelper('json', (tests) => {
|
|
179
|
+
function replaceScriptTagsInArray(array) {
|
|
180
|
+
return array.map(obj => {
|
|
181
|
+
const keysToCheck = ["steps", "stack", "title", "suite_title", "message", "code"];
|
|
182
|
+
const newObj = {};
|
|
183
|
+
|
|
184
|
+
for (const key in obj) {
|
|
185
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
186
|
+
if (key === "example") {
|
|
187
|
+
newObj[key] = {};
|
|
188
|
+
for (const subKey in obj[key]) {
|
|
189
|
+
if (Object.prototype.hasOwnProperty.call(obj[key], subKey)) {
|
|
190
|
+
newObj[key][subKey] = typeof obj[key][subKey] === "string"
|
|
191
|
+
? obj[key][subKey]
|
|
192
|
+
.replace(/<script>/g, "<$cript>")
|
|
193
|
+
.replace(/<\/script>/g, "</$cript>")
|
|
194
|
+
: obj[key][subKey];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} else if (keysToCheck.includes(key)) {
|
|
198
|
+
newObj[key] = typeof obj[key] === "string"
|
|
199
|
+
? obj[key].replace(/<script>/g, "<$cript>").replace(/<\/script>/g, "</$cript>")
|
|
200
|
+
: obj[key];
|
|
201
|
+
} else {
|
|
202
|
+
newObj[key] = obj[key];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return newObj;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Remove ANSI escape codes
|
|
212
|
+
return JSON.stringify(replaceScriptTagsInArray(tests));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#removeAnsiColorCodes(str) {
|
|
217
|
+
let updatedStr = str.replace(ansiRegExp(), "");
|
|
218
|
+
updatedStr = updatedStr.replace(/\n/g, '<br>');
|
|
219
|
+
|
|
220
|
+
return updatedStr;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
toString() {
|
|
224
|
+
return 'HTML Reporter';
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function testExecutionSumTime(tests) {
|
|
229
|
+
const totalMilliseconds = tests.reduce((sum, test) => {
|
|
230
|
+
if (typeof test.run_time === 'number') {
|
|
231
|
+
return sum + test.run_time;
|
|
232
|
+
}
|
|
233
|
+
return sum;
|
|
234
|
+
}, 0);
|
|
235
|
+
|
|
236
|
+
return formatDuration(totalMilliseconds);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function formatDuration(duration) {
|
|
240
|
+
const milliseconds = duration % 1000;
|
|
241
|
+
duration = (duration - milliseconds) / 1000;
|
|
242
|
+
const seconds = duration % 60;
|
|
243
|
+
duration = (duration - seconds) / 60;
|
|
244
|
+
const minutes = duration % 60;
|
|
245
|
+
const hours = (duration - minutes) / 60;
|
|
246
|
+
|
|
247
|
+
return `${hours}h ${minutes}m ${seconds}s ${milliseconds}ms`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getCurrentDateTimeFormatted() {
|
|
251
|
+
const currentDate = new Date();
|
|
252
|
+
const day = currentDate.getDate().toString().padStart(2, '0');
|
|
253
|
+
const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
|
|
254
|
+
const year = currentDate.getFullYear();
|
|
255
|
+
const hours = currentDate.getHours().toString().padStart(2, '0');
|
|
256
|
+
const minutes = currentDate.getMinutes().toString().padStart(2, '0');
|
|
257
|
+
const seconds = currentDate.getSeconds().toString().padStart(2, '0');
|
|
258
|
+
|
|
259
|
+
return `(${day}/${month}/${year} ${hours}:${minutes}:${seconds})`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = HtmlPipe;
|
package/lib/pipe/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const TestomatioPipe = require('./testomatio');
|
|
|
6
6
|
const GitHubPipe = require('./github');
|
|
7
7
|
const GitLabPipe = require('./gitlab');
|
|
8
8
|
const CsvPipe = require('./csv');
|
|
9
|
+
const HtmlPipe = require('./html');
|
|
9
10
|
|
|
10
11
|
function PipeFactory(params, opts) {
|
|
11
12
|
const extraPipes = [];
|
|
@@ -43,6 +44,7 @@ function PipeFactory(params, opts) {
|
|
|
43
44
|
new GitHubPipe(params, opts),
|
|
44
45
|
new GitLabPipe(params, opts),
|
|
45
46
|
new CsvPipe(params, opts),
|
|
47
|
+
new HtmlPipe(params, opts),
|
|
46
48
|
...extraPipes,
|
|
47
49
|
];
|
|
48
50
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -3,8 +3,8 @@ const chalk = require('chalk');
|
|
|
3
3
|
const axios = require('axios');
|
|
4
4
|
const JsonCycle = require('json-cycle');
|
|
5
5
|
const { APP_PREFIX, STATUS } = require('../constants');
|
|
6
|
-
const { isValidUrl } = require('../
|
|
7
|
-
const {
|
|
6
|
+
const { isValidUrl, foundedTestLog } = require('../utils/utils');
|
|
7
|
+
const { parseFilterParams, generateFilterRequestParams, setS3Credentials, } = require('../utils/pipe_utils');
|
|
8
8
|
|
|
9
9
|
const { TESTOMATIO_RUN } = process.env;
|
|
10
10
|
if (TESTOMATIO_RUN) {
|
|
@@ -27,17 +27,25 @@ class TestomatioPipe {
|
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
debug('Testomatio Pipe: Enabled');
|
|
30
|
+
this.parallel = params.parallel;
|
|
30
31
|
this.store = store || {};
|
|
31
32
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
32
33
|
this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
|
|
33
34
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
34
35
|
this.env = process.env.TESTOMATIO_ENV;
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
this.axios = axios.create({
|
|
38
|
+
baseURL: `${this.url.trim()}`,
|
|
39
|
+
timeout: 30000
|
|
40
|
+
});
|
|
41
|
+
|
|
36
42
|
this.isEnabled = true;
|
|
37
43
|
// do not finish this run (for parallel testing)
|
|
38
44
|
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
45
|
+
this.jiraId = process.env.TESTOMATIO_JIRA_ID;
|
|
39
46
|
this.runId = params.runId || process.env.runId;
|
|
40
|
-
this.createNewTests = !!process.env.TESTOMATIO_CREATE;
|
|
47
|
+
this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
|
|
48
|
+
this.hasUnmatchedTests = false;
|
|
41
49
|
|
|
42
50
|
if (!isValidUrl(this.url.trim())) {
|
|
43
51
|
this.isEnabled = false;
|
|
@@ -45,61 +53,135 @@ class TestomatioPipe {
|
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Asynchronously prepares and retrieves the Testomat.io test grepList based on the provided options.
|
|
58
|
+
* @param {Object} opts - The options for preparing the test grepList.
|
|
59
|
+
* @returns {Promise<string[]>} - An array containing the retrieved
|
|
60
|
+
* test grepList, or an empty array if no tests are found or the request is disabled.
|
|
61
|
+
* @throws {Error} - Throws an error if there was a problem while making the request.
|
|
62
|
+
*/
|
|
63
|
+
async prepareRun(opts) {
|
|
64
|
+
if (!this.isEnabled) return [];
|
|
65
|
+
|
|
66
|
+
const { type, id } = parseFilterParams(opts);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const q = generateFilterRequestParams({
|
|
70
|
+
type,
|
|
71
|
+
id,
|
|
72
|
+
apiKey: this.apiKey.trim()
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!q) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const resp = await this.axios.get('/api/test_grep', q);
|
|
80
|
+
const { data } = resp;
|
|
81
|
+
|
|
82
|
+
if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
|
|
83
|
+
foundedTestLog(APP_PREFIX, data.tests);
|
|
84
|
+
return data.tests;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.error(
|
|
91
|
+
APP_PREFIX,
|
|
92
|
+
`🚩 Error getting Testomat.io test grepList: ${err}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
48
97
|
/**
|
|
49
98
|
* @returns Promise<void>
|
|
50
99
|
*/
|
|
51
100
|
async createRun() {
|
|
101
|
+
debug('Creating run...');
|
|
52
102
|
if (!this.isEnabled) return;
|
|
53
103
|
|
|
104
|
+
let buildUrl = process.env.BUILD_URL || process.env.CI_JOB_URL || process.env.CIRCLE_BUILD_URL;
|
|
105
|
+
|
|
106
|
+
// GitHub Actions Url
|
|
107
|
+
if (!buildUrl && process.env.GITHUB_RUN_ID) {
|
|
108
|
+
// eslint-disable-next-line max-len
|
|
109
|
+
buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Azure DevOps Url
|
|
113
|
+
if (!buildUrl && process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI) {
|
|
114
|
+
const collectionUri = process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI;
|
|
115
|
+
const project = process.env.SYSTEM_TEAMPROJECT;
|
|
116
|
+
const buildId = process.env.BUILD_BUILDID;
|
|
117
|
+
buildUrl = `${collectionUri}/${project}/_build/results?buildId=${buildId}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (buildUrl && !buildUrl.startsWith('http')) buildUrl = undefined;
|
|
121
|
+
|
|
122
|
+
const accessEvent = process.env.TESTOMATIO_PUBLISH ? 'publish' : null;
|
|
123
|
+
|
|
54
124
|
const runParams = Object.fromEntries(
|
|
55
125
|
Object.entries({
|
|
126
|
+
ci_build_url: buildUrl,
|
|
127
|
+
parallel: this.parallel,
|
|
56
128
|
api_key: this.apiKey.trim(),
|
|
57
129
|
group_title: this.groupTitle,
|
|
130
|
+
access_event: accessEvent,
|
|
131
|
+
jira_id: this.jiraId,
|
|
58
132
|
env: this.env,
|
|
59
133
|
title: this.title,
|
|
60
134
|
shared_run: this.sharedRun,
|
|
61
|
-
}).filter(([, value]) => !!value)
|
|
135
|
+
}).filter(([, value]) => !!value),
|
|
62
136
|
);
|
|
137
|
+
debug('Run params', JSON.stringify(runParams, null, 2));
|
|
63
138
|
|
|
64
139
|
if (this.runId) {
|
|
65
|
-
|
|
140
|
+
debug(`Run with id ${this.runId} already created, updating...`);
|
|
141
|
+
const resp = await this.axios.put(`/api/reporter/${this.runId}`, runParams);
|
|
66
142
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
67
143
|
return;
|
|
68
144
|
}
|
|
69
145
|
|
|
70
146
|
try {
|
|
71
|
-
const resp = await this.axios.post(
|
|
147
|
+
const resp = await this.axios.post(`/api/reporter`, runParams, {
|
|
72
148
|
maxContentLength: Infinity,
|
|
73
149
|
maxBodyLength: Infinity,
|
|
74
150
|
});
|
|
75
151
|
this.runId = resp.data.uid;
|
|
76
152
|
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
153
|
+
this.runPublicUrl = resp.data.public_url;
|
|
77
154
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
78
155
|
this.store.runUrl = this.runUrl;
|
|
156
|
+
this.store.runPublicUrl = this.runPublicUrl;
|
|
79
157
|
this.store.runId = this.runId;
|
|
80
158
|
console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
|
|
81
159
|
process.env.runId = this.runId;
|
|
160
|
+
debug('Run created', this.runId);
|
|
82
161
|
} catch (err) {
|
|
83
162
|
console.error(
|
|
84
163
|
APP_PREFIX,
|
|
85
164
|
'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
|
|
165
|
+
err,
|
|
86
166
|
);
|
|
87
167
|
}
|
|
168
|
+
debug('"createRun" function finished');
|
|
88
169
|
}
|
|
89
170
|
|
|
90
171
|
/**
|
|
91
|
-
*
|
|
92
|
-
* @param testData data
|
|
93
|
-
* @returns
|
|
172
|
+
*
|
|
173
|
+
* @param testData data
|
|
174
|
+
* @returns
|
|
94
175
|
*/
|
|
95
176
|
addTest(data) {
|
|
177
|
+
debug('Adding test...');
|
|
96
178
|
if (!this.isEnabled) return;
|
|
97
179
|
if (!this.runId) return;
|
|
98
180
|
data.api_key = this.apiKey;
|
|
99
181
|
data.create = this.createNewTests;
|
|
100
182
|
const json = JsonCycle.stringify(data);
|
|
101
183
|
|
|
102
|
-
return this.axios.post(
|
|
184
|
+
return this.axios.post(`/api/reporter/${this.runId}/testrun`, json, {
|
|
103
185
|
maxContentLength: Infinity,
|
|
104
186
|
maxBodyLength: Infinity,
|
|
105
187
|
headers: {
|
|
@@ -107,29 +189,37 @@ class TestomatioPipe {
|
|
|
107
189
|
'Content-Type': 'application/json',
|
|
108
190
|
},
|
|
109
191
|
})
|
|
110
|
-
.catch(
|
|
192
|
+
.catch(err => {
|
|
111
193
|
if (err.response) {
|
|
112
194
|
if (err.response.status >= 400) {
|
|
113
195
|
const responseData = err.response.data || { message: '' };
|
|
114
196
|
console.log(
|
|
115
197
|
APP_PREFIX,
|
|
116
|
-
chalk.
|
|
117
|
-
|
|
198
|
+
chalk.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
199
|
+
chalk.grey(data?.title || ''),
|
|
118
200
|
);
|
|
201
|
+
if (err.response.data.message.includes('could not be matched')) {
|
|
202
|
+
this.hasUnmatchedTests = true;
|
|
203
|
+
}
|
|
119
204
|
return;
|
|
120
205
|
}
|
|
121
|
-
console.log(
|
|
206
|
+
console.log(
|
|
207
|
+
APP_PREFIX,
|
|
208
|
+
chalk.yellow(`Warning: ${data?.title || ''} (${err.response?.status})`),
|
|
209
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
210
|
+
);
|
|
122
211
|
} else {
|
|
123
|
-
console.log(APP_PREFIX, chalk.blue(
|
|
212
|
+
console.log(APP_PREFIX, chalk.blue(data?.title || ''), "Report couldn't be processed", err);
|
|
124
213
|
}
|
|
125
214
|
});
|
|
126
215
|
}
|
|
127
216
|
|
|
128
217
|
/**
|
|
129
|
-
* @param {import('../../types').RunData} params
|
|
130
|
-
* @returns
|
|
218
|
+
* @param {import('../../types').RunData} params
|
|
219
|
+
* @returns
|
|
131
220
|
*/
|
|
132
221
|
async finishRun(params) {
|
|
222
|
+
debug('Finishing run...');
|
|
133
223
|
if (!this.isEnabled) return;
|
|
134
224
|
|
|
135
225
|
const { status, parallel } = params;
|
|
@@ -143,7 +233,7 @@ class TestomatioPipe {
|
|
|
143
233
|
|
|
144
234
|
try {
|
|
145
235
|
if (this.runId && !this.proceed) {
|
|
146
|
-
await this.axios.put(
|
|
236
|
+
await this.axios.put(`/api/reporter/${this.runId}`, {
|
|
147
237
|
api_key: this.apiKey,
|
|
148
238
|
status_event,
|
|
149
239
|
tests: params.tests,
|
|
@@ -151,15 +241,44 @@ class TestomatioPipe {
|
|
|
151
241
|
if (this.runUrl) {
|
|
152
242
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', chalk.magenta(this.runUrl));
|
|
153
243
|
}
|
|
244
|
+
if (this.runPublicUrl) {
|
|
245
|
+
console.log(APP_PREFIX, '🌟 Public URL:', chalk.magenta(this.runPublicUrl));
|
|
246
|
+
}
|
|
154
247
|
}
|
|
155
248
|
if (this.runUrl && this.proceed) {
|
|
156
249
|
const notFinishedMessage = chalk.yellow.bold('Run was not finished because of $TESTOMATIO_PROCEED');
|
|
157
250
|
console.log(APP_PREFIX, `📊 ${notFinishedMessage}. Report URL: ${chalk.magenta(this.runUrl)}`);
|
|
158
251
|
console.log(APP_PREFIX, `🛬 Run to finish it: TESTOMATIO_RUN=${this.runId} npx start-test-run --finish`);
|
|
159
252
|
}
|
|
253
|
+
if (this.hasUnmatchedTests) {
|
|
254
|
+
console.log('');
|
|
255
|
+
// eslint-disable-next-line max-len
|
|
256
|
+
console.log(APP_PREFIX, chalk.yellow.bold('⚠️ Some reported tests were not found in Testomat.io project'));
|
|
257
|
+
// eslint-disable-next-line max-len
|
|
258
|
+
console.log(
|
|
259
|
+
APP_PREFIX,
|
|
260
|
+
`If you use Testomat.io as a reporter only, please re-run tests using ${chalk.bold('TESTOMATIO_CREATE=1')}`,
|
|
261
|
+
);
|
|
262
|
+
// eslint-disable-next-line max-len
|
|
263
|
+
console.log(
|
|
264
|
+
APP_PREFIX,
|
|
265
|
+
`But to keep your tests consistent it is recommended to ${chalk.bold('import tests first')}`,
|
|
266
|
+
);
|
|
267
|
+
console.log(APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
|
|
268
|
+
console.log(APP_PREFIX, 'You can do that automatically via command line tools:');
|
|
269
|
+
console.log(APP_PREFIX, chalk.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
|
|
270
|
+
console.log(APP_PREFIX, 'or for Cucumber:');
|
|
271
|
+
// eslint-disable-next-line max-len
|
|
272
|
+
console.log(
|
|
273
|
+
APP_PREFIX,
|
|
274
|
+
chalk.bold('npx check-cucumber ... --update-ids'),
|
|
275
|
+
'See: https://bit.ly/bdd-update-ids',
|
|
276
|
+
);
|
|
277
|
+
}
|
|
160
278
|
} catch (err) {
|
|
161
279
|
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
162
280
|
}
|
|
281
|
+
debug('Run finished');
|
|
163
282
|
}
|
|
164
283
|
|
|
165
284
|
toString() {
|
|
@@ -168,18 +287,3 @@ class TestomatioPipe {
|
|
|
168
287
|
}
|
|
169
288
|
|
|
170
289
|
module.exports = TestomatioPipe;
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
function setS3Credentials(artifacts) {
|
|
174
|
-
if (!Object.keys(artifacts).length) return;
|
|
175
|
-
|
|
176
|
-
console.log(APP_PREFIX, 'S3 were credentials obtained from Testomat.io...');
|
|
177
|
-
|
|
178
|
-
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
179
|
-
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
180
|
-
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
181
|
-
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
182
|
-
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
183
|
-
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
184
|
-
resetConfig();
|
|
185
|
-
}
|
package/lib/reporter.js
CHANGED
|
@@ -3,11 +3,12 @@ const TestomatClient = require('./client');
|
|
|
3
3
|
const TRConstants = require('./constants');
|
|
4
4
|
const TRArtifacts = require('./_ArtifactStorageOld');
|
|
5
5
|
|
|
6
|
-
const log = logger.
|
|
6
|
+
const log = logger.templateLiteralLog.bind(logger);
|
|
7
7
|
const step = logger.step.bind(logger);
|
|
8
8
|
|
|
9
9
|
module.exports = {
|
|
10
10
|
logger,
|
|
11
|
+
testomatioLogger: logger,
|
|
11
12
|
log,
|
|
12
13
|
step,
|
|
13
14
|
TestomatClient,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// TODO: use as example for the unit-tests
|
|
2
|
+
|
|
3
|
+
const result = {
|
|
4
|
+
"runId": "51eb2798",
|
|
5
|
+
"status": "failed",
|
|
6
|
+
"runUrl": "https://beta.testomat.io/projects/codecept-new-mode-exmple/runs/51eb2798/report",
|
|
7
|
+
"executionTime": "0 seconds",
|
|
8
|
+
"executionDate": "(30/07/2023 11:11:11)",
|
|
9
|
+
"tests": [{
|
|
10
|
+
"files": [],
|
|
11
|
+
"steps": "\u001b[32m\u001b[1mOn TodosPage: goto \u001b[22m\u001b[39m\n",
|
|
12
|
+
"status": "passed",
|
|
13
|
+
"stack": "I execute script () => sessionStorage.clear()",
|
|
14
|
+
"example": null,
|
|
15
|
+
"code": null,
|
|
16
|
+
"title": "Create a new todo item @T50e82737",
|
|
17
|
+
"suite_title": "Create Tasks @step:06 @smoke @story:12 @S2f5c1942",
|
|
18
|
+
"test_id": "50e82737",
|
|
19
|
+
"message": "",
|
|
20
|
+
"run_time": 121,
|
|
21
|
+
"artifacts": [],
|
|
22
|
+
"api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
|
|
23
|
+
"create": false
|
|
24
|
+
}, {
|
|
25
|
+
"files": [{
|
|
26
|
+
"path": "/home/codeceptjs-testomat-example/codeceptJS/output/Create_2_@T5b8d1186.failed.png",
|
|
27
|
+
"type": "image/png"
|
|
28
|
+
}],
|
|
29
|
+
"steps": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
|
|
30
|
+
"status": "failed",
|
|
31
|
+
"stack": "I am on page \"http://todomvc.com/examples/angularjs/#/\"",
|
|
32
|
+
"example": null,
|
|
33
|
+
"code": null,
|
|
34
|
+
"title": "Create a new todo item part 2 @T5b8d1186",
|
|
35
|
+
"suite_title": "@second Create Tasks @step:07 @smoke @story:13 @S2f5c1942",
|
|
36
|
+
"test_id": "5b8d1186",
|
|
37
|
+
"message": "expected expected number of visible is 2, but found 1 \"1\" to equal \"2\"",
|
|
38
|
+
"run_time": 176,
|
|
39
|
+
"artifacts": [null],
|
|
40
|
+
"api_key": "tstmt_gRqrhBUaVxTpezGpZjRmlahOeqcRBBbDMA1692050199",
|
|
41
|
+
"create": false
|
|
42
|
+
}]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
result
|
|
47
|
+
}
|