UNPKG

test-results-parser

Version:

Parse test results from JUnit, TestNG, xUnit, cucumber and many more

239 lines (200 loc) 7.44 kB
const { getJsonFromXMLFile } = require('../helpers/helper'); const TestResult = require('../models/TestResult'); const TestSuite = require('../models/TestSuite'); const TestCase = require('../models/TestCase'); const TestAttachment = require('../models/TestAttachment'); const SUITE_TYPES_WITH_TEST_CASES = [ "TestFixture", "ParameterizedTest", "GenericFixture", "ParameterizedMethod" // v3 ] const RESULT_MAP = { Success: "PASS", // v2 Failure: "FAIL", // v2 Ignored: "SKIP", // v2 NotRunnable: "SKIP", // v2 Error: "ERROR", // v2 Inconclusive: "FAIL", // v2 Passed: "PASS", // v3 Failed: "FAIL", // v3 Skipped: "SKIP", // v3 } function populateAttachments(rawCase, attachments) { if (rawCase.attachments && rawCase.attachments.attachment) { let rawAttachments = rawCase.attachments.attachment; for (var i = 0; i < rawAttachments.length; i++) { var attachment = new TestAttachment(); attachment.path = rawAttachments[i].filePath; if (rawAttachments[i].description) { attachment.name = rawAttachments[i].description; } attachments.push(attachment); } } } function mergeMeta(map1, map2) { for (let kvp of Object.entries(map1)) { map2[kvp[0]] = kvp[1]; } } /** * * @param {*} raw * @param {TestCase | TestSuite} test_element */ function populateMetaData(raw, test_element) { // v2 supports categories if (raw.categories) { let categories = raw.categories.category; for (let i = 0; i < categories.length; i++) { let categoryName = categories[i]["@_name"]; test_element.tags.push(categoryName); // create comma-delimited list of categories if (test_element.metadata["Categories"]) { test_element.metadata["Categories"] = test_element.metadata["Categories"].concat(",", categoryName); } else { test_element.metadata["Categories"] = categoryName; } } } // v2/v3 support properties if (raw.properties) { let properties = raw.properties.property; for (let i = 0; i < properties.length; i++) { let property = properties[i]; let propName = property["@_name"]; let propValue = property["@_value"]; // v3 treats 'Categories' as property "Category" if (propName == "Category") { if (test_element.metadata["Categories"]) { test_element.metadata["Categories"] = test_element.metadata["Categories"].concat(",", propValue); } else { test_element.metadata["Categories"] = propValue; } } else { test_element.metadata[propName] = propValue; } } } } function getNestedTestCases(rawSuite) { if (rawSuite.results) { return rawSuite.results["test-case"]; } else { return rawSuite["test-case"]; } } function hasNestedSuite(rawSuite) { return getNestedSuite(rawSuite) !== null; } function getNestedSuite(rawSuite) { // nunit v2 nests test-suite inside 'results' if (rawSuite.results && rawSuite.results["test-suite"]) { return rawSuite.results["test-suite"]; } else { // nunit v3 nests test-suites as immediate children if (rawSuite["test-suite"]) { return rawSuite["test-suite"]; } else { // not nested return null; } } } function getTestCases(rawSuite, parent_meta) { const cases = []; let rawTestCases = getNestedTestCases(rawSuite); if (rawTestCases) { for (let i = 0; i < rawTestCases.length; i++) { let rawCase = rawTestCases[i]; let testCase = new TestCase(); let result = rawCase["@_result"] testCase.id = rawCase["@_id"] ?? ""; testCase.name = rawCase["@_fullname"] ?? rawCase["@_name"]; testCase.duration = (rawCase["@_time"] ?? rawCase["@_duration"]) * 1000; // in milliseconds testCase.status = RESULT_MAP[result]; // v2 : non-executed should be tests should be Ignored if (rawCase["@_executed"] == "False") { testCase.status = "SKIP"; // exclude failures that weren't executed. } // v3 : failed tests with error label should be Error if (rawCase["@_label"] == "Error") { testCase.status = "ERROR"; } let errorDetails = rawCase.reason ?? rawCase.failure; if (errorDetails !== undefined) { testCase.setFailure(errorDetails.message); if (errorDetails["stack-trace"]) { testCase.stack_trace = errorDetails["stack-trace"] } } // populate attachments populateAttachments(rawCase, testCase.attachments); // copy parent_meta data to test case mergeMeta(parent_meta, testCase.metadata); populateMetaData(rawCase, testCase); cases.push(testCase); } } return cases; } function getTestSuites(rawSuites, assembly_meta) { const suites = []; for (let i = 0; i < rawSuites.length; i++) { let rawSuite = rawSuites[i]; if (rawSuite["@_type"] == "Assembly") { assembly_meta = {}; populateMetaData(rawSuite, { tags: [], metadata: assembly_meta }); } if (hasNestedSuite(rawSuite)) { // handle nested test-suites suites.push(...getTestSuites(getNestedSuite(rawSuite), assembly_meta)); } else if (SUITE_TYPES_WITH_TEST_CASES.indexOf(rawSuite["@_type"]) !== -1) { let suite = new TestSuite(); suite.id = rawSuite["@_id"] ?? ''; suite.name = rawSuite["@_fullname"] ?? rawSuite["@_name"]; suite.duration = (rawSuite["@_time"] ?? rawSuite["@_duration"]) * 1000; // in milliseconds suite.status = RESULT_MAP[rawSuite["@_result"]]; mergeMeta(assembly_meta, suite.metadata); populateMetaData(rawSuite, suite); suite.cases.push(...getTestCases(rawSuite, suite.metadata)); // calculate totals suite.total = suite.cases.length; suite.passed = suite.cases.filter(i => i.status == "PASS").length; suite.failed = suite.cases.filter(i => i.status == "FAIL").length; suite.errors = suite.cases.filter(i => i.status == "ERROR").length; suite.skipped = suite.cases.filter(i => i.status == "SKIP").length; suites.push(suite); } } return suites; } function getTestResult(json) { const nunitVersion = (json["test-results"] !== undefined) ? "v2" : (json["test-run"] !== undefined) ? "v3" : null; if (nunitVersion == null) { throw new Error("Unrecognized xml format"); } const result = new TestResult(); const rawResult = json["test-results"] ?? json["test-run"]; const rawSuite = rawResult["test-suite"][0]; result.name = rawResult["@_fullname"] ?? rawResult["@_name"]; result.duration = (rawSuite["@_time"] ?? rawSuite["@_duration"]) * 1000; // in milliseconds result.status = RESULT_MAP[rawSuite["@_result"]]; result.suites.push(...getTestSuites([rawSuite], null)); result.total = result.suites.reduce((total, suite) => { return total + suite.cases.length }, 0); result.passed = result.suites.reduce((total, suite) => { return total + suite.passed }, 0); result.failed = result.suites.reduce((total, suite) => { return total + suite.failed }, 0); result.skipped = result.suites.reduce((total, suite) => { return total + suite.skipped }, 0); result.errors = result.suites.reduce((total, suite) => { return total + suite.errors }, 0); return result; } function parse(file) { const json = getJsonFromXMLFile(file); return getTestResult(json); } module.exports = { parse }