@testomatio/reporter 2.3.1-beta.1-dependency → 2.3.2-beta.3-xml-import
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/data-storage.d.ts +1 -1
- package/lib/junit-adapter/csharp.d.ts +0 -1
- package/lib/junit-adapter/csharp.js +36 -7
- package/lib/pipe/debug.js +1 -1
- package/lib/pipe/testomatio.js +14 -18
- 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/links.d.ts +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/utils/utils.js +33 -4
- package/lib/xmlReader.d.ts +8 -1
- package/lib/xmlReader.js +317 -13
- package/package.json +28 -37
- package/src/junit-adapter/csharp.js +40 -6
- package/src/pipe/debug.js +3 -2
- package/src/pipe/testomatio.js +73 -74
- package/src/utils/utils.js +34 -4
- package/src/xmlReader.js +367 -13
package/lib/data-storage.d.ts
CHANGED
|
@@ -7,24 +7,53 @@ const path_1 = __importDefault(require("path"));
|
|
|
7
7
|
const adapter_js_1 = __importDefault(require("./adapter.js"));
|
|
8
8
|
class CSharpAdapter extends adapter_js_1.default {
|
|
9
9
|
formatTest(t) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (example)
|
|
13
|
-
|
|
10
|
+
// Don't override example if it already exists from NUnit XML processing
|
|
11
|
+
// The xmlReader.js already extracts parameters correctly from <arguments>
|
|
12
|
+
if (!t.example) {
|
|
13
|
+
const title = t.title.replace(/\(.*?\)/, '').trim();
|
|
14
|
+
const exampleMatch = t.title.match(/\((.*?)\)/);
|
|
15
|
+
if (exampleMatch) {
|
|
16
|
+
// Keep as array for consistency with NUnit XML processing
|
|
17
|
+
t.example = exampleMatch[1].split(',').map(param => param.trim());
|
|
18
|
+
}
|
|
19
|
+
t.title = title.trim();
|
|
20
|
+
}
|
|
14
21
|
const suite = t.suite_title.split('.');
|
|
15
22
|
t.suite_title = suite.pop();
|
|
16
23
|
t.file = namespaceToFileName(t.file);
|
|
17
|
-
t.title = title.trim();
|
|
18
24
|
return t;
|
|
19
25
|
}
|
|
20
26
|
getFilePath(t) {
|
|
21
|
-
|
|
27
|
+
if (!t.file)
|
|
28
|
+
return null;
|
|
29
|
+
// Normalize path separators for cross-platform compatibility
|
|
30
|
+
let filePath = t.file.replace(/\\/g, '/');
|
|
31
|
+
// If file already has .cs extension, use it directly
|
|
32
|
+
if (filePath.endsWith('.cs')) {
|
|
33
|
+
// Make relative path if it's absolute
|
|
34
|
+
if (path_1.default.isAbsolute(filePath)) {
|
|
35
|
+
// Try to find project-relative path
|
|
36
|
+
const cwd = process.cwd().replace(/\\/g, '/');
|
|
37
|
+
if (filePath.startsWith(cwd)) {
|
|
38
|
+
filePath = path_1.default.relative(cwd, filePath).replace(/\\/g, '/');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return filePath;
|
|
42
|
+
}
|
|
43
|
+
// Convert namespace path to file path
|
|
44
|
+
const fileName = namespaceToFileName(filePath);
|
|
22
45
|
return fileName;
|
|
23
46
|
}
|
|
24
47
|
}
|
|
25
48
|
module.exports = CSharpAdapter;
|
|
26
49
|
function namespaceToFileName(fileName) {
|
|
50
|
+
if (!fileName)
|
|
51
|
+
return '';
|
|
52
|
+
// If already a .cs file path, clean it up
|
|
53
|
+
if (fileName.endsWith('.cs')) {
|
|
54
|
+
return fileName.replace(/\\/g, '/');
|
|
55
|
+
}
|
|
27
56
|
const fileParts = fileName.split('.');
|
|
28
57
|
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
29
|
-
return `${fileParts.join(
|
|
58
|
+
return `${fileParts.join('/')}.cs`;
|
|
30
59
|
}
|
package/lib/pipe/debug.js
CHANGED
|
@@ -18,7 +18,7 @@ class DebugPipe {
|
|
|
18
18
|
this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
|
|
19
19
|
if (this.isEnabled) {
|
|
20
20
|
this.batch = {
|
|
21
|
-
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD,
|
|
21
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
22
22
|
intervalFunction: null,
|
|
23
23
|
intervalTime: 5000,
|
|
24
24
|
tests: [],
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -23,7 +23,7 @@ if (process.env.TESTOMATIO_RUN)
|
|
|
23
23
|
class TestomatioPipe {
|
|
24
24
|
constructor(params, store) {
|
|
25
25
|
this.batch = {
|
|
26
|
-
isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD,
|
|
26
|
+
isEnabled: params?.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
27
27
|
intervalFunction: null, // will be created in createRun by setInterval function
|
|
28
28
|
intervalTime: 5000, // how often tests are sent
|
|
29
29
|
tests: [], // array of tests in batch
|
|
@@ -60,7 +60,7 @@ class TestomatioPipe {
|
|
|
60
60
|
retry: constants_js_1.REPORTER_REQUEST_RETRIES.retriesPerRequest,
|
|
61
61
|
retryDelay: constants_js_1.REPORTER_REQUEST_RETRIES.retryTimeout,
|
|
62
62
|
httpMethodsToRetry: ['GET', 'PUT', 'HEAD', 'OPTIONS', 'DELETE', 'POST'],
|
|
63
|
-
shouldRetry: error => {
|
|
63
|
+
shouldRetry: (error) => {
|
|
64
64
|
if (!error.response)
|
|
65
65
|
return false;
|
|
66
66
|
switch (error.response?.status) {
|
|
@@ -73,8 +73,8 @@ class TestomatioPipe {
|
|
|
73
73
|
break;
|
|
74
74
|
}
|
|
75
75
|
return error.response?.status >= 401; // Retry on 401+ and 5xx
|
|
76
|
-
}
|
|
77
|
-
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
78
|
});
|
|
79
79
|
this.isEnabled = true;
|
|
80
80
|
// do not finish this run (for parallel testing)
|
|
@@ -171,7 +171,7 @@ class TestomatioPipe {
|
|
|
171
171
|
method: 'PUT',
|
|
172
172
|
url: `/api/reporter/${this.runId}`,
|
|
173
173
|
data: runParams,
|
|
174
|
-
responseType: 'json'
|
|
174
|
+
responseType: 'json'
|
|
175
175
|
});
|
|
176
176
|
if (resp.data.artifacts)
|
|
177
177
|
(0, pipe_utils_js_1.setS3Credentials)(resp.data.artifacts);
|
|
@@ -184,7 +184,7 @@ class TestomatioPipe {
|
|
|
184
184
|
url: '/api/reporter',
|
|
185
185
|
data: runParams,
|
|
186
186
|
maxContentLength: Infinity,
|
|
187
|
-
responseType: 'json'
|
|
187
|
+
responseType: 'json'
|
|
188
188
|
});
|
|
189
189
|
this.runId = resp.data.uid;
|
|
190
190
|
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
@@ -241,17 +241,15 @@ class TestomatioPipe {
|
|
|
241
241
|
}
|
|
242
242
|
const json = json_cycle_1.default.stringify(data);
|
|
243
243
|
debug('Adding test', json);
|
|
244
|
-
return this.client
|
|
245
|
-
.request({
|
|
244
|
+
return this.client.request({
|
|
246
245
|
method: 'POST',
|
|
247
246
|
url: `/api/reporter/${this.runId}/testrun`,
|
|
248
247
|
data: json,
|
|
249
248
|
headers: {
|
|
250
249
|
'Content-Type': 'application/json',
|
|
251
250
|
},
|
|
252
|
-
maxContentLength: Infinity
|
|
253
|
-
})
|
|
254
|
-
.catch(err => {
|
|
251
|
+
maxContentLength: Infinity
|
|
252
|
+
}).catch(err => {
|
|
255
253
|
this.requestFailures++;
|
|
256
254
|
this.notReportedTestsCount++;
|
|
257
255
|
if (err.response) {
|
|
@@ -296,21 +294,19 @@ class TestomatioPipe {
|
|
|
296
294
|
// get tests from batch and clear batch
|
|
297
295
|
const testsToSend = this.batch.tests.splice(0);
|
|
298
296
|
debug('📨 Batch upload', testsToSend.length, 'tests');
|
|
299
|
-
return this.client
|
|
300
|
-
.request({
|
|
297
|
+
return this.client.request({
|
|
301
298
|
method: 'POST',
|
|
302
299
|
url: `/api/reporter/${this.runId}/testrun`,
|
|
303
300
|
data: {
|
|
304
301
|
api_key: this.apiKey,
|
|
305
302
|
tests: testsToSend,
|
|
306
|
-
batch_index: this.batch.batchIndex
|
|
303
|
+
batch_index: this.batch.batchIndex
|
|
307
304
|
},
|
|
308
305
|
headers: {
|
|
309
306
|
'Content-Type': 'application/json',
|
|
310
307
|
},
|
|
311
|
-
maxContentLength: Infinity
|
|
312
|
-
})
|
|
313
|
-
.catch(err => {
|
|
308
|
+
maxContentLength: Infinity
|
|
309
|
+
}).catch(err => {
|
|
314
310
|
this.requestFailures++;
|
|
315
311
|
this.notReportedTestsCount += testsToSend.length;
|
|
316
312
|
if (err.response) {
|
|
@@ -398,7 +394,7 @@ class TestomatioPipe {
|
|
|
398
394
|
status_event,
|
|
399
395
|
detach: params.detach,
|
|
400
396
|
tests: params.tests,
|
|
401
|
-
}
|
|
397
|
+
}
|
|
402
398
|
});
|
|
403
399
|
if (this.runUrl) {
|
|
404
400
|
console.log(constants_js_1.APP_PREFIX, '📊 Report Saved. Report URL:', picocolors_1.default.magenta(this.runUrl));
|
package/lib/reporter.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export const artifact: (data: string | {
|
|
|
5
5
|
}, context?: any) => void;
|
|
6
6
|
export const log: (...args: any[]) => void;
|
|
7
7
|
export const logger: {
|
|
8
|
-
#originalUserLogger: {
|
|
8
|
+
"__#13@#originalUserLogger": {
|
|
9
9
|
assert(condition?: boolean, ...data: any[]): void;
|
|
10
10
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
11
11
|
clear(): void;
|
|
@@ -50,13 +50,13 @@ export const logger: {
|
|
|
50
50
|
profile(label?: string): void;
|
|
51
51
|
profileEnd(label?: string): void;
|
|
52
52
|
};
|
|
53
|
-
#userLoggerWithOverridenMethods: any;
|
|
53
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
54
54
|
logLevel: string;
|
|
55
55
|
step(strings: any, ...values: any[]): void;
|
|
56
56
|
getLogs(context: string): string[];
|
|
57
|
-
#stringifyLogs(...args: any[]): string;
|
|
57
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
58
58
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
59
|
-
#logWrapper(argsArray: any, level: any): void;
|
|
59
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
60
60
|
assert(...args: any[]): void;
|
|
61
61
|
debug(...args: any[]): void;
|
|
62
62
|
error(...args: any[]): void;
|
|
@@ -81,7 +81,7 @@ export const linkTest: (...testIds: string[]) => void;
|
|
|
81
81
|
export const linkJira: (...jiraIds: string[]) => void;
|
|
82
82
|
declare namespace _default {
|
|
83
83
|
let testomatioLogger: {
|
|
84
|
-
#originalUserLogger: {
|
|
84
|
+
"__#13@#originalUserLogger": {
|
|
85
85
|
assert(condition?: boolean, ...data: any[]): void;
|
|
86
86
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
87
87
|
clear(): void;
|
|
@@ -126,13 +126,13 @@ declare namespace _default {
|
|
|
126
126
|
profile(label?: string): void;
|
|
127
127
|
profileEnd(label?: string): void;
|
|
128
128
|
};
|
|
129
|
-
#userLoggerWithOverridenMethods: any;
|
|
129
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
130
130
|
logLevel: string;
|
|
131
131
|
step(strings: any, ...values: any[]): void;
|
|
132
132
|
getLogs(context: string): string[];
|
|
133
|
-
#stringifyLogs(...args: any[]): string;
|
|
133
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
134
134
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
135
|
-
#logWrapper(argsArray: any, level: any): void;
|
|
135
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
136
136
|
assert(...args: any[]): void;
|
|
137
137
|
debug(...args: any[]): void;
|
|
138
138
|
error(...args: any[]): void;
|
|
@@ -155,7 +155,7 @@ declare namespace _default {
|
|
|
155
155
|
}, context?: any) => void;
|
|
156
156
|
let log: (...args: any[]) => void;
|
|
157
157
|
let logger: {
|
|
158
|
-
#originalUserLogger: {
|
|
158
|
+
"__#13@#originalUserLogger": {
|
|
159
159
|
assert(condition?: boolean, ...data: any[]): void;
|
|
160
160
|
assert(value: any, message?: string, ...optionalParams: any[]): void;
|
|
161
161
|
clear(): void;
|
|
@@ -200,13 +200,13 @@ declare namespace _default {
|
|
|
200
200
|
profile(label?: string): void;
|
|
201
201
|
profileEnd(label?: string): void;
|
|
202
202
|
};
|
|
203
|
-
#userLoggerWithOverridenMethods: any;
|
|
203
|
+
"__#13@#userLoggerWithOverridenMethods": any;
|
|
204
204
|
logLevel: string;
|
|
205
205
|
step(strings: any, ...values: any[]): void;
|
|
206
206
|
getLogs(context: string): string[];
|
|
207
|
-
#stringifyLogs(...args: any[]): string;
|
|
207
|
+
"__#13@#stringifyLogs"(...args: any[]): string;
|
|
208
208
|
_templateLiteralLog(strings: any, ...args: any[]): void;
|
|
209
|
-
#logWrapper(argsArray: any, level: any): void;
|
|
209
|
+
"__#13@#logWrapper"(argsArray: any, level: any): void;
|
|
210
210
|
assert(...args: any[]): void;
|
|
211
211
|
debug(...args: any[]): void;
|
|
212
212
|
error(...args: any[]): void;
|
package/lib/services/links.d.ts
CHANGED
package/lib/services/logger.d.ts
CHANGED
package/lib/utils/utils.js
CHANGED
|
@@ -172,6 +172,8 @@ exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
|
|
|
172
172
|
exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
173
173
|
exports.SUITE_ID_REGEX = /@S([\w\d]{8})/;
|
|
174
174
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
175
|
+
if (!code)
|
|
176
|
+
return null;
|
|
175
177
|
const comments = code
|
|
176
178
|
.split('\n')
|
|
177
179
|
.map(l => l.trim())
|
|
@@ -214,10 +216,29 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
214
216
|
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
215
217
|
}
|
|
216
218
|
else if (opts.lang === 'csharp') {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
if (lineIndex === -1)
|
|
219
|
+
// Enhanced C# method detection for NUnit tests
|
|
220
|
+
lineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
|
221
|
+
if (lineIndex === -1) {
|
|
222
|
+
lineIndex = lines.findIndex(l => l.includes(`public async Task ${title}(`));
|
|
223
|
+
}
|
|
224
|
+
if (lineIndex === -1) {
|
|
220
225
|
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
226
|
+
}
|
|
227
|
+
// Look for TestCase or Test attributes above the method
|
|
228
|
+
if (lineIndex === -1) {
|
|
229
|
+
const testAttributeIndex = lines.findIndex((l, index) => {
|
|
230
|
+
if (l.includes('[TestCase') || l.includes('[Test')) {
|
|
231
|
+
// Check next few lines for the method
|
|
232
|
+
const nextLines = lines.slice(index, Math.min(lines.length, index + 5));
|
|
233
|
+
const hasMethod = nextLines.some(nextLine => nextLine.includes(`${title}(`));
|
|
234
|
+
return hasMethod;
|
|
235
|
+
}
|
|
236
|
+
return false;
|
|
237
|
+
});
|
|
238
|
+
if (testAttributeIndex !== -1) {
|
|
239
|
+
lineIndex = testAttributeIndex;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
221
242
|
}
|
|
222
243
|
else {
|
|
223
244
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
@@ -226,7 +247,7 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
226
247
|
if (opts.prepend) {
|
|
227
248
|
lineIndex -= opts.prepend;
|
|
228
249
|
}
|
|
229
|
-
if (lineIndex) {
|
|
250
|
+
if (lineIndex !== -1 && lineIndex !== undefined) {
|
|
230
251
|
const result = [];
|
|
231
252
|
for (let i = lineIndex; i < lineIndex + limit; i++) {
|
|
232
253
|
if (lines[i] === undefined)
|
|
@@ -269,6 +290,14 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
269
290
|
break;
|
|
270
291
|
if (opts.lang === 'java' && lines[i].includes(' class '))
|
|
271
292
|
break;
|
|
293
|
+
if (opts.lang === 'csharp' && lines[i].trim().match(/^\[Test/))
|
|
294
|
+
break;
|
|
295
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public void '))
|
|
296
|
+
break;
|
|
297
|
+
if (opts.lang === 'csharp' && lines[i].includes(' public async Task '))
|
|
298
|
+
break;
|
|
299
|
+
if (opts.lang === 'csharp' && lines[i].includes(' class ') && lines[i].includes('public'))
|
|
300
|
+
break;
|
|
272
301
|
}
|
|
273
302
|
result.push(lines[i]);
|
|
274
303
|
}
|
package/lib/xmlReader.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ declare class XmlReader {
|
|
|
46
46
|
passed_count: number;
|
|
47
47
|
skipped_count: number;
|
|
48
48
|
status: string;
|
|
49
|
-
tests: any
|
|
49
|
+
tests: any;
|
|
50
50
|
tests_count: number;
|
|
51
51
|
};
|
|
52
52
|
processNUnit(jsonSuite: any): {
|
|
@@ -77,6 +77,13 @@ declare class XmlReader {
|
|
|
77
77
|
skipped_count: number;
|
|
78
78
|
tests: any[];
|
|
79
79
|
};
|
|
80
|
+
deduplicateTestsByFQN(tests: any): any[];
|
|
81
|
+
generateFQN(test: any): string;
|
|
82
|
+
generateNormalizedFQN(test: any): string;
|
|
83
|
+
extractAssemblyName(test: any): any;
|
|
84
|
+
extractNamespace(test: any): any;
|
|
85
|
+
extractClassName(test: any): any;
|
|
86
|
+
extractCsFileFromPath(test: any): any;
|
|
80
87
|
calculateStats(): {};
|
|
81
88
|
fetchSourceCode(): void;
|
|
82
89
|
formatTests(): void;
|