diff --git a/spec/JUnitXmlReporterSpec.js b/spec/JUnitXmlReporterSpec.js index b2a65d8..0953296 100644 --- a/spec/JUnitXmlReporterSpec.js +++ b/spec/JUnitXmlReporterSpec.js @@ -159,6 +159,34 @@ describe("JUnitXmlReporter", function(){ }); }); + function assertTestsuitesTagAttributes(testSuitesTag, {disabled, errors, failures, tests} = {}) { + expect(testSuitesTag.getAttribute('disabled')).toBe(disabled); + expect(testSuitesTag.getAttribute('errors')).toBe(errors); + expect(testSuitesTag.getAttribute('failures')).toBe(failures); + expect(testSuitesTag.getAttribute('tests')).toBe(tests); + }; + + + it("the testsuites tags should include a time attribute", function() { + var testSuitesTags = writeCalls[0].xmldoc.getElementsByTagName('testsuites'); + expect(testSuitesTags.length).toBe(1); + var testSuitesTag = testSuitesTags[0]; + expect(testSuitesTag.getAttribute('time')).not.toBe(''); + }); + + describe("no xml output generation", function() { + beforeEach(function() { + setupReporterWithOptions({consolidateAll:true}); + triggerRunnerEvents(); + }); + + it("testsuites tags should default disabled, errors, failures to 0 when undefined", function() { + assertTestsuitesTagAttributes( + writeCalls[0].xmldoc.getElementsByTagName('testsuites')[0], + {disabled: '0', errors: '0', failures: '0', tests: '1'}); + }); + }); + describe("generated xml output", function(){ var subSuite, subSubSuite, siblingSuite; function itShouldIncludeXmlPreambleInAllFiles() { @@ -184,9 +212,11 @@ describe("JUnitXmlReporter", function(){ fakeSpec(subSuite, "should be one level down"); fakeSpec(subSubSuite, "should be two levels down"); var skipped = fakeSpec(subSubSuite, "should be skipped two levels down"); + var disabled = fakeSpec(subSubSuite, "should be disabled two levels down"); var failed = fakeSpec(subSubSuite, "should be failed two levels down"); fakeSpec(siblingSuite, "should be a sibling of Parent"); skipped.result.status = "pending"; + disabled.result.status = "disabled"; failed.result.status = "failed"; failed.result.failedExpectations.push({ passed: false, @@ -212,6 +242,11 @@ describe("JUnitXmlReporter", function(){ it("should write a single file using filePrefix as the filename", function() { expect(writeCalls[0].args[0]).toBe('results.xml'); }); + it("testsuites tags should include disabled, errors, failures, and tests (count) when defined", function() { + assertTestsuitesTagAttributes( + writeCalls[0].xmldoc.getElementsByTagName('testsuites')[0], + {disabled: '1', errors: '0', failures: '1', tests: '7'}); + }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); @@ -231,6 +266,12 @@ describe("JUnitXmlReporter", function(){ expect(writeCalls[0].args[0]).toBe('results-ParentSuite.xml'); expect(writeCalls[1].args[0]).toBe('results-SiblingSuiteWithInvalidChars.xml'); }); + it("testsuites tags should include disabled, errors, failures, and tests (count) when defined", function() { + assertTestsuitesTagAttributes(writeCalls[0].xmldoc.getElementsByTagName('testsuites')[0], + {disabled: '1', errors: '0', failures: '1', tests: '6'}); + assertTestsuitesTagAttributes(writeCalls[1].xmldoc.getElementsByTagName('testsuites')[0], + {disabled: '0', errors: '0', failures: '0', tests: '1'}); + }); itShouldHaveOneTestsuitesElementPerFile(); itShouldIncludeXmlPreambleInAllFiles(); }); @@ -300,7 +341,7 @@ describe("JUnitXmlReporter", function(){ }); it("should include total / failed / skipped counts for each suite (ignoring descendent results)", function() { expect(suites[1].getAttribute('tests')).toBe('1'); - expect(suites[2].getAttribute('tests')).toBe('3'); + expect(suites[2].getAttribute('tests')).toBe('4'); expect(suites[2].getAttribute('skipped')).toBe('1'); expect(suites[2].getAttribute('failures')).toBe('1'); }); @@ -346,7 +387,7 @@ describe("JUnitXmlReporter", function(){ }); it("should include specs in order", function() { expect(specs[0].getAttribute('name')).toContain('should be a dummy'); - expect(specs[4].getAttribute('name')).toBe('should be failed two levels down'); + expect(specs[5].getAttribute('name')).toBe('should be failed two levels down'); }); it("should escape bad xml characters in spec description", function() { expect(writeCalls[0].output).toContain("& < > " '"); @@ -355,15 +396,15 @@ describe("JUnitXmlReporter", function(){ expect(Number(specs[0].getAttribute('time'))).not.toEqual(NaN); }); it("should include failed matcher name as the failure type", function() { - var failure = specs[4].getElementsByTagName('failure')[0]; + var failure = specs[5].getElementsByTagName('failure')[0]; expect(failure.getAttribute('type')).toBe('toBe'); }); it("should include failure messages", function() { - var failure = specs[4].getElementsByTagName('failure')[0]; + var failure = specs[5].getElementsByTagName('failure')[0]; expect(failure.getAttribute('message')).toBe('Expected true to be false.'); }); it("should include stack traces for failed specs (using CDATA to preserve special characters)", function() { - var failure = specs[4].getElementsByTagName('failure')[0]; + var failure = specs[5].getElementsByTagName('failure')[0]; expect(failure.textContent).toContain('cool & can have "special" characters <3'); }); it("should include <skipped/> for skipped specs", function() { diff --git a/src/junit_reporter.js b/src/junit_reporter.js index 11c25ed..98e3da2 100644 --- a/src/junit_reporter.js +++ b/src/junit_reporter.js @@ -289,12 +289,18 @@ self.suiteDone(fakeFocusedSuite); } var output = ''; + var testSuitesResults = { disabled: 0, errors: 0, failures: 0, tests: 0, time: 0 }; for (var i = 0; i < suites.length; i++) { output += self.getOrWriteNestedOutput(suites[i]); + // retrieve nested suite data to include in the testsuites tag + var suiteResults = self.getNestedSuiteData(suites[i]); + for (var key in suiteResults) { + testSuitesResults[key] += suiteResults[key]; + }; } // if we have anything to write here, write out the consolidated file if (output) { - wrapOutputAndWriteFile(self.filePrefix, output); + wrapOutputAndWriteFile(self.filePrefix, output, testSuitesResults); } //log("Specs skipped but not reported (entire suite skipped or targeted to specific specs)", totalSpecsDefined - totalSpecsExecuted + totalSpecsDisabled); @@ -306,6 +312,27 @@ } }; + self.formatSuiteData = function(suite) { + return { + disabled: suite._disabled || 0, + errors: 0, + failures: suite._failures || 0, + tests: suite._specs.length || 0, + time: (suite._endTime.getTime() - suite._startTime.getTime()) || 0 + }; + }; + + self.getNestedSuiteData = function (suite) { + var suiteResults = self.formatSuiteData(suite); + for (var i = 0; i < suite._suites.length; i++) { + var childSuiteResults = self.getNestedSuiteData(suite._suites[i]); + for (var key in suiteResults) { + suiteResults[key] += childSuiteResults[key]; + }; + } + return suiteResults; + }; + self.getOrWriteNestedOutput = function(suite) { var output = suiteAsXml(suite); for (var i = 0; i < suite._suites.length; i++) { @@ -315,7 +342,7 @@ return output; } else { // if we aren't supposed to consolidate output, just write it now - wrapOutputAndWriteFile(generateFilename(suite), output); + wrapOutputAndWriteFile(generateFilename(suite), output, self.getNestedSuiteData(suite)); return ''; } }; @@ -467,17 +494,20 @@ self.logEntries.splice(0, self.logEntries.length); } } - - // To remove complexity and be more DRY about the silly preamble and <testsuites> element - var prefix = '<?xml version="1.0" encoding="UTF-8" ?>'; - if (self.stylesheetPath) { - prefix += '\n<?xml-stylesheet type="text/xsl" href="' + self.stylesheetPath + '" ?>'; + function getPrefix({disabled, errors, failures, tests, time} = {}) { + // To remove complexity and be more DRY about the silly preamble and <testsuites> element + var prefix = '<?xml version="1.0" encoding="UTF-8" ?>'; + if (self.stylesheetPath) { + prefix += '\n<?xml-stylesheet type="text/xsl" href="' + self.stylesheetPath + '" ?>'; + } + prefix += '\n<testsuites disabled="' + disabled + '" errors="' + errors + '" failures="' + failures + + '" tests="' + tests + '" time="' + time + '">'; + return prefix; } - prefix += '\n<testsuites>'; var suffix = '\n</testsuites>'; - function wrapOutputAndWriteFile(filename, text) { + function wrapOutputAndWriteFile(filename, text, testSuitesResults) { if (filename.substr(-4) !== '.xml') { filename += '.xml'; } - self.writeFile(filename, (prefix + text + suffix)); + self.writeFile(filename, (getPrefix(testSuitesResults) + text + suffix)); } }; })(this);