import { ParseOptions , TestParser } from '../../test-parser' ; import { parseStringPromise } from 'xml2js' ; import { JunitReport , TestCase , TestSuite } from './jest-junit-types' ; import { getExceptionSource } from '../../utils/node-utils' ; import { getBasePath , normalizeFilePath } from '../../utils/path-utils' ; import { TestExecutionResult , TestRunResult , TestSuiteResult , TestGroupResult , TestCaseResult , TestCaseError } from '../../test-results' ; export class JestJunitParser implements TestParser { assumedWorkDir : string | undefined ; constructor ( readonly options ) { } async parse ( path , content ) { const ju = await this . getJunitReport ( path , content ) ; return this . getTestRunResult ( path , ju ) ; } private async getJunitReport ( path , content ) { try { return ( await parseStringPromise ( content ) ) as JunitReport ; } catch ( e ) { throw new Error ( `Invalid XML at ${ path }\n\n${ e }` ) ; } } private getTestRunResult ( path , junit ) { const suites = junit . testsuites . testsuite === undefined ? [ ] : junit . testsuites . testsuite . map ( ts => { const name = ts . $ . name . trim ( ) ; const time = parseFloat ( ts . $ . time ) * 1000 ; const sr = new TestSuiteResult ( name , this . getGroups ( ts ) , time ) ; return sr ; } ) ; const time = parseFloat ( junit . testsuites . $ . time ) * 1000 ; return new TestRunResult ( path , suites , time ) ; } private getGroups ( suite ) { const groups = [ ] ; for ( const tc of suite . testcase ) { let grp = groups . find ( g => g . describe === tc . $ . classname ) ; if ( grp === undefined ) { grp = { describe : tc . $ . classname , tests : [ ] } ; groups . push ( grp ) ; } grp . tests . push ( tc ) ; } return groups . map ( grp => { const tests = grp . tests . map ( tc => { const name = tc . $ . name . trim ( ) ; const result = this . getTestCaseResult ( tc ) ; const time = parseFloat ( tc . $ . time ) * 1000 ; const error = this . getTestCaseError ( tc ) ; return new TestCaseResult ( name , result , time , error ) ; } ) ; return new TestGroupResult ( grp . describe , tests ) ; } ) ; } private getTestCaseResult ( test ) { if ( test . failure ) return 'failed' ; if ( test . skipped ) return 'skipped' ; return 'success' ; } private getTestCaseError ( tc ) { if ( ! this . options . parseErrors || ! tc . failure ) { return undefined ; } const details = tc . failure [ 0 ] ; let path ; let line ; const src = getExceptionSource ( details , this . options . trackedFiles , file => this . getRelativePath ( file ) ) ; if ( src ) { path = src . path ; line = src . line ; } return { path , line , details } ; } private getRelativePath ( path ) { path = normalizeFilePath ( path ) ; const workDir = this . getWorkDir ( path ) ; if ( workDir !== undefined && path . startsWith ( workDir ) ) { path = path . substr ( workDir . length ) ; } return path ; } private getWorkDir ( path ) { return ( this . options . workDir ?? this . assumedWorkDir ?? ( this . assumedWorkDir = getBasePath ( path , this . options . trackedFiles ) ) ) ; } }