UNPKG

jest-junit

Version:

A jest reporter that generates junit xml files

171 lines (145 loc) 5.61 kB
'use strict'; const stripAnsi = require('strip-ansi'); const constants = require('../constants/index'); const path = require('path'); // Wrap the varName with template tags const toTemplateTag = function (varName) { return "{" + varName + "}"; } // Replaces var using a template string or a function. // When strOrFunc is a template string replaces {varname} with the value from the variables map. // When strOrFunc is a function it returns the result of the function to which the variables are passed. const replaceVars = function (strOrFunc, variables) { if (typeof strOrFunc === 'string') { let str = strOrFunc; Object.keys(variables).forEach((varName) => { str = str.replace(toTemplateTag(varName), variables[varName]); }); return str; } else { const func = strOrFunc; const resolvedStr = func(variables); if (typeof resolvedStr !== 'string') { throw new Error('Template function should return a string'); } return resolvedStr; } }; const executionTime = function (startTime, endTime) { return (endTime - startTime) / 1000; } module.exports = function (report, appDirectory, options) { // Generate a single XML file for all jest tests let jsonResults = { 'testsuites': [{ '_attr': { 'name': options.suiteName, 'tests': 0, 'failures': 0, // Overall execution time: // Since tests are typically executed in parallel this time can be significantly smaller // than the sum of the individual test suites 'time': executionTime(report.startTime, Date.now()) } }] }; // Iterate through outer testResults (test suites) report.testResults.forEach((suite) => { // Skip empty test suites if (suite.testResults.length <= 0) { return; } // If the usePathForSuiteName option is true and the // suiteNameTemplate value is set to the default, overrides // the suiteNameTemplate. if (options.usePathForSuiteName === 'true' && options.suiteNameTemplate === toTemplateTag(constants.TITLE_VAR)) { options.suiteNameTemplate = toTemplateTag(constants.FILEPATH_VAR); } // Build variables for suite name const filepath = path.relative(appDirectory, suite.testFilePath); const filename = path.basename(filepath); const suiteTitle = suite.testResults[0].ancestorTitles[0]; const displayName = suite.displayName; // Build replacement map let suiteNameVariables = {}; suiteNameVariables[constants.FILEPATH_VAR] = filepath; suiteNameVariables[constants.FILENAME_VAR] = filename; suiteNameVariables[constants.TITLE_VAR] = suiteTitle; suiteNameVariables[constants.DISPLAY_NAME_VAR] = displayName; // Add <testsuite /> properties const suiteNumTests = suite.numFailingTests + suite.numPassingTests + suite.numPendingTests; const suiteExecutionTime = executionTime(suite.perfStats.start, suite.perfStats.end); let testSuite = { 'testsuite': [{ _attr: { name: replaceVars(options.suiteNameTemplate, suiteNameVariables), errors: 0, // not supported failures: suite.numFailingTests, skipped: suite.numPendingTests, timestamp: (new Date(suite.perfStats.start)).toISOString().slice(0, -5), time: suiteExecutionTime, tests: suiteNumTests } }] }; // Update top level testsuites properties jsonResults.testsuites[0]._attr.failures += suite.numFailingTests; jsonResults.testsuites[0]._attr.tests += suiteNumTests; // Write stdout console output if available if (options.includeConsoleOutput === 'true' && suite.console && suite.console.length) { // Stringify the entire console object // Easier this way because formatting in a readable way is tough with XML // And this can be parsed more easily let testSuiteConsole = { 'system-out': { _cdata: JSON.stringify(suite.console, null, 2) } }; testSuite.testsuite.push(testSuiteConsole); } // Iterate through test cases suite.testResults.forEach((tc) => { const classname = tc.ancestorTitles.join(options.ancestorSeparator); const testTitle = tc.title; // Build replacement map let testVariables = {}; testVariables[constants.FILEPATH_VAR] = filepath; testVariables[constants.FILENAME_VAR] = filename; testVariables[constants.CLASSNAME_VAR] = classname; testVariables[constants.TITLE_VAR] = testTitle; testVariables[constants.DISPLAY_NAME_VAR] = displayName; let testCase = { 'testcase': [{ _attr: { classname: replaceVars(options.classNameTemplate, testVariables), name: replaceVars(options.titleTemplate, testVariables), time: tc.duration / 1000 } }] }; if (options.addFileAttribute === 'true') { testCase.testcase[0]._attr.file = filepath; } // Write out all failure messages as <failure> tags // Nested underneath <testcase> tag if (tc.status === 'failed') { tc.failureMessages.forEach((failure) => { testCase.testcase.push({ 'failure': stripAnsi(failure) }); }) } // Write out a <skipped> tag if test is skipped // Nested underneath <testcase> tag if (tc.status === 'pending') { testCase.testcase.push({ skipped: {} }); } testSuite.testsuite.push(testCase); }); jsonResults.testsuites.push(testSuite); }); return jsonResults; };