junit-xml
Version:
JUnit XML report builder with TypeScript support
114 lines (96 loc) • 3.73 kB
text/typescript
import { TestSuiteReport, TestSuite, TestCase } from './TestResults';
/**
*
* @param report A report in the default schema, as supported by GitLab
*/
export function getXmlObject(report: TestSuiteReport) {
const testSuites = report.suites.map(attachTestSuiteMetadata);
const xmlObject: { testsuites: any[] } = {
testsuites: testSuites
.filter(testSuite => testSuite.tests > 0)
.map((testSuite, index) => ({ testsuite: getTestSuiteXmlObject(testSuite, index) })),
};
return xmlObject;
}
function getTestSuiteXmlObject(testSuite: TestSuite & TestSuiteMetadata, index: number) {
const systemOuts = testSuite.testCases
.filter((testCase) => Array.isArray(testCase.systemOut) && testCase.systemOut.length > 0)
.map((outTestCase) => outTestCase.systemOut!.join('\n'))
.join('\n');
const systemErrs = testSuite.testCases
.filter((testCase) => Array.isArray(testCase.systemErr) && testCase.systemErr.length > 0)
.map((errTestCase) => errTestCase.systemErr!.join('\n'))
.join('\n');
const testSuiteXmlObject: any[] = testSuite.testCases
.map((testCase) => ({ testcase: getTestCaseXmlObject(testCase) }));
testSuiteXmlObject.push({ 'system-out': systemOuts });
testSuiteXmlObject.push({ 'system-err': systemErrs });
testSuiteXmlObject.unshift({ properties: [] })
const testsuiteAttributes: { [key: string]: string } = {
package: testSuite.name || `Test suite #${index}`,
id: index.toString(),
name: testSuite.name || `Test suite #${index}`,
timestamp: mangleIsoDate((testSuite.timestamp || new Date(0)).toISOString()),
hostname: testSuite.hostname || 'localhost',
tests: testSuite.tests.toString(),
errors: testSuite.errors.toString(),
failures: testSuite.failures.toString(),
time: (testSuite.time || 0).toString(),
};
testSuiteXmlObject.push({ _attr: testsuiteAttributes });
return testSuiteXmlObject;
}
function getTestCaseXmlObject(testCase: TestCase) {
const testCaseXmlObject: any[] = [];
const testcaseAttributes: { [key: string]: string } = {
name: testCase.name,
classname: testCase.classname || testCase.name,
time: (testCase.time || 0).toString(),
};
testCaseXmlObject.push({ _attr: testcaseAttributes });
if (Array.isArray(testCase.errors)) {
testCaseXmlObject.push({ error: formatMessages(testCase.errors) });
}
if (Array.isArray(testCase.failures)) {
testCaseXmlObject.push({ failure: formatMessages(testCase.failures) });
}
return testCaseXmlObject;
}
interface TestSuiteMetadata {
tests: number;
failures: number;
errors: number;
skipped: number;
};
function attachTestSuiteMetadata(testSuite: TestSuite): TestSuite & TestSuiteMetadata {
return {
...testSuite,
tests: testSuite.testCases.length,
failures: testSuite.testCases.filter(isFailing).length,
errors: testSuite.testCases.filter(hasErrors).length,
skipped: testSuite.testCases.filter(isSkipped).length,
};
}
function isSkipped(testCase: TestCase): boolean {
return testCase.skipped === true;
}
function isFailing(testCase: TestCase): boolean {
return Array.isArray(testCase.failures) && testCase.failures.length > 0;
}
function hasErrors(testCase: TestCase): boolean {
return Array.isArray(testCase.errors) && testCase.errors.length > 0;
}
function formatMessages(messages: Array<{ message: string; type?: string }>) {
return {
_attr: {
message: messages.map(message => message.message).join(', '),
type: 'Unkown',
},
};
}
/**
* The schema uses an ISO8601 date regex that does not include the timezone; this function strips it.
*/
function mangleIsoDate(isoDate: string): string {
return isoDate.substring(0, isoDate.length - 5);
}