@testomatio/reporter 2.0.1-beta-2-ignore-xml → 2.0.1-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/lib/adapter/codecept.js +0 -2
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +18 -8
- package/lib/bin/cli.js +73 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +18 -9
- package/lib/config.js +2 -2
- package/lib/data-storage.d.ts +1 -1
- package/lib/data-storage.js +17 -7
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +17 -3
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.d.ts +1 -0
- package/lib/pipe/html.js +1 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +3 -2
- package/lib/pipe/testomatio.js +85 -75
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +237 -0
- package/lib/reporter.d.ts +12 -12
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +10 -6
- package/lib/utils/utils.d.ts +3 -1
- package/lib/utils/utils.js +54 -21
- package/lib/xmlReader.js +54 -19
- package/package.json +8 -9
- package/src/adapter/codecept.js +0 -2
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/webdriver.js +2 -2
- package/src/bin/cli.js +70 -2
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +1 -2
- package/src/config.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +18 -3
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +3 -4
- package/src/pipe/testomatio.js +106 -105
- package/src/replay.js +245 -0
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +11 -6
- package/src/utils/utils.js +31 -12
- package/src/xmlReader.js +69 -17
package/lib/pipe/debug.js
CHANGED
|
@@ -27,7 +27,21 @@ class DebugPipe {
|
|
|
27
27
|
this.logFilePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.debug.${Date.now()}.json`);
|
|
28
28
|
debug('Creating debug file:', this.logFilePath);
|
|
29
29
|
fs_1.default.writeFileSync(this.logFilePath, '');
|
|
30
|
-
|
|
30
|
+
// Create symlink to ensure consistent path to latest debug file
|
|
31
|
+
const symlinkPath = path_1.default.join(os_1.default.tmpdir(), 'testomatio.debug.latest.json');
|
|
32
|
+
try {
|
|
33
|
+
// Remove existing symlink if it exists
|
|
34
|
+
if (fs_1.default.existsSync(symlinkPath)) {
|
|
35
|
+
fs_1.default.unlinkSync(symlinkPath);
|
|
36
|
+
}
|
|
37
|
+
// Create new symlink pointing to the timestamped debug file
|
|
38
|
+
fs_1.default.symlinkSync(this.logFilePath, symlinkPath);
|
|
39
|
+
debug('Created symlink:', symlinkPath, '->', this.logFilePath);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
debug('Failed to create symlink:', err.message);
|
|
43
|
+
}
|
|
44
|
+
console.log(constants_js_1.APP_PREFIX, '🪲 Debug file created');
|
|
31
45
|
this.testomatioEnvVars = Object.keys(process.env)
|
|
32
46
|
.filter(key => key.startsWith('TESTOMATIO_'))
|
|
33
47
|
.reduce((acc, key) => {
|
|
@@ -93,11 +107,11 @@ class DebugPipe {
|
|
|
93
107
|
async finishRun(params) {
|
|
94
108
|
if (!this.isEnabled)
|
|
95
109
|
return;
|
|
96
|
-
this.logToFile({ actions: 'finishRun', params });
|
|
97
110
|
await this.batchUpload();
|
|
98
111
|
if (this.batch.intervalFunction)
|
|
99
112
|
clearInterval(this.batch.intervalFunction);
|
|
100
|
-
|
|
113
|
+
this.logToFile({ action: 'finishRun', params });
|
|
114
|
+
console.log(constants_js_1.APP_PREFIX, '🪲 Debug Saved to', this.logFilePath);
|
|
101
115
|
}
|
|
102
116
|
toString() {
|
|
103
117
|
return 'Debug Reporter';
|
package/lib/pipe/github.d.ts
CHANGED
|
@@ -23,8 +23,8 @@ declare class GitHubPipe implements Pipe {
|
|
|
23
23
|
createRun(): Promise<void>;
|
|
24
24
|
addTest(test: any): void;
|
|
25
25
|
finishRun(runParams: any): Promise<void>;
|
|
26
|
-
octokit: import("@octokit/core").Octokit & {
|
|
26
|
+
octokit: import("@octokit/core").Octokit & import("@octokit/plugin-rest-endpoint-methods/dist-types/generated/method-types.js").RestEndpointMethods & import("@octokit/plugin-rest-endpoint-methods").Api & {
|
|
27
27
|
paginate: import("@octokit/plugin-paginate-rest").PaginateInterface;
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
29
|
toString(): string;
|
|
30
30
|
}
|
package/lib/pipe/github.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -8,7 +41,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
41
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
42
|
const humanize_duration_1 = __importDefault(require("humanize-duration"));
|
|
10
43
|
const lodash_merge_1 = __importDefault(require("lodash.merge"));
|
|
11
|
-
const rest_1 = require("@octokit/rest");
|
|
12
44
|
const constants_js_1 = require("../constants.js");
|
|
13
45
|
const utils_js_1 = require("../utils/utils.js");
|
|
14
46
|
const pipe_utils_js_1 = require("../utils/pipe_utils.js");
|
|
@@ -62,7 +94,8 @@ class GitHubPipe {
|
|
|
62
94
|
return;
|
|
63
95
|
if (runParams.tests)
|
|
64
96
|
runParams.tests.forEach(t => this.addTest(t));
|
|
65
|
-
|
|
97
|
+
const { Octokit } = await Promise.resolve().then(() => __importStar(require('@octokit/rest')));
|
|
98
|
+
this.octokit = new Octokit({
|
|
66
99
|
auth: this.token,
|
|
67
100
|
});
|
|
68
101
|
const [owner, repo] = (this.repo || '').split('/');
|
|
@@ -130,7 +163,6 @@ class GitHubPipe {
|
|
|
130
163
|
if (this.tests.length > 0) {
|
|
131
164
|
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
132
165
|
body += this.tests
|
|
133
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
134
166
|
.sort((a, b) => b?.run_time - a?.run_time)
|
|
135
167
|
.slice(0, 5)
|
|
136
168
|
.map(t => `* ${(0, pipe_utils_js_1.fullName)(t)} (${(0, humanize_duration_1.default)(parseFloat(t.run_time))})`)
|
package/lib/pipe/gitlab.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ declare class GitLabPipe {
|
|
|
14
14
|
tests: any[];
|
|
15
15
|
token: any;
|
|
16
16
|
hiddenCommentData: string;
|
|
17
|
+
client: Gaxios;
|
|
17
18
|
prepareRun(): Promise<void>;
|
|
18
19
|
createRun(): Promise<void>;
|
|
19
20
|
addTest(test: any): void;
|
|
@@ -21,3 +22,4 @@ declare class GitLabPipe {
|
|
|
21
22
|
toString(): string;
|
|
22
23
|
updateRun(): void;
|
|
23
24
|
}
|
|
25
|
+
import { Gaxios } from 'gaxios';
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const debug_1 = __importDefault(require("debug"));
|
|
7
|
-
const
|
|
7
|
+
const gaxios_1 = require("gaxios");
|
|
8
8
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
9
|
const humanize_duration_1 = __importDefault(require("humanize-duration"));
|
|
10
10
|
const lodash_merge_1 = __importDefault(require("lodash.merge"));
|
|
@@ -39,6 +39,12 @@ class GitLabPipe {
|
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
this.isEnabled = true;
|
|
42
|
+
this.client = new gaxios_1.Gaxios({
|
|
43
|
+
baseURL: 'https://gitlab.com/api/v4',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
}
|
|
47
|
+
});
|
|
42
48
|
debug('GitLab Pipe: Enabled');
|
|
43
49
|
}
|
|
44
50
|
// TODO: to using SET opts as argument => prepareRun(opts)
|
|
@@ -119,7 +125,6 @@ class GitLabPipe {
|
|
|
119
125
|
if (this.tests.length > 0) {
|
|
120
126
|
body += '\n<details>\n<summary><h3>🐢 Slowest Tests</h3></summary>\n\n';
|
|
121
127
|
body += this.tests
|
|
122
|
-
// eslint-disable-next-line no-unsafe-optional-chaining
|
|
123
128
|
.sort((a, b) => b?.run_time - a?.run_time)
|
|
124
129
|
.slice(0, 5)
|
|
125
130
|
.map(t => `* ${(0, pipe_utils_js_1.fullName)(t)} (${(0, humanize_duration_1.default)(parseFloat(t.run_time))})`)
|
|
@@ -127,13 +132,18 @@ class GitLabPipe {
|
|
|
127
132
|
body += '\n</details>';
|
|
128
133
|
}
|
|
129
134
|
// eslint-disable-next-line max-len
|
|
130
|
-
const commentsRequestURL =
|
|
135
|
+
const commentsRequestURL = `/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
|
|
131
136
|
// delete previous report
|
|
132
|
-
await deletePreviousReport(
|
|
137
|
+
await deletePreviousReport(this.client, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
133
138
|
// add current report
|
|
134
139
|
debug(`Adding comment via url: ${commentsRequestURL}`);
|
|
135
140
|
try {
|
|
136
|
-
const addCommentResponse = await
|
|
141
|
+
const addCommentResponse = await this.client.request({
|
|
142
|
+
method: 'POST',
|
|
143
|
+
url: commentsRequestURL,
|
|
144
|
+
params: { access_token: this.token },
|
|
145
|
+
data: { body }
|
|
146
|
+
});
|
|
137
147
|
const commentID = addCommentResponse.data.id;
|
|
138
148
|
// eslint-disable-next-line max-len
|
|
139
149
|
const commentURL = `${this.ENV.CI_PROJECT_URL}/-/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}#note_${commentID}`;
|
|
@@ -150,13 +160,17 @@ class GitLabPipe {
|
|
|
150
160
|
}
|
|
151
161
|
updateRun() { }
|
|
152
162
|
}
|
|
153
|
-
async function deletePreviousReport(
|
|
163
|
+
async function deletePreviousReport(client, commentsRequestURL, hiddenCommentData, token) {
|
|
154
164
|
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS)
|
|
155
165
|
return;
|
|
156
166
|
// get comments
|
|
157
167
|
let comments = [];
|
|
158
168
|
try {
|
|
159
|
-
const response = await
|
|
169
|
+
const response = await client.request({
|
|
170
|
+
method: 'GET',
|
|
171
|
+
url: commentsRequestURL,
|
|
172
|
+
params: { access_token: token }
|
|
173
|
+
});
|
|
160
174
|
comments = response.data;
|
|
161
175
|
}
|
|
162
176
|
catch (e) {
|
|
@@ -169,8 +183,12 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
|
|
|
169
183
|
if (comment.body.includes(hiddenCommentData)) {
|
|
170
184
|
try {
|
|
171
185
|
// delete previous comment
|
|
172
|
-
const deleteCommentURL = `${commentsRequestURL}/${comment.id}
|
|
173
|
-
await
|
|
186
|
+
const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
|
|
187
|
+
await client.request({
|
|
188
|
+
method: 'DELETE',
|
|
189
|
+
url: deleteCommentURL,
|
|
190
|
+
params: { access_token: token }
|
|
191
|
+
});
|
|
174
192
|
}
|
|
175
193
|
catch (e) {
|
|
176
194
|
console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
package/lib/pipe/html.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ declare class HtmlPipe {
|
|
|
15
15
|
templateFolderPath: string;
|
|
16
16
|
templateHtmlPath: string;
|
|
17
17
|
createRun(): Promise<void>;
|
|
18
|
+
prepareRun(): Promise<void>;
|
|
18
19
|
updateRun(): void;
|
|
19
20
|
/**
|
|
20
21
|
* Add test data to the result array for saving. As a result of this function, we get a result object to save.
|
package/lib/pipe/html.js
CHANGED
|
@@ -53,6 +53,7 @@ class HtmlPipe {
|
|
|
53
53
|
async createRun() {
|
|
54
54
|
// empty
|
|
55
55
|
}
|
|
56
|
+
async prepareRun() { }
|
|
56
57
|
updateRun() {
|
|
57
58
|
// empty
|
|
58
59
|
}
|
|
@@ -192,7 +193,6 @@ class HtmlPipe {
|
|
|
192
193
|
<option value="1">25</option>
|
|
193
194
|
<option value="2">50</option>
|
|
194
195
|
</select>`));
|
|
195
|
-
/* eslint-disable */
|
|
196
196
|
handlebars_1.default.registerHelper('emptyDataComponent', () => {
|
|
197
197
|
const svgFilePath = path_1.default.join(__dirname, '..', 'template', 'emptyData.svg');
|
|
198
198
|
const svgContent = fs_1.default.readFileSync(svgFilePath, 'utf8');
|
|
@@ -206,7 +206,6 @@ class HtmlPipe {
|
|
|
206
206
|
</div>
|
|
207
207
|
<div>`);
|
|
208
208
|
});
|
|
209
|
-
/* eslint-enable */
|
|
210
209
|
handlebars_1.default.registerHelper('pageDispleyElements', tests => {
|
|
211
210
|
// We wrapp the lines to the HTML format we need
|
|
212
211
|
const totalTests = JSON.parse(JSON.stringify(tests)
|
|
@@ -234,7 +233,6 @@ class HtmlPipe {
|
|
|
234
233
|
}
|
|
235
234
|
statuses.forEach(status => {
|
|
236
235
|
for (const option in paginationOptions) {
|
|
237
|
-
// eslint-disable-next-line no-prototype-builtins
|
|
238
236
|
if (paginationOptions.hasOwnProperty(option)) {
|
|
239
237
|
const pageSize = paginationOptions[option];
|
|
240
238
|
let filteredItems = totalTests;
|
package/lib/pipe/index.js
CHANGED
|
@@ -15,13 +15,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
25
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
37
|
};
|
package/lib/pipe/testomatio.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ declare class TestomatioPipe implements Pipe {
|
|
|
31
31
|
groupTitle: any;
|
|
32
32
|
env: string;
|
|
33
33
|
label: string;
|
|
34
|
-
|
|
34
|
+
client: Gaxios;
|
|
35
35
|
proceed: string;
|
|
36
36
|
jiraId: string;
|
|
37
37
|
runId: any;
|
|
@@ -59,7 +59,7 @@ declare class TestomatioPipe implements Pipe {
|
|
|
59
59
|
/**
|
|
60
60
|
* Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
|
|
61
61
|
*/
|
|
62
|
-
addTest(data: any): void
|
|
62
|
+
addTest(data: any): Promise<void | import("gaxios").GaxiosResponse<any>>;
|
|
63
63
|
/**
|
|
64
64
|
* @param {import('../../types/types.js').RunData} params
|
|
65
65
|
* @returns
|
|
@@ -68,3 +68,4 @@ declare class TestomatioPipe implements Pipe {
|
|
|
68
68
|
toString(): string;
|
|
69
69
|
#private;
|
|
70
70
|
}
|
|
71
|
+
import { Gaxios } from 'gaxios';
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -5,19 +5,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const debug_1 = __importDefault(require("debug"));
|
|
7
7
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
8
|
-
|
|
9
|
-
const axios_retry_1 = __importDefault(require("axios-retry"));
|
|
10
|
-
// Default axios instance
|
|
11
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const gaxios_1 = require("gaxios");
|
|
12
9
|
const json_cycle_1 = __importDefault(require("json-cycle"));
|
|
13
10
|
const constants_js_1 = require("../constants.js");
|
|
14
11
|
const utils_js_1 = require("../utils/utils.js");
|
|
15
12
|
const pipe_utils_js_1 = require("../utils/pipe_utils.js");
|
|
16
13
|
const config_js_1 = require("../config.js");
|
|
17
14
|
const debug = (0, debug_1.default)('@testomatio/reporter:pipe:testomatio');
|
|
18
|
-
if (process.env.TESTOMATIO_RUN)
|
|
19
|
-
|
|
20
|
-
}
|
|
15
|
+
if (process.env.TESTOMATIO_RUN)
|
|
16
|
+
process.env.runId = process.env.TESTOMATIO_RUN;
|
|
21
17
|
/**
|
|
22
18
|
* @typedef {import('../../types/types.js').Pipe} Pipe
|
|
23
19
|
* @typedef {import('../../types/types.js').TestData} TestData
|
|
@@ -55,48 +51,37 @@ class TestomatioPipe {
|
|
|
55
51
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
56
52
|
this.env = process.env.TESTOMATIO_ENV;
|
|
57
53
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
58
|
-
// Create a new instance of
|
|
59
|
-
this.
|
|
54
|
+
// Create a new instance of gaxios with a custom config
|
|
55
|
+
this.client = new gaxios_1.Gaxios({
|
|
60
56
|
baseURL: `${this.url.trim()}`,
|
|
61
57
|
timeout: constants_js_1.AXIOS_TIMEOUT,
|
|
62
|
-
proxy: proxy
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
:
|
|
69
|
-
|
|
70
|
-
// Pass the axios instance to the retry function
|
|
71
|
-
(0, axios_retry_1.default)(this.axios, {
|
|
72
|
-
// do not use retries for unit tests
|
|
73
|
-
retries: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
|
|
74
|
-
shouldResetTimeout: true,
|
|
75
|
-
retryCondition: error => {
|
|
76
|
-
if (!error.response)
|
|
77
|
-
return false;
|
|
78
|
-
switch (error.response?.status) {
|
|
79
|
-
case 400: // Bad request (probably wrong API key)
|
|
80
|
-
case 404: // Test not matched
|
|
81
|
-
case 429: // Rate limit exceeded
|
|
82
|
-
case 500: // Internal server error
|
|
58
|
+
proxy: proxy ? proxy.toString() : undefined,
|
|
59
|
+
retry: true,
|
|
60
|
+
retryConfig: {
|
|
61
|
+
retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
62
|
+
retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
63
|
+
httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
|
|
64
|
+
shouldRetry: (error) => {
|
|
65
|
+
if (!error.response)
|
|
83
66
|
return false;
|
|
84
|
-
|
|
85
|
-
|
|
67
|
+
switch (error.response?.status) {
|
|
68
|
+
case 400: // Bad request (probably wrong API key)
|
|
69
|
+
case 404: // Test not matched
|
|
70
|
+
case 429: // Rate limit exceeded
|
|
71
|
+
case 500: // Internal server error
|
|
72
|
+
return false;
|
|
73
|
+
default:
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
86
77
|
}
|
|
87
|
-
|
|
88
|
-
},
|
|
89
|
-
retryDelay: () => constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
|
|
90
|
-
onRetry: async (retryCount, error) => {
|
|
91
|
-
this.retriesTimestamps.push(Date.now());
|
|
92
|
-
debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
|
|
93
|
-
},
|
|
78
|
+
}
|
|
94
79
|
});
|
|
95
80
|
this.isEnabled = true;
|
|
96
81
|
// do not finish this run (for parallel testing)
|
|
97
82
|
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
98
83
|
this.jiraId = process.env.TESTOMATIO_JIRA_ID;
|
|
99
|
-
this.runId = params.runId || process.env.
|
|
84
|
+
this.runId = params.runId || process.env.TESTOMATIO_RUN;
|
|
100
85
|
this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
|
|
101
86
|
this.hasUnmatchedTests = false;
|
|
102
87
|
this.requestFailures = 0;
|
|
@@ -125,11 +110,14 @@ class TestomatioPipe {
|
|
|
125
110
|
if (!q) {
|
|
126
111
|
return;
|
|
127
112
|
}
|
|
128
|
-
const resp = await this.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
const resp = await this.client.request({
|
|
114
|
+
method: 'GET',
|
|
115
|
+
url: '/api/test_grep',
|
|
116
|
+
params: q
|
|
117
|
+
});
|
|
118
|
+
if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
|
|
119
|
+
(0, utils_js_1.foundedTestLog)(constants_js_1.APP_PREFIX, resp.data.tests);
|
|
120
|
+
return resp.data.tests;
|
|
133
121
|
}
|
|
134
122
|
console.log(constants_js_1.APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
135
123
|
}
|
|
@@ -181,16 +169,23 @@ class TestomatioPipe {
|
|
|
181
169
|
if (this.runId) {
|
|
182
170
|
this.store.runId = this.runId;
|
|
183
171
|
debug(`Run with id ${this.runId} already created, updating...`);
|
|
184
|
-
const resp = await this.
|
|
172
|
+
const resp = await this.client.request({
|
|
173
|
+
method: 'PUT',
|
|
174
|
+
url: `/api/reporter/${this.runId}`,
|
|
175
|
+
data: runParams
|
|
176
|
+
});
|
|
185
177
|
if (resp.data.artifacts)
|
|
186
178
|
(0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
|
|
187
179
|
return;
|
|
188
180
|
}
|
|
189
181
|
debug('Creating run...');
|
|
190
182
|
try {
|
|
191
|
-
const resp = await this.
|
|
183
|
+
const resp = await this.client.request({
|
|
184
|
+
method: 'POST',
|
|
185
|
+
url: '/api/reporter',
|
|
186
|
+
data: runParams,
|
|
192
187
|
maxContentLength: Infinity,
|
|
193
|
-
|
|
188
|
+
responseType: 'json'
|
|
194
189
|
});
|
|
195
190
|
this.runId = resp.data.uid;
|
|
196
191
|
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
@@ -206,6 +201,7 @@ class TestomatioPipe {
|
|
|
206
201
|
}
|
|
207
202
|
catch (err) {
|
|
208
203
|
const errorText = err.response?.data?.message || err.message;
|
|
204
|
+
debug('Error creating run', err);
|
|
209
205
|
console.log(errorText || err);
|
|
210
206
|
if (!this.apiKey)
|
|
211
207
|
console.error('Testomat.io API key is not set');
|
|
@@ -246,7 +242,15 @@ class TestomatioPipe {
|
|
|
246
242
|
}
|
|
247
243
|
const json = json_cycle_1.default.stringify(data);
|
|
248
244
|
debug('Adding test', json);
|
|
249
|
-
return this.
|
|
245
|
+
return this.client.request({
|
|
246
|
+
method: 'POST',
|
|
247
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
248
|
+
data: json,
|
|
249
|
+
headers: {
|
|
250
|
+
'Content-Type': 'application/json',
|
|
251
|
+
},
|
|
252
|
+
maxContentLength: Infinity
|
|
253
|
+
}).catch(err => {
|
|
250
254
|
this.requestFailures++;
|
|
251
255
|
this.notReportedTestsCount++;
|
|
252
256
|
if (err.response) {
|
|
@@ -291,9 +295,19 @@ class TestomatioPipe {
|
|
|
291
295
|
// get tests from batch and clear batch
|
|
292
296
|
const testsToSend = this.batch.tests.splice(0);
|
|
293
297
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
294
|
-
return this.
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
return this.client.request({
|
|
299
|
+
method: 'POST',
|
|
300
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
301
|
+
data: {
|
|
302
|
+
api_key: this.apiKey,
|
|
303
|
+
tests: testsToSend,
|
|
304
|
+
batch_index: this.batch.batchIndex
|
|
305
|
+
},
|
|
306
|
+
headers: {
|
|
307
|
+
'Content-Type': 'application/json',
|
|
308
|
+
},
|
|
309
|
+
maxContentLength: Infinity
|
|
310
|
+
}).catch(err => {
|
|
297
311
|
this.requestFailures++;
|
|
298
312
|
this.notReportedTestsCount += testsToSend.length;
|
|
299
313
|
if (err.response) {
|
|
@@ -317,6 +331,7 @@ class TestomatioPipe {
|
|
|
317
331
|
* Adds a test to the batch uploader (or reports a single test if batch uploading is disabled)
|
|
318
332
|
*/
|
|
319
333
|
addTest(data) {
|
|
334
|
+
this.isEnabled = this.apiKey ?? this.isEnabled;
|
|
320
335
|
if (!this.isEnabled)
|
|
321
336
|
return;
|
|
322
337
|
if (!this.runId)
|
|
@@ -326,13 +341,16 @@ class TestomatioPipe {
|
|
|
326
341
|
data.rid = `${this.runId}-${data.rid}`;
|
|
327
342
|
data.api_key = this.apiKey;
|
|
328
343
|
data.create = this.createNewTests;
|
|
344
|
+
let uploading = null;
|
|
329
345
|
if (!this.batch.isEnabled)
|
|
330
|
-
this.#uploadSingleTest(data);
|
|
346
|
+
uploading = this.#uploadSingleTest(data);
|
|
331
347
|
else
|
|
332
348
|
this.batch.tests.push(data);
|
|
333
349
|
// if test is added after run which is already finished
|
|
334
350
|
if (!this.batch.intervalFunction)
|
|
335
|
-
this.#batchUpload();
|
|
351
|
+
uploading = this.#batchUpload();
|
|
352
|
+
// return promise to be able to wait for it
|
|
353
|
+
return uploading;
|
|
336
354
|
}
|
|
337
355
|
/**
|
|
338
356
|
* @param {import('../../types/types.js').RunData} params
|
|
@@ -367,12 +385,16 @@ class TestomatioPipe {
|
|
|
367
385
|
status_event += '_parallel';
|
|
368
386
|
try {
|
|
369
387
|
if (this.runId && !this.proceed) {
|
|
370
|
-
await this.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
388
|
+
await this.client.request({
|
|
389
|
+
method: 'PUT',
|
|
390
|
+
url: `/api/reporter/${this.runId}`,
|
|
391
|
+
data: {
|
|
392
|
+
api_key: this.apiKey,
|
|
393
|
+
duration: params.duration,
|
|
394
|
+
status_event,
|
|
395
|
+
detach: params.detach,
|
|
396
|
+
tests: params.tests,
|
|
397
|
+
}
|
|
376
398
|
});
|
|
377
399
|
if (this.runUrl) {
|
|
378
400
|
console.log(constants_js_1.APP_PREFIX, '📊 Report Saved. Report URL:', picocolors_1.default.magenta(this.runUrl));
|
|
@@ -388,17 +410,13 @@ class TestomatioPipe {
|
|
|
388
410
|
}
|
|
389
411
|
if (this.hasUnmatchedTests) {
|
|
390
412
|
console.log('');
|
|
391
|
-
// eslint-disable-next-line max-len
|
|
392
413
|
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.yellow(picocolors_1.default.bold('⚠️ Some reported tests were not found in Testomat.io project')));
|
|
393
|
-
// eslint-disable-next-line max-len
|
|
394
414
|
console.log(constants_js_1.APP_PREFIX, `If you use Testomat.io as a reporter only, please re-run tests using ${picocolors_1.default.bold('TESTOMATIO_CREATE=1')}`);
|
|
395
|
-
// eslint-disable-next-line max-len
|
|
396
415
|
console.log(constants_js_1.APP_PREFIX, `But to keep your tests consistent it is recommended to ${picocolors_1.default.bold('import tests first')}`);
|
|
397
416
|
console.log(constants_js_1.APP_PREFIX, 'If tests were imported but still not matched, assign test IDs to your tests.');
|
|
398
417
|
console.log(constants_js_1.APP_PREFIX, 'You can do that automatically via command line tools:');
|
|
399
418
|
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold('npx check-tests ... --update-ids'), 'See: https://bit.ly/js-update-ids');
|
|
400
419
|
console.log(constants_js_1.APP_PREFIX, 'or for Cucumber:');
|
|
401
|
-
// eslint-disable-next-line max-len
|
|
402
420
|
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold('npx check-cucumber ... --update-ids'), 'See: https://bit.ly/bdd-update-ids');
|
|
403
421
|
}
|
|
404
422
|
}
|
|
@@ -420,24 +438,16 @@ function printCreateIssue(err) {
|
|
|
420
438
|
process.on('exit', () => {
|
|
421
439
|
console.log();
|
|
422
440
|
console.log(constants_js_1.APP_PREFIX, 'There was an error reporting to Testomat.io:');
|
|
423
|
-
console.log(constants_js_1.APP_PREFIX, 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new');
|
|
441
|
+
console.log(constants_js_1.APP_PREFIX, 'If you think this is a bug please create an issue: https://github.com/testomatio/reporter/issues/new');
|
|
424
442
|
console.log(constants_js_1.APP_PREFIX, 'Provide this information:');
|
|
425
443
|
console.log('Error:', err.message || err.code);
|
|
426
444
|
if (!err.config)
|
|
427
445
|
return;
|
|
428
446
|
const time = new Date().toUTCString();
|
|
429
|
-
const {
|
|
447
|
+
const { body, url, baseURL, method } = err?.config || {};
|
|
430
448
|
console.log('```js');
|
|
431
|
-
console.log({
|
|
449
|
+
console.log({ body: body?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
432
450
|
console.log('```');
|
|
433
451
|
});
|
|
434
452
|
}
|
|
435
|
-
const axiosAddTestrunRequestConfig = {
|
|
436
|
-
maxContentLength: Infinity,
|
|
437
|
-
maxBodyLength: Infinity,
|
|
438
|
-
headers: {
|
|
439
|
-
// Overwrite Axios's automatically set Content-Type
|
|
440
|
-
'Content-Type': 'application/json',
|
|
441
|
-
},
|
|
442
|
-
};
|
|
443
453
|
module.exports = TestomatioPipe;
|
package/lib/replay.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class Replay {
|
|
2
|
+
constructor(options?: {});
|
|
3
|
+
apiKey: any;
|
|
4
|
+
dryRun: any;
|
|
5
|
+
onProgress: any;
|
|
6
|
+
onLog: any;
|
|
7
|
+
onError: any;
|
|
8
|
+
/**
|
|
9
|
+
* Get the default debug file path
|
|
10
|
+
* @returns {string} Path to the latest debug file
|
|
11
|
+
*/
|
|
12
|
+
getDefaultDebugFile(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Parse a debug file and extract test data
|
|
15
|
+
* @param {string} debugFile - Path to the debug file
|
|
16
|
+
* @returns {Object} Parsed debug data
|
|
17
|
+
*/
|
|
18
|
+
parseDebugFile(debugFile: string): any;
|
|
19
|
+
/**
|
|
20
|
+
* Restore environment variables from debug data
|
|
21
|
+
* @param {Object} envVars - Environment variables to restore
|
|
22
|
+
*/
|
|
23
|
+
restoreEnvironmentVariables(envVars: any): void;
|
|
24
|
+
/**
|
|
25
|
+
* Replay test data to Testomat.io
|
|
26
|
+
* @param {string} debugFile - Path to debug file (optional, uses default if not provided)
|
|
27
|
+
* @returns {Promise<Object>} Replay results
|
|
28
|
+
*/
|
|
29
|
+
replay(debugFile: string): Promise<any>;
|
|
30
|
+
}
|
|
31
|
+
export default Replay;
|