@testomatio/reporter 1.3.5-beta → 1.4.1-beta-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 +4 -4
- package/lib/adapter/codecept.js +11 -9
- package/lib/adapter/cucumber/current.js +4 -4
- package/lib/adapter/cucumber/legacy.js +3 -3
- package/lib/adapter/cypress-plugin/index.js +1 -1
- package/lib/adapter/jasmine.js +1 -1
- package/lib/adapter/jest.js +12 -17
- package/lib/adapter/mocha.js +22 -50
- package/lib/adapter/playwright.js +34 -22
- package/lib/adapter/webdriver.js +1 -1
- package/lib/bin/reportXml.js +1 -0
- package/lib/bin/startTest.js +25 -4
- package/lib/client.js +93 -27
- package/lib/constants.js +4 -6
- package/lib/fileUploader.js +106 -38
- package/lib/junit-adapter/java.js +27 -9
- package/lib/pipe/csv.js +4 -1
- package/lib/pipe/github.js +5 -16
- package/lib/pipe/gitlab.js +5 -16
- package/lib/pipe/testomatio.js +136 -34
- package/lib/reporter-functions.js +43 -0
- package/lib/reporter.js +11 -5
- package/lib/storages/artifact-storage.js +70 -0
- package/lib/storages/data-storage.js +307 -0
- package/lib/storages/key-value-storage.js +58 -0
- package/lib/{logger.js → storages/logger.js} +103 -114
- package/lib/utils/pipe_utils.js +135 -0
- package/lib/{util.js → utils/utils.js} +129 -12
- package/lib/xmlReader.js +132 -44
- package/package.json +9 -7
- package/lib/_ArtifactStorageOld.js +0 -142
- package/lib/artifactStorage.js +0 -25
- package/lib/dataStorage.js +0 -244
package/lib/xmlReader.js
CHANGED
|
@@ -4,12 +4,17 @@ const chalk = require('chalk');
|
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
const { XMLParser } = require("fast-xml-parser");
|
|
6
6
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
7
|
-
const { fetchFilesFromStackTrace,
|
|
7
|
+
const { fetchFilesFromStackTrace,
|
|
8
|
+
fetchIdFromOutput,
|
|
9
|
+
fetchSourceCode,
|
|
10
|
+
fetchSourceCodeFromStackTrace,
|
|
11
|
+
fetchIdFromCode,
|
|
12
|
+
humanize
|
|
13
|
+
} = require('./utils/utils');
|
|
8
14
|
const upload = require('./fileUploader');
|
|
9
15
|
const pipesFactory = require('./pipe');
|
|
10
16
|
const adapterFactory = require('./junit-adapter');
|
|
11
17
|
|
|
12
|
-
|
|
13
18
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || "https://app.testomat.io";
|
|
14
19
|
const TESTOMATIO = process.env.TESTOMATIO; // key?
|
|
15
20
|
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
@@ -25,7 +30,7 @@ const options = {
|
|
|
25
30
|
const reduceOptions = {};
|
|
26
31
|
|
|
27
32
|
class XmlReader {
|
|
28
|
-
|
|
33
|
+
|
|
29
34
|
constructor(opts = {}) {
|
|
30
35
|
this.requestParams = {
|
|
31
36
|
apiKey: opts.apiKey || TESTOMATIO,
|
|
@@ -35,12 +40,12 @@ class XmlReader {
|
|
|
35
40
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
36
41
|
};
|
|
37
42
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
38
|
-
this.adapter = adapterFactory(
|
|
43
|
+
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
|
|
39
44
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
40
45
|
|
|
41
46
|
this.opts = opts || {};
|
|
42
|
-
|
|
43
|
-
this.pipes = pipesFactory(opts, store);
|
|
47
|
+
this.store = {}
|
|
48
|
+
this.pipes = pipesFactory(opts, this.store);
|
|
44
49
|
|
|
45
50
|
this.parser = new XMLParser(options);
|
|
46
51
|
this.tests = []
|
|
@@ -49,27 +54,33 @@ class XmlReader {
|
|
|
49
54
|
this.filesToUpload = {}
|
|
50
55
|
|
|
51
56
|
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
52
|
-
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
57
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
connectAdapter() {
|
|
56
|
-
if (this.opts.javaTests)
|
|
57
|
-
|
|
61
|
+
if (this.opts.javaTests) {
|
|
62
|
+
this.adapter = adapterFactory('java', this.opts);
|
|
63
|
+
return this.adapter;
|
|
64
|
+
}
|
|
65
|
+
this.adapter = adapterFactory(this.stats.language, this.opts);
|
|
66
|
+
return this.adapter;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
|
-
parse(fileName) {
|
|
69
|
+
parse(fileName) {
|
|
61
70
|
const xmlData = fs.readFileSync(path.resolve(fileName));
|
|
62
71
|
const jsonResult = this.parser.parse(xmlData);
|
|
63
72
|
let jsonSuite;
|
|
64
|
-
|
|
73
|
+
|
|
65
74
|
if (jsonResult.testsuites) {
|
|
66
75
|
jsonSuite = jsonResult.testsuites;
|
|
67
76
|
} else if (jsonResult.testsuite) {
|
|
68
77
|
jsonSuite = jsonResult;
|
|
69
78
|
} else if (jsonResult.TestRun) {
|
|
70
|
-
return this.processTRX(jsonResult);
|
|
79
|
+
return this.processTRX(jsonResult);
|
|
71
80
|
} else if (jsonResult['test-run']) {
|
|
72
81
|
return this.processNUnit(jsonResult['test-run']);
|
|
82
|
+
} else if (jsonResult.assemblies) {
|
|
83
|
+
return this.processXUnit(jsonResult.assemblies);
|
|
73
84
|
} else {
|
|
74
85
|
console.log(jsonResult)
|
|
75
86
|
throw new Error("Format can't be parsed")
|
|
@@ -83,9 +94,9 @@ class XmlReader {
|
|
|
83
94
|
|
|
84
95
|
reduceOptions.preferClassname = this.stats.language === 'python';
|
|
85
96
|
const resultTests = processTestSuite(testsuite);
|
|
86
|
-
|
|
97
|
+
|
|
87
98
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
88
|
-
const status = (
|
|
99
|
+
const status = (failures > 0 || errors > 0 || hasFailures) ? 'failed' : 'passed';
|
|
89
100
|
|
|
90
101
|
this.tests = this.tests.concat(resultTests);
|
|
91
102
|
|
|
@@ -118,7 +129,7 @@ class XmlReader {
|
|
|
118
129
|
skipped_count: parseInt(inconclusive + skipped, 10),
|
|
119
130
|
tests: resultTests,
|
|
120
131
|
};
|
|
121
|
-
}
|
|
132
|
+
}
|
|
122
133
|
|
|
123
134
|
processTRX(jsonSuite) {
|
|
124
135
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
@@ -127,10 +138,10 @@ class XmlReader {
|
|
|
127
138
|
const tests = defs.map(td => {
|
|
128
139
|
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
129
140
|
let example = td.name.match(/\((.*?)\)/);
|
|
130
|
-
if (example) example = { ...example[1].split(',')};
|
|
141
|
+
if (example) example = { ...example[1].split(',') };
|
|
131
142
|
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
132
143
|
const suite_title = suite.pop();
|
|
133
|
-
return {
|
|
144
|
+
return {
|
|
134
145
|
title,
|
|
135
146
|
example,
|
|
136
147
|
file: suite.join('/'),
|
|
@@ -143,18 +154,18 @@ class XmlReader {
|
|
|
143
154
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
144
155
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
145
156
|
|
|
146
|
-
const results = result.map(td => ({
|
|
157
|
+
const results = result.map(td => ({
|
|
147
158
|
id: td.executionId,
|
|
148
159
|
// seconds are used in junit reports, but ms are used by testomatio
|
|
149
160
|
run_time: parseFloat(td.duration) * 1000,
|
|
150
|
-
status: td.outcome,
|
|
161
|
+
status: td.outcome,
|
|
151
162
|
stack: td.Output.StdOut,
|
|
152
163
|
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
153
164
|
}));
|
|
154
165
|
|
|
155
|
-
|
|
166
|
+
|
|
156
167
|
results.forEach(r => {
|
|
157
|
-
const test = tests.find(t => t.id === r.id) || {
|
|
168
|
+
const test = tests.find(t => t.id === r.id) || {};
|
|
158
169
|
r.suite_title = test.suite_title;
|
|
159
170
|
r.title = test.title?.trim();
|
|
160
171
|
if (test.code) r.code = test.code;
|
|
@@ -169,7 +180,7 @@ class XmlReader {
|
|
|
169
180
|
});
|
|
170
181
|
|
|
171
182
|
debug(results);
|
|
172
|
-
|
|
183
|
+
|
|
173
184
|
const counters = jsonSuite?.TestRun?.ResultSummary?.Counters || {};
|
|
174
185
|
|
|
175
186
|
const failed_count = parseInt(counters.failed, 10) + parseInt(counters.error, 10);
|
|
@@ -187,7 +198,80 @@ class XmlReader {
|
|
|
187
198
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
188
199
|
failed_count,
|
|
189
200
|
tests: results,
|
|
190
|
-
};
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
processXUnit(assemblies) {
|
|
205
|
+
const tests = [];
|
|
206
|
+
|
|
207
|
+
assemblies = Array.isArray(assemblies.assembly) ? assemblies.assembly : [assemblies.assembly];
|
|
208
|
+
|
|
209
|
+
assemblies.forEach(assembly => {
|
|
210
|
+
const { collection } = assembly;
|
|
211
|
+
|
|
212
|
+
const suites = Array.isArray(collection) ? collection : [collection];
|
|
213
|
+
|
|
214
|
+
suites.forEach(suite => {
|
|
215
|
+
const { test } = suite;
|
|
216
|
+
if (!test) return;
|
|
217
|
+
const cases = Array.isArray(test) ? test : [test];
|
|
218
|
+
cases.forEach(testCase => {
|
|
219
|
+
const { type, time, result } = testCase;
|
|
220
|
+
|
|
221
|
+
let message = '';
|
|
222
|
+
let stack = '';
|
|
223
|
+
|
|
224
|
+
if (testCase.failure) {
|
|
225
|
+
message = testCase.failure.message;
|
|
226
|
+
stack = testCase.failure['stack-trace']
|
|
227
|
+
}
|
|
228
|
+
if (testCase.reason) {
|
|
229
|
+
message = testCase.reason.message;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
let status = STATUS.PASSED;
|
|
233
|
+
if (result === 'Pass') status = STATUS.PASSED;
|
|
234
|
+
if (result === 'Fail') status = STATUS.FAILED;
|
|
235
|
+
if (result === 'Skip') status = STATUS.SKIPPED;
|
|
236
|
+
|
|
237
|
+
const pathParts = type.split('.');
|
|
238
|
+
const suite_title = pathParts[pathParts.length - 1];
|
|
239
|
+
const file = pathParts.slice(0, -1).join('/');
|
|
240
|
+
const title = testCase.method || testCase.name.split('.').pop();
|
|
241
|
+
const run_time = parseFloat(time) * 1000;
|
|
242
|
+
|
|
243
|
+
tests.push({
|
|
244
|
+
create: true,
|
|
245
|
+
stack,
|
|
246
|
+
message,
|
|
247
|
+
file,
|
|
248
|
+
status,
|
|
249
|
+
title,
|
|
250
|
+
suite_title,
|
|
251
|
+
run_time,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const hasFailures = tests.filter(t => t.status === STATUS.FAILED).length > 0;
|
|
259
|
+
const status = hasFailures ? STATUS.FAILED : STATUS.PASSED;
|
|
260
|
+
|
|
261
|
+
this.tests = tests;
|
|
262
|
+
|
|
263
|
+
debug(tests);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
status,
|
|
267
|
+
create_tests: true,
|
|
268
|
+
name: 'xUnit',
|
|
269
|
+
tests_count: tests.length,
|
|
270
|
+
passed_count: tests.filter(t => t.status === STATUS.PASSED).length,
|
|
271
|
+
failed_count: tests.filter(t => t.status === STATUS.FAILED).length,
|
|
272
|
+
skipped_count: tests.filter(t => t.status === STATUS.SKIPPED).length,
|
|
273
|
+
tests,
|
|
274
|
+
};
|
|
191
275
|
}
|
|
192
276
|
|
|
193
277
|
calculateStats() {
|
|
@@ -221,12 +305,19 @@ class XmlReader {
|
|
|
221
305
|
if (file.endsWith('.py')) this.stats.language = 'python';
|
|
222
306
|
if (file.endsWith('.java')) this.stats.language = 'java';
|
|
223
307
|
if (file.endsWith('.rb')) this.stats.language = 'ruby';
|
|
224
|
-
if (file.endsWith('.js')) this.stats.language
|
|
308
|
+
if (file.endsWith('.js')) this.stats.language = 'js';
|
|
225
309
|
if (file.endsWith('.ts')) this.stats.language = 'ts';
|
|
226
310
|
}
|
|
227
311
|
|
|
312
|
+
if (!fs.existsSync(file)) {
|
|
313
|
+
debug('Failed to open file with the source code', file)
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
228
316
|
const contents = fs.readFileSync(file).toString();
|
|
229
317
|
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
|
|
318
|
+
if (t.code) debug('Fetched code for test %s', t.title);
|
|
319
|
+
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
|
|
320
|
+
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
230
321
|
} catch (err) {
|
|
231
322
|
debug(err)
|
|
232
323
|
}
|
|
@@ -241,18 +332,7 @@ class XmlReader {
|
|
|
241
332
|
|
|
242
333
|
this.adapter.formatTest(t)
|
|
243
334
|
|
|
244
|
-
t.title = t.title
|
|
245
|
-
// insert a space before all caps
|
|
246
|
-
.replace(/([A-Z])/g, ' $1')
|
|
247
|
-
// _ chars to spaces
|
|
248
|
-
.replace(/_/g, ' ')
|
|
249
|
-
// uppercase the first character
|
|
250
|
-
.replace(/^(.)|\s(.)/g, $1 => $1.toLowerCase())
|
|
251
|
-
|
|
252
|
-
// remove standard prefixes
|
|
253
|
-
.replace(/^test\s/, '')
|
|
254
|
-
.replace(/^should\s/, '')
|
|
255
|
-
.trim();
|
|
335
|
+
t.title = humanize(t.title);
|
|
256
336
|
});
|
|
257
337
|
}
|
|
258
338
|
|
|
@@ -281,8 +361,12 @@ class XmlReader {
|
|
|
281
361
|
let files = [];
|
|
282
362
|
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
283
363
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
284
|
-
|
|
285
|
-
|
|
364
|
+
|
|
365
|
+
if (!files.length) continue;
|
|
366
|
+
|
|
367
|
+
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
368
|
+
test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, runId)));
|
|
369
|
+
console.log(APP_PREFIX, `🗄️ Uploaded ${chalk.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
286
370
|
}
|
|
287
371
|
}
|
|
288
372
|
|
|
@@ -292,7 +376,7 @@ class XmlReader {
|
|
|
292
376
|
title: this.requestParams.title,
|
|
293
377
|
env: this.requestParams.env,
|
|
294
378
|
group_title: this.requestParams.group_title,
|
|
295
|
-
};
|
|
379
|
+
};
|
|
296
380
|
|
|
297
381
|
debug("Run", runParams);
|
|
298
382
|
|
|
@@ -310,9 +394,9 @@ class XmlReader {
|
|
|
310
394
|
debug(
|
|
311
395
|
'Uploading data',
|
|
312
396
|
{
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
397
|
+
...this.stats,
|
|
398
|
+
tests: this.tests,
|
|
399
|
+
})
|
|
316
400
|
|
|
317
401
|
const dataString = {
|
|
318
402
|
...this.stats,
|
|
@@ -334,6 +418,8 @@ function reduceTestCases(prev, item) {
|
|
|
334
418
|
if (!Array.isArray(testCases)) {
|
|
335
419
|
testCases = [testCases]
|
|
336
420
|
}
|
|
421
|
+
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
422
|
+
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
337
423
|
testCases.filter(t => !!t).forEach(testCaseItem => {
|
|
338
424
|
const file = testCaseItem.file || item.filepath || '';
|
|
339
425
|
|
|
@@ -349,8 +435,9 @@ function reduceTestCases(prev, item) {
|
|
|
349
435
|
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
350
436
|
if (!message) message = stack.trim().split('\n')[0];
|
|
351
437
|
|
|
352
|
-
//
|
|
353
|
-
stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}`.trim()
|
|
438
|
+
// eslint-disable-next-line
|
|
439
|
+
stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim()
|
|
440
|
+
const testId = fetchIdFromOutput(stack);
|
|
354
441
|
|
|
355
442
|
let status = STATUS.PASSED.toString();
|
|
356
443
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
@@ -360,6 +447,7 @@ function reduceTestCases(prev, item) {
|
|
|
360
447
|
create: true,
|
|
361
448
|
file,
|
|
362
449
|
stack,
|
|
450
|
+
test_id: testId,
|
|
363
451
|
message,
|
|
364
452
|
line: testCaseItem.lineno,
|
|
365
453
|
// seconds are used in junit reports, but ms are used by testomatio
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1-beta-1",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -20,15 +20,16 @@
|
|
|
20
20
|
"debug": "^4.3.4",
|
|
21
21
|
"dotenv": "^16.0.1",
|
|
22
22
|
"fast-xml-parser": "^4.0.8",
|
|
23
|
-
"glob": "^
|
|
23
|
+
"glob": "^10.3",
|
|
24
24
|
"has-flag": "^5.0.1",
|
|
25
25
|
"humanize-duration": "^3.27.3",
|
|
26
26
|
"is-valid-path": "^0.1.1",
|
|
27
27
|
"json-cycle": "^1.3.0",
|
|
28
|
-
"lodash": "^4.17.21",
|
|
29
28
|
"lodash.memoize": "^4.1.2",
|
|
30
29
|
"lodash.merge": "^4.6.2",
|
|
31
|
-
"
|
|
30
|
+
"minimatch": "^9.0.3",
|
|
31
|
+
"uuid": "^9.0.0",
|
|
32
|
+
"promise-retry": "^2.0.1"
|
|
32
33
|
},
|
|
33
34
|
"files": [
|
|
34
35
|
"bin",
|
|
@@ -42,21 +43,21 @@
|
|
|
42
43
|
"lint:fix": "eslint lib --fix",
|
|
43
44
|
"test": "mocha tests/**",
|
|
44
45
|
"init": "cd ./tests/adapter/examples/cucumber && npm i",
|
|
45
|
-
"test:unit": "mocha tests",
|
|
46
46
|
"test:adapter": "mocha './tests/adapter/index.test.js'",
|
|
47
47
|
"test:pipes": "mocha './tests/pipes/*_test.js'",
|
|
48
48
|
"test:adapter:jest:example": "jest './tests/adapter/examples/jest/index.test.js' --config='./tests/adapter/examples/jest/jest.config.js'",
|
|
49
49
|
"test:adapter:mocha:example": "mocha './tests/adapter/examples/mocha/index.test.js' --config='./tests/adapter/examples/mocha/mocha.config.js'",
|
|
50
50
|
"test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
|
|
51
51
|
"test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
|
|
52
|
-
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js"
|
|
52
|
+
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
|
|
53
|
+
"test:storage": "npx mocha ./tests-storage/**"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
56
|
"@cucumber/cucumber": "^9.3.0",
|
|
56
57
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
57
58
|
"@wdio/reporter": "^7.16.13",
|
|
58
59
|
"chai": "^4.3.6",
|
|
59
|
-
"codeceptjs": "
|
|
60
|
+
"codeceptjs": "latest",
|
|
60
61
|
"cucumber": "^6.0.7",
|
|
61
62
|
"eslint": "^8.7.0",
|
|
62
63
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"jest": "^27.4.7",
|
|
67
68
|
"mocha": "^9.2.0",
|
|
68
69
|
"mock-http-server": "^1.4.5",
|
|
70
|
+
"pino": "^8.15.0",
|
|
69
71
|
"prettier": "2.5.1",
|
|
70
72
|
"puppeteer": "^13.1.2"
|
|
71
73
|
},
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
const debug = require('debug')('@testomatio/reporter:storage');
|
|
2
|
-
const { join, resolve } = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const uuid = require('uuid');
|
|
6
|
-
const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
|
|
7
|
-
const { specificTestInfo } = require('./util');
|
|
8
|
-
|
|
9
|
-
class ArtifactStorage {
|
|
10
|
-
|
|
11
|
-
constructor(params) {
|
|
12
|
-
this.isFile = false;
|
|
13
|
-
this.isMemory = true;
|
|
14
|
-
this.storage = params?.toFile;
|
|
15
|
-
|
|
16
|
-
if (this.storage) {
|
|
17
|
-
// this is fine to use when you start saving artifacts;
|
|
18
|
-
// but when you need to retrieve them, you will not know if there is "isFile" param,
|
|
19
|
-
// because you need to create a new instance of ArtifactStorage inside client
|
|
20
|
-
// so the solution is make it opposite to isMemory (which will be retrieved from global)
|
|
21
|
-
this.isFile = true;
|
|
22
|
-
this.isMemory = false;
|
|
23
|
-
this.tmpDirFullpath = this.createTestomatTmpDir(ArtifactStorage._tmpPrefix);
|
|
24
|
-
debug('SAVE to tmp folder mode enabled!');
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
|
|
29
|
-
// this assumes to use multiple files for each test. do we really want this?
|
|
30
|
-
const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
|
|
31
|
-
const dirpath = join(os.tmpdir(), tmpDirName);
|
|
32
|
-
// why json?
|
|
33
|
-
const filepath = resolve(dirpath, `${suffix}.json`);
|
|
34
|
-
|
|
35
|
-
return fs.promises.appendFile(filepath, JSON.stringify(artifact));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static async artifact(artifact, context) {
|
|
39
|
-
// TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
|
|
40
|
-
// const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
|
|
41
|
-
|
|
42
|
-
// you save the artifact without specifying its source - test id
|
|
43
|
-
if (Array.isArray(global.testomatioArtifacts)) {
|
|
44
|
-
debug("Saving artifacts to global storage");
|
|
45
|
-
|
|
46
|
-
global.testomatioArtifacts.push(artifact);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (global?.testomatioArtifacts === undefined) {
|
|
50
|
-
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
51
|
-
|
|
52
|
-
const testSuffix = specificTestInfo(context.test);
|
|
53
|
-
|
|
54
|
-
if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
|
|
55
|
-
debug("Saving artifacts to memory tmp folder");
|
|
56
|
-
|
|
57
|
-
return ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async artifactByTestName(test) {
|
|
63
|
-
const list = [];
|
|
64
|
-
|
|
65
|
-
if (this.isFile && this.tmpDirFullpath) {
|
|
66
|
-
const files = fs.readdirSync(this.tmpDirFullpath);
|
|
67
|
-
|
|
68
|
-
for (const file of files) {
|
|
69
|
-
if (file.includes(test)) {
|
|
70
|
-
const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
|
|
71
|
-
|
|
72
|
-
list.push(JSON.parse(buff.toString()));
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return list;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// returns all the content from all files; do we really need it?
|
|
81
|
-
async tmpContents() {
|
|
82
|
-
const contents = [];
|
|
83
|
-
|
|
84
|
-
if (this.isFile && this.tmpDirFullpath) {
|
|
85
|
-
const files = fs.readdirSync(this.tmpDirFullpath);
|
|
86
|
-
|
|
87
|
-
for (const file of files) {
|
|
88
|
-
const buff = await fs.promises.readFile(`${this.tmpDirFullpath }/${ file}`);
|
|
89
|
-
const content = buff.toString();
|
|
90
|
-
|
|
91
|
-
contents.push(content);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return contents;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
createTestomatTmpDir(clientPrefix) {
|
|
99
|
-
return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
clearTmpDirByName(name) {
|
|
103
|
-
const tmpDirPath = join(os.tmpdir(), name);
|
|
104
|
-
|
|
105
|
-
if (fs.existsSync(tmpDirPath)) {
|
|
106
|
-
fs.rmSync(tmpDirPath, { recursive: true });
|
|
107
|
-
debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// no need to use multiple dirs; we can implement it later if required
|
|
114
|
-
static tmpTestomatDirNames() {
|
|
115
|
-
const subname = ArtifactStorage._tmpPrefix || "tsmt_reporter";
|
|
116
|
-
|
|
117
|
-
return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
|
|
118
|
-
.filter((item) => item.isDirectory())
|
|
119
|
-
.map((item) => item.name)
|
|
120
|
-
.filter((name) => name.includes(subname));
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
cleanup() {
|
|
124
|
-
// when you do a cleanup, I know nothing about isFile param
|
|
125
|
-
if (this.isFile && this.tmpDirFullpath) {
|
|
126
|
-
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
127
|
-
|
|
128
|
-
if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
|
|
129
|
-
for (const name of tmpDirNames) {
|
|
130
|
-
this.clearTmpDirByName(name);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
debug("The tmp folder has not been created! Nothing to delete");
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
ArtifactStorage._tmpPrefix = "tsmt_reporter";
|
|
141
|
-
|
|
142
|
-
module.exports = ArtifactStorage;
|
package/lib/artifactStorage.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// const debug = require('debug')('@testomatio/reporter:logger');
|
|
2
|
-
const { DataStorage } = require('./dataStorage');
|
|
3
|
-
|
|
4
|
-
class ArtifactStorage {
|
|
5
|
-
constructor(params = {}) {
|
|
6
|
-
this.dataStorage = new DataStorage('artifact', params);
|
|
7
|
-
|
|
8
|
-
// singleton
|
|
9
|
-
if (!ArtifactStorage.instance) {
|
|
10
|
-
ArtifactStorage.instance = this;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
save(data, context = null) {
|
|
15
|
-
this.dataStorage.putData(data, context);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get(context) {
|
|
19
|
-
this.dataStorage.getData(context);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
ArtifactStorage.instance = null;
|
|
24
|
-
|
|
25
|
-
module.exports = new ArtifactStorage();
|