Skip to content

Commit

Permalink
Merge pull request #160 from sginter/noStackTrace
Browse files Browse the repository at this point in the history
Add noStackTrace config setting to omit stack traces from the reports
  • Loading branch information
palmerj3 committed Oct 4, 2021
2 parents 017af76 + 8aab1d5 commit 6a84a3e
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 2 deletions.
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -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 `<testsuites>` | `"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
Expand All @@ -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 `<testsuite>` | `"false"` | N/A


Expand Down
81 changes: 81 additions & 0 deletions __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
}
48 changes: 48 additions & 0 deletions __tests__/buildJsonResults.test.js
Expand Up @@ -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');

Expand Down
2 changes: 2 additions & 0 deletions constants/index.js
Expand Up @@ -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'
},
Expand All @@ -32,6 +33,7 @@ module.exports = {
includeConsoleOutput: 'false',
includeShortConsoleOutput: 'false',
reportTestSuiteErrors: 'false',
noStackTrace: 'false',
testSuitePropertiesFile: 'junitProperties.js'
},
SUITENAME_VAR: 'suitename',
Expand Down
5 changes: 4 additions & 1 deletion utils/buildJsonResults.js
Expand Up @@ -188,7 +188,10 @@ module.exports = function (report, appDirectory, options) {
// Write out all failure messages as <failure> tags
// Nested underneath <testcase> 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)
Expand Down

0 comments on commit 6a84a3e

Please sign in to comment.