import { Serenity } from '@serenity-js/core' ; import { DomainEvent , RetryableSceneDetected , SceneFinished , SceneFinishes , SceneStarts , SceneTagged , TestRunFinished , TestRunFinishes , TestRunnerDetected , TestRunStarts , TestSuiteFinished , TestSuiteStarts , } from '@serenity-js/core/lib/events' ; import { FileSystemLocation , Path } from '@serenity-js/core/lib/io' ; import { ArbitraryTag , CorrelationId , ExecutionFailedWithError , ExecutionRetriedTag , ExecutionSuccessful , FeatureTag , Name , TestSuiteDetails } from '@serenity-js/core/lib/model' ; import { MochaOptions , reporters , Runnable , Runner , Suite , Test } from 'mocha' ; import { MochaOutcomeMapper , MochaTestMapper } from './mappers' ; import { OutcomeRecorder } from './OutcomeRecorder' ; package export class SerenityReporterForMocha extends reporters . Base { private readonly testMapper : MochaTestMapper = new MochaTestMapper ( ) ; private readonly outcomeMapper : MochaOutcomeMapper = new MochaOutcomeMapper ( ) ; private readonly recorder : OutcomeRecorder = new OutcomeRecorder ( ) ; private suiteIds : CorrelationId [ ] = [ ] ; private currentSceneId : CorrelationId = undefined ; param { Serenity } serenity param { mocha param { mocha constructor ( private readonly serenity , runner , options ? ) { super ( runner , options ) ; runner . on ( Runner . constants . EVENT_RUN_BEGIN , ( ) => { this . emit ( new TestRunStarts ( this . serenity . currentTime ( ) ) ) ; } ) ; runner . on ( Runner . constants . EVENT_SUITE_BEGIN , ( suite ) => { if ( suite . root === false ) { this . announceSuiteStartsFor ( suite ) ; } } ) ; runner . on ( Runner . constants . EVENT_SUITE_END , ( suite ) => { if ( suite . root === false ) { this . announceSuiteFinishedFor ( suite ) ; } } ) ; runner . on ( Runner . constants . EVENT_TEST_BEGIN , ( test ) => { this . recorder . started ( test ) ; this . announceSceneStartsFor ( test ) ; } ) ; runner . on ( Runner . constants . EVENT_TEST_PASS , ( test ) => { this . announceRetryIfNeeded ( test ) ; this . recorder . finished ( test . ctx ? test . ctx . currentTest : test , this . outcomeMapper . outcomeOf ( test ) ) ; } ) ; runner . on ( Runner . constants . EVENT_TEST_FAIL , ( test , error ) => { this . announceRetryIfNeeded ( test ) ; this . recorder . finished ( test . ctx ? test . ctx . currentTest : test , this . outcomeMapper . outcomeOf ( test , error ) ) ; } ) ; runner . on ( Runner . constants . EVENT_TEST_RETRY , ( test , error ) => { this . announceRetryIfNeeded ( test ) ; this . recorder . finished ( ! ! test . ctx && test . ctx . currentTest ? test . ctx . currentTest : test , this . outcomeMapper . outcomeOf ( test , error ) ) ; } ) ; const announceSceneFinishedFor = SerenityReporterForMocha . prototype . announceSceneFinishedFor . bind ( this ) ; runner . suite . afterEach ( 'Serenity/JS' , function ( ) { return announceSceneFinishedFor ( this . currentTest , this . test ) ; } ) ; runner . on ( 'test:after:run' , ( test ) => { return announceSceneFinishedFor ( test , test ) ; } ) ; runner . on ( Runner . constants . EVENT_TEST_PENDING , ( test ) => { if ( ! test . fn ) { this . announceSceneSkippedFor ( test ) ; } } ) ; } public done ( failures , fn ? ) { this . emit ( new TestRunFinishes ( this . serenity . currentTime ( ) ) ) ; this . serenity . waitForNextCue ( ) . then ( ( ) => { this . emit ( new TestRunFinished ( this . serenity . currentTime ( ) ) ) ; } ) . catch ( error => { this . emit ( new TestRunFinished ( this . serenity . currentTime ( ) ) ) ; } ) . then ( ( ) => fn ( failures ) ) ; } private announceSuiteStartsFor ( suite ) { const suiteId = CorrelationId . create ( ) ; this . suiteIds . push ( suiteId ) ; const details = new TestSuiteDetails ( new Name ( suite . title ) , new FileSystemLocation ( Path . from ( suite . file ! ) ) , suiteId ) ; this . emit ( new TestSuiteStarts ( details , this . serenity . currentTime ( ) ) ) ; } private announceSuiteFinishedFor ( suite ) { const details = new TestSuiteDetails ( new Name ( suite . title ) , new FileSystemLocation ( Path . from ( suite . file ! ) ) , this . suiteIds . pop ( ) ) ; const outcomes = suite . tests . map ( test => this . recorder . outcomeOf ( test ) || this . outcomeMapper . outcomeOf ( test ) ) ; const worstOutcome = outcomes . reduce ( ( worstSoFar , outcome ) => outcome . isWorseThan ( worstSoFar ) ? outcome : worstSoFar , new ExecutionSuccessful ( ) ) ; this . emit ( new TestSuiteFinished ( details , worstOutcome , this . serenity . currentTime ( ) ) ) ; } private announceSceneStartsFor ( test ) { this . currentSceneId = this . serenity . assignNewSceneId ( ) ; const scenario = this . testMapper . detailsOf ( test ) ; this . emit ( new SceneStarts ( this . currentSceneId , scenario , this . serenity . currentTime ( ) ) , new SceneTagged ( this . currentSceneId , new FeatureTag ( this . testMapper . featureNameFor ( test ) ) , this . serenity . currentTime ( ) ) , new TestRunnerDetected ( this . currentSceneId , new Name ( 'Mocha' ) , this . serenity . currentTime ( ) ) ) ; } private announceSceneFinishedFor ( test , runnable ) { const scenario = this . testMapper . detailsOf ( test ) , outcome = this . recorder . outcomeOf ( test ) || this . outcomeMapper . outcomeOf ( test ) ; this . emit ( new SceneFinishes ( this . currentSceneId , scenario , outcome , this . serenity . currentTime ( ) ) ) ; return this . serenity . waitForNextCue ( ) . then ( ( ) => { this . emit ( new SceneFinished ( this . currentSceneId , scenario , outcome , this . serenity . currentTime ( ) ) ) ; this . recorder . erase ( test ) ; } , error => { this . emit ( new SceneFinished ( this . currentSceneId , scenario , new ExecutionFailedWithError ( error ) , this . serenity . currentTime ( ) ) ) ; this . recorder . erase ( test ) ; ( runnable as any ) . error ( error ) ; } ) ; } private announceSceneSkippedFor ( test ) { const scenario = this . testMapper . detailsOf ( test ) , outcome = this . outcomeMapper . outcomeOf ( test ) ; this . announceSceneStartsFor ( test ) ; this . emit ( new SceneFinishes ( this . currentSceneId , scenario , outcome , this . serenity . currentTime ( ) ) , new SceneFinished ( this . currentSceneId , scenario , outcome , this . serenity . currentTime ( ) ) ) ; } private announceRetryIfNeeded ( test ) { if ( ! this . isRetriable ( test ) ) { return void 0 ; } this . emit ( new RetryableSceneDetected ( this . currentSceneId ) , new SceneTagged ( this . currentSceneId , new ArbitraryTag ( 'retried' ) , this . serenity . currentTime ( ) ) ) ; if ( this . currentRetryOf ( test ) > 0 ) { this . emit ( new SceneTagged ( this . currentSceneId , new ExecutionRetriedTag ( this . currentRetryOf ( test ) ) , this . serenity . currentTime ( ) ) ) ; } } private isRetriable ( test ) { return ( test as any ) . retries ( ) >= 0 ; } private currentRetryOf ( test ) { return ( test as any ) . currentRetry ( ) ; } private emit ( ... events ) { events . forEach ( event => this . serenity . announce ( event ) ) ; } }