import { Allure , AllureConfig , AllureGroup , AllureRuntime , AllureStep , AllureTest , AttachmentOptions , ContentType , ExecutableItemWrapper , isPromise , Label , LabelName , Stage , Status , StepInterface , } from "allure-js-commons" ; import FailedExpectation = jasmine . FailedExpectation ; const findAnyError = ( expectations ? ) : FailedExpectation | null => { expectations = expectations || [ ] ; if ( expectations . length > 0 ) { return expectations [ 0 ] ; } return null ; } ; const findMessageAboutThrow = ( expectations ? ) : FailedExpectation | null => { for ( const e of expectations || [ ] ) { if ( e . matcherName === "" ) { return e ; } } return null ; } ; enum SpecStatus { PASSED = "passed" , FAILED = "failed" , BROKEN = "broken" , PENDING = "pending" , DISABLED = "disabled" , EXCLUDED = "excluded" } type JasmineBeforeAfterFn = ( action : ( done : DoneFn ) => void , timeout ? : number ) => void ; export class JasmineAllureReporter implements jasmine . CustomReporter { private groupStack : AllureGroup [ ] = [ ] ; private labelStack : Label [ ] [ ] = [ [ ] ] ; private runningTest : AllureTest | null = null ; private stepStack : AllureStep [ ] = [ ] ; private runningExecutable : ExecutableItemWrapper | null = null ; private readonly runtime : AllureRuntime ; constructor ( config ) { this . runtime = new AllureRuntime ( config ) ; this . installHooks ( ) ; } get currentGroup ( ) : AllureGroup { const currentGroup = this . getCurrentGroup ( ) ; if ( currentGroup === null ) { throw new Error ( "No active group" ) ; } return currentGroup ; } getInterface ( ) { return new JasmineAllureInterface ( this , this . runtime ) ; } get currentTest ( ) : AllureTest { if ( this . runningTest === null ) { throw new Error ( "No active test" ) ; } return this . runningTest ; } get currentExecutable ( ) : ExecutableItemWrapper | null { return this . runningExecutable ; } writeAttachment ( content , options ) { return this . runtime . writeAttachment ( content , options ) ; } jasmineStarted ( ) { } suiteStarted ( suite ) { const name = suite . description ; const group = ( this . getCurrentGroup ( ) || this . runtime ) . startGroup ( name ) ; this . groupStack . push ( group ) ; this . labelStack . push ( [ ] ) ; } specStarted ( spec ) { let currentGroup = this . getCurrentGroup ( ) ; if ( currentGroup === null ) { throw new Error ( "No active suite" ) ; } currentGroup = currentGroup . startGroup ( "Test wrapper" ) ; this . groupStack . push ( currentGroup ) ; const name = spec . description ; const allureTest = currentGroup . startTest ( name ) ; if ( this . runningTest != null ) { throw new Error ( "Test is starting before other ended!" ) ; } this . runningTest = allureTest ; allureTest . fullName = spec . fullName ; allureTest . historyId = spec . fullName ; allureTest . stage = Stage . RUNNING ; if ( this . groupStack . length > 1 ) { allureTest . addLabel ( LabelName . PARENT_SUITE , this . groupStack [ 0 ] . name ) ; } if ( this . groupStack . length > 2 ) { allureTest . addLabel ( LabelName . SUITE , this . groupStack [ 1 ] . name ) ; } if ( this . groupStack . length > 3 ) { allureTest . addLabel ( LabelName . SUB_SUITE , this . groupStack [ 2 ] . name ) ; } for ( const labels of this . labelStack ) { for ( const label of labels ) { allureTest . addLabel ( label . name , label . value ) ; } } } specDone ( spec ) { const currentTest = this . runningTest ; if ( currentTest === null ) { throw new Error ( "specDone while no test is running" ) ; } if ( this . stepStack . length > 0 ) { console . error ( "Allure reporter issue: step stack is not empty on specDone" ) ; for ( const step of this . stepStack . reverse ( ) ) { step . status = Status . BROKEN ; step . stage = Stage . INTERRUPTED ; step . detailsMessage = "Timeout" ; step . endStep ( ) ; } this . stepStack = [ ] ; } if ( spec . status === SpecStatus . PENDING || spec . status === SpecStatus . DISABLED || spec . status === SpecStatus . EXCLUDED ) { currentTest . status = Status . SKIPPED ; currentTest . stage = Stage . PENDING ; currentTest . detailsMessage = spec . pendingReason || "Suite disabled" ; } currentTest . stage = Stage . FINISHED ; if ( spec . status === SpecStatus . PASSED ) { currentTest . status = Status . PASSED ; } if ( spec . status === SpecStatus . BROKEN ) { currentTest . status = Status . BROKEN ; } if ( spec . status === SpecStatus . FAILED ) { currentTest . status = Status . FAILED ; } const exceptionInfo = findMessageAboutThrow ( spec . failedExpectations ) || findAnyError ( spec . failedExpectations ) ; if ( exceptionInfo !== null ) { currentTest . detailsMessage = exceptionInfo . message ; currentTest . detailsTrace = exceptionInfo . stack ; } currentTest . endTest ( ) ; this . runningTest = null ; this . currentGroup . endGroup ( ) ; this . groupStack . pop ( ) ; } suiteDone ( ) { if ( this . runningTest !== null ) { console . error ( "Allure reporter issue: running test on suiteDone" ) ; } const currentGroup = this . getCurrentGroup ( ) ; if ( currentGroup === null ) { throw new Error ( "No active suite" ) ; } currentGroup . endGroup ( ) ; this . groupStack . pop ( ) ; this . labelStack . pop ( ) ; } jasmineDone ( ) { } addLabel ( name , value ) { if ( this . labelStack . length ) { this . labelStack [ this . labelStack . length - 1 ] . push ( { name , value } ) ; } } pushStep ( step ) { this . stepStack . push ( step ) ; } popStep ( ) { this . stepStack . pop ( ) ; } get currentStep ( ) : AllureStep | null { if ( this . stepStack . length > 0 ) { return this . stepStack [ this . stepStack . length - 1 ] ; } return null ; } private getCurrentGroup ( ) { if ( this . groupStack . length === 0 ) { return null ; } return this . groupStack [ this . groupStack . length - 1 ] ; } private installHooks ( ) { const reporter = this ; const jasmineBeforeAll = eval ( "global.beforeAll" ) ; const jasmineAfterAll = eval ( "global.afterAll" ) ; const jasmineBeforeEach = eval ( "global.beforeEach" ) ; const jasmineAfterEach = eval ( "global.afterEach" ) ; const makeWrapperAll = ( wrapped , fun ) => { return ( action , timeout ? ) : void => { wrapped ( ( done ) => { reporter . runningExecutable = fun ( ) ; let ret ; if ( action . length > 0 ) { ret = reporter . runningExecutable . wrap ( ( ) => new Promise ( ( resolve , reject ) => { const t = resolve ; t . fail = reject ; action ( t ) ; } ) ) ( ) ; } else { ret = reporter . runningExecutable . wrap ( action ) ( ) ; } if ( isPromise ( ret ) ) { ( ret as Promise < any > ) . then ( ( ) => { reporter . runningExecutable = null ; done ( ) ; } ) . catch ( ( e ) => { reporter . runningExecutable = null ; done . fail ( e ) ; } ) ; } else { reporter . runningExecutable = null ; done ( ) ; } } , timeout ) ; } ; } ; const wrapperBeforeAll = makeWrapperAll ( jasmineBeforeAll , ( ) => reporter . currentGroup . addBefore ( ) ) ; const wrapperAfterAll = makeWrapperAll ( jasmineAfterAll , ( ) => reporter . currentGroup . addAfter ( ) ) ; const wrapperBeforeEach = makeWrapperAll ( jasmineBeforeEach , ( ) => reporter . currentGroup . addBefore ( ) ) ; const wrapperAfterEach = makeWrapperAll ( jasmineAfterEach , ( ) => reporter . currentGroup . addAfter ( ) ) ; eval ( "global.beforeAll = wrapperBeforeAll;" ) ; eval ( "global.afterAll = wrapperAfterAll;" ) ; eval ( "global.beforeEach = wrapperBeforeEach;" ) ; eval ( "global.afterEach = wrapperAfterEach;" ) ; } } export class JasmineAllureInterface extends Allure { constructor ( private readonly reporter , runtime ) { super ( runtime ) ; } label ( name , value ) { try { this . reporter . currentTest . addLabel ( name , value ) ; } catch { this . reporter . addLabel ( name , value ) ; } } step < T > ( name , body ) { const wrappedStep = this . startStep ( name ) ; let result ; try { result = wrappedStep . run ( body ) ; } catch ( err ) { wrappedStep . setError ( err as Error ) ; wrappedStep . endStep ( ) ; throw err ; } if ( isPromise ( result ) ) { const promise = result as any as Promise < any > ; return promise . then ( ( a ) => { wrappedStep . endStep ( ) ; return a ; } ) . catch ( ( e ) => { wrappedStep . setError ( e ) ; wrappedStep . endStep ( ) ; throw e ; } ) as any as T ; } else { wrappedStep . endStep ( ) ; return result ; } } logStep ( name , status ? ) { const step = this . startStep ( name ) ; step . setStatus ( status ) ; step . endStep ( ) ; } attachment ( name , content , options ) { const file = this . reporter . writeAttachment ( content , options ) ; this . currentExecutable . addAttachment ( name , options , file ) ; } testAttachment ( name , content , options ) { const file = this . reporter . writeAttachment ( content , options ) ; this . currentTest . addAttachment ( name , options , file ) ; } protected get currentExecutable ( ) : ExecutableItemWrapper { return ( this . reporter . currentStep || this . reporter . currentExecutable || this . reporter . currentTest ) ; } protected get currentTest ( ) : AllureTest { return this . reporter . currentTest ; } private startStep ( name ) { const allureStep = this . currentExecutable . startStep ( name ) ; this . reporter . pushStep ( allureStep ) ; return new WrappedStep ( this . reporter , allureStep ) ; } } class WrappedStep { constructor ( private readonly reporter , private readonly step ) { } startStep ( name ) { const step = this . step . startStep ( name ) ; this . reporter . pushStep ( step ) ; return new WrappedStep ( this . reporter , step ) ; } setStatus ( status ? ) { this . step . status = status ; } setError ( error ) { this . step . status = Status . FAILED ; this . step . detailsMessage = error . message ; this . step . detailsTrace = error . stack ; } endStep ( ) { this . reporter . popStep ( ) ; this . step . endStep ( ) ; } run < T > ( body ) { return this . step . wrap ( body ) ( ) ; } }