From b25caa1899ea0edf006eb5b5a373df1f92be4250 Mon Sep 17 00:00:00 2001 From: Anudeep Date: Sun, 3 Jul 2022 18:39:49 +0530 Subject: [PATCH 1/2] feat: read multiple files --- package-lock.json | 12 +- package.json | 6 +- src/helpers/helper.js | 24 +++- src/parsers/cucumber.js | 4 +- src/parsers/index.js | 68 ++++++++-- src/parsers/junit.js | 4 +- src/parsers/mocha.js | 4 +- src/parsers/testng.js | 4 +- src/parsers/xunit.js | 4 +- tests/parser.junit.spec.js | 72 ++++++++++ tests/parser.testng.spec.js | 253 ++++++++++++++++++++++++++++++++++++ 11 files changed, 428 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 50a4224..5252f7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "test-results-parser", - "version": "0.0.11", + "version": "0.1.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -292,6 +292,11 @@ "is-glob": "^4.0.1" } }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -597,6 +602,11 @@ "is-number": "^7.0.0" } }, + "totalist": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.0.tgz", + "integrity": "sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==" + }, "workerpool": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", diff --git a/package.json b/package.json index acb6920..e07a642 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "test-results-parser", - "version": "0.0.11", + "version": "0.1.0", "description": "Parse test results from JUnit, TestNG, xUnit and many more", "main": "src/index.js", "types": "./src/index.d.ts", @@ -29,7 +29,9 @@ }, "homepage": "https://github.com/test-results-reporter/parser#readme", "dependencies": { - "fast-xml-parser": "^3.20.0" + "fast-xml-parser": "^3.20.0", + "globrex": "^0.1.2", + "totalist": "^3.0.0" }, "devDependencies": { "mocha": "^10.0.0" diff --git a/src/helpers/helper.js b/src/helpers/helper.js index 4d4e981..de7830d 100644 --- a/src/helpers/helper.js +++ b/src/helpers/helper.js @@ -1,6 +1,8 @@ const fs = require('fs'); const path = require('path'); const parser = require('fast-xml-parser'); +const { totalist } = require('totalist/sync'); +const globrex = require('globrex'); function getJsonFromXMLFile(filePath) { const cwd = process.cwd(); @@ -8,6 +10,26 @@ function getJsonFromXMLFile(filePath) { return parser.parse(xml, { arrayMode: true, ignoreAttributes: false, parseAttributeValue: true }); } +/** + * @param {string} file_path + */ +function getMatchingFilePaths(file_path) { + if (file_path.includes('*')) { + const file_paths = []; + const result = globrex(file_path); + const dir_name = path.dirname(file_path.substring(0, file_path.indexOf('*') + 1)); + totalist(dir_name, (name) => { + const current_file_path = `${dir_name}/${name}`; + if (result.regex.test(current_file_path)) { + file_paths.push(current_file_path); + } + }); + return file_paths; + } + return [file_path]; +} + module.exports = { - getJsonFromXMLFile + getJsonFromXMLFile, + getMatchingFilePaths } \ No newline at end of file diff --git a/src/parsers/cucumber.js b/src/parsers/cucumber.js index 9d72758..42eac3a 100644 --- a/src/parsers/cucumber.js +++ b/src/parsers/cucumber.js @@ -92,9 +92,9 @@ function preprocess(rawjson) { return formattedResult; } -function parse(options) { +function parse(file) { const cwd = process.cwd(); - const json = require(path.join(cwd, options.files[0])); + const json = require(path.join(cwd, file)); return getTestResult(json); } diff --git a/src/parsers/index.js b/src/parsers/index.js index d4c0348..c8b5343 100644 --- a/src/parsers/index.js +++ b/src/parsers/index.js @@ -3,22 +3,64 @@ const junit = require('./junit'); const xunit = require('./xunit'); const mocha = require('./mocha'); const cucumber = require('./cucumber'); +const TestResult = require('../models/TestResult'); +const { getMatchingFilePaths } = require('../helpers/helper'); +/** + * @param {import('../models/TestResult')[]} results + */ +function merge(results) { + const main_result = new TestResult(); + for (let i = 0; i < results.length; i++) { + const current_result = results[i]; + if (!main_result.name) { + main_result.name = current_result.name; + } + main_result.total = main_result.total + current_result.total; + main_result.passed = main_result.passed + current_result.passed; + main_result.failed = main_result.failed + current_result.failed; + main_result.errors = main_result.errors + current_result.errors; + main_result.skipped = main_result.skipped + current_result.skipped; + main_result.retried = main_result.retried + current_result.retried; + main_result.duration = main_result.duration + current_result.duration; + main_result.suites = main_result.suites.concat(...current_result.suites); + } + main_result.status = results.every(_result => _result.status === 'PASS') ? 'PASS' : 'FAIL'; + return main_result; +} + +/** + * @param {import('../index').ParseOptions} options + */ function parse(options) { - switch (options.type) { - case 'testng': - return testng.parse(options); - case 'junit': - return junit.parse(options); - case 'xunit': - return xunit.parse(options); - case 'mocha': - return mocha.parse(options); - case 'cucumber': - return cucumber.parse(options); - default: - throw `UnSupported Result Type - ${options.type}`; + const results = []; + for (let i = 0; i < options.files.length; i++) { + const matched_files = getMatchingFilePaths(options.files[i]); + for (let j = 0; j < matched_files.length; j++) { + const file = matched_files[j]; + switch (options.type) { + case 'testng': + results.push(testng.parse(file)); + break; + case 'junit': + results.push(junit.parse(file)); + break; + case 'xunit': + results.push(xunit.parse(file)); + break; + case 'mocha': + results.push(mocha.parse(file)); + break; + case 'cucumber': + results.push(cucumber.parse(file)); + break; + default: + throw `UnSupported Result Type - ${options.type}`; + } + } + } + return merge(results); } module.exports = { diff --git a/src/parsers/junit.js b/src/parsers/junit.js index a2a29f5..b941117 100644 --- a/src/parsers/junit.js +++ b/src/parsers/junit.js @@ -60,8 +60,8 @@ function getTestResult(json) { return result; } -function parse(options) { - const json = getJsonFromXMLFile(options.files[0]); +function parse(file) { + const json = getJsonFromXMLFile(file); return getTestResult(json); } diff --git a/src/parsers/mocha.js b/src/parsers/mocha.js index b8f44a6..878b412 100644 --- a/src/parsers/mocha.js +++ b/src/parsers/mocha.js @@ -108,9 +108,9 @@ function formatMochaJsonReport(rawjson) { return formattedJson; } -function parse(options) { +function parse(file) { const cwd = process.cwd(); - const json = require(path.join(cwd, options.files[0])); + const json = require(path.join(cwd, file)); return getTestResult(json); } diff --git a/src/parsers/testng.js b/src/parsers/testng.js index 1e7b957..ebe374f 100644 --- a/src/parsers/testng.js +++ b/src/parsers/testng.js @@ -72,9 +72,9 @@ function getTestSuite(rawSuite) { return suite; } -function parse(options) { +function parse(file) { // TODO - loop through files - const json = getJsonFromXMLFile(options.files[0]); + const json = getJsonFromXMLFile(file); const result = new TestResult(); const results = json['testng-results'][0]; result.failed = results['@_failed']; diff --git a/src/parsers/xunit.js b/src/parsers/xunit.js index b94dabc..18448f1 100644 --- a/src/parsers/xunit.js +++ b/src/parsers/xunit.js @@ -68,8 +68,8 @@ function getTestResult(json) { return result; } -function parse(options) { - const json = getJsonFromXMLFile(options.files[0]); +function parse(file) { + const json = getJsonFromXMLFile(file); return getTestResult(json); } diff --git a/tests/parser.junit.spec.js b/tests/parser.junit.spec.js index afc38de..25de08c 100644 --- a/tests/parser.junit.spec.js +++ b/tests/parser.junit.spec.js @@ -210,4 +210,76 @@ describe('Parser - JUnit', () => { }); }); + it('multiple single suite files', () => { + const result = parse({ type: 'junit', files: ['tests/data/junit/single-suite.xml', 'tests/data/junit/single-suite.xml'] }); + assert.deepEqual(result, { + id: '', + name: 'result name', + total: 2, + passed: 0, + failed: 2, + errors: 0, + skipped: 0, + retried: 0, + duration: 20000, + status: 'FAIL', + suites: [ + { + id: '', + name: 'suite name', + total: 1, + passed: 0, + failed: 1, + errors: 0, + skipped: 0, + duration: 10000, + status: 'FAIL', + cases: [ + { + duration: 10000, + errors: 0, + failed: 0, + failure: "PROGRAM.cbl:2 Use a program name that matches the source file name", + id: "", + name: "Use a program name that matches the source file name", + passed: 0, + skipped: 0, + stack_trace: "", + status: "FAIL", + steps: [], + total: 0 + } + ] + }, + { + id: '', + name: 'suite name', + total: 1, + passed: 0, + failed: 1, + errors: 0, + skipped: 0, + duration: 10000, + status: 'FAIL', + cases: [ + { + duration: 10000, + errors: 0, + failed: 0, + failure: "PROGRAM.cbl:2 Use a program name that matches the source file name", + id: "", + name: "Use a program name that matches the source file name", + passed: 0, + skipped: 0, + stack_trace: "", + status: "FAIL", + steps: [], + total: 0 + } + ] + } + ] + }); + }); + }); \ No newline at end of file diff --git a/tests/parser.testng.spec.js b/tests/parser.testng.spec.js index 46d1d90..4c873e9 100644 --- a/tests/parser.testng.spec.js +++ b/tests/parser.testng.spec.js @@ -673,4 +673,257 @@ describe('Parser - TestNG', () => { }); }); + it('results using glob', () => { + const result = parse({ type: 'testng', files: ['tests/data/testng/single-*.xml'] }); + assert.deepEqual(result, { + "id": "", + "name": "Regression Tests", + "total": 24, + "passed": 12, + "failed": 11, + "errors": 0, + "skipped": 1, + "retried": 0, + "duration": 1405931, + "status": "FAIL", + "suites": [ + { + "id": "", + "name": "desktop-chrome", + "total": 5, + "passed": 2, + "failed": 3, + "errors": 0, + "skipped": 0, + "duration": 202082, + "status": "FAIL", + "cases": [ + { + "id": "", + "name": "GU", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 27168, + "status": "FAIL", + "failure": "expected [A] but found [948474]", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 28313, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 15381, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP_WA", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 57111, + "status": "FAIL", + "failure": "Expected condition failed: : 95ddbda01ea4b3dbcb049e681a6...}", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "CB", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 13221, + "status": "FAIL", + "failure": "element click intercepted:", + "stack_trace": "", + "steps": [] + } + ] + }, + { + "id": "", + "name": "mobile-ios", + "total": 5, + "passed": 2, + "failed": 2, + "errors": 0, + "skipped": 1, + "duration": 545598, + "status": "FAIL", + "cases": [ + { + "id": "", + "name": "GU", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 69776, + "status": "FAIL", + "failure": "expected [A] but found [948474]", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 103463, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 66833, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "SBP_WA", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 250674, + "status": "FAIL", + "failure": "Appium error: An unknown sr='Search...']}", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "CB", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 0, + "status": "SKIP", + "failure": "A script did not complete ", + "stack_trace": "", + "steps": [] + } + ] + }, + { + "id": "", + "name": "Default test", + "total": 4, + "passed": 4, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 2000, + "status": "PASS", + "cases": [ + { + "id": "", + "name": "c2", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 0, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "c3", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 10, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "c1", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 0, + "status": "PASS", + "failure": "", + "stack_trace": "", + "steps": [] + }, + { + "id": "", + "name": "c4", + "total": 0, + "passed": 0, + "failed": 0, + "errors": 0, + "skipped": 0, + "duration": 0, + "status": "PASS", + "failure": "expected [true] but found [false]", + "stack_trace": "", + "steps": [] + } + ] + } + ] + }); + }); + }); \ No newline at end of file From 780fc96bf16a3efa5cc6f9b59daf15dda56ed4d1 Mon Sep 17 00:00:00 2001 From: Anudeep Date: Sun, 3 Jul 2022 19:45:05 +0530 Subject: [PATCH 2/2] refactor: get parser --- src/parsers/index.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/parsers/index.js b/src/parsers/index.js index c8b5343..6cacfc4 100644 --- a/src/parsers/index.js +++ b/src/parsers/index.js @@ -29,36 +29,35 @@ function merge(results) { return main_result; } +function getParser(type) { + switch (type) { + case 'testng': + return testng; + case 'junit': + return junit; + case 'xunit': + return xunit; + case 'mocha': + return mocha; + case 'cucumber': + return cucumber; + default: + throw `UnSupported Result Type - ${options.type}`; + } +} + /** * @param {import('../index').ParseOptions} options */ function parse(options) { + const parser = getParser(options.type); const results = []; for (let i = 0; i < options.files.length; i++) { const matched_files = getMatchingFilePaths(options.files[i]); for (let j = 0; j < matched_files.length; j++) { const file = matched_files[j]; - switch (options.type) { - case 'testng': - results.push(testng.parse(file)); - break; - case 'junit': - results.push(junit.parse(file)); - break; - case 'xunit': - results.push(xunit.parse(file)); - break; - case 'mocha': - results.push(mocha.parse(file)); - break; - case 'cucumber': - results.push(cucumber.parse(file)); - break; - default: - throw `UnSupported Result Type - ${options.type}`; - } + results.push(parser.parse(file)); } - } return merge(results); }