@testomatio/reporter 2.0.0-beta.2-xml → 2.0.0-beta.3-gaxios
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/pipe/testomatio.js +2 -3
- package/package.json +5 -6
- package/src/adapter/nightwatch.js +1 -1
- package/src/adapter/webdriver.js +1 -1
- package/src/bin/cli.js +1 -2
- package/src/bin/reportXml.js +1 -4
- package/src/bin/startTest.js +1 -2
- package/src/bin/uploadArtifacts.js +1 -2
- package/src/junit-adapter/csharp.js +1 -13
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/gitlab.js +27 -8
- package/src/pipe/testomatio.js +92 -93
- package/src/utils/utils.js +1 -14
- package/src/xmlReader.js +11 -47
package/lib/pipe/testomatio.js
CHANGED
|
@@ -57,7 +57,6 @@ class TestomatioPipe {
|
|
|
57
57
|
timeout: constants_js_1.AXIOS_TIMEOUT,
|
|
58
58
|
proxy: proxy ? proxy.toString() : undefined,
|
|
59
59
|
retry: true,
|
|
60
|
-
agent: new (require('https').Agent)({ rejectUnauthorized: false, keepAlive: false }),
|
|
61
60
|
retryConfig: {
|
|
62
61
|
retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
63
62
|
retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
@@ -439,9 +438,9 @@ function printCreateIssue(err) {
|
|
|
439
438
|
if (!err.config)
|
|
440
439
|
return;
|
|
441
440
|
const time = new Date().toUTCString();
|
|
442
|
-
const {
|
|
441
|
+
const { data, url, baseURL, method } = err?.config || {};
|
|
443
442
|
console.log('```js');
|
|
444
|
-
console.log({
|
|
443
|
+
console.log({ data: data?.replace(/"(tstmt_[^"]+)"/g, 'tstmt_*'), url, baseURL, method, time });
|
|
445
444
|
console.log('```');
|
|
446
445
|
});
|
|
447
446
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.3-gaxios",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
"@cucumber/cucumber": "^10.9.0",
|
|
17
17
|
"@octokit/rest": "^21.1.1",
|
|
18
18
|
"aws-sdk": "^2.1072.0",
|
|
19
|
-
"
|
|
20
|
-
"axios-retry": "^3.9.1",
|
|
19
|
+
"gaxios": "7.0.0-rc.4",
|
|
21
20
|
"callsite-record": "^4.1.4",
|
|
22
21
|
"commander": "^12",
|
|
23
22
|
"cross-spawn": "^7.0.3",
|
|
@@ -49,6 +48,7 @@
|
|
|
49
48
|
"testcafe"
|
|
50
49
|
],
|
|
51
50
|
"scripts": {
|
|
51
|
+
"@cucumber/cucumber": "^10.9.0",
|
|
52
52
|
"clear-exportdir": "rm -rf export/",
|
|
53
53
|
"pretty": "npx prettier --check .",
|
|
54
54
|
"pretty:fix": "prettier --write .",
|
|
@@ -67,9 +67,8 @@
|
|
|
67
67
|
"test:adapter:playwright:example": "npx playwright test --config='./tests/adapter/examples/playwright/playwright.config.ts'",
|
|
68
68
|
"test:adapter:vitest:example": "npx vitest --config='./tests/adapter/examples/vitest/vitest.config.ts'",
|
|
69
69
|
"test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && TESTOMATIO_INTERCEPT_CONSOLE_LOGS=true npx mocha tests-storage/logger.test.js && npx mocha tests-storage/logger-2.test.js && npx mocha tests-storage/reporter-functions.test.js",
|
|
70
|
-
"
|
|
71
|
-
"build
|
|
72
|
-
"build:watch:bun": "rm -rf ./cjs && bun build ./src/bin/reportXml.js ./src/bin/startTest.js ./src/bin/uploadArtifacts.js --outdir ./cjs --target node --watch --onSuccess \"build/scripts/post-build.js\""
|
|
70
|
+
"//": "builds code from /src (esm) into /lib (commonjs)",
|
|
71
|
+
"build": "rm -rf ./cjs && tsc --module commonjs && npx tsx build/scripts/edit-js-files.js && npx tsx build/scripts/edit-package-json.js && chmod +x ./build/scripts/copy-tesmplate.sh && ./build/scripts/copy-tesmplate.sh"
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
74
|
"@playwright/test": "^1.49.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import TestomatClient from '../client.js';
|
|
2
2
|
import { config } from '../config.js';
|
|
3
|
-
import { STATUS } from '../constants
|
|
3
|
+
import { STATUS } from '../constants';
|
|
4
4
|
import { getTestomatIdFromTestTitle } from '../utils/utils.js';
|
|
5
5
|
|
|
6
6
|
const apiKey = config.TESTOMATIO;
|
package/src/adapter/webdriver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import WDIOReporter, { RunnerStats } from '@wdio/reporter';
|
|
2
2
|
import TestomatClient from '../client.js';
|
|
3
3
|
import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
|
|
4
4
|
import { services } from '../services/index.js';
|
package/src/bin/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import createDebugMessages from 'debug';
|
|
|
7
7
|
import TestomatClient from '../client.js';
|
|
8
8
|
import XmlReader from '../xmlReader.js';
|
|
9
9
|
import { APP_PREFIX, STATUS } from '../constants.js';
|
|
10
|
-
import {
|
|
10
|
+
import { version } from '../../package.json';
|
|
11
11
|
import { config } from '../config.js';
|
|
12
12
|
import { readLatestRunId } from '../utils/utils.js';
|
|
13
13
|
import pc from 'picocolors';
|
|
@@ -15,7 +15,6 @@ import { filesize as prettyBytes } from 'filesize';
|
|
|
15
15
|
import dotenv from 'dotenv';
|
|
16
16
|
|
|
17
17
|
const debug = createDebugMessages('@testomatio/reporter:xml-cli');
|
|
18
|
-
const version = getPackageVersion();
|
|
19
18
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
20
19
|
const program = new Command();
|
|
21
20
|
|
package/src/bin/reportXml.js
CHANGED
|
@@ -5,11 +5,8 @@ import { glob } from 'glob';
|
|
|
5
5
|
import createDebugMessages from 'debug';
|
|
6
6
|
import { APP_PREFIX } from '../constants.js';
|
|
7
7
|
import XmlReader from '../xmlReader.js';
|
|
8
|
-
import {
|
|
8
|
+
import { version } from '../../package.json';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
|
|
12
|
-
const version = getPackageVersion();
|
|
13
10
|
|
|
14
11
|
const debug = createDebugMessages('@testomatio/reporter:xml-cli');
|
|
15
12
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
|
package/src/bin/startTest.js
CHANGED
|
@@ -4,11 +4,10 @@ import { Command } from 'commander';
|
|
|
4
4
|
import pc from 'picocolors';
|
|
5
5
|
import TestomatClient from '../client.js';
|
|
6
6
|
import { APP_PREFIX, STATUS } from '../constants.js';
|
|
7
|
-
import {
|
|
7
|
+
import { version } from '../../package.json';
|
|
8
8
|
import { config } from '../config.js';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
|
|
11
|
-
const version = getPackageVersion();
|
|
12
11
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
13
12
|
const program = new Command();
|
|
14
13
|
|
|
@@ -5,13 +5,12 @@ import pc from 'picocolors';
|
|
|
5
5
|
import createDebugMessages from 'debug';
|
|
6
6
|
import TestomatClient from '../client.js';
|
|
7
7
|
import { APP_PREFIX } from '../constants.js';
|
|
8
|
-
import {
|
|
8
|
+
import { version } from '../../package.json';
|
|
9
9
|
import { config } from '../config.js';
|
|
10
10
|
import { readLatestRunId } from '../utils/utils.js';
|
|
11
11
|
import dotenv from 'dotenv';
|
|
12
12
|
|
|
13
13
|
const debug = createDebugMessages('@testomatio/reporter:upload-cli');
|
|
14
|
-
const version = getPackageVersion();
|
|
15
14
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
16
15
|
const program = new Command();
|
|
17
16
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
1
|
import Adapter from './adapter.js';
|
|
3
2
|
|
|
4
3
|
class CSharpAdapter extends Adapter {
|
|
@@ -8,21 +7,10 @@ class CSharpAdapter extends Adapter {
|
|
|
8
7
|
if (example) t.example = { ...example[1].split(',') };
|
|
9
8
|
const suite = t.suite_title.split('.');
|
|
10
9
|
t.suite_title = suite.pop();
|
|
11
|
-
t.file =
|
|
10
|
+
t.file = suite.join('/');
|
|
12
11
|
t.title = title.trim();
|
|
13
12
|
return t;
|
|
14
13
|
}
|
|
15
|
-
|
|
16
|
-
getFilePath(t) {
|
|
17
|
-
const fileName = namespaceToFileName(t.file);
|
|
18
|
-
return fileName;
|
|
19
|
-
}
|
|
20
14
|
}
|
|
21
15
|
|
|
22
16
|
export default CSharpAdapter;
|
|
23
|
-
|
|
24
|
-
function namespaceToFileName(fileName) {
|
|
25
|
-
const fileParts = fileName.split('.');
|
|
26
|
-
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
27
|
-
return `${fileParts.join(path.sep)}.cs`;
|
|
28
|
-
}
|
package/src/pipe/bitbucket.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { APP_PREFIX, testomatLogoURL } from '../constants.js';
|
|
2
2
|
import { ansiRegExp, isSameTest } from '../utils/utils.js';
|
|
3
3
|
import { statusEmoji, fullName } from '../utils/pipe_utils.js';
|
|
4
|
-
import
|
|
4
|
+
import { Gaxios } from 'gaxios';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import humanizeDuration from 'humanize-duration';
|
|
7
7
|
import merge from 'lodash.merge';
|
|
@@ -40,6 +40,13 @@ export class BitbucketPipe {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
this.isEnabled = true;
|
|
43
|
+
this.client = new Gaxios({
|
|
44
|
+
baseURL: 'https://api.bitbucket.org/2.0',
|
|
45
|
+
headers: {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
'Authorization': `Bearer ${this.token}`
|
|
48
|
+
}
|
|
49
|
+
});
|
|
43
50
|
|
|
44
51
|
debug('Bitbucket Pipe: Enabled');
|
|
45
52
|
}
|
|
@@ -166,26 +173,21 @@ export class BitbucketPipe {
|
|
|
166
173
|
|
|
167
174
|
// Construct Bitbucket API URL for comments
|
|
168
175
|
// eslint-disable-next-line max-len
|
|
169
|
-
const commentsRequestURL =
|
|
176
|
+
const commentsRequestURL = `/repositories/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pullrequests/${this.ENV.BITBUCKET_PR_ID}/comments`;
|
|
170
177
|
|
|
171
178
|
// Delete previous report
|
|
172
|
-
await deletePreviousReport(
|
|
179
|
+
await deletePreviousReport(this.client, commentsRequestURL, this.hiddenCommentData);
|
|
173
180
|
|
|
174
181
|
// Add current report
|
|
175
182
|
debug(`Adding comment via URL: ${commentsRequestURL}`);
|
|
176
183
|
debug(`Final Bitbucket API call body: ${body}`);
|
|
177
184
|
|
|
178
185
|
try {
|
|
179
|
-
const addCommentResponse = await
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{
|
|
183
|
-
|
|
184
|
-
Authorization: `Bearer ${this.token}`,
|
|
185
|
-
'Content-Type': 'application/json',
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
);
|
|
186
|
+
const addCommentResponse = await this.client.request({
|
|
187
|
+
method: 'POST',
|
|
188
|
+
url: commentsRequestURL,
|
|
189
|
+
data: { content: { raw: body } }
|
|
190
|
+
});
|
|
189
191
|
|
|
190
192
|
const commentID = addCommentResponse.data.id;
|
|
191
193
|
// eslint-disable-next-line max-len
|
|
@@ -210,18 +212,16 @@ export class BitbucketPipe {
|
|
|
210
212
|
updateRun() {}
|
|
211
213
|
}
|
|
212
214
|
|
|
213
|
-
async function deletePreviousReport(
|
|
215
|
+
async function deletePreviousReport(client, commentsRequestURL, hiddenCommentData) {
|
|
214
216
|
if (process.env.BITBUCKET_KEEP_OUTDATED_REPORTS) return;
|
|
215
217
|
|
|
216
218
|
// Get comments
|
|
217
219
|
let comments = [];
|
|
218
220
|
|
|
219
221
|
try {
|
|
220
|
-
const response = await
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
'Content-Type': 'application/json',
|
|
224
|
-
},
|
|
222
|
+
const response = await client.request({
|
|
223
|
+
method: 'GET',
|
|
224
|
+
url: commentsRequestURL
|
|
225
225
|
});
|
|
226
226
|
comments = response.data.values;
|
|
227
227
|
} catch (e) {
|
|
@@ -236,11 +236,9 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
|
|
|
236
236
|
try {
|
|
237
237
|
// Delete previous comment
|
|
238
238
|
const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
|
|
239
|
-
await
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
'Content-Type': 'application/json',
|
|
243
|
-
},
|
|
239
|
+
await client.request({
|
|
240
|
+
method: 'DELETE',
|
|
241
|
+
url: deleteCommentURL
|
|
244
242
|
});
|
|
245
243
|
} catch (e) {
|
|
246
244
|
console.warn(`Can't delete previously added comment with testomat.io report. Ignored.`);
|
package/src/pipe/gitlab.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
|
-
import
|
|
2
|
+
import { Gaxios } from 'gaxios';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import humanizeDuration from 'humanize-duration';
|
|
5
5
|
import merge from 'lodash.merge';
|
|
@@ -45,6 +45,12 @@ class GitLabPipe {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
this.isEnabled = true;
|
|
48
|
+
this.client = new Gaxios({
|
|
49
|
+
baseURL: 'https://gitlab.com/api/v4',
|
|
50
|
+
headers: {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
}
|
|
53
|
+
});
|
|
48
54
|
|
|
49
55
|
debug('GitLab Pipe: Enabled');
|
|
50
56
|
}
|
|
@@ -157,16 +163,21 @@ class GitLabPipe {
|
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
// eslint-disable-next-line max-len
|
|
160
|
-
const commentsRequestURL =
|
|
166
|
+
const commentsRequestURL = `/projects/${this.ENV.CI_PROJECT_ID}/merge_requests/${this.ENV.CI_MERGE_REQUEST_IID}/notes`;
|
|
161
167
|
|
|
162
168
|
// delete previous report
|
|
163
|
-
await deletePreviousReport(
|
|
169
|
+
await deletePreviousReport(this.client, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
164
170
|
|
|
165
171
|
// add current report
|
|
166
172
|
debug(`Adding comment via url: ${commentsRequestURL}`);
|
|
167
173
|
|
|
168
174
|
try {
|
|
169
|
-
const addCommentResponse = await
|
|
175
|
+
const addCommentResponse = await this.client.request({
|
|
176
|
+
method: 'POST',
|
|
177
|
+
url: commentsRequestURL,
|
|
178
|
+
params: { access_token: this.token },
|
|
179
|
+
data: { body }
|
|
180
|
+
});
|
|
170
181
|
|
|
171
182
|
const commentID = addCommentResponse.data.id;
|
|
172
183
|
// eslint-disable-next-line max-len
|
|
@@ -191,14 +202,18 @@ class GitLabPipe {
|
|
|
191
202
|
updateRun() {}
|
|
192
203
|
}
|
|
193
204
|
|
|
194
|
-
async function deletePreviousReport(
|
|
205
|
+
async function deletePreviousReport(client, commentsRequestURL, hiddenCommentData, token) {
|
|
195
206
|
if (process.env.GITLAB_KEEP_OUTDATED_REPORTS) return;
|
|
196
207
|
|
|
197
208
|
// get comments
|
|
198
209
|
let comments = [];
|
|
199
210
|
|
|
200
211
|
try {
|
|
201
|
-
const response = await
|
|
212
|
+
const response = await client.request({
|
|
213
|
+
method: 'GET',
|
|
214
|
+
url: commentsRequestURL,
|
|
215
|
+
params: { access_token: token }
|
|
216
|
+
});
|
|
202
217
|
comments = response.data;
|
|
203
218
|
} catch (e) {
|
|
204
219
|
console.error('Error while attempt to retrieve comments on GitLab Merge Request:\n', e);
|
|
@@ -211,8 +226,12 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
|
|
|
211
226
|
if (comment.body.includes(hiddenCommentData)) {
|
|
212
227
|
try {
|
|
213
228
|
// delete previous comment
|
|
214
|
-
const deleteCommentURL = `${commentsRequestURL}/${comment.id}
|
|
215
|
-
await
|
|
229
|
+
const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
|
|
230
|
+
await client.request({
|
|
231
|
+
method: 'DELETE',
|
|
232
|
+
url: deleteCommentURL,
|
|
233
|
+
params: { access_token: token }
|
|
234
|
+
});
|
|
216
235
|
} catch (e) {
|
|
217
236
|
console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
218
237
|
}
|
package/src/pipe/testomatio.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import createDebugMessages from 'debug';
|
|
2
2
|
import pc from 'picocolors';
|
|
3
|
-
|
|
4
|
-
// Retry interceptor function
|
|
5
|
-
import axiosRetry from 'axios-retry';
|
|
6
|
-
|
|
7
|
-
// Default axios instance
|
|
8
|
-
import axios from 'axios';
|
|
9
|
-
|
|
3
|
+
import { Gaxios } from 'gaxios';
|
|
10
4
|
import JsonCycle from 'json-cycle';
|
|
11
5
|
import { APP_PREFIX, STATUS, AXIOS_TIMEOUT, REPORTER_REQUEST_RETRIES } from '../constants.js';
|
|
12
6
|
import { isValidUrl, foundedTestLog } from '../utils/utils.js';
|
|
@@ -57,43 +51,30 @@ class TestomatioPipe {
|
|
|
57
51
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
58
52
|
this.env = process.env.TESTOMATIO_ENV;
|
|
59
53
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
|
|
55
|
+
// Create a new instance of gaxios with a custom config
|
|
56
|
+
this.client = new Gaxios({
|
|
62
57
|
baseURL: `${this.url.trim()}`,
|
|
63
58
|
timeout: AXIOS_TIMEOUT,
|
|
64
|
-
proxy: proxy
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
proxy: proxy ? proxy.toString() : undefined,
|
|
60
|
+
retry: true,
|
|
61
|
+
retryConfig: {
|
|
62
|
+
retry: REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
63
|
+
retryDelay: REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
64
|
+
shouldRetry: (error) => {
|
|
65
|
+
if (!error.response) return false;
|
|
66
|
+
switch (error.response?.status) {
|
|
67
|
+
case 400: // Bad request (probably wrong API key)
|
|
68
|
+
case 404: // Test not matched
|
|
69
|
+
case 429: // Rate limit exceeded
|
|
70
|
+
case 500: // Internal server error
|
|
71
|
+
return false;
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
69
74
|
}
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
// Pass the axios instance to the retry function
|
|
74
|
-
axiosRetry(this.axios, {
|
|
75
|
-
// do not use retries for unit tests
|
|
76
|
-
retries: REPORTER_REQUEST_RETRIES.retriesPerRequest, // Number of retries
|
|
77
|
-
shouldResetTimeout: true,
|
|
78
|
-
retryCondition: error => {
|
|
79
|
-
if (!error.response) return false;
|
|
80
|
-
switch (error.response?.status) {
|
|
81
|
-
case 400: // Bad request (probably wrong API key)
|
|
82
|
-
case 404: // Test not matched
|
|
83
|
-
case 429: // Rate limit exceeded
|
|
84
|
-
case 500: // Internal server error
|
|
85
|
-
return false;
|
|
86
|
-
default:
|
|
87
|
-
break;
|
|
75
|
+
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
88
76
|
}
|
|
89
|
-
|
|
90
|
-
},
|
|
91
|
-
retryDelay: () => REPORTER_REQUEST_RETRIES.retryTimeout, // sum = 15sec
|
|
92
|
-
onRetry: async (retryCount, error) => {
|
|
93
|
-
this.retriesTimestamps.push(Date.now());
|
|
94
|
-
|
|
95
|
-
debug(`${error.message || `Request failed ${error.status}`}. Retry #${retryCount} ...`);
|
|
96
|
-
},
|
|
77
|
+
}
|
|
97
78
|
});
|
|
98
79
|
|
|
99
80
|
this.isEnabled = true;
|
|
@@ -134,12 +115,15 @@ class TestomatioPipe {
|
|
|
134
115
|
return;
|
|
135
116
|
}
|
|
136
117
|
|
|
137
|
-
const resp = await this.
|
|
138
|
-
|
|
118
|
+
const resp = await this.client.request({
|
|
119
|
+
method: 'GET',
|
|
120
|
+
url: '/api/test_grep',
|
|
121
|
+
params: q
|
|
122
|
+
});
|
|
139
123
|
|
|
140
|
-
if (Array.isArray(data?.tests) && data?.tests?.length > 0) {
|
|
141
|
-
foundedTestLog(APP_PREFIX, data.tests);
|
|
142
|
-
return data.tests;
|
|
124
|
+
if (Array.isArray(resp.data?.tests) && resp.data?.tests?.length > 0) {
|
|
125
|
+
foundedTestLog(APP_PREFIX, resp.data.tests);
|
|
126
|
+
return resp.data.tests;
|
|
143
127
|
}
|
|
144
128
|
|
|
145
129
|
console.log(APP_PREFIX, `⛔ No tests found for your --filter --> ${type}=${id}`);
|
|
@@ -163,7 +147,6 @@ class TestomatioPipe {
|
|
|
163
147
|
|
|
164
148
|
// GitHub Actions Url
|
|
165
149
|
if (!buildUrl && process.env.GITHUB_RUN_ID) {
|
|
166
|
-
// eslint-disable-next-line max-len
|
|
167
150
|
buildUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
|
168
151
|
}
|
|
169
152
|
|
|
@@ -199,16 +182,23 @@ class TestomatioPipe {
|
|
|
199
182
|
if (this.runId) {
|
|
200
183
|
this.store.runId = this.runId;
|
|
201
184
|
debug(`Run with id ${this.runId} already created, updating...`);
|
|
202
|
-
const resp = await this.
|
|
185
|
+
const resp = await this.client.request({
|
|
186
|
+
method: 'PUT',
|
|
187
|
+
url: `/api/reporter/${this.runId}`,
|
|
188
|
+
data: runParams
|
|
189
|
+
});
|
|
203
190
|
if (resp.data.artifacts) setS3Credentials(resp.data.artifacts);
|
|
204
191
|
return;
|
|
205
192
|
}
|
|
206
193
|
|
|
207
194
|
debug('Creating run...');
|
|
208
195
|
try {
|
|
209
|
-
const resp = await this.
|
|
196
|
+
const resp = await this.client.request({
|
|
197
|
+
method: 'POST',
|
|
198
|
+
url: '/api/reporter',
|
|
199
|
+
data: runParams,
|
|
210
200
|
maxContentLength: Infinity,
|
|
211
|
-
|
|
201
|
+
responseType: 'json'
|
|
212
202
|
});
|
|
213
203
|
|
|
214
204
|
this.runId = resp.data.uid;
|
|
@@ -225,6 +215,7 @@ class TestomatioPipe {
|
|
|
225
215
|
debug('Run created', this.runId);
|
|
226
216
|
} catch (err) {
|
|
227
217
|
const errorText = err.response?.data?.message || err.message;
|
|
218
|
+
debug('Error creating run', err);
|
|
228
219
|
console.log(errorText || err);
|
|
229
220
|
if (!this.apiKey) console.error('Testomat.io API key is not set');
|
|
230
221
|
if (!this.apiKey?.startsWith('tstmt')) console.error('Testomat.io API key is invalid');
|
|
@@ -271,7 +262,15 @@ class TestomatioPipe {
|
|
|
271
262
|
|
|
272
263
|
debug('Adding test', json);
|
|
273
264
|
|
|
274
|
-
return this.
|
|
265
|
+
return this.client.request({
|
|
266
|
+
method: 'POST',
|
|
267
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
268
|
+
data: json,
|
|
269
|
+
headers: {
|
|
270
|
+
'Content-Type': 'application/json',
|
|
271
|
+
},
|
|
272
|
+
maxContentLength: Infinity
|
|
273
|
+
}).catch(err => {
|
|
275
274
|
this.requestFailures++;
|
|
276
275
|
this.notReportedTestsCount++;
|
|
277
276
|
if (err.response) {
|
|
@@ -323,38 +322,43 @@ class TestomatioPipe {
|
|
|
323
322
|
const testsToSend = this.batch.tests.splice(0);
|
|
324
323
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
325
324
|
|
|
326
|
-
return this.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
this.hasUnmatchedTests = true;
|
|
345
|
-
}
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
325
|
+
return this.client.request({
|
|
326
|
+
method: 'POST',
|
|
327
|
+
url: `/api/reporter/${this.runId}/testrun`,
|
|
328
|
+
data: {
|
|
329
|
+
api_key: this.apiKey,
|
|
330
|
+
tests: testsToSend,
|
|
331
|
+
batch_index: this.batch.batchIndex
|
|
332
|
+
},
|
|
333
|
+
headers: {
|
|
334
|
+
'Content-Type': 'application/json',
|
|
335
|
+
},
|
|
336
|
+
maxContentLength: Infinity
|
|
337
|
+
}).catch(err => {
|
|
338
|
+
this.requestFailures++;
|
|
339
|
+
this.notReportedTestsCount += testsToSend.length;
|
|
340
|
+
if (err.response) {
|
|
341
|
+
if (err.response.status >= 400) {
|
|
342
|
+
const responseData = err.response.data || { message: '' };
|
|
348
343
|
console.log(
|
|
349
344
|
APP_PREFIX,
|
|
350
|
-
pc.yellow(`Warning: (${err.response
|
|
351
|
-
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
345
|
+
pc.yellow(`Warning: ${responseData.message} (${err.response.status})`),
|
|
352
346
|
);
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
347
|
+
if (err.response?.data?.message?.includes('could not be matched')) {
|
|
348
|
+
this.hasUnmatchedTests = true;
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
356
351
|
}
|
|
357
|
-
|
|
352
|
+
console.log(
|
|
353
|
+
APP_PREFIX,
|
|
354
|
+
pc.yellow(`Warning: (${err.response?.status})`),
|
|
355
|
+
`Report couldn't be processed: ${err?.response?.data?.message}`,
|
|
356
|
+
);
|
|
357
|
+
printCreateIssue(err);
|
|
358
|
+
} else {
|
|
359
|
+
console.log(APP_PREFIX, "Report couldn't be processed", err);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
358
362
|
};
|
|
359
363
|
|
|
360
364
|
/**
|
|
@@ -413,12 +417,16 @@ class TestomatioPipe {
|
|
|
413
417
|
|
|
414
418
|
try {
|
|
415
419
|
if (this.runId && !this.proceed) {
|
|
416
|
-
await this.
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
420
|
+
await this.client.request({
|
|
421
|
+
method: 'PUT',
|
|
422
|
+
url: `/api/reporter/${this.runId}`,
|
|
423
|
+
data: {
|
|
424
|
+
api_key: this.apiKey,
|
|
425
|
+
duration: params.duration,
|
|
426
|
+
status_event,
|
|
427
|
+
detach: params.detach,
|
|
428
|
+
tests: params.tests,
|
|
429
|
+
}
|
|
422
430
|
});
|
|
423
431
|
if (this.runUrl) {
|
|
424
432
|
console.log(APP_PREFIX, '📊 Report Saved. Report URL:', pc.magenta(this.runUrl));
|
|
@@ -485,13 +493,4 @@ function printCreateIssue(err) {
|
|
|
485
493
|
});
|
|
486
494
|
}
|
|
487
495
|
|
|
488
|
-
const axiosAddTestrunRequestConfig = {
|
|
489
|
-
maxContentLength: Infinity,
|
|
490
|
-
maxBodyLength: Infinity,
|
|
491
|
-
headers: {
|
|
492
|
-
// Overwrite Axios's automatically set Content-Type
|
|
493
|
-
'Content-Type': 'application/json',
|
|
494
|
-
},
|
|
495
|
-
};
|
|
496
|
-
|
|
497
496
|
export default TestomatioPipe;
|
package/src/utils/utils.js
CHANGED
|
@@ -5,13 +5,9 @@ import fs from 'fs';
|
|
|
5
5
|
import isValid from 'is-valid-path';
|
|
6
6
|
import createDebugMessages from 'debug';
|
|
7
7
|
import os from 'os';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
8
|
|
|
10
9
|
const debug = createDebugMessages('@testomatio/reporter:util');
|
|
11
10
|
|
|
12
|
-
// Use __dirname directly since we're compiling to CommonJS
|
|
13
|
-
const __dirname = path.resolve();
|
|
14
|
-
|
|
15
11
|
/**
|
|
16
12
|
* @param {String} testTitle - Test title
|
|
17
13
|
*
|
|
@@ -111,7 +107,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
111
107
|
.join('\n');
|
|
112
108
|
};
|
|
113
109
|
|
|
114
|
-
|
|
110
|
+
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
115
111
|
|
|
116
112
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
117
113
|
const comments = code
|
|
@@ -154,9 +150,6 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
154
150
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
155
151
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
156
152
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
157
|
-
} else if (opts.lang === 'csharp') {
|
|
158
|
-
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
159
|
-
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
160
153
|
} else {
|
|
161
154
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
162
155
|
}
|
|
@@ -360,12 +353,6 @@ function formatStep(step, shift = 0) {
|
|
|
360
353
|
return lines;
|
|
361
354
|
}
|
|
362
355
|
|
|
363
|
-
export function getPackageVersion() {
|
|
364
|
-
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
365
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
366
|
-
return packageJson.version;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
356
|
export {
|
|
370
357
|
ansiRegExp,
|
|
371
358
|
isSameTest,
|
package/src/xmlReader.js
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
fetchSourceCodeFromStackTrace,
|
|
14
14
|
fetchIdFromCode,
|
|
15
15
|
humanize,
|
|
16
|
-
TEST_ID_REGEX,
|
|
17
16
|
} from './utils/utils.js';
|
|
18
17
|
import { pipesFactory } from './pipe/index.js';
|
|
19
18
|
import adapterFactory from './junit-adapter/index.js';
|
|
@@ -27,9 +26,8 @@ const debug = createDebugMessages('@testomatio/reporter:xml');
|
|
|
27
26
|
const ridRunId = randomUUID();
|
|
28
27
|
|
|
29
28
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
30
|
-
const { TESTOMATIO_RUNGROUP_TITLE,
|
|
31
|
-
|
|
32
|
-
TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } = process.env;
|
|
29
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } =
|
|
30
|
+
process.env;
|
|
33
31
|
|
|
34
32
|
const options = {
|
|
35
33
|
ignoreDeclaration: true,
|
|
@@ -39,8 +37,6 @@ const options = {
|
|
|
39
37
|
parseTagValue: true,
|
|
40
38
|
};
|
|
41
39
|
|
|
42
|
-
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
43
|
-
|
|
44
40
|
const reduceOptions = {};
|
|
45
41
|
|
|
46
42
|
class XmlReader {
|
|
@@ -95,7 +91,7 @@ class XmlReader {
|
|
|
95
91
|
];
|
|
96
92
|
|
|
97
93
|
for (const regex of cutRegexes) {
|
|
98
|
-
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0,
|
|
94
|
+
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, 5000)}${p3}`);
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
const jsonResult = this.parser.parse(xmlData);
|
|
@@ -345,7 +341,6 @@ class XmlReader {
|
|
|
345
341
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
346
342
|
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
347
343
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
348
|
-
if (file.endsWith('.cs')) this.stats.language = 'csharp';
|
|
349
344
|
}
|
|
350
345
|
|
|
351
346
|
if (!fs.existsSync(file)) {
|
|
@@ -399,14 +394,13 @@ class XmlReader {
|
|
|
399
394
|
async uploadArtifacts() {
|
|
400
395
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
401
396
|
let files = [];
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
files = test.files.map(f => path.isAbsolute(f) ? f : path.join(process.cwd(), f));
|
|
397
|
+
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
|
|
398
|
+
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
405
399
|
|
|
406
400
|
if (!files.length) continue;
|
|
407
401
|
|
|
408
402
|
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
409
|
-
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId
|
|
403
|
+
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
|
|
410
404
|
console.log(APP_PREFIX, `🗄️ Uploaded ${pc.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
411
405
|
}
|
|
412
406
|
}
|
|
@@ -477,7 +471,7 @@ function reduceTestCases(prev, item) {
|
|
|
477
471
|
testCases
|
|
478
472
|
.filter(t => !!t)
|
|
479
473
|
.forEach(testCaseItem => {
|
|
480
|
-
const file = testCaseItem.file || item.filepath ||
|
|
474
|
+
const file = testCaseItem.file || item.filepath || '';
|
|
481
475
|
|
|
482
476
|
let stack = '';
|
|
483
477
|
let message = '';
|
|
@@ -511,38 +505,15 @@ function reduceTestCases(prev, item) {
|
|
|
511
505
|
stack = `${
|
|
512
506
|
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
513
507
|
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (tags?.length && !testId) {
|
|
517
|
-
testId = tags.filter(t => t.startsWith('T')).map(t => `@${t}`).find(t => t.match(TEST_ID_REGEX))?.slice(2);
|
|
518
|
-
}
|
|
508
|
+
const testId = fetchIdFromOutput(stack);
|
|
519
509
|
|
|
520
510
|
let status = STATUS.PASSED.toString();
|
|
521
511
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
522
512
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
523
|
-
if (testCaseItem.result && Object.values(STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
524
|
-
status = testCaseItem.result.toLowerCase();
|
|
525
|
-
}
|
|
526
513
|
|
|
527
514
|
let rid = null;
|
|
528
515
|
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
529
516
|
|
|
530
|
-
// Extract attachments
|
|
531
|
-
let files = [];
|
|
532
|
-
if (testCaseItem.attachments) {
|
|
533
|
-
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
534
|
-
? testCaseItem.attachments.attachment
|
|
535
|
-
: [testCaseItem.attachments.attachment];
|
|
536
|
-
|
|
537
|
-
files = attachments
|
|
538
|
-
.filter(a => a && a.filePath)
|
|
539
|
-
.map(a => a.filePath);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Extract files from stack trace using existing utility
|
|
543
|
-
const stackFiles = fetchFilesFromStackTrace(stack);
|
|
544
|
-
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
545
|
-
|
|
546
517
|
prev.push({
|
|
547
518
|
rid,
|
|
548
519
|
file,
|
|
@@ -557,9 +528,7 @@ function reduceTestCases(prev, item) {
|
|
|
557
528
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
558
529
|
status,
|
|
559
530
|
title,
|
|
560
|
-
root_suite_id: TESTOMATIO_SUITE,
|
|
561
531
|
suite_title: suiteTitle,
|
|
562
|
-
files,
|
|
563
532
|
});
|
|
564
533
|
});
|
|
565
534
|
return prev;
|
|
@@ -586,15 +555,10 @@ function fetchProperties(item) {
|
|
|
586
555
|
|
|
587
556
|
if (!item.properties) return {};
|
|
588
557
|
|
|
589
|
-
|
|
590
|
-
const properties = Array.isArray(item.properties.property)
|
|
591
|
-
? item.properties.property
|
|
592
|
-
: [item.properties.property].filter(Boolean);
|
|
593
|
-
|
|
594
|
-
const prop = properties.find(p => p.name === 'Description');
|
|
558
|
+
const prop = [item.properties?.property].flat().find(p => p.name === 'Description');
|
|
595
559
|
if (prop) title = prop.value;
|
|
596
|
-
|
|
597
|
-
|
|
560
|
+
[item.properties?.property]
|
|
561
|
+
.flat()
|
|
598
562
|
.filter(p => p.name === 'Category')
|
|
599
563
|
.forEach(p => tags.push(p.value));
|
|
600
564
|
return { title, tags };
|