diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4e626ca..79e57f976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber ## [Unreleased] +### Added +- Add the timestamp of the start time of the testsuite to the JUnit reporter ([#2443](https://github.com/cucumber/cucumber-js/pull/2443)) + ## [11.0.1] - 2024-09-14 ### Fixed - Add missing setParallelCanAssign export ([#2427](https://github.com/cucumber/cucumber-js/pull/2427)) diff --git a/src/formatter/junit_formatter.ts b/src/formatter/junit_formatter.ts index 03722198d..e0f6d73e3 100644 --- a/src/formatter/junit_formatter.ts +++ b/src/formatter/junit_formatter.ts @@ -9,7 +9,9 @@ import { Rule, TestStepResult, TestStepResultStatus, + TimeConversion, } from '@cucumber/messages' + import { doesHaveValue } from '../value_checker' import { valueOrDefault } from '../value_checker' import { ITestCaseAttempt } from './helpers/event_data_collector' @@ -26,6 +28,7 @@ interface IJUnitTestSuite { skipped: number time: number tests: IJUnitTestCase[] + timestamp?: string } interface IJUnitTestCase { @@ -70,6 +73,7 @@ interface IBuildJUnitTestStepOptions { export default class JunitFormatter extends Formatter { private readonly names: Record = {} private readonly suiteName: string + private testRunStarted?: messages.Timestamp public static readonly documentation: string = 'Outputs JUnit report' constructor(options: IFormatterOptions) { @@ -79,6 +83,9 @@ export default class JunitFormatter extends Formatter { 'cucumber-js' ) options.eventBroadcaster.on('envelope', (envelope: messages.Envelope) => { + if (doesHaveValue(envelope.testRunStarted)) { + this.testRunStarted = envelope.testRunStarted.timestamp + } if (doesHaveValue(envelope.testRunFinished)) { this.onTestRunFinished() } @@ -90,6 +97,15 @@ export default class JunitFormatter extends Formatter { .getTestCaseAttempts() .filter((attempt) => !attempt.willBeRetried) } + private getTimestamp(): string | undefined { + if (!this.testRunStarted) { + return undefined + } + const ms = TimeConversion.timestampToMillisecondsSinceEpoch( + this.testRunStarted + ) + return new Date(ms).toISOString().slice(0, -5) + } private getTestSteps( testCaseAttempt: ITestCaseAttempt, @@ -253,6 +269,7 @@ export default class JunitFormatter extends Formatter { failures, skipped, time: tests.reduce((total, test) => total + test.time, 0), + timestamp: this.getTimestamp(), } this.log(this.buildXmlReport(testSuite)) @@ -266,6 +283,9 @@ export default class JunitFormatter extends Formatter { .att('name', testSuite.name) .att('time', testSuite.time) .att('tests', testSuite.tests.length) + if (testSuite.timestamp) { + xmlReport.att('timestamp', testSuite.timestamp) + } testSuite.tests.forEach((test) => { const xmlTestCase = xmlReport.ele('testcase', { classname: test.classname, diff --git a/src/formatter/junit_formatter_spec.ts b/src/formatter/junit_formatter_spec.ts index a0721b3b3..f2194b452 100644 --- a/src/formatter/junit_formatter_spec.ts +++ b/src/formatter/junit_formatter_spec.ts @@ -76,7 +76,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '' + '' ) }) }) @@ -112,7 +112,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -147,7 +147,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -181,7 +181,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -218,7 +218,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -252,7 +252,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -289,7 +289,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -366,7 +366,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + @@ -408,7 +408,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -460,7 +460,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -511,7 +511,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -553,7 +553,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' in my belly................................passed]]>\n' + ' \n' + @@ -593,7 +593,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -628,7 +628,7 @@ describe('JunitFormatter', () => { // Assert expect(output).xml.to.deep.equal( '\n' + - '\n' + + '\n' + ' \n' + ' \n' + ' \n' + @@ -636,4 +636,29 @@ describe('JunitFormatter', () => { ) }) }) + describe('testsuite timestamp', () => { + it('has start time of the tests', async () => { + // Arrange + + // Act + const first = await testFormatter({ type: 'junit' }) + + // Assert + expect(first).xml.to.deep.equal( + '\n' + + '' + ) + + // Arrange + clock.tick(1000) + // Act + const second = await testFormatter({ type: 'junit' }) + + // Assert + expect(second).xml.to.deep.equal( + '\n' + + '' + ) + }) + }) })