in action-junit-report/src/testParser.ts [170:349]
async function parseSuite(
/* eslint-disable @typescript-eslint/no-explicit-any */
suite: any,
parentName: string,
suiteRegex: string,
annotatePassed = false,
checkRetries = false,
excludeSources: string[],
checkTitleTemplate: string | undefined = undefined,
testFilesPrefix = '',
transformer: Transformer[]
): Promise<InternalTestResult> {
let totalCount = 0
let skipped = 0
const annotations: Annotation[] = []
if (!suite.testsuite && !suite.testsuites) {
return {totalCount, skipped, annotations}
}
const testsuites = suite.testsuite
? Array.isArray(suite.testsuite)
? suite.testsuite
: [suite.testsuite]
: Array.isArray(suite.testsuites.testsuite)
? suite.testsuites.testsuite
: [suite.testsuites.testsuite]
for (const testsuite of testsuites) {
if (!testsuite) {
return {totalCount, skipped, annotations}
}
let suiteName = ''
if (suiteRegex) {
if (parentName) {
suiteName = `${parentName}/${testsuite._attributes.name}`
} else if (suiteRegex !== '*') {
suiteName = testsuite._attributes.name.match(suiteRegex)
}
if (!suiteName) {
suiteName = testsuite._attributes.name
}
}
const res = await parseSuite(
testsuite,
suiteName,
suiteRegex,
annotatePassed,
checkRetries,
excludeSources,
checkTitleTemplate,
testFilesPrefix,
transformer
)
totalCount += res.totalCount
skipped += res.skipped
annotations.push(...res.annotations)
if (!testsuite.testcase) {
continue
}
let testcases = Array.isArray(testsuite.testcase)
? testsuite.testcase
: testsuite.testcase
? [testsuite.testcase]
: []
if (checkRetries) {
// identify duplicates, in case of flaky tests, and remove them
const testcaseMap = new Map<string, any>()
for (const testcase of testcases) {
const key = testcase._attributes.name
if (testcaseMap.get(key) !== undefined) {
// testcase with matching name exists
const failed = testcase.failure || testcase.error
const previousFailed = testcaseMap.get(key).failure || testcaseMap.get(key).error
if (failed && !previousFailed) {
// previous is a success, drop failure
core.debug(`Drop flaky test failure for (1): ${key}`)
} else if (!failed && previousFailed) {
// previous failed, new one not, replace
testcaseMap.set(key, testcase)
core.debug(`Drop flaky test failure for (2): ${key}`)
}
} else {
testcaseMap.set(key, testcase)
}
}
testcases = Array.from(testcaseMap.values())
}
for (const testcase of testcases) {
totalCount++
const failed = testcase.failure || testcase.error
const success = !failed
if (testcase.skipped || testcase._attributes.status === 'disabled') {
skipped++
}
const stackTrace: string = (
(testcase.failure && testcase.failure._cdata) ||
(testcase.failure && testcase.failure._text) ||
(testcase.error && testcase.error._cdata) ||
(testcase.error && testcase.error._text) ||
''
)
.toString()
.trim()
const message: string = (
(testcase.failure && testcase.failure._attributes && testcase.failure._attributes.message) ||
(testcase.error && testcase.error._attributes && testcase.error._attributes.message) ||
stackTrace.split('\n').slice(0, 2).join('\n') ||
testcase._attributes.name
).trim()
const pos = await resolveFileAndLine(
testcase._attributes.file || (testsuite._attributes !== undefined ? testsuite._attributes.file : null),
testcase._attributes.line || (testsuite._attributes !== undefined ? testsuite._attributes.line : null),
testcase._attributes.classname ? testcase._attributes.classname : testcase._attributes.name,
stackTrace
)
let transformedFileName = pos.fileName
for (const r of transformer) {
transformedFileName = applyTransformer(r, transformedFileName)
}
let resolvedPath =
failed || (annotatePassed && success)
? await resolvePath(transformedFileName, excludeSources)
: transformedFileName
core.debug(`Path prior to stripping: ${resolvedPath}`)
const githubWorkspacePath = process.env['GITHUB_WORKSPACE']
if (githubWorkspacePath) {
resolvedPath = resolvedPath.replace(`${githubWorkspacePath}/`, '') // strip workspace prefix, make the path relative
}
let title = ''
if (checkTitleTemplate) {
// ensure to not duplicate the test_name if file_name is equal
const fileName = pos.fileName !== testcase._attributes.name ? pos.fileName : ''
title = checkTitleTemplate
.replace(templateVar('FILE_NAME'), fileName)
.replace(templateVar('SUITE_NAME'), suiteName ?? '')
.replace(templateVar('TEST_NAME'), testcase._attributes.name)
} else if (pos.fileName !== testcase._attributes.name) {
title = suiteName
? `${pos.fileName}.${suiteName}/${testcase._attributes.name}`
: `${pos.fileName}.${testcase._attributes.name}`
} else {
title = suiteName ? `${suiteName}/${testcase._attributes.name}` : `${testcase._attributes.name}`
}
// optionally attach the prefix to the path
resolvedPath = testFilesPrefix ? pathHelper.join(testFilesPrefix, resolvedPath) : resolvedPath
core.info(`${resolvedPath}:${pos.line} | ${message.replace(/\n/g, ' ')}`)
annotations.push({
path: resolvedPath,
start_line: pos.line,
end_line: pos.line,
start_column: 0,
end_column: 0,
annotation_level: success ? 'notice' : 'failure',
title: escapeEmoji(title),
message: escapeEmoji(message),
raw_details: escapeEmoji(stackTrace)
})
}
}
return {totalCount, skipped, annotations}
}