/**
 * Add Screenshot and Video attachments to the Cucumber logs
 */ import * as fs from 'fs' ; import * as path from 'path' ; import * as glob from 'glob' ; import { IJsonFeature , IJsonStep , } from '@cucumber/cucumber/lib/formatter/json_formatter' ; const [ , , cucumberDirPath = 'cypress/reports/cucumber' , screenshotsPath = 'cypress/screenshots' , videosPath = 'cypress/videos' , ] = process . argv ; const cucumberMap = { } ; const cucumberFileMap = { } ; param { IJsonFeature } cuke param { string } scenarioName returns { IJsonStep | undefined } const getFailedStepFromScenario = ( cuke , scenarioName ) : IJsonStep | undefined => { const myScenario = cuke . elements . find ( ( e ) => e . name . replace ( /"/g , '' ) === scenarioName ) ; const myStep = myScenario ?. steps . find ( ( step ) => step . result . status !== 'passed' ) ; return myStep ; } ; param { string } screenshotPath param { IJsonStep } step const addScreenshotToStep = ( screenshotPath , step ) : void => { const regExp = new RegExp ( `^(?:.*${ screenshotsPath })(.*/)([^/]*)$` ) ; const found = regExp . exec ( screenshotPath ) ; if ( ! found ) { return ; } const [ , screenshotFolder , screenshotName ] = found ; const encodedPath = `../assets/screenshots${ encodeURI ( screenshotFolder ) }\
${ encodeURIComponent ( screenshotName ) }` ; const screenshotImage = `<img class="screenshot" src="${ encodedPath }" />` ; if ( ! step . embeddings ) { step . embeddings = [ ] ; } step . embeddings . push ( { data : screenshotImage , mime_type : 'text/html' } ) ; } ; param { string } videoPath returns { string } const videoContainer = ( videoPath ) : string => { const videoHTML = `
  <a href="#video" data-toggle="collapse" class="collapsed">+ Show Video</a>
  <div id="video" class="scenario-step-collapse collapse" aria-expanded="false" style="height: 0px;">
    <div class="info">
      <video controls="" width="500"><source type="video/mp4" src="${ encodeURI ( videoPath ) }"></video>
    </div>
  </div>` ; return videoHTML ; } ; param { string } cukeDirPath param { Object } cukeFileMap param { Object } cukeMap const readCucumberFiles = ( cukeDirPath , cukeFileMap , cukeMap ) : void => { fs . readdirSync ( cukeDirPath , { withFileTypes : true } ) . filter ( ( file ) => file . isFile ( ) ) . map ( ( file ) => file . name ) . forEach ( ( fileName ) => { const arr = fileName . split ( '.' ) ; const featureName = `${ arr [ 0 ] }.feature` ; const json = JSON . parse ( fs . readFileSync ( path . join ( cukeDirPath , fileName ) ) . toString ( ) ) ; cukeFileMap [ featureName ] = fileName ; cukeMap [ featureName ] = json [ 0 ] ; } ) ; } ; param { string } videosDirPath param { Object } cukeMap const addVideoLinks = ( videosDirPath , cukeMap ) : void => { glob . sync ( `${ videosDirPath }/**/*.mp4` ) . map ( ( videoPath ) => ( { featureName : path . basename ( videoPath , '.mp4' ) , videoAbsolutePath : path . resolve ( videoPath ) , } ) ) . filter ( ( { featureName } ) => ! ! cukeMap [ featureName ] ) . forEach ( ( { featureName , videoAbsolutePath } ) => { const videoLink = videoContainer ( videoAbsolutePath ) ; const featureDescription = `${ cukeMap [ featureName ] . description }\n<br />${ videoLink }` ; cukeMap [ featureName ] . description = featureDescription ; } ) ; } ; param { string } screenshotsDirPath param { Object } cukeMap const addScreenshotLinks = ( screenshotsDirPath , cukeMap ) : void => { glob . sync ( `${ screenshotsDirPath }/**/*.png` ) . map ( ( screenshotPath ) => { return { featureName : path . basename ( path . dirname ( screenshotPath ) ) , screenshotName : path . basename ( screenshotPath ) , screenshotPath , } ; } ) . filter ( ( { featureName } ) => ! ! cukeMap [ featureName ] ) . forEach ( ( { featureName , screenshotName , screenshotPath } ) => { const scenarioName = screenshotName . match ( /--\s(.+).png/ ) [ 1 ] . replace ( '(failed)' , '' ) . replace ( /\(example #\d+\)/ , '' ) . trim ( ) ; const myStep = getFailedStepFromScenario ( cukeMap [ featureName ] , scenarioName ) ; if ( myStep ) { addScreenshotToStep ( screenshotPath , myStep ) ; } } ) ; } ; param { string } cukeDirPath param { Object } cukeFileMap param { Object } cukeMap const overrideCucumberFiles = ( cukeDirPath , cukeFileMap , cukeMap ) : void => { Object . entries ( cukeMap ) . forEach ( ( [ featureName , featureJson ] ) => { fs . writeFileSync ( path . join ( cukeDirPath , cukeFileMap [ featureName ] ) , JSON . stringify ( [ featureJson ] , null , 2 ) ) ; } ) ; } ; readCucumberFiles ( cucumberDirPath , cucumberFileMap , cucumberMap ) ; addVideoLinks ( videosPath , cucumberMap ) ; addScreenshotLinks ( screenshotsPath , cucumberMap ) ; overrideCucumberFiles ( cucumberDirPath , cucumberFileMap , cucumberMap ) ;