Skip to content

Commit f75ac16

Browse files
authored
Merge pull request #491 from boxine/junit-output
Add option to output test results as JUnit report
2 parents 8920007 + 1b6c9dc commit f75ac16

4 files changed

Lines changed: 78 additions & 6 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Browser tests using [puppeteer](https://pptr.dev/) benefit from special support
88

99
Depending on the environment (you can set up configurations to run the same tests against dev, stage, prod etc.), tests can be skipped, or marked as _expected to fail_ for test driven development where you write tests first before fixing a bug or implementing a feature.
1010
A locking system prevents two tests or the same tests on two different machines from accessing a shared resource, e.g. a test account.
11-
You can review test results in a PDF report.
11+
You can review test results in a PDF report or create a JUnit report file to be consumed by CI systems.
1212

1313
## Installation
1414

@@ -391,6 +391,9 @@ The keys are up to you; for example you probably want to have a main entry point
391391
-J, --json Write tests results as a JSON file.
392392
--json-file FILE.json
393393
JSON file to write to. Defaults to results.json .
394+
--junit Write tests results as a JUnit XML file.
395+
--junit-file FILE.xml
396+
JUnit XML file to write to. Defaults to results.xml .
394397
-H, --html Write test results as an HTML file.
395398
--html-file FILE.html
396399
HTML file to write a report to. Defaults to results.html .

src/config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ function computeConcurrency(spec, { cpuCount = undefined } = {}) {
9191
function parseArgs(options, raw_args) {
9292
const DEFAULT_HTML_NAME = 'results.html';
9393
const DEFAULT_JSON_NAME = 'results.json';
94+
const DEFAULT_JUNIT_NAME = 'results.xml';
9495
const DEFAULT_MARKDOWN_NAME = 'results.md';
9596
const DEFAULT_PDF_NAME = 'results.pdf';
9697

@@ -184,6 +185,16 @@ function parseArgs(options, raw_args) {
184185
defaultValue: DEFAULT_JSON_NAME,
185186
help: 'JSON file to write to. Defaults to %(defaultValue)s .',
186187
});
188+
results_group.addArgument(['--junit'], {
189+
action: 'storeTrue',
190+
help: 'Write tests results as a JUnit XML file.',
191+
});
192+
results_group.addArgument(['--junit-file'], {
193+
metavar: 'FILE.xml',
194+
dest: 'junit_file',
195+
defaultValue: DEFAULT_JUNIT_NAME,
196+
help: 'JUnit XML file to write to. Defaults to %(defaultValue)s .',
197+
});
187198
results_group.addArgument(['-H', '--html'], {
188199
action: 'storeTrue',
189200
help: 'Write test results as an HTML file.',

src/render.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function craftResults(config, test_info) {
8888
async function doRender(config, results) {
8989
output.logVerbose(
9090
config,
91-
`[results] Render results JSON: ${config.json}, Markdown: ${config.markdown}, HTML: ${config.html}, PDF: ${config.pdf}`
91+
`[results] Render results JSON: ${config.json}, JUnit: ${config.junit}, Markdown: ${config.markdown}, HTML: ${config.html}, PDF: ${config.pdf}`
9292
);
9393

9494
if (config.json) {
@@ -125,6 +125,14 @@ async function doRender(config, results) {
125125
output.logVerbose(config, `Rendering to PDF ${config.pdf_file}...`);
126126
await pdf(config, config.pdf_file, results);
127127
}
128+
129+
if (config.junit) {
130+
output.logVerbose(config, `Rendering to JUnit ${config.junit_file}...`);
131+
const junit_xml = junit(results);
132+
await fs.promises.writeFile(config.junit_file, junit_xml, {
133+
encoding: 'utf-8',
134+
});
135+
}
128136
}
129137

130138
function format_duration(ms) {
@@ -650,6 +658,48 @@ async function pdf(config, path, results) {
650658
return html2pdf(config, path, html(results));
651659
}
652660

661+
/**
662+
* @param {import('./internal').CraftedResults} results
663+
*/
664+
function junit(results) {
665+
const testcases = results.tests.map(testResult => {
666+
const { skipped, taskResults } = testResult;
667+
668+
// Just take the first error if there is any, on retries it would probably be the same one
669+
const error = taskResults.find(tr => tr.status === 'error');
670+
671+
const res =
672+
`<testcase classname="${testResult.group}" name="${
673+
testResult.name
674+
}" time="${escape_html(_calcDuration(taskResults))}">` +
675+
(skipped ? `<skipped>${testResult.skipReason}</skipped>` : '') +
676+
(error
677+
? `<failure>${
678+
escape_html(error.error_stack) ||
679+
'INTERNAL ERROR: no error stack'
680+
}</failure>`
681+
: '') +
682+
(testResult.description
683+
? `<system-out>${escape_html(
684+
testResult.description
685+
)}</system-out>`
686+
: '') +
687+
`</testcase>`;
688+
return res;
689+
});
690+
691+
const result =
692+
'<?xml version="1.0"?>\n' +
693+
'<testsuites>\n' +
694+
'<testsuite>\n' +
695+
testcases.join('\n') +
696+
'\n' +
697+
'</testsuite>\n' +
698+
'</testsuites>';
699+
700+
return result;
701+
}
702+
653703
module.exports = {
654704
craftResults,
655705
doRender,

tests/selftest_flaky_result_repeat.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,21 @@ async function run() {
4545
}
4646
}
4747

48-
const summary = lines.slice(0, 3).map(s => s.trim());
49-
assert.deepEqual(summary, [
48+
const summary = lines.map(s => s.trim());
49+
const expected = [
5050
'3 tests passed',
5151
'3 failed (error[0], error[1], error[2])',
5252
'3 flaky (flaky[0], flaky[1], flaky[2])',
53-
]);
54-
assert.match(lines[3], /3 slowest tests: (.+), (.+), (.+)/);
53+
];
54+
for (const item of expected) {
55+
assert(
56+
summary.includes(item),
57+
`Expected test summary to include: ${item}\nActual summary: ${JSON.stringify(
58+
summary
59+
)}`
60+
);
61+
}
62+
assert.match(lines[lines.length - 1], /3 slowest tests: (.+), (.+), (.+)/);
5563
}
5664

5765
module.exports = {

0 commit comments

Comments
 (0)