@testomatio/reporter 1.1.1-beta-codecept-logger → 1.1.1-file-upload.1
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 +11 -17
- package/lib/adapter/codecept.js +26 -14
- package/lib/adapter/cucumber/current.js +15 -8
- package/lib/adapter/cucumber/legacy.js +2 -1
- package/lib/adapter/cypress-plugin/index.js +46 -26
- package/lib/adapter/jest.js +46 -8
- package/lib/adapter/mocha.js +45 -11
- package/lib/adapter/playwright.js +42 -16
- package/lib/bin/startTest.js +2 -1
- package/lib/client.js +30 -16
- package/lib/config.js +5 -0
- package/lib/{storages/data-storage.js → data-storage.js} +76 -43
- package/lib/fileUploader.js +9 -7
- package/lib/junit-adapter/index.js +3 -2
- package/lib/pipe/testomatio.js +4 -2
- package/lib/reporter-functions.js +10 -18
- package/lib/reporter.js +8 -13
- package/lib/{storages/artifact-storage.js → services/artifacts.js} +9 -31
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +10 -25
- package/lib/{storages → services}/logger.js +13 -30
- package/lib/utils/utils.js +25 -2
- package/lib/xmlReader.js +106 -104
- package/package.json +2 -2
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
const debug = require('debug')('@testomatio/reporter:key-value
|
|
2
|
-
const
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:services-key-value');
|
|
2
|
+
const { dataStorage } = require('../data-storage');
|
|
3
3
|
|
|
4
4
|
class KeyValueStorage {
|
|
5
5
|
static #instance;
|
|
6
6
|
|
|
7
|
-
#context;
|
|
8
|
-
|
|
9
|
-
constructor() {
|
|
10
|
-
this.dataStorage = new DataStorage('keyvalue');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
/**
|
|
14
|
-
*
|
|
8
|
+
*
|
|
15
9
|
* @returns {KeyValueStorage}
|
|
16
10
|
*/
|
|
17
11
|
static getInstance() {
|
|
@@ -21,20 +15,14 @@ class KeyValueStorage {
|
|
|
21
15
|
return this.#instance;
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} context - suite title + test title
|
|
26
|
-
*/
|
|
27
|
-
setContext(context) {
|
|
28
|
-
this.#context = context;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
18
|
/**
|
|
32
19
|
* Stores key-value pair and passes it to reporter
|
|
33
20
|
* @param {{key: string}} keyValue - key-value pair(s) as object
|
|
21
|
+
* @param {*} context - full test title
|
|
34
22
|
*/
|
|
35
|
-
put(keyValue) {
|
|
23
|
+
put(keyValue, context = null) {
|
|
36
24
|
if (!keyValue) return;
|
|
37
|
-
|
|
25
|
+
dataStorage.putData('keyvalue', keyValue, context);
|
|
38
26
|
}
|
|
39
27
|
|
|
40
28
|
#isKeyValueObject(smth) {
|
|
@@ -44,14 +32,11 @@ class KeyValueStorage {
|
|
|
44
32
|
/**
|
|
45
33
|
* Returns key-values pairs for the test as object
|
|
46
34
|
* @param {*} context testId or test context from test runner
|
|
47
|
-
* @returns {
|
|
35
|
+
* @returns {{[key: string]: string} | {}} key-values pairs as object, e.g. {priority: 'high', browser: 'chrome'}
|
|
48
36
|
*/
|
|
49
37
|
get(context = null) {
|
|
50
|
-
|
|
51
|
-
if (!
|
|
52
|
-
|
|
53
|
-
const keyValuesList = this.dataStorage.getData(context);
|
|
54
|
-
if (!keyValuesList || !keyValuesList?.length) return null;
|
|
38
|
+
const keyValuesList = dataStorage.getData('keyvalue', context);
|
|
39
|
+
if (!keyValuesList || !keyValuesList?.length) return {};
|
|
55
40
|
|
|
56
41
|
const keyValues = {};
|
|
57
42
|
for (const keyValue of keyValuesList) {
|
|
@@ -66,7 +51,7 @@ class KeyValueStorage {
|
|
|
66
51
|
}
|
|
67
52
|
}
|
|
68
53
|
|
|
69
|
-
return
|
|
54
|
+
return keyValues;
|
|
70
55
|
}
|
|
71
56
|
}
|
|
72
57
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
|
-
const debug = require('debug')('@testomatio/reporter:logger');
|
|
3
|
-
const
|
|
2
|
+
const debug = require('debug')('@testomatio/reporter:services-logger');
|
|
3
|
+
const { dataStorage } = require('../data-storage');
|
|
4
4
|
|
|
5
5
|
const LOG_METHODS = ['assert', 'debug', 'error', 'info', 'log', 'trace', 'warn'];
|
|
6
6
|
const LEVELS = {
|
|
@@ -26,9 +26,7 @@ class Logger {
|
|
|
26
26
|
// set default logger to be used in log, warn, error, etc methods
|
|
27
27
|
#originalUserLogger = { ...console };
|
|
28
28
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
#context = null;
|
|
29
|
+
#isConsoleIntercepted = false;
|
|
32
30
|
|
|
33
31
|
static #instance;
|
|
34
32
|
|
|
@@ -46,27 +44,10 @@ class Logger {
|
|
|
46
44
|
logLevel = process?.env?.LOG_LEVEL?.toUpperCase() || 'ALL';
|
|
47
45
|
|
|
48
46
|
constructor() {
|
|
49
|
-
this.#dataStorage = new DataStorage('log');
|
|
50
|
-
|
|
51
47
|
// intercept console by default
|
|
52
48
|
this.intercept(console);
|
|
53
49
|
}
|
|
54
50
|
|
|
55
|
-
/**
|
|
56
|
-
* @param { 'file' | 'global' } storageType
|
|
57
|
-
*/
|
|
58
|
-
setStorageType(storageType) {
|
|
59
|
-
this.isFileStorage = storageType === 'file';
|
|
60
|
-
this.#dataStorage.isFileStorage = this.isFileStorage;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @param {string} context - suite title + test title
|
|
65
|
-
*/
|
|
66
|
-
setContext(context) {
|
|
67
|
-
this.#context = context;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
51
|
/**
|
|
71
52
|
* Allows you to define a step inside a test. Step name is attached to the report and
|
|
72
53
|
* helps to understand the test flow.
|
|
@@ -82,7 +63,7 @@ class Logger {
|
|
|
82
63
|
}
|
|
83
64
|
}
|
|
84
65
|
logs = chalk.blue(`> ${logs}`);
|
|
85
|
-
|
|
66
|
+
dataStorage.putData('log', logs);
|
|
86
67
|
}
|
|
87
68
|
|
|
88
69
|
/**
|
|
@@ -91,7 +72,8 @@ class Logger {
|
|
|
91
72
|
* @returns {string[]}
|
|
92
73
|
*/
|
|
93
74
|
getLogs(context) {
|
|
94
|
-
const logs =
|
|
75
|
+
const logs = dataStorage.getData('log', context);
|
|
76
|
+
if (!logs) return [];
|
|
95
77
|
return logs;
|
|
96
78
|
}
|
|
97
79
|
|
|
@@ -151,7 +133,7 @@ class Logger {
|
|
|
151
133
|
logs = this.#stringifyLogs(strings, ...args);
|
|
152
134
|
}
|
|
153
135
|
this.#originalUserLogger.log(logs);
|
|
154
|
-
|
|
136
|
+
dataStorage.putData('log', logs);
|
|
155
137
|
}
|
|
156
138
|
|
|
157
139
|
/**
|
|
@@ -168,11 +150,12 @@ class Logger {
|
|
|
168
150
|
|
|
169
151
|
const logs = this.#stringifyLogs(...argsArray);
|
|
170
152
|
|
|
171
|
-
// skip logs from testomatio reporter itself
|
|
172
|
-
if (logs.includes('[TESTOMATIO]')) return;
|
|
173
|
-
|
|
174
153
|
const colorizedLogs = chalk[LEVELS[level].color](logs);
|
|
175
|
-
|
|
154
|
+
// do not attach logs from testomatio reporter itself
|
|
155
|
+
if (!logs.includes('[TESTOMATIO]')) {
|
|
156
|
+
dataStorage.putData('log', colorizedLogs);
|
|
157
|
+
}
|
|
158
|
+
|
|
176
159
|
try {
|
|
177
160
|
// level.toLowerCase() represents method name (log, warn, error, etc)
|
|
178
161
|
this.#originalUserLogger[level.toLowerCase()](colorizedLogs);
|
|
@@ -218,7 +201,7 @@ class Logger {
|
|
|
218
201
|
/* prevent multiple console interceptions (cause of infinite loop)
|
|
219
202
|
actual only for "console", because its used as default output and is intercepted by default */
|
|
220
203
|
const isUserLoggerConsole = userLogger.toString?.().toLowerCase() === '[object console]';
|
|
221
|
-
if (isUserLoggerConsole &&
|
|
204
|
+
if (isUserLoggerConsole && this.#isConsoleIntercepted) {
|
|
222
205
|
debug(`Try to intercept console, but it is already intercepted`);
|
|
223
206
|
return;
|
|
224
207
|
}
|
package/lib/utils/utils.js
CHANGED
|
@@ -141,7 +141,15 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
141
141
|
// remove special chars from title
|
|
142
142
|
if (!lineIndex && opts.title) {
|
|
143
143
|
const title = opts.title.replace(/[([@].*/g, '');
|
|
144
|
-
|
|
144
|
+
|
|
145
|
+
if (opts.lang === 'java') {
|
|
146
|
+
lineIndex = lines.findIndex(l => l.includes(`test${title}`));
|
|
147
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
148
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
149
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
150
|
+
} else {
|
|
151
|
+
lineIndex = lines.findIndex(l => l.includes(title));
|
|
152
|
+
}
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
if (opts.prepend) {
|
|
@@ -290,6 +298,20 @@ function removeColorCodes(input) {
|
|
|
290
298
|
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
const jestHelpers = {
|
|
302
|
+
getIdOfCurrentlyRunningTest: () => {
|
|
303
|
+
if (!process.env.JEST_WORKER_ID) return null;
|
|
304
|
+
try {
|
|
305
|
+
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
306
|
+
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
307
|
+
// eslint-disable-next-line no-undef
|
|
308
|
+
return expect?.getState()?.currentTestName;
|
|
309
|
+
} catch (e) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
|
|
293
315
|
module.exports = {
|
|
294
316
|
isSameTest,
|
|
295
317
|
fetchSourceCode,
|
|
@@ -306,5 +328,6 @@ module.exports = {
|
|
|
306
328
|
parseSuite,
|
|
307
329
|
humanize,
|
|
308
330
|
removeColorCodes,
|
|
309
|
-
foundedTestLog
|
|
331
|
+
foundedTestLog,
|
|
332
|
+
jestHelpers,
|
|
310
333
|
};
|
package/lib/xmlReader.js
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
const debug = require('debug')('@testomatio/reporter:xml');
|
|
2
|
-
const path = require(
|
|
2
|
+
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const fs = require(
|
|
5
|
-
const { XMLParser } = require(
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { XMLParser } = require('fast-xml-parser');
|
|
6
6
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const {
|
|
8
|
+
fetchFilesFromStackTrace,
|
|
9
|
+
fetchIdFromOutput,
|
|
10
|
+
fetchSourceCode,
|
|
11
|
+
fetchSourceCodeFromStackTrace,
|
|
12
|
+
fetchIdFromCode,
|
|
13
|
+
humanize,
|
|
14
|
+
} = require('./utils/utils');
|
|
14
15
|
const upload = require('./fileUploader');
|
|
15
16
|
const pipesFactory = require('./pipe');
|
|
16
17
|
const adapterFactory = require('./junit-adapter');
|
|
18
|
+
const { TESTOMATIO } = require('./config');
|
|
17
19
|
|
|
18
|
-
const TESTOMATIO_URL = process.env.TESTOMATIO_URL ||
|
|
19
|
-
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN
|
|
20
|
+
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
21
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
20
22
|
|
|
21
23
|
const options = {
|
|
22
24
|
ignoreDeclaration: true,
|
|
23
25
|
ignoreAttributes: false,
|
|
24
26
|
alwaysCreateTextNode: false,
|
|
25
|
-
attributeNamePrefix:
|
|
27
|
+
attributeNamePrefix: '',
|
|
26
28
|
parseTagValue: true,
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
const reduceOptions = {};
|
|
30
32
|
|
|
31
33
|
class XmlReader {
|
|
32
|
-
|
|
33
34
|
constructor(opts = {}) {
|
|
34
35
|
this.requestParams = {
|
|
35
36
|
apiKey: opts.apiKey || TESTOMATIO,
|
|
@@ -39,18 +40,18 @@ class XmlReader {
|
|
|
39
40
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
40
41
|
};
|
|
41
42
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
42
|
-
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
|
|
43
|
+
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
|
|
43
44
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
44
45
|
|
|
45
46
|
this.opts = opts || {};
|
|
46
|
-
this.store = {}
|
|
47
|
+
this.store = {};
|
|
47
48
|
this.pipes = pipesFactory(opts, this.store);
|
|
48
49
|
|
|
49
50
|
this.parser = new XMLParser(options);
|
|
50
|
-
this.tests = []
|
|
51
|
-
this.stats = {}
|
|
51
|
+
this.tests = [];
|
|
52
|
+
this.stats = {};
|
|
52
53
|
this.stats.language = opts.lang?.toLowerCase();
|
|
53
|
-
this.filesToUpload = {}
|
|
54
|
+
this.filesToUpload = {};
|
|
54
55
|
|
|
55
56
|
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
56
57
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
@@ -81,8 +82,8 @@ class XmlReader {
|
|
|
81
82
|
} else if (jsonResult.assemblies) {
|
|
82
83
|
return this.processXUnit(jsonResult.assemblies);
|
|
83
84
|
} else {
|
|
84
|
-
console.log(jsonResult)
|
|
85
|
-
throw new Error("Format can't be parsed")
|
|
85
|
+
console.log(jsonResult);
|
|
86
|
+
throw new Error("Format can't be parsed");
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
return this.processJUnit(jsonSuite);
|
|
@@ -95,7 +96,7 @@ class XmlReader {
|
|
|
95
96
|
const resultTests = processTestSuite(testsuite);
|
|
96
97
|
|
|
97
98
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
98
|
-
const status =
|
|
99
|
+
const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
|
|
99
100
|
|
|
100
101
|
this.tests = this.tests.concat(resultTests);
|
|
101
102
|
|
|
@@ -134,21 +135,22 @@ class XmlReader {
|
|
|
134
135
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
135
136
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|
|
136
137
|
|
|
137
|
-
const tests =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
138
|
+
const tests =
|
|
139
|
+
defs.map(td => {
|
|
140
|
+
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
141
|
+
let example = td.name.match(/\((.*?)\)/);
|
|
142
|
+
if (example) example = { ...example[1].split(',') };
|
|
143
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
144
|
+
const suite_title = suite.pop();
|
|
145
|
+
return {
|
|
146
|
+
title,
|
|
147
|
+
example,
|
|
148
|
+
file: suite.join('/'),
|
|
149
|
+
description: td.Description,
|
|
150
|
+
suite_title,
|
|
151
|
+
id: td.Execution.id,
|
|
152
|
+
};
|
|
153
|
+
}) || [];
|
|
152
154
|
|
|
153
155
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
154
156
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
@@ -159,10 +161,9 @@ class XmlReader {
|
|
|
159
161
|
run_time: parseFloat(td.duration) * 1000,
|
|
160
162
|
status: td.outcome,
|
|
161
163
|
stack: td.Output.StdOut,
|
|
162
|
-
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
164
|
+
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
163
165
|
}));
|
|
164
166
|
|
|
165
|
-
|
|
166
167
|
results.forEach(r => {
|
|
167
168
|
const test = tests.find(t => t.id === r.id) || {};
|
|
168
169
|
r.suite_title = test.suite_title;
|
|
@@ -222,7 +223,7 @@ class XmlReader {
|
|
|
222
223
|
|
|
223
224
|
if (testCase.failure) {
|
|
224
225
|
message = testCase.failure.message;
|
|
225
|
-
stack = testCase.failure['stack-trace']
|
|
226
|
+
stack = testCase.failure['stack-trace'];
|
|
226
227
|
}
|
|
227
228
|
if (testCase.reason) {
|
|
228
229
|
message = testCase.reason.message;
|
|
@@ -249,7 +250,6 @@ class XmlReader {
|
|
|
249
250
|
suite_title,
|
|
250
251
|
run_time,
|
|
251
252
|
});
|
|
252
|
-
|
|
253
253
|
});
|
|
254
254
|
});
|
|
255
255
|
});
|
|
@@ -282,12 +282,12 @@ class XmlReader {
|
|
|
282
282
|
passed_count: 0,
|
|
283
283
|
failed_count: 0,
|
|
284
284
|
skipped_count: 0,
|
|
285
|
-
}
|
|
285
|
+
};
|
|
286
286
|
this.tests.forEach(t => {
|
|
287
287
|
this.stats.tests_count++;
|
|
288
288
|
if (t.status === 'passed') this.stats.passed_count++;
|
|
289
289
|
if (t.status === 'failed') this.stats.failed_count++;
|
|
290
|
-
})
|
|
290
|
+
});
|
|
291
291
|
if (this.stats.failed_count) this.stats.status = 'failed';
|
|
292
292
|
|
|
293
293
|
return this.stats;
|
|
@@ -296,7 +296,7 @@ class XmlReader {
|
|
|
296
296
|
fetchSourceCode() {
|
|
297
297
|
this.tests.forEach(t => {
|
|
298
298
|
try {
|
|
299
|
-
const file = this.adapter.getFilePath(t)
|
|
299
|
+
const file = this.adapter.getFilePath(t);
|
|
300
300
|
if (!file) return;
|
|
301
301
|
|
|
302
302
|
if (!this.stats.language) {
|
|
@@ -309,16 +309,16 @@ class XmlReader {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
if (!fs.existsSync(file)) {
|
|
312
|
-
debug('Failed to open file with the source code', file)
|
|
312
|
+
debug('Failed to open file with the source code', file);
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
const contents = fs.readFileSync(file).toString();
|
|
316
|
-
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
|
|
316
|
+
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
|
|
317
317
|
if (t.code) debug('Fetched code for test %s', t.title);
|
|
318
|
-
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
|
|
318
|
+
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
|
|
319
319
|
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
320
320
|
} catch (err) {
|
|
321
|
-
debug(err)
|
|
321
|
+
debug(err);
|
|
322
322
|
}
|
|
323
323
|
});
|
|
324
324
|
}
|
|
@@ -326,21 +326,22 @@ class XmlReader {
|
|
|
326
326
|
formatTests() {
|
|
327
327
|
this.tests.forEach(t => {
|
|
328
328
|
if (t.file) {
|
|
329
|
-
t.file = t.file.replace(process.cwd() + path.sep, '')
|
|
329
|
+
t.file = t.file.replace(process.cwd() + path.sep, '');
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
this.adapter.formatTest(t)
|
|
332
|
+
this.adapter.formatTest(t);
|
|
333
333
|
|
|
334
334
|
t.title = humanize(t.title);
|
|
335
335
|
});
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
formatErrors() {
|
|
339
|
-
this.tests
|
|
340
|
-
t
|
|
341
|
-
t
|
|
342
|
-
|
|
343
|
-
|
|
339
|
+
this.tests
|
|
340
|
+
.filter(t => !!t.stack)
|
|
341
|
+
.forEach(t => {
|
|
342
|
+
t.stack = this.formatStack(t);
|
|
343
|
+
t.message = this.adapter.formatMessage(t);
|
|
344
|
+
});
|
|
344
345
|
}
|
|
345
346
|
|
|
346
347
|
formatStack(t) {
|
|
@@ -358,7 +359,7 @@ class XmlReader {
|
|
|
358
359
|
async uploadArtifacts() {
|
|
359
360
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
360
361
|
let files = [];
|
|
361
|
-
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
362
|
+
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
|
|
362
363
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
363
364
|
|
|
364
365
|
if (!files.length) continue;
|
|
@@ -377,7 +378,7 @@ class XmlReader {
|
|
|
377
378
|
group_title: this.requestParams.group_title,
|
|
378
379
|
};
|
|
379
380
|
|
|
380
|
-
debug(
|
|
381
|
+
debug('Run', runParams);
|
|
381
382
|
|
|
382
383
|
return Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
383
384
|
}
|
|
@@ -390,12 +391,10 @@ class XmlReader {
|
|
|
390
391
|
this.formatErrors();
|
|
391
392
|
this.formatTests();
|
|
392
393
|
|
|
393
|
-
debug(
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
tests: this.tests,
|
|
398
|
-
})
|
|
394
|
+
debug('Uploading data', {
|
|
395
|
+
...this.stats,
|
|
396
|
+
tests: this.tests,
|
|
397
|
+
});
|
|
399
398
|
|
|
400
399
|
const dataString = {
|
|
401
400
|
...this.stats,
|
|
@@ -410,59 +409,62 @@ class XmlReader {
|
|
|
410
409
|
|
|
411
410
|
module.exports = XmlReader;
|
|
412
411
|
|
|
413
|
-
|
|
414
412
|
function reduceTestCases(prev, item) {
|
|
415
413
|
let testCases = item.testcase;
|
|
416
414
|
if (!testCases) testCases = item['test-case'];
|
|
417
415
|
if (!Array.isArray(testCases)) {
|
|
418
|
-
testCases = [testCases]
|
|
416
|
+
testCases = [testCases];
|
|
419
417
|
}
|
|
420
418
|
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
421
419
|
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
422
|
-
testCases
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
420
|
+
testCases
|
|
421
|
+
.filter(t => !!t)
|
|
422
|
+
.forEach(testCaseItem => {
|
|
423
|
+
const file = testCaseItem.file || item.filepath || '';
|
|
424
|
+
|
|
425
|
+
let stack = '';
|
|
426
|
+
let message = '';
|
|
427
|
+
if (testCaseItem.error) stack = testCaseItem.error;
|
|
428
|
+
if (testCaseItem.failure) stack = testCaseItem.failure;
|
|
429
|
+
if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
|
|
430
|
+
if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
|
|
431
|
+
if (testCaseItem?.error?.message) message = testCaseItem.error.message;
|
|
432
|
+
|
|
433
|
+
if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
|
|
434
|
+
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
435
|
+
if (!message) message = stack.trim().split('\n')[0];
|
|
436
|
+
|
|
437
|
+
// eslint-disable-next-line
|
|
438
|
+
stack = `${
|
|
439
|
+
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
440
|
+
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
441
|
+
const testId = fetchIdFromOutput(stack);
|
|
442
|
+
|
|
443
|
+
let status = STATUS.PASSED.toString();
|
|
444
|
+
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
445
|
+
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
446
|
+
|
|
447
|
+
prev.push({
|
|
448
|
+
create: true,
|
|
449
|
+
file,
|
|
450
|
+
stack,
|
|
451
|
+
test_id: testId,
|
|
452
|
+
message,
|
|
453
|
+
line: testCaseItem.lineno,
|
|
454
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
455
|
+
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
456
|
+
status,
|
|
457
|
+
title: testCaseItem.name,
|
|
458
|
+
suite_title: reduceOptions.preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname,
|
|
459
|
+
});
|
|
460
|
+
});
|
|
459
461
|
return prev;
|
|
460
462
|
}
|
|
461
463
|
|
|
462
464
|
function processTestSuite(testsuite) {
|
|
463
465
|
if (!testsuite) return [];
|
|
464
|
-
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite)
|
|
465
|
-
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite'])
|
|
466
|
+
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
|
|
467
|
+
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite']);
|
|
466
468
|
|
|
467
469
|
let suites = testsuite;
|
|
468
470
|
if (!Array.isArray(testsuite)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.1.1-
|
|
3
|
+
"version": "1.1.1-file-upload.1",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
|
|
53
53
|
"test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
|
|
54
54
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
|
|
55
|
-
"test:storage": "npx mocha
|
|
55
|
+
"test:storage": "npx mocha tests-storage/artifact-storage.test.js && npx mocha tests-storage/data-storage.test.js && npx mocha tests-storage/logger.test.js && npx mocha tests-storage/reporter-functions.test.js"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@cucumber/cucumber": "^9.3.0",
|