diff --git a/README.md b/README.md index d59e2ae..fc40f6a 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ With this configuration, you can run the tests with `npm test`. If the `TEAMCITY Jest config currently does not support passing any options to testResultsProcessor config. However, if used as a reporter, this plugin can be configured. +**rootDir** To change the rootDir config, pass it as a configuration to your reporter as so: ```javascript @@ -50,6 +51,49 @@ To change the rootDir config, pass it as a configuration to your reporter as so: "reporters": [["jest-teamcity", {"rootDir": "/path/to/your/dir"}]] ``` +**testNameGenerator** +You can change how test names are generated to configure how test names show up in teamcity. +Example: +```javascript +//jest.config.js +reporters: [ + [ + 'jest-teamcity', + { + testNameGenerator: (test) => { + return [...test.ancestorTitles, test.title].join('.'); + }, + }, + ], +], +``` + +**suiteNameGenerator** +You can also now change how suite names are chosen, rather than simply using file path. +Example: +```javascript +//jest.config.js +reporters: [ + [ + 'jest-teamcity', + { + suiteNameGenerator: (test, filename, suites) => { + if (filename.includes('automation/tests')) { + return 'Integration tests'; + } + + if (filename.indexOf('service/' === 0)) { + const [_, serviceName] = filename.split('/'); + return serviceName; + } + + return filename; + }, + }, + ], +], +``` + ### License MIT © [Ivan Tereshchenkov] diff --git a/lib/formatter.js b/lib/formatter.js index d457fc5..8ae7720 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -28,43 +28,78 @@ module.exports = { .replace(/'/g, "|'"); }, + /** + * Default test name generator + * @param {object} test + * @returns {string} + */ + testName(test) { + if (!test) { + return ""; + } + return test.title; + }, + + /** + * Default suite name generator + * @param {object} test + * @returns {string} + */ + suiteName(test, filename, suites) { + // todo this should probably be easier to read + const path = [filename].concat(test.ancestorTitles); + + // find current suite, creating each level if necessary + let currentSuite = suites; + let p; + for (p of path) { + if (!Object.prototype.hasOwnProperty.call(currentSuite, p)) { + currentSuite[p] = {}; + } + currentSuite = currentSuite[p]; + } + return p; + }, + + /** * Prints test message * @param {object} tests */ - printTestLog(tests, flowId) { + printTestLog(tests, flowId, nameGenerator) { if (tests) { Object.keys(tests).forEach(suiteName => { if (suiteName === "_tests_") { // print test details tests[suiteName].forEach(test => { - this.log(`##teamcity[testStarted name='${this.escape(test.title)}' flowId='${flowId}']`); + const testName = this.escape(nameGenerator(test)); + this.log(`##teamcity[testStarted name='${testName}' flowId='${flowId}']`); switch (test.status) { case "failed": if (test.failureMessages) { test.failureMessages.forEach(error => { const [message, ...stack] = error.split(errorMessageStackSeparator); this.log( - `##teamcity[testFailed name='${this.escape(test.title)}' message='${this.escape( + `##teamcity[testFailed name='${testName}' message='${this.escape( message )}' details='${this.escape(stack.join(errorMessageStackSeparator))}' flowId='${flowId}']` ); }); } else { - this.log(`##teamcity[testFailed name='${this.escape(test.title)}' flowId='${flowId}']`); + this.log(`##teamcity[testFailed name='${testName}' flowId='${flowId}']`); } break; case "pending": this.log( - `##teamcity[testIgnored name='${this.escape(test.title)}' message='pending' flowId='${flowId}']` + `##teamcity[testIgnored name='${testName}' message='pending' flowId='${flowId}']` ); break; case "passed": break; } this.log( - `##teamcity[testFinished name='${this.escape(test.title)}' duration='${ + `##teamcity[testFinished name='${testName}' duration='${ test.duration }' flowId='${flowId}']` ); @@ -72,7 +107,7 @@ module.exports = { } else { // print suite names this.log(`##teamcity[testSuiteStarted name='${this.escape(suiteName)}' flowId='${flowId}']`); - this.printTestLog(tests[suiteName], flowId); + this.printTestLog(tests[suiteName], flowId, nameGenerator); this.log(`##teamcity[testSuiteFinished name='${this.escape(suiteName)}' flowId='${flowId}']`); } }); @@ -92,7 +127,7 @@ module.exports = { * @param {array} testResults * @returns {object} */ - collectSuites(testResults, cwd) { + collectSuites(testResults, cwd, suiteNameGenerator) { if (!testResults) { return {}; } @@ -101,32 +136,39 @@ module.exports = { } const suites = {}; + const suitesByFileName = {}; testResults.forEach(testFile => { const filename = path.relative(cwd, testFile.testFilePath); testFile.testResults.forEach(test => { - const path = [filename].concat(test.ancestorTitles); - - // find current suite, creating each level if necessary - let currentSuite = suites; - for (const p of path) { - if (!Object.prototype.hasOwnProperty.call(currentSuite, p)) { - currentSuite[p] = {}; - } - currentSuite = currentSuite[p]; + const suiteName = suiteNameGenerator(test, filename, suites); + + if(!suites[suiteName]){ + suites[suiteName] = {}; } + const currentSuite = suites[suiteName]; + suitesByFileName[filename] = currentSuite; + // last level is array of test results if (!currentSuite["_tests_"]) { currentSuite["_tests_"] = []; } // add the current test - currentSuite["_tests_"].push(test); + currentSuite["_tests_"].push({ + ...test, + filename, + }); }); if (testFile.testExecError) { - suites[filename] = suites[filename] || {}; - suites[filename]['_tests_'] = [{ + if(!suitesByFileName[filename]) { + suites[filename] = {}; + } + + const suite = suitesByFileName[filename] || suites[filename]; + + suite['_tests_'] = [{ status: 'failed', title: 'Jest failed to execute suite', duration: 0, @@ -142,8 +184,8 @@ module.exports = { * Formats and outputs tests results * @param {array} testResults */ - formatReport(testResults, cwd, flowId) { - const suites = this.collectSuites(testResults, cwd); - this.printTestLog(suites, flowId); + formatReport(testResults, cwd, flowId, testNameGenerator, suiteNameGenerator) { + const suites = this.collectSuites(testResults, cwd, suiteNameGenerator || this.suiteName); + this.printTestLog(suites, flowId, testNameGenerator || this.testName); } }; diff --git a/lib/index.js b/lib/index.js index 484bbe1..5328817 100644 --- a/lib/index.js +++ b/lib/index.js @@ -12,10 +12,16 @@ module.exports = function(resultOrGlobalConfig, optionsIfReporter) { // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/new.target if (new.target) { if (teamCityVersion) { - const rootDir = (optionsIfReporter && optionsIfReporter.rootDir) || process.cwd(); + if(!optionsIfReporter) { + optionsIfReporter = {}; + } + + const rootDir = optionsIfReporter.rootDir || process.cwd(); + const testNameGenerator = optionsIfReporter.testNameGenerator; + const suiteNameGenerator = optionsIfReporter.suiteNameGenerator; this.onTestResult = (_, result) => { - formatter.formatReport([result], rootDir, flowId) + formatter.formatReport([result], rootDir, flowId, testNameGenerator, suiteNameGenerator) } }