diff --git a/README.md b/README.md index b89eb23..ad98cfc 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Environment variable and package.json configuration should be **strings**. Reporter options should also be strings exception for suiteNameTemplate, classNameTemplate, titleNameTemplate that can also accept a function returning a string. | Environment Variable Name | Reporter Config Name| Description | Default | Possible Injection Values -|--|--|--|--|--| +|---|---|---|---|---| | `JEST_SUITE_NAME` | `suiteName` | `name` attribute of `` | `"jest tests"` | N/A | `JEST_JUNIT_OUTPUT_DIR` | `outputDirectory` | Directory to save the output. | `process.cwd()` | N/A | `JEST_JUNIT_OUTPUT_NAME` | `outputName` | File name for the output. | `"junit.xml"` | N/A @@ -69,6 +69,7 @@ Reporter options should also be strings exception for suiteNameTemplate, classNa | `JEST_JUNIT_INCLUDE_CONSOLE_OUTPUT` | `includeConsoleOutput` | Adds console output to any testSuite that generates stdout during a test run. | `false` | N/A | `JEST_JUNIT_INCLUDE_SHORT_CONSOLE_OUTPUT` | `includeShortConsoleOutput` | Adds short console output (only message value) to any testSuite that generates stdout during a test run. | `false` | N/A | `JEST_JUNIT_REPORT_TEST_SUITE_ERRORS` | `reportTestSuiteErrors` | Reports test suites that failed to execute altogether as `error`. _Note:_ since the suite name cannot be determined from files that fail to load, it will default to file path.| `false` | N/A +| `JEST_JUNIT_NO_STACK_TRACE` | `noStackTrace` | Omit stack traces from test failure reports, similar to `jest --noStackTrace` | `false` | N/A | `JEST_USE_PATH_FOR_SUITE_NAME` | `usePathForSuiteName` | **DEPRECATED. Use `suiteNameTemplate` instead.** Use file path as the `name` attribute of `` | `"false"` | N/A diff --git a/__mocks__/failing-tests-with-failure-details.json b/__mocks__/failing-tests-with-failure-details.json new file mode 100644 index 0000000..ded1b5c --- /dev/null +++ b/__mocks__/failing-tests-with-failure-details.json @@ -0,0 +1,81 @@ +{ + "numFailedTestSuites": 0, + "numFailedTests": 0, + "numPassedTestSuites": 1, + "numPassedTests": 1, + "numPendingTestSuites": 0, + "numPendingTests": 0, + "numRuntimeErrorTestSuites": 0, + "numTotalTestSuites": 1, + "numTotalTests": 1, + "snapshot": { + "added": 0, + "failure": false, + "filesAdded": 0, + "filesRemoved": 0, + "filesUnmatched": 0, + "filesUpdated": 0, + "matched": 0, + "total": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "startTime": 1489712747092, + "success": true, + "testResults": [ + { + "console": [], + "failureMessage": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n", + "numFailingTests": 1, + "numPassingTests": 0, + "numPendingTests": 0, + "perfStats": { + "end": 1499904221109, + "start": 1499904215586 + }, + "snapshot": { + "added": 0, + "fileDeleted": false, + "matched": 0, + "unchecked": 0, + "unmatched": 0, + "updated": 0 + }, + "testFilePath": "/path/to/failing.test.js", + "testResults": [ + { + "ancestorTitles": [ + "Sample Failing Test", + "Inner", + "Inner Inner" + ], + "duration": 3930, + "failureDetails": [ + { + "error": { + "matcherResult": { + }, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2m", + "stack": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n" + }, + "message": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2m", + "passed": false, + "stack": "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n" + } + ], + "failureMessages": [ + "\u001b[1m\u001b[31m \u001b[1m● \u001b[1mSample Failing Test › Inner › Inner Inner › Should fail\u001b[39m\u001b[22m\n\n foobar\n\u001b[2m \n \u001b[2mat _callee$ (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:26:15)\u001b[2m\n \u001b[2mat tryCatch (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:64:40)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.invoke [as _invoke] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:299:22)\u001b[2m\n \u001b[2mat GeneratorFunctionPrototype.prototype.(anonymous function) [as next] (\u001b[2m\u001b[0m\u001b[36mnode_modules/regenerator-runtime/runtime.js\u001b[39m\u001b[0m\u001b[2m:116:21)\u001b[2m\n \u001b[2mat step (\u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:394)\u001b[2m\n \u001b[2mat \u001b[2m\u001b[0m\u001b[36mpath/to/failing.test.js\u001b[39m\u001b[0m\u001b[2m:2:554\u001b[2m\u001b[22m\n" + ], + "fullName": "Sample Failing Test Inner Inner Inner Should fail", + "numPassingAsserts": 0, + "status": "failed", + "title": "Should fail" + } + ], + "sourceMaps": {}, + "skipped": false + } + ], + "wasInterrupted": false +} diff --git a/__tests__/buildJsonResults.test.js b/__tests__/buildJsonResults.test.js index 6a7ecc5..0f52054 100644 --- a/__tests__/buildJsonResults.test.js +++ b/__tests__/buildJsonResults.test.js @@ -273,6 +273,54 @@ describe('buildJsonResults', () => { }); + it('should parse messages without stack trace when notStackTrace set to true and jest >= 26.3.0', () => { + const failingTestsReport = require('../__mocks__/failing-tests-with-failure-details.json'); + jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', + Object.assign({}, constants.DEFAULT_OPTIONS, { + noStackTrace: "true" + })); + + const failureMsg = jsonResults.testsuites[1].testsuite[2].testcase[1].failure; + + // Make sure no escape codes are there that exist in the mock + expect(failureMsg.includes('\u001b')).toBe(false); + expect(failureMsg).toMatch('Should fail'); + expect(failureMsg).not.toMatch('at _callee$ (path/to/failing.test.js:26:15)'); + expect(failureMsg).not.toMatch('at path/to/failing.test.js:2:554'); + + }); + + it('should parse messages with stack trace when notStackTrace set to false and jest >= 26.3.0', () => { + const failingTestsReport = require('../__mocks__/failing-tests-with-failure-details.json'); + jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', + Object.assign({}, constants.DEFAULT_OPTIONS, { + noStackTrace: "false" + })); + + const failureMsg = jsonResults.testsuites[1].testsuite[2].testcase[1].failure; + + // Make sure no escape codes are there that exist in the mock + expect(failureMsg.includes('\u001b')).toBe(false); + expect(failureMsg).toMatch('Should fail'); + expect(failureMsg).toMatch('at _callee$ (path/to/failing.test.js:26:15)'); + expect(failureMsg).toMatch('at path/to/failing.test.js:2:554'); + + }); + + it('should parse failure messages for failing tests and not crash when notStackTrace set to true and jest < 26.3.0', () => { + const failingTestsReport = require('../__mocks__/failing-tests.json'); // no failure details + jsonResults = buildJsonResults(failingTestsReport, '/path/to/test', + Object.assign({}, constants.DEFAULT_OPTIONS, { + noStackTrace: "true" + })); + + const failureMsg = jsonResults.testsuites[1].testsuite[2].testcase[1].failure; + + // Make sure no escape codes are there that exist in the mock + expect(failureMsg.includes('\u001b')).toBe(false); + expect(failureMsg).toMatch('Should fail'); + }); + it('should support displayName template var for jest multi-project', () => { const multiProjectNoFailingTestsReport = require('../__mocks__/multi-project-no-failing-tests.json'); diff --git a/constants/index.js b/constants/index.js index 42f9662..db51dbd 100644 --- a/constants/index.js +++ b/constants/index.js @@ -15,6 +15,7 @@ module.exports = { JEST_JUNIT_INCLUDE_CONSOLE_OUTPUT: 'includeConsoleOutput', JEST_JUNIT_INCLUDE_SHORT_CONSOLE_OUTPUT: 'includeShortConsoleOutput', JEST_JUNIT_REPORT_TEST_SUITE_ERRORS: 'reportTestSuiteErrors', + JEST_JUNIT_NO_STACK_TRACE: "noStackTrace", JEST_USE_PATH_FOR_SUITE_NAME: 'usePathForSuiteName', JEST_JUNIT_TEST_SUITE_PROPERTIES_JSON_FILE: 'testSuitePropertiesFile' }, @@ -32,6 +33,7 @@ module.exports = { includeConsoleOutput: 'false', includeShortConsoleOutput: 'false', reportTestSuiteErrors: 'false', + noStackTrace: 'false', testSuitePropertiesFile: 'junitProperties.js' }, SUITENAME_VAR: 'suitename', diff --git a/utils/buildJsonResults.js b/utils/buildJsonResults.js index fc5161c..e5b8c3b 100644 --- a/utils/buildJsonResults.js +++ b/utils/buildJsonResults.js @@ -188,7 +188,10 @@ module.exports = function (report, appDirectory, options) { // Write out all failure messages as tags // Nested underneath tag if (tc.status === 'failed'|| tc.status === 'error') { - tc.failureMessages.forEach((failure) => { + const failureMessages = options.noStackTrace === 'true' && tc.failureDetails ? + tc.failureDetails.map(detail => detail.message) : tc.failureMessages; + + failureMessages.forEach((failure) => { const tagName = tc.status === 'failed' ? 'failure': 'error' testCase.testcase.push({ [tagName]: stripAnsi(failure)