Skip to content

Commit 81dd1ab

Browse files
authored
Add JUnit XML reporter (#38)
1 parent 502b9c1 commit 81dd1ab

File tree

6 files changed

+154
-36
lines changed

6 files changed

+154
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
9+
### Added
10+
- Add JUnit XML reporter ([#38](https://github.com/cucumber/cucumber-node/pull/38))
911

1012
## [0.2.0] - 2025-02-20
1113
### Added

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ You might even be able to go without any extra dependencies and instead lean on
146146
Some Cucumber formatters are included as Node.js test reporters:
147147

148148
- HTML `--test-reporter=@cucumber/node/reporters/html --test-reporter-destination=./report.html`
149+
- JUnit `--test-reporter=@cucumber/node/reporters/junit --test-reporter-destination=./TEST-cucumber.xml`
149150
- Message `--test-reporter=@cucumber/node/reporters/message --test-reporter-destination=./messages.ndjson`
150151

151152
There are some caveats that apply when using these reporters (but not otherwise):

package-lock.json

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@cucumber/cucumber-expressions": "18.0.1",
3232
"@cucumber/gherkin": "30.0.4",
3333
"@cucumber/html-formatter": "21.9.0",
34+
"@cucumber/junit-xml-formatter": "0.7.1",
3435
"@cucumber/messages": "27.2.0",
3536
"@cucumber/tag-expressions": "6.1.2",
3637
"globby": "^14.1.0",

src/reporters/builtin/junit.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { TestEvent } from 'node:test/reporters'
2+
3+
import plugin from '@cucumber/junit-xml-formatter'
4+
import { Envelope } from '@cucumber/messages'
5+
6+
import { generateEnvelopes } from '../generateEnvelopes.js'
7+
8+
/*
9+
Signal to the runner that we are listening for messages so it should emit them
10+
*/
11+
process.env.CUCUMBER_MESSAGES_LISTENING = 'true'
12+
13+
export default async function* (source: AsyncIterable<TestEvent>): AsyncGenerator<string> {
14+
const output: string[] = []
15+
let handler: (envelope: Envelope) => void = () => {}
16+
plugin.formatter({
17+
on(_, newHandler) {
18+
handler = newHandler
19+
},
20+
write: (chunk) => output.push(chunk),
21+
options: {},
22+
})
23+
24+
const envelopes = generateEnvelopes(source)
25+
for await (const envelope of envelopes) {
26+
handler(envelope)
27+
}
28+
29+
for (const chunk of output) {
30+
yield chunk
31+
}
32+
}

test/integration/reporters.spec.ts

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,85 @@ import { makeTestHarness } from '../utils.js'
44
import path from 'node:path'
55

66
describe('Reporters', () => {
7-
it('does not emit messages as diagnostics if no cucumber reporters', async () => {
8-
const harness = await makeTestHarness()
9-
await harness.writeFile(
10-
'features/first.feature',
11-
`Feature:
7+
describe('spec', () => {
8+
it('does not emit messages as diagnostics if no cucumber reporters', async () => {
9+
const harness = await makeTestHarness()
10+
await harness.writeFile(
11+
'features/first.feature',
12+
`Feature:
1213
Scenario:
1314
Given a step
1415
`
15-
)
16-
await harness.writeFile(
17-
'features/steps.js',
18-
`import { Given } from '@cucumber/node'
16+
)
17+
await harness.writeFile(
18+
'features/steps.js',
19+
`import { Given } from '@cucumber/node'
1920
Given('a step', () => {})
2021
`
21-
)
22-
const [output] = await harness.run('spec')
23-
const sanitised = stripVTControlCharacters(output.trim())
24-
expect(sanitised).not.to.include('@cucumber/messages:')
25-
})
22+
)
23+
const [output] = await harness.run('spec')
24+
const sanitised = stripVTControlCharacters(output.trim())
25+
expect(sanitised).not.to.include('@cucumber/messages:')
26+
})
2627

27-
it('provides a useful error for an ambiguous step', async () => {
28-
const harness = await makeTestHarness()
29-
await harness.writeFile(
30-
'features/first.feature',
31-
`Feature:
28+
it('provides a useful error for an ambiguous step', async () => {
29+
const harness = await makeTestHarness()
30+
await harness.writeFile(
31+
'features/first.feature',
32+
`Feature:
3233
Scenario:
3334
Given a step`
34-
)
35-
await harness.writeFile(
36-
'features/steps.js',
37-
`import { Given } from '@cucumber/node'
35+
)
36+
await harness.writeFile(
37+
'features/steps.js',
38+
`import { Given } from '@cucumber/node'
3839
Given('a step', () => {})
3940
Given('a step', () => {})`
40-
)
41-
const [output] = await harness.run('spec')
42-
const sanitised = stripVTControlCharacters(output.trim())
43-
expect(sanitised).to.include(`Multiple matching step definitions found for text "a step":
41+
)
42+
const [output] = await harness.run('spec')
43+
const sanitised = stripVTControlCharacters(output.trim())
44+
expect(sanitised).to.include(`Multiple matching step definitions found for text "a step":
4445
1) ${path.join('features', 'steps.js')}:2:1
4546
2) ${path.join('features', 'steps.js')}:3:1`)
47+
})
48+
49+
it('provides a useful error for an undefined step', async () => {
50+
const harness = await makeTestHarness()
51+
await harness.writeFile(
52+
'features/first.feature',
53+
`Feature:
54+
Scenario:
55+
Given a step
56+
`
57+
)
58+
const [output] = await harness.run('spec')
59+
const sanitised = stripVTControlCharacters(output.trim())
60+
expect(sanitised).to.include('No matching step definitions found for text "a step"')
61+
})
4662
})
4763

48-
it('provides a useful error for an undefined step', async () => {
49-
const harness = await makeTestHarness()
50-
await harness.writeFile(
51-
'features/first.feature',
52-
`Feature:
64+
describe('junit', () => {
65+
it('outputs a junit xml report', async () => {
66+
const harness = await makeTestHarness()
67+
await harness.writeFile(
68+
'features/first.feature',
69+
`Feature:
5370
Scenario:
5471
Given a step
5572
`
56-
)
57-
const [output] = await harness.run('spec')
58-
const sanitised = stripVTControlCharacters(output.trim())
59-
expect(sanitised).to.include('No matching step definitions found for text "a step"')
73+
)
74+
await harness.writeFile(
75+
'features/steps.js',
76+
`import { Given } from '@cucumber/node'
77+
Given('a step', () => {})
78+
`
79+
)
80+
81+
const [output] = await harness.run('@cucumber/node/reporters/junit')
82+
83+
expect(output).to.include(
84+
'<system-out><![CDATA[Given a step................................................................passed]]></system-out>'
85+
)
86+
})
6087
})
6188
})

0 commit comments

Comments
 (0)