@testomatio/reporter 1.4.1-beta-1 → 1.4.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 +60 -57
- package/lib/adapter/codecept.js +86 -27
- package/lib/adapter/cucumber/current.js +17 -10
- package/lib/adapter/cucumber/legacy.js +5 -4
- package/lib/adapter/cucumber.js +2 -2
- package/lib/adapter/cypress-plugin/index.js +53 -26
- package/lib/adapter/jasmine.js +2 -2
- package/lib/adapter/jest.js +48 -10
- package/lib/adapter/mocha.js +90 -10
- package/lib/adapter/playwright.js +68 -18
- package/lib/adapter/webdriver.js +47 -2
- package/lib/bin/reportXml.js +21 -16
- package/lib/bin/startTest.js +12 -12
- package/lib/client.js +112 -52
- package/lib/config.js +34 -0
- package/lib/constants.js +28 -1
- package/lib/data-storage.js +203 -0
- package/lib/fileUploader.js +66 -53
- package/lib/junit-adapter/adapter.js +0 -2
- package/lib/junit-adapter/csharp.js +3 -4
- package/lib/junit-adapter/index.js +3 -3
- package/lib/junit-adapter/java.js +11 -11
- package/lib/junit-adapter/javascript.js +1 -2
- package/lib/junit-adapter/python.js +12 -14
- package/lib/junit-adapter/ruby.js +1 -2
- package/lib/pipe/csv.js +35 -32
- package/lib/pipe/github.js +4 -3
- package/lib/pipe/gitlab.js +17 -10
- package/lib/pipe/html.js +361 -0
- package/lib/pipe/index.js +3 -1
- package/lib/pipe/testomatio.js +230 -51
- package/lib/reporter-functions.js +12 -9
- package/lib/reporter.js +8 -12
- package/lib/services/artifacts.js +57 -0
- package/lib/services/index.js +13 -0
- package/lib/{storages/key-value-storage.js → services/key-values.js} +19 -19
- package/lib/{storages → services}/logger.js +58 -37
- package/lib/template/emptyData.svg +23 -0
- package/lib/template/testomatio.hbs +1421 -0
- package/lib/utils/pipe_utils.js +73 -79
- package/lib/utils/utils.js +53 -40
- package/lib/xmlReader.js +113 -105
- package/package.json +13 -7
- package/lib/storages/artifact-storage.js +0 -70
- package/lib/storages/data-storage.js +0 -307
package/lib/xmlReader.js
CHANGED
|
@@ -1,57 +1,59 @@
|
|
|
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 config = require('./config');
|
|
17
19
|
|
|
18
|
-
const TESTOMATIO_URL = process.env.TESTOMATIO_URL ||
|
|
19
|
-
const TESTOMATIO = process.env.TESTOMATIO; // key?
|
|
20
|
+
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
20
21
|
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
21
22
|
|
|
22
23
|
const options = {
|
|
23
24
|
ignoreDeclaration: true,
|
|
24
25
|
ignoreAttributes: false,
|
|
25
26
|
alwaysCreateTextNode: false,
|
|
26
|
-
attributeNamePrefix:
|
|
27
|
+
attributeNamePrefix: '',
|
|
27
28
|
parseTagValue: true,
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
const reduceOptions = {};
|
|
31
32
|
|
|
32
33
|
class XmlReader {
|
|
33
|
-
|
|
34
34
|
constructor(opts = {}) {
|
|
35
35
|
this.requestParams = {
|
|
36
|
-
apiKey: opts.apiKey || TESTOMATIO,
|
|
36
|
+
apiKey: opts.apiKey || config.TESTOMATIO,
|
|
37
37
|
url: opts.url || TESTOMATIO_URL,
|
|
38
38
|
title: TESTOMATIO_TITLE,
|
|
39
39
|
env: TESTOMATIO_ENV,
|
|
40
40
|
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
41
|
+
// batch uploading is implemented for xml already
|
|
42
|
+
isBatchEnabled: false,
|
|
41
43
|
};
|
|
42
44
|
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
43
|
-
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts)
|
|
45
|
+
this.adapter = adapterFactory(opts.lang?.toLowerCase(), opts);
|
|
44
46
|
if (!this.adapter) throw new Error('XML adapter for this format not found');
|
|
45
47
|
|
|
46
48
|
this.opts = opts || {};
|
|
47
|
-
this.store = {}
|
|
49
|
+
this.store = {};
|
|
48
50
|
this.pipes = pipesFactory(opts, this.store);
|
|
49
51
|
|
|
50
52
|
this.parser = new XMLParser(options);
|
|
51
|
-
this.tests = []
|
|
52
|
-
this.stats = {}
|
|
53
|
+
this.tests = [];
|
|
54
|
+
this.stats = {};
|
|
53
55
|
this.stats.language = opts.lang?.toLowerCase();
|
|
54
|
-
this.filesToUpload = {}
|
|
56
|
+
this.filesToUpload = {};
|
|
55
57
|
|
|
56
58
|
this.version = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json')).toString()).version;
|
|
57
59
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
@@ -82,8 +84,8 @@ class XmlReader {
|
|
|
82
84
|
} else if (jsonResult.assemblies) {
|
|
83
85
|
return this.processXUnit(jsonResult.assemblies);
|
|
84
86
|
} else {
|
|
85
|
-
console.log(jsonResult)
|
|
86
|
-
throw new Error("Format can't be parsed")
|
|
87
|
+
console.log(jsonResult);
|
|
88
|
+
throw new Error("Format can't be parsed");
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
return this.processJUnit(jsonSuite);
|
|
@@ -96,7 +98,7 @@ class XmlReader {
|
|
|
96
98
|
const resultTests = processTestSuite(testsuite);
|
|
97
99
|
|
|
98
100
|
const hasFailures = resultTests.filter(t => t.status === 'failed').length > 0;
|
|
99
|
-
const status =
|
|
101
|
+
const status = failures > 0 || errors > 0 || hasFailures ? 'failed' : 'passed';
|
|
100
102
|
|
|
101
103
|
this.tests = this.tests.concat(resultTests);
|
|
102
104
|
|
|
@@ -135,21 +137,22 @@ class XmlReader {
|
|
|
135
137
|
let defs = jsonSuite?.TestRun?.TestDefinitions?.UnitTest;
|
|
136
138
|
if (!Array.isArray(defs)) defs = [defs].filter(d => !!d);
|
|
137
139
|
|
|
138
|
-
const tests =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
const tests =
|
|
141
|
+
defs.map(td => {
|
|
142
|
+
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
143
|
+
let example = td.name.match(/\((.*?)\)/);
|
|
144
|
+
if (example) example = { ...example[1].split(',') };
|
|
145
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
146
|
+
const suite_title = suite.pop();
|
|
147
|
+
return {
|
|
148
|
+
title,
|
|
149
|
+
example,
|
|
150
|
+
file: suite.join('/'),
|
|
151
|
+
description: td.Description,
|
|
152
|
+
suite_title,
|
|
153
|
+
id: td.Execution.id,
|
|
154
|
+
};
|
|
155
|
+
}) || [];
|
|
153
156
|
|
|
154
157
|
let result = jsonSuite?.TestRun?.Results?.UnitTestResult;
|
|
155
158
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
@@ -160,10 +163,9 @@ class XmlReader {
|
|
|
160
163
|
run_time: parseFloat(td.duration) * 1000,
|
|
161
164
|
status: td.outcome,
|
|
162
165
|
stack: td.Output.StdOut,
|
|
163
|
-
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
166
|
+
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path),
|
|
164
167
|
}));
|
|
165
168
|
|
|
166
|
-
|
|
167
169
|
results.forEach(r => {
|
|
168
170
|
const test = tests.find(t => t.id === r.id) || {};
|
|
169
171
|
r.suite_title = test.suite_title;
|
|
@@ -223,7 +225,7 @@ class XmlReader {
|
|
|
223
225
|
|
|
224
226
|
if (testCase.failure) {
|
|
225
227
|
message = testCase.failure.message;
|
|
226
|
-
stack = testCase.failure['stack-trace']
|
|
228
|
+
stack = testCase.failure['stack-trace'];
|
|
227
229
|
}
|
|
228
230
|
if (testCase.reason) {
|
|
229
231
|
message = testCase.reason.message;
|
|
@@ -250,7 +252,6 @@ class XmlReader {
|
|
|
250
252
|
suite_title,
|
|
251
253
|
run_time,
|
|
252
254
|
});
|
|
253
|
-
|
|
254
255
|
});
|
|
255
256
|
});
|
|
256
257
|
});
|
|
@@ -283,12 +284,12 @@ class XmlReader {
|
|
|
283
284
|
passed_count: 0,
|
|
284
285
|
failed_count: 0,
|
|
285
286
|
skipped_count: 0,
|
|
286
|
-
}
|
|
287
|
+
};
|
|
287
288
|
this.tests.forEach(t => {
|
|
288
289
|
this.stats.tests_count++;
|
|
289
290
|
if (t.status === 'passed') this.stats.passed_count++;
|
|
290
291
|
if (t.status === 'failed') this.stats.failed_count++;
|
|
291
|
-
})
|
|
292
|
+
});
|
|
292
293
|
if (this.stats.failed_count) this.stats.status = 'failed';
|
|
293
294
|
|
|
294
295
|
return this.stats;
|
|
@@ -297,7 +298,7 @@ class XmlReader {
|
|
|
297
298
|
fetchSourceCode() {
|
|
298
299
|
this.tests.forEach(t => {
|
|
299
300
|
try {
|
|
300
|
-
const file = this.adapter.getFilePath(t)
|
|
301
|
+
const file = this.adapter.getFilePath(t);
|
|
301
302
|
if (!file) return;
|
|
302
303
|
|
|
303
304
|
if (!this.stats.language) {
|
|
@@ -310,16 +311,16 @@ class XmlReader {
|
|
|
310
311
|
}
|
|
311
312
|
|
|
312
313
|
if (!fs.existsSync(file)) {
|
|
313
|
-
debug('Failed to open file with the source code', file)
|
|
314
|
+
debug('Failed to open file with the source code', file);
|
|
314
315
|
return;
|
|
315
316
|
}
|
|
316
317
|
const contents = fs.readFileSync(file).toString();
|
|
317
|
-
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language })
|
|
318
|
+
t.code = fetchSourceCode(contents, { ...t, lang: this.stats.language });
|
|
318
319
|
if (t.code) debug('Fetched code for test %s', t.title);
|
|
319
|
-
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language })
|
|
320
|
+
t.test_id = fetchIdFromCode(t.code, { lang: this.stats.language });
|
|
320
321
|
if (t.test_id) debug('Fetched test id %s for test %s', t.test_id, t.title);
|
|
321
322
|
} catch (err) {
|
|
322
|
-
debug(err)
|
|
323
|
+
debug(err);
|
|
323
324
|
}
|
|
324
325
|
});
|
|
325
326
|
}
|
|
@@ -327,21 +328,22 @@ class XmlReader {
|
|
|
327
328
|
formatTests() {
|
|
328
329
|
this.tests.forEach(t => {
|
|
329
330
|
if (t.file) {
|
|
330
|
-
t.file = t.file.replace(process.cwd() + path.sep, '')
|
|
331
|
+
t.file = t.file.replace(process.cwd() + path.sep, '');
|
|
331
332
|
}
|
|
332
333
|
|
|
333
|
-
this.adapter.formatTest(t)
|
|
334
|
+
this.adapter.formatTest(t);
|
|
334
335
|
|
|
335
336
|
t.title = humanize(t.title);
|
|
336
337
|
});
|
|
337
338
|
}
|
|
338
339
|
|
|
339
340
|
formatErrors() {
|
|
340
|
-
this.tests
|
|
341
|
-
t
|
|
342
|
-
t
|
|
343
|
-
|
|
344
|
-
|
|
341
|
+
this.tests
|
|
342
|
+
.filter(t => !!t.stack)
|
|
343
|
+
.forEach(t => {
|
|
344
|
+
t.stack = this.formatStack(t);
|
|
345
|
+
t.message = this.adapter.formatMessage(t);
|
|
346
|
+
});
|
|
345
347
|
}
|
|
346
348
|
|
|
347
349
|
formatStack(t) {
|
|
@@ -359,7 +361,7 @@ class XmlReader {
|
|
|
359
361
|
async uploadArtifacts() {
|
|
360
362
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
361
363
|
let files = [];
|
|
362
|
-
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
364
|
+
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f));
|
|
363
365
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
364
366
|
|
|
365
367
|
if (!files.length) continue;
|
|
@@ -376,9 +378,10 @@ class XmlReader {
|
|
|
376
378
|
title: this.requestParams.title,
|
|
377
379
|
env: this.requestParams.env,
|
|
378
380
|
group_title: this.requestParams.group_title,
|
|
381
|
+
isBatchEnabled: this.requestParams.isBatchEnabled,
|
|
379
382
|
};
|
|
380
383
|
|
|
381
|
-
debug(
|
|
384
|
+
debug('Run', runParams);
|
|
382
385
|
|
|
383
386
|
return Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
384
387
|
}
|
|
@@ -391,12 +394,10 @@ class XmlReader {
|
|
|
391
394
|
this.formatErrors();
|
|
392
395
|
this.formatTests();
|
|
393
396
|
|
|
394
|
-
debug(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
tests: this.tests,
|
|
399
|
-
})
|
|
397
|
+
debug('Uploading data', {
|
|
398
|
+
...this.stats,
|
|
399
|
+
tests: this.tests,
|
|
400
|
+
});
|
|
400
401
|
|
|
401
402
|
const dataString = {
|
|
402
403
|
...this.stats,
|
|
@@ -407,63 +408,70 @@ class XmlReader {
|
|
|
407
408
|
|
|
408
409
|
return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
|
|
409
410
|
}
|
|
411
|
+
|
|
412
|
+
async _finishRun() {
|
|
413
|
+
return Promise.all(this.pipes.map(p => p.finishRun({ status: 'finished' })));
|
|
414
|
+
}
|
|
410
415
|
}
|
|
411
416
|
|
|
412
417
|
module.exports = XmlReader;
|
|
413
418
|
|
|
414
|
-
|
|
415
419
|
function reduceTestCases(prev, item) {
|
|
416
420
|
let testCases = item.testcase;
|
|
417
421
|
if (!testCases) testCases = item['test-case'];
|
|
418
422
|
if (!Array.isArray(testCases)) {
|
|
419
|
-
testCases = [testCases]
|
|
423
|
+
testCases = [testCases];
|
|
420
424
|
}
|
|
421
425
|
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
422
426
|
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
423
|
-
testCases
|
|
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
|
-
|
|
459
|
-
|
|
427
|
+
testCases
|
|
428
|
+
.filter(t => !!t)
|
|
429
|
+
.forEach(testCaseItem => {
|
|
430
|
+
const file = testCaseItem.file || item.filepath || '';
|
|
431
|
+
|
|
432
|
+
let stack = '';
|
|
433
|
+
let message = '';
|
|
434
|
+
if (testCaseItem.error) stack = testCaseItem.error;
|
|
435
|
+
if (testCaseItem.failure) stack = testCaseItem.failure;
|
|
436
|
+
if (testCaseItem?.failure?.['stack-trace']) stack = testCaseItem.failure['stack-trace'];
|
|
437
|
+
if (testCaseItem?.failure?.message) message = testCaseItem.failure.message;
|
|
438
|
+
if (testCaseItem?.error?.message) message = testCaseItem.error.message;
|
|
439
|
+
|
|
440
|
+
if (testCaseItem.failure && testCaseItem.failure['#text']) stack = testCaseItem.failure['#text'];
|
|
441
|
+
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
442
|
+
if (!message) message = stack.trim().split('\n')[0];
|
|
443
|
+
|
|
444
|
+
// eslint-disable-next-line
|
|
445
|
+
stack = `${
|
|
446
|
+
testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''
|
|
447
|
+
}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
448
|
+
const testId = fetchIdFromOutput(stack);
|
|
449
|
+
|
|
450
|
+
let status = STATUS.PASSED.toString();
|
|
451
|
+
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
452
|
+
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
453
|
+
|
|
454
|
+
prev.push({
|
|
455
|
+
create: true,
|
|
456
|
+
file,
|
|
457
|
+
stack,
|
|
458
|
+
test_id: testId,
|
|
459
|
+
message,
|
|
460
|
+
line: testCaseItem.lineno,
|
|
461
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
462
|
+
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
463
|
+
status,
|
|
464
|
+
title: testCaseItem.name,
|
|
465
|
+
suite_title: reduceOptions.preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname,
|
|
466
|
+
});
|
|
467
|
+
});
|
|
460
468
|
return prev;
|
|
461
469
|
}
|
|
462
470
|
|
|
463
471
|
function processTestSuite(testsuite) {
|
|
464
472
|
if (!testsuite) return [];
|
|
465
|
-
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite)
|
|
466
|
-
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite'])
|
|
473
|
+
if (testsuite.testsuite) return processTestSuite(testsuite.testsuite);
|
|
474
|
+
if (testsuite['test-suite']) return processTestSuite(testsuite['test-suite']);
|
|
467
475
|
|
|
468
476
|
let suites = testsuite;
|
|
469
477
|
if (!Array.isArray(testsuite)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.4.1
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"@aws-sdk/lib-storage": "^3.279.0",
|
|
13
13
|
"@octokit/rest": "^19.0.5",
|
|
14
14
|
"aws-sdk": "^2.1072.0",
|
|
15
|
-
"axios": "^
|
|
15
|
+
"axios": "^1.6.2",
|
|
16
|
+
"axios-retry": "^3.9.1",
|
|
16
17
|
"callsite-record": "^4.1.4",
|
|
17
18
|
"chalk": "^4.1.0",
|
|
18
19
|
"commander": "^4.1.1",
|
|
@@ -20,7 +21,9 @@
|
|
|
20
21
|
"debug": "^4.3.4",
|
|
21
22
|
"dotenv": "^16.0.1",
|
|
22
23
|
"fast-xml-parser": "^4.0.8",
|
|
24
|
+
"file-url": "3.0.0",
|
|
23
25
|
"glob": "^10.3",
|
|
26
|
+
"handlebars": "^4.7.8",
|
|
24
27
|
"has-flag": "^5.0.1",
|
|
25
28
|
"humanize-duration": "^3.27.3",
|
|
26
29
|
"is-valid-path": "^0.1.1",
|
|
@@ -28,8 +31,8 @@
|
|
|
28
31
|
"lodash.memoize": "^4.1.2",
|
|
29
32
|
"lodash.merge": "^4.6.2",
|
|
30
33
|
"minimatch": "^9.0.3",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
34
|
+
"promise-retry": "^2.0.1",
|
|
35
|
+
"uuid": "^9.0.0"
|
|
33
36
|
},
|
|
34
37
|
"files": [
|
|
35
38
|
"bin",
|
|
@@ -38,9 +41,11 @@
|
|
|
38
41
|
],
|
|
39
42
|
"scripts": {
|
|
40
43
|
"clear-exportdir": "rm -rf export/",
|
|
41
|
-
"pretty": "prettier --
|
|
44
|
+
"pretty": "npx prettier --debug-check --check .",
|
|
45
|
+
"pretty:fix": "prettier --write .",
|
|
42
46
|
"lint": "eslint lib",
|
|
43
47
|
"lint:fix": "eslint lib --fix",
|
|
48
|
+
"format": "npm run lint:fix && npm run pretty:fix",
|
|
44
49
|
"test": "mocha tests/**",
|
|
45
50
|
"init": "cd ./tests/adapter/examples/cucumber && npm i",
|
|
46
51
|
"test:adapter": "mocha './tests/adapter/index.test.js'",
|
|
@@ -50,7 +55,7 @@
|
|
|
50
55
|
"test:adapter:jasmine:example": "./tests/adapter/examples/jasmine/passReporterOpts.sh && jasmine './tests/adapter/examples/jasmine/index.test.js' --reporter=./../../../lib/adapter/jasmine.js",
|
|
51
56
|
"test:adapter:codecept:example": "codeceptjs run --config='./tests/adapter/examples/codecept/codecept.conf.js'",
|
|
52
57
|
"test:adapter:cucumber:example": "cd ./tests/adapter/examples/cucumber && npx cucumber-js",
|
|
53
|
-
"test:storage": "npx mocha
|
|
58
|
+
"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"
|
|
54
59
|
},
|
|
55
60
|
"devDependencies": {
|
|
56
61
|
"@cucumber/cucumber": "^9.3.0",
|
|
@@ -65,10 +70,11 @@
|
|
|
65
70
|
"eslint-plugin-import": "^2.25.4",
|
|
66
71
|
"jasmine": "^3.10.0",
|
|
67
72
|
"jest": "^27.4.7",
|
|
73
|
+
"jsdom": "^22.1.0",
|
|
68
74
|
"mocha": "^9.2.0",
|
|
69
75
|
"mock-http-server": "^1.4.5",
|
|
70
76
|
"pino": "^8.15.0",
|
|
71
|
-
"prettier": "2.5
|
|
77
|
+
"prettier": "^3.2.5",
|
|
72
78
|
"puppeteer": "^13.1.2"
|
|
73
79
|
},
|
|
74
80
|
"bin": {
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const debug = require('debug')('@testomatio/reporter:artifact-storage');
|
|
2
|
-
const DataStorage = require('./data-storage');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Artifact storage is supposed to store file paths
|
|
6
|
-
*/
|
|
7
|
-
class ArtifactStorage {
|
|
8
|
-
|
|
9
|
-
// there is autocompletion for the class methods if implemented singleton this way
|
|
10
|
-
constructor() {
|
|
11
|
-
this.dataStorage = new DataStorage('artifact');
|
|
12
|
-
|
|
13
|
-
// singleton
|
|
14
|
-
if (!ArtifactStorage.instance) {
|
|
15
|
-
ArtifactStorage.instance = this;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// singleton
|
|
20
|
-
static getInstance() {
|
|
21
|
-
if (!ArtifactStorage.instance) {
|
|
22
|
-
ArtifactStorage.instance = new ArtifactStorage();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return ArtifactStorage.instance;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Stores path to file as artifact and uploads it to the S3 storage
|
|
30
|
-
* @param {string | {path: string, type: string, name: string}} data - path to file or object with path, type and name
|
|
31
|
-
* @param {*} context testId or test title
|
|
32
|
-
*/
|
|
33
|
-
put(data, context = null) {
|
|
34
|
-
if (!data) return;
|
|
35
|
-
debug('Save artifact:', data);
|
|
36
|
-
this.dataStorage.putData(data, context);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Returns list of artifacts to upload
|
|
41
|
-
* @param {*} context testId or test context from test runner
|
|
42
|
-
* @returns {string | {path: string, type: string, name: string}[]}
|
|
43
|
-
*/
|
|
44
|
-
get(context) {
|
|
45
|
-
if (!context) return null;
|
|
46
|
-
|
|
47
|
-
// array of any data
|
|
48
|
-
let artifacts = this.dataStorage.getData(context);
|
|
49
|
-
if (!artifacts || !artifacts.length) return null;
|
|
50
|
-
|
|
51
|
-
artifacts = artifacts.map(artifactData => {
|
|
52
|
-
// artifact could be an object ({type, path, name} props) or string
|
|
53
|
-
let artifact;
|
|
54
|
-
try {
|
|
55
|
-
artifact = JSON.parse(artifactData);
|
|
56
|
-
} catch (e) {
|
|
57
|
-
artifact = artifactData;
|
|
58
|
-
}
|
|
59
|
-
return artifact;
|
|
60
|
-
});
|
|
61
|
-
artifacts = artifacts.filter(artifact => !!artifact);
|
|
62
|
-
return artifacts.length ? artifacts : null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
ArtifactStorage.instance = null;
|
|
67
|
-
|
|
68
|
-
// const artifactStorage = ArtifactStorage.getInstance();
|
|
69
|
-
const artifactStorage = new ArtifactStorage();
|
|
70
|
-
module.exports = artifactStorage;
|