async function parseSuite()

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}
}