UNPKG

7.09 kBJavaScriptView Raw
1'use strict';
2
3const stripAnsi = require('strip-ansi');
4const constants = require('../constants/index');
5const path = require('path');
6const fs = require('fs');
7
8// Wrap the varName with template tags
9const toTemplateTag = function (varName) {
10 return "{" + varName + "}";
11}
12
13// Replaces var using a template string or a function.
14// When strOrFunc is a template string replaces {varname} with the value from the variables map.
15// When strOrFunc is a function it returns the result of the function to which the variables are passed.
16const replaceVars = function (strOrFunc, variables) {
17 if (typeof strOrFunc === 'string') {
18 let str = strOrFunc;
19 Object.keys(variables).forEach((varName) => {
20 str = str.replace(toTemplateTag(varName), variables[varName]);
21 });
22 return str;
23 } else {
24 const func = strOrFunc;
25 const resolvedStr = func(variables);
26 if (typeof resolvedStr !== 'string') {
27 throw new Error('Template function should return a string');
28 }
29 return resolvedStr;
30 }
31};
32
33const executionTime = function (startTime, endTime) {
34 return (endTime - startTime) / 1000;
35}
36
37module.exports = function (report, appDirectory, options) {
38 // Check if there is a junitProperties.js (or whatever they called it)
39 const junitSuitePropertiesFilePath = path.join(process.cwd(), options.testSuitePropertiesFile);
40 let ignoreSuitePropertiesCheck = !fs.existsSync(junitSuitePropertiesFilePath);
41
42 // Generate a single XML file for all jest tests
43 let jsonResults = {
44 'testsuites': [{
45 '_attr': {
46 'name': options.suiteName,
47 'tests': 0,
48 'failures': 0,
49 // Overall execution time:
50 // Since tests are typically executed in parallel this time can be significantly smaller
51 // than the sum of the individual test suites
52 'time': executionTime(report.startTime, Date.now())
53 }
54 }]
55 };
56
57 // Iterate through outer testResults (test suites)
58 report.testResults.forEach((suite) => {
59 // Skip empty test suites
60 if (suite.testResults.length <= 0) {
61 return;
62 }
63
64 // If the usePathForSuiteName option is true and the
65 // suiteNameTemplate value is set to the default, overrides
66 // the suiteNameTemplate.
67 if (options.usePathForSuiteName === 'true' &&
68 options.suiteNameTemplate === toTemplateTag(constants.TITLE_VAR)) {
69
70 options.suiteNameTemplate = toTemplateTag(constants.FILEPATH_VAR);
71 }
72
73 // Build variables for suite name
74 const filepath = path.relative(appDirectory, suite.testFilePath);
75 const filename = path.basename(filepath);
76 const suiteTitle = suite.testResults[0].ancestorTitles[0];
77 const displayName = suite.displayName;
78
79 // Build replacement map
80 let suiteNameVariables = {};
81 suiteNameVariables[constants.FILEPATH_VAR] = filepath;
82 suiteNameVariables[constants.FILENAME_VAR] = filename;
83 suiteNameVariables[constants.TITLE_VAR] = suiteTitle;
84 suiteNameVariables[constants.DISPLAY_NAME_VAR] = displayName;
85
86 // Add <testsuite /> properties
87 const suiteNumTests = suite.numFailingTests + suite.numPassingTests + suite.numPendingTests;
88 const suiteExecutionTime = executionTime(suite.perfStats.start, suite.perfStats.end);
89
90 let testSuite = {
91 'testsuite': [{
92 _attr: {
93 name: replaceVars(options.suiteNameTemplate, suiteNameVariables),
94 errors: 0, // not supported
95 failures: suite.numFailingTests,
96 skipped: suite.numPendingTests,
97 timestamp: (new Date(suite.perfStats.start)).toISOString().slice(0, -5),
98 time: suiteExecutionTime,
99 tests: suiteNumTests
100 }
101 }]
102 };
103
104 // Update top level testsuites properties
105 jsonResults.testsuites[0]._attr.failures += suite.numFailingTests;
106 jsonResults.testsuites[0]._attr.tests += suiteNumTests;
107
108 // Write stdout console output if available
109 if (options.includeConsoleOutput === 'true' && suite.console && suite.console.length) {
110 // Stringify the entire console object
111 // Easier this way because formatting in a readable way is tough with XML
112 // And this can be parsed more easily
113 let testSuiteConsole = {
114 'system-out': {
115 _cdata: JSON.stringify(suite.console, null, 2)
116 }
117 };
118
119 testSuite.testsuite.push(testSuiteConsole);
120 }
121
122 // Write short stdout console output if available
123 if (options.includeShortConsoleOutput === 'true' && suite.console && suite.console.length) {
124 // Extract and then Stringify the console message value
125 // Easier this way because formatting in a readable way is tough with XML
126 // And this can be parsed more easily
127 let testSuiteConsole = {
128 'system-out': {
129 _cdata: JSON.stringify(suite.console.map(item => item.message), null, 2)
130 }
131 };
132
133 testSuite.testsuite.push(testSuiteConsole);
134 }
135
136 if (!ignoreSuitePropertiesCheck) {
137 let junitSuiteProperties = require(junitSuitePropertiesFilePath)(suite);
138
139 // Add any test suite properties
140 let testSuitePropertyMain = {
141 'properties': []
142 };
143
144 Object.keys(junitSuiteProperties).forEach((p) => {
145 let testSuiteProperty = {
146 'property': {
147 _attr: {
148 name: p,
149 value: replaceVars(junitSuiteProperties[p], suiteNameVariables)
150 }
151 }
152 };
153
154 testSuitePropertyMain.properties.push(testSuiteProperty);
155 });
156
157 testSuite.testsuite.push(testSuitePropertyMain);
158 }
159
160 // Iterate through test cases
161 suite.testResults.forEach((tc) => {
162 const classname = tc.ancestorTitles.join(options.ancestorSeparator);
163 const testTitle = tc.title;
164
165 // Build replacement map
166 let testVariables = {};
167 testVariables[constants.FILEPATH_VAR] = filepath;
168 testVariables[constants.FILENAME_VAR] = filename;
169 testVariables[constants.CLASSNAME_VAR] = classname;
170 testVariables[constants.TITLE_VAR] = testTitle;
171 testVariables[constants.DISPLAY_NAME_VAR] = displayName;
172
173 let testCase = {
174 'testcase': [{
175 _attr: {
176 classname: replaceVars(options.classNameTemplate, testVariables),
177 name: replaceVars(options.titleTemplate, testVariables),
178 time: tc.duration / 1000
179 }
180 }]
181 };
182
183 if (options.addFileAttribute === 'true') {
184 testCase.testcase[0]._attr.file = filepath;
185 }
186
187 // Write out all failure messages as <failure> tags
188 // Nested underneath <testcase> tag
189 if (tc.status === 'failed') {
190 tc.failureMessages.forEach((failure) => {
191 testCase.testcase.push({
192 'failure': stripAnsi(failure)
193 });
194 })
195 }
196
197 // Write out a <skipped> tag if test is skipped
198 // Nested underneath <testcase> tag
199 if (tc.status === 'pending') {
200 testCase.testcase.push({
201 skipped: {}
202 });
203 }
204
205 testSuite.testsuite.push(testCase);
206 });
207
208 jsonResults.testsuites.push(testSuite);
209 });
210
211 return jsonResults;
212};