Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add noStackTrace config setting to omit stack traces from the reports #160

Merged
merged 1 commit into from Oct 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
|--|--|--|--|--|
|---|---|---|---|---|
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, but broke my IntelliJ parser, and markup docs say it should be 3 dashes :-)

| `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 @@ -68,6 +68,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 @@ -272,6 +272,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 @@ -14,6 +14,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 @@ -31,6 +32,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 @@ -186,7 +186,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