test-results-parser
Version:
Parse test results from JUnit, TestNG, xUnit, cucumber and many more
243 lines (221 loc) • 7.46 kB
JavaScript
const path = require('path');
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');
function getTestCase(rawCase, suite_meta) {
const test_case = new TestCase();
test_case.name = rawCase["@_name"];
test_case.duration = rawCase["@_time"] * 1000;
test_case.metadata = Object.assign({}, suite_meta);
setAttachments(rawCase, test_case);
setMetaData(rawCase, test_case);
if (rawCase.failure && rawCase.failure.length > 0) {
test_case.status = 'FAIL';
setErrorAndStackTrace(test_case, rawCase);
} else if (rawCase.skipped != undefined) {
test_case.status = 'SKIP';
} else {
test_case.status = 'PASS';
}
return test_case;
}
function setErrorAndStackTrace(test_case, raw_case) {
test_case.setFailure(raw_case.failure[0]["@_message"]);
// wdio junit reporter
if (!test_case.failure && raw_case.error && raw_case.error.length > 0) {
test_case.setFailure(raw_case.error[0]["@_message"]);
}
if (raw_case['system-err'] && raw_case['system-err'].length > 0) {
test_case.stack_trace = raw_case['system-err'][0];
}
if (!test_case.stack_trace) {
if (raw_case.failure[0]["#text"]) {
test_case.stack_trace = raw_case.failure[0]["#text"];
}
}
}
/**
*
* @param {object} rawSuite
* @param {import('..').ParseOptions} options
* @returns
*/
function getTestSuite(rawSuite) {
const suite = new TestSuite();
suite.name = rawSuite["@_name"];
suite.total = rawSuite["@_tests"];
suite.failed = rawSuite["@_failures"];
const errors = rawSuite["@_errors"];
if (errors) {
suite.errors = errors;
}
const skipped = rawSuite["@_skipped"];
if (skipped) {
suite.skipped = skipped;
}
suite.total = suite.total - suite.skipped;
suite.passed = suite.total - suite.failed - suite.errors;
suite.duration = rawSuite["@_time"] * 1000;
suite.status = suite.total === suite.passed ? 'PASS' : 'FAIL';
setMetaData(rawSuite, suite);
const raw_test_cases = rawSuite.testcase;
if (raw_test_cases) {
for (let i = 0; i < raw_test_cases.length; i++) {
suite.cases.push(getTestCase(raw_test_cases[i], suite.metadata));
}
}
return suite;
}
/**
* @param {import('./junit.result').JUnitTestSuite | import('./junit.result').JUnitTestCase} rawElement
* @param {TestCase | TestSuite} test_element
*/
function setMetaData(rawElement, test_element) {
// Read properties from test suite or test case
if (rawElement.properties && rawElement.properties.property.length > 0) {
const raw_properties = rawElement.properties.property;
for (const raw_property of raw_properties) {
test_element.metadata[raw_property["@_name"]] = raw_property["@_value"];
}
}
// Read inline properties from system.out
setInlineMetadata(rawElement, test_element);
// Handle testsuite specific attributes
if (test_element instanceof TestSuite) {
if (rawElement["@_hostname"]) {
test_element.metadata["hostname"] = rawElement["@_hostname"];
}
}
}
function setInlineMetadata(rawElement, test_element) {
// Scan system.out for PROPERTY attributes
if (rawElement['system.out'] || rawElement['system-out']) {
const systemOut = rawElement['system.out'] || rawElement['system-out'];
// Regex for single-line properties: [[PROPERTY|key=value]]
const singleLineRegex = /\[\[PROPERTY\|([^=]+)=([^\]]+)\]\]/g;
let match;
while ((match = singleLineRegex.exec(systemOut)) !== null) {
const key = match[1].trim();
const value = match[2].trim();
test_element.metadata[key] = value;
}
// Regex for multi-line properties: [[PROPERTY|key]]value\nvalue\n[[/PROPERTY]]
const multiLineRegex = /\[\[PROPERTY\|([^\]=]+)\]\]([\s\S]*?)\[\[\/PROPERTY\]\]/g;
while ((match = multiLineRegex.exec(systemOut)) !== null) {
const key = match[1].trim();
const value = match[2].trim();
test_element.metadata[key] = value;
}
}
}
/**
* @param {import('./junit.result').JUnitTestCase} rawCase
* @param {TestCase} test_element
*/
function setAttachments(rawCase, test_element) {
if (rawCase['system.out'] || rawCase['system-out']) {
const systemOut = rawCase['system.out'] || rawCase['system-out'];
// junit attachments plug syntax is [[ATTACHMENT|/absolute/path/to/file.png]]
const regex = new RegExp('\\[\\[ATTACHMENT\\|([^\\]]+)\\]\\]', 'g');
while ((m = regex.exec(systemOut)) !== null) {
// avoid infinite loops with zero-width matches
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
let filePath = m[1].trim();
if (filePath.length > 0) {
const attachment = new TestAttachment();
attachment.path = filePath;
attachment.name = path.parse(filePath).base;
test_element.attachments.push(attachment);
}
}
}
}
/**
* @param {TestResult} result
*/
function setAggregateResults(result) {
if (Number.isNaN(result.passed) || Number.isNaN(result.failed)) {
let total = 0;
let passed = 0;
let failed = 0;
let errors = 0;
let skipped = 0;
result.suites.forEach(_suite => {
total = _suite.total + total;
passed = _suite.passed + passed;
failed = _suite.failed + failed;
errors = _suite.errors + errors;
skipped = _suite.skipped + skipped;
});
result.passed = passed;
result.failed = failed;
result.errors = errors;
result.skipped = skipped;
result.total = total;
}
if (Number.isNaN(result.duration)) {
let duration = 0;
result.suites.forEach(_suite => {
duration = _suite.duration + duration;
});
result.duration = duration;
}
}
/**
*
* @param {import('./junit.result').JUnitResultJson} json
* @param {import('..').ParseOptions} options
* @returns
*/
function getTestResult(json, options) {
const result = new TestResult();
const rawResult = json["testsuites"] ? json["testsuites"][0] : json["testsuite"];
result.name = rawResult["@_name"] || '';
result.total = rawResult["@_tests"];
result.failed = rawResult["@_failures"];
const errors = rawResult["@_errors"];
if (errors) {
result.errors = errors;
}
const skipped = rawResult["@_skipped"];
if (skipped) {
result.skipped = skipped;
}
result.total = result.total - result.skipped;
result.passed = result.total - result.failed - result.errors;
result.duration = rawResult["@_time"] * 1000;
// top-level element is testsuites
if (json["testsuites"]) {
const rawSuites = rawResult["testsuite"];
// Don't filter if there are no testsuite objects
if (!(typeof rawSuites === "undefined")) {
const filteredSuites = rawSuites.filter(suite => suite.testcase);
for (let i = 0; i < filteredSuites.length; i++) {
result.suites.push(getTestSuite(filteredSuites[i], options));
}
}
} else {
// top level element is testsuite
result.suites.push(getTestSuite(rawResult, options));
}
setAggregateResults(result);
result.status = result.total === result.passed ? 'PASS' : 'FAIL';
return result;
}
/**
*
* @param {string} file
* @param {import('..').ParseOptions} options
* @returns
*/
function parse(file, options) {
const json = getJsonFromXMLFile(file);
return getTestResult(json, options);
}
module.exports = {
parse
}