@testomatio/reporter 2.0.1-beta-ignore-xml → 2.0.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/lib/adapter/codecept.js +0 -2
- package/lib/adapter/cypress-plugin/index.js +0 -2
- package/lib/adapter/mocha.js +0 -1
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/webdriver.d.ts +1 -1
- package/lib/adapter/webdriver.js +17 -8
- package/lib/bin/cli.js +126 -8
- package/lib/bin/reportXml.js +4 -2
- package/lib/bin/startTest.js +3 -2
- package/lib/bin/uploadArtifacts.js +5 -4
- package/lib/client.js +18 -9
- package/lib/config.js +2 -2
- package/lib/data-storage.d.ts +1 -1
- package/lib/data-storage.js +17 -7
- package/lib/junit-adapter/csharp.d.ts +1 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/pipe/bitbucket.d.ts +2 -0
- package/lib/pipe/bitbucket.js +38 -26
- package/lib/pipe/debug.js +17 -3
- package/lib/pipe/github.d.ts +2 -2
- package/lib/pipe/github.js +35 -3
- package/lib/pipe/gitlab.d.ts +2 -0
- package/lib/pipe/gitlab.js +27 -9
- package/lib/pipe/html.d.ts +1 -0
- package/lib/pipe/html.js +1 -3
- package/lib/pipe/index.js +17 -7
- package/lib/pipe/testomatio.d.ts +2 -1
- package/lib/pipe/testomatio.js +79 -73
- 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/logger.d.ts +1 -1
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +443 -68
- package/lib/uploader.js +2 -2
- package/lib/utils/utils.d.ts +2 -0
- package/lib/utils/utils.js +41 -18
- package/lib/xmlReader.js +54 -19
- package/package.json +8 -9
- package/src/adapter/codecept.js +0 -2
- package/src/adapter/cypress-plugin/index.js +0 -2
- package/src/adapter/mocha.js +0 -1
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/webdriver.js +1 -2
- package/src/bin/cli.js +131 -2
- package/src/bin/reportXml.js +4 -1
- package/src/bin/startTest.js +2 -1
- package/src/bin/uploadArtifacts.js +2 -1
- package/src/client.js +1 -2
- package/src/config.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +22 -24
- package/src/pipe/debug.js +18 -3
- package/src/pipe/github.js +1 -2
- package/src/pipe/gitlab.js +27 -9
- package/src/pipe/html.js +3 -4
- package/src/pipe/testomatio.js +98 -103
- package/src/services/logger.js +1 -2
- package/src/template/testomatio.hbs +443 -68
- package/src/uploader.js +2 -2
- package/src/utils/utils.js +19 -9
- package/src/xmlReader.js +69 -17
package/lib/xmlReader.js
CHANGED
|
@@ -20,7 +20,7 @@ const uploader_js_1 = require("./uploader.js");
|
|
|
20
20
|
const debug = (0, debug_1.default)('@testomatio/reporter:xml');
|
|
21
21
|
const ridRunId = (0, crypto_1.randomUUID)();
|
|
22
22
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
23
|
-
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED } = process.env;
|
|
23
|
+
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_MAX_STACK_TRACE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN, TESTOMATIO_MARK_DETACHED, } = process.env;
|
|
24
24
|
const options = {
|
|
25
25
|
ignoreDeclaration: true,
|
|
26
26
|
ignoreAttributes: false,
|
|
@@ -28,6 +28,7 @@ const options = {
|
|
|
28
28
|
attributeNamePrefix: '',
|
|
29
29
|
parseTagValue: true,
|
|
30
30
|
};
|
|
31
|
+
const MAX_OUTPUT_LENGTH = parseInt(TESTOMATIO_MAX_STACK_TRACE, 10) || 10000;
|
|
31
32
|
const reduceOptions = {};
|
|
32
33
|
class XmlReader {
|
|
33
34
|
constructor(opts = {}) {
|
|
@@ -75,7 +76,7 @@ class XmlReader {
|
|
|
75
76
|
/(<system-out><!\[CDATA\[)([\s\S]*?)(\]\]><\/system-out>)/g,
|
|
76
77
|
];
|
|
77
78
|
for (const regex of cutRegexes) {
|
|
78
|
-
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0,
|
|
79
|
+
xmlData = xmlData.replace(regex, (_, p1, p2, p3) => `${p1}${p2.substring(0, MAX_OUTPUT_LENGTH)}${p3}`);
|
|
79
80
|
}
|
|
80
81
|
const jsonResult = this.parser.parse(xmlData);
|
|
81
82
|
let jsonSuite;
|
|
@@ -202,7 +203,7 @@ class XmlReader {
|
|
|
202
203
|
this.tests = results.filter(t => !!t.title);
|
|
203
204
|
return {
|
|
204
205
|
status,
|
|
205
|
-
create_tests:
|
|
206
|
+
create_tests: !process.env.IGNORE_NEW_TESTS,
|
|
206
207
|
tests_count: parseInt(counters.total, 10),
|
|
207
208
|
passed_count: parseInt(counters.passed, 10),
|
|
208
209
|
skipped_count: parseInt(counters.notExecuted, 10),
|
|
@@ -313,6 +314,8 @@ class XmlReader {
|
|
|
313
314
|
this.stats.language = 'js';
|
|
314
315
|
if (file.endsWith('.ts'))
|
|
315
316
|
this.stats.language = 'ts';
|
|
317
|
+
if (file.endsWith('.cs'))
|
|
318
|
+
this.stats.language = 'csharp';
|
|
316
319
|
}
|
|
317
320
|
if (!fs_1.default.existsSync(file)) {
|
|
318
321
|
debug('Failed to open file with the source code', file);
|
|
@@ -359,13 +362,13 @@ class XmlReader {
|
|
|
359
362
|
async uploadArtifacts() {
|
|
360
363
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
361
364
|
let files = [];
|
|
362
|
-
if (test.files?.length)
|
|
363
|
-
|
|
364
|
-
files =
|
|
365
|
+
if (!test.files?.length)
|
|
366
|
+
continue;
|
|
367
|
+
files = test.files.map(f => (path_1.default.isAbsolute(f) ? f : path_1.default.join(process.cwd(), f)));
|
|
365
368
|
if (!files.length)
|
|
366
369
|
continue;
|
|
367
370
|
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
368
|
-
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId])));
|
|
371
|
+
test.artifacts = await Promise.all(files.map(f => this.uploader.uploadFileByPath(f, [runId, path_1.default.basename(f)])));
|
|
369
372
|
console.log(constants_js_1.APP_PREFIX, `🗄️ Uploaded ${picocolors_1.default.bold(`${files.length} artifacts`)} for test ${test.title}`);
|
|
370
373
|
}
|
|
371
374
|
}
|
|
@@ -415,14 +418,17 @@ function reduceTestCases(prev, item) {
|
|
|
415
418
|
testCases = [testCases];
|
|
416
419
|
}
|
|
417
420
|
// suite inside test case
|
|
418
|
-
|
|
419
|
-
|
|
421
|
+
const testCase = item['test-suite']?.['test-case'];
|
|
422
|
+
if (testCase) {
|
|
423
|
+
const nestedCases = Array.isArray(testCase) ? testCase : [testCase];
|
|
424
|
+
testCases.push(...nestedCases);
|
|
425
|
+
}
|
|
420
426
|
const suiteOutput = item['system-out'] || item.output || item.log || '';
|
|
421
427
|
const suiteErr = item['system-err'] || item.output || item.log || '';
|
|
422
428
|
testCases
|
|
423
429
|
.filter(t => !!t)
|
|
424
430
|
.forEach(testCaseItem => {
|
|
425
|
-
const file = testCaseItem.file || item.filepath || '';
|
|
431
|
+
const file = testCaseItem.file || item.filepath || item.fullname || '';
|
|
426
432
|
let stack = '';
|
|
427
433
|
let message = '';
|
|
428
434
|
if (testCaseItem.error)
|
|
@@ -444,7 +450,7 @@ function reduceTestCases(prev, item) {
|
|
|
444
450
|
const isParametrized = item.type === 'ParameterizedMethod';
|
|
445
451
|
const preferClassname = reduceOptions.preferClassname || isParametrized;
|
|
446
452
|
// SpecFlow config
|
|
447
|
-
let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
453
|
+
let { title, tags, testId } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
448
454
|
let example = null;
|
|
449
455
|
const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
450
456
|
title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
|
|
@@ -454,17 +460,38 @@ function reduceTestCases(prev, item) {
|
|
|
454
460
|
example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
|
|
455
461
|
title = title.replace(/\(.*?\)/, '').trim();
|
|
456
462
|
}
|
|
457
|
-
// eslint-disable-next-line
|
|
458
463
|
stack = `${testCaseItem['system-out'] || testCaseItem.output || testCaseItem.log || ''}\n\n${stack}\n\n${suiteOutput}\n\n${suiteErr}`.trim();
|
|
459
|
-
|
|
464
|
+
if (!testId)
|
|
465
|
+
testId = (0, utils_js_1.fetchIdFromOutput)(stack);
|
|
466
|
+
if (tags?.length && !testId) {
|
|
467
|
+
testId = tags
|
|
468
|
+
.filter(t => t.startsWith('T'))
|
|
469
|
+
.map(t => `@${t}`)
|
|
470
|
+
.find(t => t.match(utils_js_1.TEST_ID_REGEX))
|
|
471
|
+
?.slice(2);
|
|
472
|
+
}
|
|
460
473
|
let status = constants_js_1.STATUS.PASSED.toString();
|
|
461
474
|
if ('failure' in testCaseItem || 'error' in testCaseItem)
|
|
462
475
|
status = constants_js_1.STATUS.FAILED;
|
|
463
476
|
if ('skipped' in testCaseItem)
|
|
464
477
|
status = constants_js_1.STATUS.SKIPPED;
|
|
478
|
+
if (testCaseItem.result && Object.values(constants_js_1.STATUS).includes(testCaseItem.result.toLowerCase())) {
|
|
479
|
+
status = testCaseItem.result.toLowerCase();
|
|
480
|
+
}
|
|
465
481
|
let rid = null;
|
|
466
482
|
if (testCaseItem.id)
|
|
467
483
|
rid = `${ridRunId}-${testCaseItem.id}`;
|
|
484
|
+
// Extract attachments
|
|
485
|
+
let files = [];
|
|
486
|
+
if (testCaseItem.attachments) {
|
|
487
|
+
const attachments = Array.isArray(testCaseItem.attachments.attachment)
|
|
488
|
+
? testCaseItem.attachments.attachment
|
|
489
|
+
: [testCaseItem.attachments.attachment];
|
|
490
|
+
files = attachments.filter(a => a && a.filePath).map(a => a.filePath);
|
|
491
|
+
}
|
|
492
|
+
// Extract files from stack trace using existing utility
|
|
493
|
+
const stackFiles = (0, utils_js_1.fetchFilesFromStackTrace)(stack);
|
|
494
|
+
files = [...new Set([...files, ...stackFiles])]; // Remove duplicates
|
|
468
495
|
prev.push({
|
|
469
496
|
rid,
|
|
470
497
|
file,
|
|
@@ -479,7 +506,9 @@ function reduceTestCases(prev, item) {
|
|
|
479
506
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
480
507
|
status,
|
|
481
508
|
title,
|
|
509
|
+
root_suite_id: TESTOMATIO_SUITE,
|
|
482
510
|
suite_title: suiteTitle,
|
|
511
|
+
files,
|
|
483
512
|
});
|
|
484
513
|
});
|
|
485
514
|
return prev;
|
|
@@ -503,12 +532,18 @@ function fetchProperties(item) {
|
|
|
503
532
|
let title = '';
|
|
504
533
|
if (!item.properties)
|
|
505
534
|
return {};
|
|
506
|
-
|
|
535
|
+
// Handle both single property and array of properties
|
|
536
|
+
const properties = Array.isArray(item.properties.property)
|
|
537
|
+
? item.properties.property
|
|
538
|
+
: [item.properties.property].filter(Boolean);
|
|
539
|
+
const prop = properties.find(p => p.name === 'Description');
|
|
507
540
|
if (prop)
|
|
508
541
|
title = prop.value;
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
542
|
+
let testId = properties.find(p => p.name === 'ID')?.value;
|
|
543
|
+
if (testId?.startsWith('@'))
|
|
544
|
+
testId = testId.slice(1);
|
|
545
|
+
if (testId?.startsWith('T'))
|
|
546
|
+
testId = testId.slice(1);
|
|
547
|
+
properties.filter(p => p.name === 'Category').forEach(p => tags.push(p.value));
|
|
548
|
+
return { title, tags, testId };
|
|
514
549
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "2.0.1-beta
|
|
3
|
+
"version": "2.0.1-beta.1",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
@@ -14,10 +14,9 @@
|
|
|
14
14
|
"@aws-sdk/client-s3": "^3.279.0",
|
|
15
15
|
"@aws-sdk/lib-storage": "^3.279.0",
|
|
16
16
|
"@cucumber/cucumber": "^10.9.0",
|
|
17
|
-
"@octokit/rest": "^
|
|
17
|
+
"@octokit/rest": "^21.1.1",
|
|
18
18
|
"aws-sdk": "^2.1072.0",
|
|
19
|
-
"
|
|
20
|
-
"axios-retry": "^3.9.1",
|
|
19
|
+
"gaxios": ">=6.0 || >=7.0.0-rc.4 || <8",
|
|
21
20
|
"callsite-record": "^4.1.4",
|
|
22
21
|
"commander": "^12",
|
|
23
22
|
"cross-spawn": "^7.0.3",
|
|
@@ -49,7 +48,6 @@
|
|
|
49
48
|
"testcafe"
|
|
50
49
|
],
|
|
51
50
|
"scripts": {
|
|
52
|
-
"@cucumber/cucumber": "^10.9.0",
|
|
53
51
|
"clear-exportdir": "rm -rf export/",
|
|
54
52
|
"pretty": "npx prettier --check .",
|
|
55
53
|
"pretty:fix": "prettier --write .",
|
|
@@ -68,8 +66,9 @@
|
|
|
68
66
|
"test:adapter:playwright:example": "npx playwright test --config='./tests/adapter/examples/playwright/playwright.config.ts'",
|
|
69
67
|
"test:adapter:vitest:example": "npx vitest --config='./tests/adapter/examples/vitest/vitest.config.ts'",
|
|
70
68
|
"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",
|
|
71
|
-
"
|
|
72
|
-
"build": "rm -rf ./cjs &&
|
|
69
|
+
"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",
|
|
70
|
+
"build:bun": "rm -rf ./cjs && bun build ./src/reporter.js ./src/bin/reportXml.js ./src/bin/startTest.js ./src/bin/uploadArtifacts.js --outdir ./cjs --target node && bun build/scripts/edit-js-files.js && bun build/scripts/edit-package-json.js && chmod +x ./build/scripts/copy-tesmplate.sh && ./build/scripts/copy-tesmplate.sh",
|
|
71
|
+
"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\""
|
|
73
72
|
},
|
|
74
73
|
"devDependencies": {
|
|
75
74
|
"@playwright/test": "^1.49.1",
|
|
@@ -81,8 +80,7 @@
|
|
|
81
80
|
"chai": "^4.3.6",
|
|
82
81
|
"codeceptjs": "^3.6.5",
|
|
83
82
|
"cucumber": "^6.0.7",
|
|
84
|
-
"eslint": "^
|
|
85
|
-
"eslint-config-airbnb-base": "^15.0.0",
|
|
83
|
+
"eslint": "^9.24.0",
|
|
86
84
|
"eslint-config-prettier": "^8.3.0",
|
|
87
85
|
"eslint-plugin-import": "^2.25.4",
|
|
88
86
|
"jasmine": "^5.2.0",
|
|
@@ -123,6 +121,7 @@
|
|
|
123
121
|
"./lib/adapter/mocha/mocha.js": "./lib/adapter/mocha.js",
|
|
124
122
|
"./mocha": "./lib/adapter/mocha.js",
|
|
125
123
|
"./lib/adapter/playwright.js": "./lib/adapter/playwright.js",
|
|
124
|
+
"./nightwatch": "./lib/adapter/nightwatch.js",
|
|
126
125
|
"./playwright": "./lib/adapter/playwright.js",
|
|
127
126
|
"./vitest": "./lib/adapter/vitest.js",
|
|
128
127
|
"./lib/adapter/webdriver.js": "./lib/adapter/webdriver.js",
|
package/src/adapter/codecept.js
CHANGED
|
@@ -4,7 +4,6 @@ import TestomatClient from '../client.js';
|
|
|
4
4
|
import { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } from '../constants.js';
|
|
5
5
|
import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
|
|
6
6
|
import { services } from '../services/index.js';
|
|
7
|
-
// eslint-disable-next-line
|
|
8
7
|
import codeceptjs from 'codeceptjs';
|
|
9
8
|
|
|
10
9
|
const debug = createDebugMessages('@testomatio/reporter:adapter:codeceptjs');
|
|
@@ -271,7 +270,6 @@ function CodeceptReporter(config) {
|
|
|
271
270
|
for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
|
|
272
271
|
if (currentMetaStep[i] !== metaSteps[i]) {
|
|
273
272
|
stepShift = 2 * i;
|
|
274
|
-
// eslint-disable-next-line no-continue
|
|
275
273
|
if (!metaSteps[i]) continue;
|
|
276
274
|
if (metaSteps[i].isBDD()) {
|
|
277
275
|
// output.push(repeat(stepShift) + pc.bold(metaSteps[i].toString()) + metaSteps[i].comment);
|
|
@@ -44,7 +44,6 @@ const testomatioReporter = on => {
|
|
|
44
44
|
|
|
45
45
|
if (!error && test.displayError) {
|
|
46
46
|
error = { message: test.displayError };
|
|
47
|
-
// eslint-disable-next-line
|
|
48
47
|
error.inspect = function () {
|
|
49
48
|
return this.message;
|
|
50
49
|
};
|
|
@@ -56,7 +55,6 @@ const testomatioReporter = on => {
|
|
|
56
55
|
name: error.name,
|
|
57
56
|
inspect:
|
|
58
57
|
error.inspect ||
|
|
59
|
-
// eslint-disable-next-line
|
|
60
58
|
function () {
|
|
61
59
|
return this.message;
|
|
62
60
|
},
|
package/src/adapter/mocha.js
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import TestomatClient from '../client.js';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
import { STATUS } from '../constants.js';
|
|
4
|
+
import { getTestomatIdFromTestTitle } from '../utils/utils.js';
|
|
5
|
+
|
|
6
|
+
const apiKey = config.TESTOMATIO;
|
|
7
|
+
const client = new TestomatClient({ apiKey });
|
|
8
|
+
|
|
9
|
+
export default {
|
|
10
|
+
write: async (results, options, done) => {
|
|
11
|
+
await client.createRun();
|
|
12
|
+
|
|
13
|
+
const testFiles = results.modules;
|
|
14
|
+
|
|
15
|
+
for (const fileName in testFiles) {
|
|
16
|
+
// in nightwatch: object containing tests from a single file
|
|
17
|
+
const testModule = testFiles[fileName];
|
|
18
|
+
|
|
19
|
+
// passed and failed tests (tests with assertions)
|
|
20
|
+
const completedTests = testModule.completed;
|
|
21
|
+
|
|
22
|
+
// skipped tests (skipped by user or tests without assertions)
|
|
23
|
+
const skippedTests = testModule.skipped;
|
|
24
|
+
|
|
25
|
+
const tags = testModule.tags || [];
|
|
26
|
+
|
|
27
|
+
// if test file contains multiple suites, the last suite name is used as a name 🤷♂️
|
|
28
|
+
// no other places which contain suite name (even inside test object)
|
|
29
|
+
const suiteTitle = testModule.name;
|
|
30
|
+
|
|
31
|
+
for (const testTitle in completedTests) {
|
|
32
|
+
const test = completedTests[testTitle];
|
|
33
|
+
let status;
|
|
34
|
+
switch (test.status) {
|
|
35
|
+
case 'pass':
|
|
36
|
+
status = STATUS.PASSED;
|
|
37
|
+
break;
|
|
38
|
+
case 'fail':
|
|
39
|
+
status = STATUS.FAILED;
|
|
40
|
+
break;
|
|
41
|
+
// probably not required (because skipped tests are in separate array), but just in case
|
|
42
|
+
case 'skip':
|
|
43
|
+
status = STATUS.SKIPPED;
|
|
44
|
+
console.info('Skipped test is in completed tests array:', test, 'Not expected behavior.');
|
|
45
|
+
break;
|
|
46
|
+
default:
|
|
47
|
+
console.error('Test status processing error:', test.status);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const testId = getTestomatIdFromTestTitle(testTitle);
|
|
51
|
+
|
|
52
|
+
client.addTestRun(status, {
|
|
53
|
+
error: { name: test.assertions?.[0]?.name, message: test.assertions?.[0]?.message, stack: test.stackTrace },
|
|
54
|
+
file: testModule.modulePath?.replace(process.cwd(), ''),
|
|
55
|
+
message: test.assertions?.[0]?.message,
|
|
56
|
+
rid: `${testModule.uuid || ''}_${testTitle || ''}`,
|
|
57
|
+
stack: test.stackTrace,
|
|
58
|
+
suite_title: suiteTitle,
|
|
59
|
+
tags,
|
|
60
|
+
test_id: testId,
|
|
61
|
+
time: test.timeMs,
|
|
62
|
+
title: testTitle,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// just array with skipped tests titles, no any other info
|
|
67
|
+
for (const testTitle of skippedTests) {
|
|
68
|
+
client.addTestRun(STATUS.SKIPPED, {
|
|
69
|
+
suite_title: suiteTitle,
|
|
70
|
+
tags,
|
|
71
|
+
rid: `${testModule.uuid || ''}_${testTitle || ''}`,
|
|
72
|
+
title: testTitle,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @type {'passed' | 'failed' | 'finished'}
|
|
79
|
+
*/
|
|
80
|
+
let runStatus = 'finished';
|
|
81
|
+
if (results.failed) runStatus = 'failed';
|
|
82
|
+
else if (results.passed) runStatus = 'passed';
|
|
83
|
+
|
|
84
|
+
await client.updateRunStatus(runStatus);
|
|
85
|
+
|
|
86
|
+
done();
|
|
87
|
+
},
|
|
88
|
+
};
|
package/src/adapter/webdriver.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
import WDIOReporter, { RunnerStats } from '@wdio/reporter';
|
|
1
|
+
import { default as WDIOReporter, RunnerStats } from '@wdio/reporter';
|
|
3
2
|
import TestomatClient from '../client.js';
|
|
4
3
|
import { getTestomatIdFromTestTitle, fileSystem } from '../utils/utils.js';
|
|
5
4
|
import { services } from '../services/index.js';
|
package/src/bin/cli.js
CHANGED
|
@@ -2,19 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { spawn } from 'cross-spawn';
|
|
5
|
-
import glob from 'glob';
|
|
5
|
+
import { glob } from 'glob';
|
|
6
6
|
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 { getPackageVersion } from '../utils/utils.js';
|
|
11
11
|
import { config } from '../config.js';
|
|
12
12
|
import { readLatestRunId } from '../utils/utils.js';
|
|
13
13
|
import pc from 'picocolors';
|
|
14
14
|
import { filesize as prettyBytes } from 'filesize';
|
|
15
15
|
import dotenv from 'dotenv';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import os from 'os';
|
|
16
19
|
|
|
17
20
|
const debug = createDebugMessages('@testomatio/reporter:xml-cli');
|
|
21
|
+
const version = getPackageVersion();
|
|
18
22
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
19
23
|
const program = new Command();
|
|
20
24
|
|
|
@@ -120,6 +124,28 @@ program
|
|
|
120
124
|
}
|
|
121
125
|
});
|
|
122
126
|
|
|
127
|
+
// program
|
|
128
|
+
// .command('xml')
|
|
129
|
+
// .description('Parse XML reports and upload to Testomat.io')
|
|
130
|
+
// .argument('<pattern>', 'XML file pattern')
|
|
131
|
+
// .option('-d, --dir <dir>', 'Project directory')
|
|
132
|
+
// .option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
|
|
133
|
+
// .option('--lang <lang>', 'Language used (python, ruby, java)')
|
|
134
|
+
// .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
135
|
+
// .action(async (pattern, opts) => {
|
|
136
|
+
// if (!pattern.endsWith('.xml')) {
|
|
137
|
+
// pattern += '.xml';
|
|
138
|
+
// }
|
|
139
|
+
// let { javaTests, lang } = opts;
|
|
140
|
+
// if (javaTests === true) javaTests = 'src/test/java';
|
|
141
|
+
// lang = lang?.toLowerCase();
|
|
142
|
+
// const runReader = new XmlReader({ javaTests, lang });
|
|
143
|
+
// const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
144
|
+
// if (!files.length) {
|
|
145
|
+
// console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
|
|
146
|
+
// process.exit(1);
|
|
147
|
+
// }
|
|
148
|
+
|
|
123
149
|
program
|
|
124
150
|
.command('xml')
|
|
125
151
|
.description('Parse XML reports and upload to Testomat.io')
|
|
@@ -273,6 +299,109 @@ program
|
|
|
273
299
|
}
|
|
274
300
|
});
|
|
275
301
|
|
|
302
|
+
program
|
|
303
|
+
.command('replay')
|
|
304
|
+
.description('Replay test data from debug file and re-send to Testomat.io')
|
|
305
|
+
.argument('[debug-file]', 'Path to debug file (defaults to /tmp/testomatio.debug.latest.json)')
|
|
306
|
+
.action(async (debugFile, opts) => {
|
|
307
|
+
// Use default debug file if none provided
|
|
308
|
+
if (!debugFile) {
|
|
309
|
+
debugFile = path.join(os.tmpdir(), 'testomatio.debug.latest.json');
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!fs.existsSync(debugFile)) {
|
|
313
|
+
console.log(APP_PREFIX, `❌ Debug file not found: ${debugFile}`);
|
|
314
|
+
return process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
console.log(APP_PREFIX, `🪲 Replaying data from debug file: ${debugFile}`);
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
const fileContent = fs.readFileSync(debugFile, 'utf-8');
|
|
321
|
+
const lines = fileContent.trim().split('\n').filter(Boolean);
|
|
322
|
+
|
|
323
|
+
if (lines.length === 0) {
|
|
324
|
+
console.log(APP_PREFIX, '❌ Debug file is empty');
|
|
325
|
+
return process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
let runParams = {};
|
|
329
|
+
let finishParams = {};
|
|
330
|
+
let parseErrors = 0;
|
|
331
|
+
const allTests = [];
|
|
332
|
+
|
|
333
|
+
// Parse debug file line by line
|
|
334
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
335
|
+
try {
|
|
336
|
+
const logEntry = JSON.parse(line);
|
|
337
|
+
|
|
338
|
+
if (logEntry.data === 'variables' && logEntry.testomatioEnvVars) {
|
|
339
|
+
Object.assign(process.env, logEntry.testomatioEnvVars, process.env);
|
|
340
|
+
} else if (logEntry.action === 'createRun') {
|
|
341
|
+
runParams = logEntry.params || {};
|
|
342
|
+
} else if (logEntry.action === 'addTestsBatch' && logEntry.tests) {
|
|
343
|
+
allTests.push(...logEntry.tests);
|
|
344
|
+
} else if (logEntry.action === 'addTest' && logEntry.testId) {
|
|
345
|
+
allTests.push(logEntry.testId);
|
|
346
|
+
} else if (logEntry.actions === 'finishRun') {
|
|
347
|
+
finishParams = logEntry.params || {};
|
|
348
|
+
}
|
|
349
|
+
} catch (err) {
|
|
350
|
+
parseErrors++;
|
|
351
|
+
if (parseErrors <= 3) {
|
|
352
|
+
// Only show first 3 parse errors
|
|
353
|
+
console.warn(APP_PREFIX, `⚠️ Failed to parse line ${lineIndex + 1}: ${line.substring(0, 100)}...`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (parseErrors > 3) {
|
|
359
|
+
console.warn(APP_PREFIX, `⚠️ ${parseErrors - 3} more parse errors occurred`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(APP_PREFIX, `📊 Found ${allTests.length} tests to replay`);
|
|
363
|
+
|
|
364
|
+
if (allTests.length === 0) {
|
|
365
|
+
console.log(APP_PREFIX, '❌ No test data found in debug file');
|
|
366
|
+
return process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const apiKey = config.TESTOMATIO;
|
|
370
|
+
if (!apiKey) {
|
|
371
|
+
console.log(APP_PREFIX, '❌ TESTOMATIO API key not found. Set TESTOMATIO environment variable.');
|
|
372
|
+
return process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Create client and restore the run
|
|
376
|
+
const client = new TestomatClient({
|
|
377
|
+
apiKey,
|
|
378
|
+
isBatchEnabled: true,
|
|
379
|
+
...runParams,
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
console.log(APP_PREFIX, '🚀 Publishing to run...');
|
|
383
|
+
await client.createRun(runParams);
|
|
384
|
+
|
|
385
|
+
// Send each test result - let client.js handle the data mapping and formatting
|
|
386
|
+
for (const [index, test] of allTests.entries()) {
|
|
387
|
+
try {
|
|
388
|
+
await client.addTestRun(test.status, test);
|
|
389
|
+
} catch (err) {
|
|
390
|
+
console.warn(APP_PREFIX, `⚠️ Failed to send test ${index + 1}: ${err.message}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await client.updateRunStatus(finishParams.status || STATUS.FINISHED, finishParams.parallel || false);
|
|
395
|
+
|
|
396
|
+
console.log(APP_PREFIX, `✅ Successfully replayed ${allTests.length} tests from debug file`);
|
|
397
|
+
process.exit(0);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
console.error(APP_PREFIX, '❌ Error replaying debug data:', err.message);
|
|
400
|
+
console.error(err.stack);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
276
405
|
program.parse(process.argv);
|
|
277
406
|
|
|
278
407
|
if (!process.argv.slice(2).length) {
|
package/src/bin/reportXml.js
CHANGED
|
@@ -5,8 +5,11 @@ 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 { getPackageVersion } from '../utils/utils.js';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const version = getPackageVersion();
|
|
10
13
|
|
|
11
14
|
const debug = createDebugMessages('@testomatio/reporter:xml-cli');
|
|
12
15
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io XML Reporter v${version}`)));
|
package/src/bin/startTest.js
CHANGED
|
@@ -4,10 +4,11 @@ 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 { getPackageVersion } from '../utils/utils.js';
|
|
8
8
|
import { config } from '../config.js';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
|
|
11
|
+
const version = getPackageVersion();
|
|
11
12
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
12
13
|
const program = new Command();
|
|
13
14
|
|
|
@@ -5,12 +5,13 @@ 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 { getPackageVersion } from '../utils/utils.js';
|
|
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();
|
|
14
15
|
console.log(pc.cyan(pc.bold(` 🤩 Testomat.io Reporter v${version}`)));
|
|
15
16
|
const program = new Command();
|
|
16
17
|
|
package/src/client.js
CHANGED
|
@@ -31,7 +31,6 @@ class Client {
|
|
|
31
31
|
* Create a Testomat client instance
|
|
32
32
|
* @returns
|
|
33
33
|
*/
|
|
34
|
-
// eslint-disable-next-line
|
|
35
34
|
constructor(params = {}) {
|
|
36
35
|
this.paramsForPipesFactory = params;
|
|
37
36
|
this.pipeStore = {};
|
|
@@ -79,7 +78,7 @@ class Client {
|
|
|
79
78
|
try {
|
|
80
79
|
const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
|
|
81
80
|
|
|
82
|
-
if (!filterPipe
|
|
81
|
+
if (!filterPipe?.isEnabled) {
|
|
83
82
|
// TODO:for the future for the another pipes
|
|
84
83
|
console.warn(
|
|
85
84
|
APP_PREFIX,
|
package/src/config.js
CHANGED
|
@@ -6,10 +6,10 @@ const debug = createDebugMessages('@testomatio/reporter:config');
|
|
|
6
6
|
/* for possibility to use multiple env files (reading different paths)
|
|
7
7
|
const envFileVars = dotenv.config({ path: '.env' }).parsed; */
|
|
8
8
|
|
|
9
|
-
if (process.env.TESTOMATIO_API_KEY) {
|
|
9
|
+
if (process.env.TESTOMATIO_API_KEY && !process.env.TESTOMATIO) {
|
|
10
10
|
process.env.TESTOMATIO = process.env.TESTOMATIO_API_KEY;
|
|
11
11
|
}
|
|
12
|
-
if (process.env.TESTOMATIO_TOKEN) {
|
|
12
|
+
if (process.env.TESTOMATIO_TOKEN && !process.env.TESTOMATIO) {
|
|
13
13
|
process.env.TESTOMATIO = process.env.TESTOMATIO_TOKEN;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import Adapter from './adapter.js';
|
|
2
3
|
|
|
3
4
|
class CSharpAdapter extends Adapter {
|
|
@@ -7,10 +8,21 @@ class CSharpAdapter extends Adapter {
|
|
|
7
8
|
if (example) t.example = { ...example[1].split(',') };
|
|
8
9
|
const suite = t.suite_title.split('.');
|
|
9
10
|
t.suite_title = suite.pop();
|
|
10
|
-
t.file =
|
|
11
|
+
t.file = namespaceToFileName(t.file);
|
|
11
12
|
t.title = title.trim();
|
|
12
13
|
return t;
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
getFilePath(t) {
|
|
17
|
+
const fileName = namespaceToFileName(t.file);
|
|
18
|
+
return fileName;
|
|
19
|
+
}
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
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
|
+
}
|