Last active
April 14, 2025 12:55
-
-
Save hassankhan/6f15f27bab6e4f5493c48c035a3ebe4b to your computer and use it in GitHub Desktop.
Script that merges JUnit test reports generated by Jest in an Nx monorepo and improves them for use in Azure DevOps
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env zx | |
import { $, fs, path } from 'zx'; | |
import { XMLParser, XMLBuilder } from 'fast-xml-parser'; | |
const junitDir = path.resolve(process.cwd(), 'coverage', 'junit'); | |
const mergedOutputFile = path.join(junitDir, 'complete-junit.xml'); | |
try { | |
const fileExists = await fs.exists(mergedOutputFile); | |
if (fileExists) { | |
await fs.remove(mergedOutputFile); | |
console.log(`Removed existing ${mergedOutputFile}`); | |
} | |
} catch (error) { | |
if (error.code !== 'ENOENT') { | |
console.error(`Error removing existing ${mergedOutputFile}:`, error); | |
} | |
} | |
const files = await fs.readdir(junitDir); | |
const reportFiles = files.filter( | |
(file) => file.endsWith('.junit.xml') && file !== 'complete-junit.xml', | |
); | |
console.log(`Found ${reportFiles.length} report files to process.`); | |
for (const reportFile of reportFiles) { | |
const projectNameMatch = reportFile.match(/^(.*?)\.junit\.xml$/); | |
if (!projectNameMatch) { | |
console.warn( | |
`Could not extract project name from ${reportFile}, skipping.`, | |
); | |
continue; | |
} | |
const projectName = projectNameMatch[1]; | |
const reportFilePath = path.join(junitDir, reportFile); | |
try { | |
const projectInfoRaw = await $`pnpm nx show project ${projectName} --json`; | |
const projectInfo = JSON.parse(projectInfoRaw.stdout); | |
const projectRoot = projectInfo.root; | |
if (!projectRoot) { | |
console.warn( | |
`Could not find root for project ${projectName}, skipping file ${reportFile}`, | |
); | |
continue; | |
} | |
console.log( | |
`Processing ${reportFile} for project ${projectName} (root: ${projectRoot})...`, | |
); | |
const xmlContent = await fs.readFile(reportFilePath, 'utf-8'); | |
const parser = new XMLParser({ | |
ignoreAttributes: false, | |
attributeNamePrefix: '', | |
}); | |
const parsedXml = parser.parse(xmlContent); | |
if (parsedXml.testsuites && parsedXml.testsuites.testsuite) { | |
const suites = Array.isArray(parsedXml.testsuites.testsuite) | |
? parsedXml.testsuites.testsuite | |
: [parsedXml.testsuites.testsuite]; | |
suites.forEach((suite) => { | |
if (suite.name && !suite.name.startsWith(projectRoot + '/')) { | |
suite.name = path.join(projectRoot, suite.name); | |
} | |
if (suite.testcase) { | |
const cases = Array.isArray(suite.testcase) | |
? suite.testcase | |
: [suite.testcase]; | |
cases.forEach((testcase) => { | |
if (testcase.file && !testcase.file.startsWith(projectRoot + '/')) { | |
testcase.file = path.join(projectRoot, testcase.file); | |
} | |
}); | |
} | |
}); | |
} else if (parsedXml.testsuite) { | |
const suite = parsedXml.testsuite; | |
if (suite.name && !suite.name.startsWith(projectRoot + '/')) { | |
suite.name = path.join(projectRoot, suite.name); | |
} | |
if (suite.testcase) { | |
const cases = Array.isArray(suite.testcase) | |
? suite.testcase | |
: [suite.testcase]; | |
cases.forEach((testcase) => { | |
if (testcase.file && !testcase.file.startsWith(projectRoot + '/')) { | |
testcase.file = path.join(projectRoot, testcase.file); | |
} | |
}); | |
} | |
} | |
const builder = new XMLBuilder({ | |
ignoreAttributes: false, | |
attributeNamePrefix: '', | |
format: true, | |
}); | |
const modifiedXmlContent = builder.build(parsedXml); | |
await fs.writeFile(reportFilePath, modifiedXmlContent); | |
console.log(` Processed and saved ${reportFile}`); | |
} catch (error) { | |
console.error( | |
`Error processing file ${reportFile} for project ${projectName}:`, | |
error, | |
); | |
} | |
} | |
console.log(`Merging reports into ${mergedOutputFile}...`); | |
try { | |
await $`pnpm junit-merge --dir=${junitDir} --out=${mergedOutputFile}`; | |
console.log('Successfully merged JUnit reports.'); | |
} catch (mergeError) { | |
console.error('Error merging JUnit reports:', mergeError); | |
process.exit(1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment