cucumber-html-report-generator
Version:
Generate beautiful cucumberjs html reports for multiple instances (browsers / devices)
209 lines (175 loc) • 8.63 kB
text/typescript
import * as Messages from '../helpers/console-messages';
import * as Models from '../models/models';
import * as jsonFuture from 'json-future';
import type * as messages from '@cucumber/messages';
import * as path from 'path';
import { format, intervalToDuration } from 'date-fns';
import fs, { promises as fsp } from 'fs';
import type { Readable } from 'stream';
import type { Stats } from 'fs';
import os from 'os';
export const streamToArray = async ( readableStream: Readable ): Promise<messages.Envelope[]> => {
return new Promise<messages.Envelope[]>(
(
resolve: ( wrappers: messages.Envelope[] ) => void,
reject: ( err: Error ) => void
) => {
const items: messages.Envelope[] = [];
readableStream.on( 'data', ( item: messages.Envelope ) => {
items.push( item );
} );
/* istanbul ignore next */
readableStream.on( 'error', ( err: Error ) => {
reject( err );
} );
readableStream.on( 'end', () => {
resolve( items );
} );
}
);
};
export const updatePercentages = ( results: Models.FeatureModuleResults | Models.ModuleResults ): Models.FeatureModuleResults | Models.ModuleResults => {
results.ambiguousPercentage = ( results.ambiguous ? results.ambiguous / results.total * 100 : 0 ).toFixed( 2 );
results.failedPercentage = ( results.failed ? results.failed / results.total * 100 : 0 ).toFixed( 2 );
results.undefinedPercentage = ( results.undefined ? results.undefined / results.total * 100 : 0 ).toFixed( 2 );
results.passedPercentage = ( results.passed ? results.passed / results.total * 100 : 0 ).toFixed( 2 );
results.pendingPercentage = ( results.pending ? results.pending / results.total * 100 : 0 ).toFixed( 2 );
results.skippedPercentage = ( results.skipped ? results.skipped / results.total * 100 : 0 ).toFixed( 2 );
if ( 'variousPercentage' in results ) {
results.variousPercentage = ( results.various ? results.various / results.total * 100 : 0 ).toFixed( 2 );
return results;
}
return results;
};
export const updateResultsAndPercentages = ( sourceResults: Models.ModuleResults, targetResults: Models.ModuleResults ): Models.ModuleResults => {
sourceResults.passed += targetResults.passed;
sourceResults.failed += targetResults.failed;
sourceResults.skipped += targetResults.skipped;
sourceResults.pending += targetResults.pending;
sourceResults.undefined += targetResults.undefined;
sourceResults.ambiguous += targetResults.ambiguous;
sourceResults.total += targetResults.total;
sourceResults.ambiguousPercentage = ( sourceResults.ambiguous ? sourceResults.ambiguous / sourceResults.total * 100 : 0 ).toFixed( 2 );
sourceResults.failedPercentage = ( sourceResults.failed ? sourceResults.failed / sourceResults.total * 100 : 0 ).toFixed( 2 );
sourceResults.undefinedPercentage = ( sourceResults.undefined ? sourceResults.undefined / sourceResults.total * 100 : 0 ).toFixed( 2 );
sourceResults.passedPercentage = ( sourceResults.passed ? sourceResults.passed / sourceResults.total * 100 : 0 ).toFixed( 2 );
sourceResults.pendingPercentage = ( sourceResults.pending ? sourceResults.pending / sourceResults.total * 100 : 0 ).toFixed( 2 );
sourceResults.skippedPercentage = ( sourceResults.skipped ? sourceResults.skipped / sourceResults.total * 100 : 0 ).toFixed( 2 );
return sourceResults;
};
export const updateStatus = ( featureResults: Models.FeatureResults ): Models.Result[] => {
const result = <Models.Result[]>[];
if ( featureResults.scenarios.ambiguous ) {
result.push( { icon: 'fas fa-question-circle ambiguous-color', status: Models.Status.ambiguous } );
}
if ( featureResults.scenarios.pending ) {
result.push( { icon: 'fas fa-question-circle pending-color', status: Models.Status.pending } );
}
if ( featureResults.scenarios.undefined ) {
result.push( { icon: 'fa fa-question-circle undefined-color', status: Models.Status.undefined } );
}
if ( featureResults.scenarios.skipped ) {
result.push( { icon: 'arrow-circle-right skipped-color', status: Models.Status.skipped } );
}
if ( featureResults.scenarios.failed ) {
result.push( { icon: 'exclamation-circle failed-color', status: Models.Status.failed } );
}
if ( featureResults.scenarios.passed ) {
result.push( { icon: 'check-circle passed-color', status: Models.Status.passed } );
}
return result;
};
export const isMetadata = ( metadata: Models.Metadata[] | undefined ): metadata is Models.Metadata[] =>
Array.isArray( metadata )
? !metadata.filter( metadataElement => ( !( 'name' in metadataElement ) || !( 'value' in metadataElement ) ) ).length
: false;
export const escapeHTML = ( value: string ): string => value
.replace( /&/gu, '&' )
.replace( /</gu, '<' )
.replace( />/gu, '>' );
export const isBase64 = ( value: string ): boolean => {
const base64regex = /^(?<firstGroup>[0-9a-zA-Z+/]{4})*(?<secondGroup>(?<thirdGroup>[0-9a-zA-Z+/]{2}==)|(?<fourthGroup>[0-9a-zA-Z+/]{3}=))?$/u;
return base64regex.test( value );
};
export const getFilesAsync = async ( dir: string ): Promise<string[]> => {
let files: string[] = [];
const getFiles = async ( folder: string ): Promise<string[]> => {
files = await fsp.readdir( folder );
const result: Promise<string[]>[] = files.map( async ( file ) => {
const fileNameAndPath = path.join( folder, file );
return fsp.stat( fileNameAndPath ).then( ( stat ) => ( stat.isDirectory() ? getFiles( fileNameAndPath ) : [ fileNameAndPath ] ) );
} );
return <string[]>Array.prototype.concat( ...( await Promise.all( result ) ) );
};
return getFiles( dir );
};
export const checkFolder = ( file: string | undefined ): boolean => {
if ( typeof file === 'undefined' ) {
return false;
}
let stats = <Stats>{};
try {
stats = fs.statSync( file );
} catch ( error: unknown ) {
throw new Error( Messages.invalidPathProvided( file ) );
}
return stats.isDirectory();
};
export const readJsonFile = async <T> ( file: string, encoding: BufferEncoding = 'utf8' ): Promise<T | null> => {
const report = await fsp.readFile( file, { encoding } );
try {
return <T>jsonFuture.parse( report );
} catch ( error: unknown ) {
console.log( ( Messages.invalidJsonProvided( file, ( <Error>error ).message ) ) );
}
return null;
};
export const saveJsonFile = async<T> ( filePath: string | undefined, fileName: string, json: T ): Promise<boolean> => {
const fileNameAndPath = path.join( filePath!, fileName );
const jsonString = await jsonFuture.stringifyAsync( { data: json, replacer: null, space: 4 } );
await fsp.writeFile( fileNameAndPath, jsonString, { encoding: 'utf8' } );
return true;
};
export const getDateFormatted = ( date: Date ): string => format( date, 'yyyy-MM-dd HH:mm:ss' );
export const convertTimeFromNanoSecondsToHHMMSS = ( time: number ): string => {
const duration = intervalToDuration( { start: 0, end: time / 1000000 } );
const hours = ( `${String( duration.hours )}` ).padStart( 2, '0' );
const minutes = ( `${String( duration.minutes )}` ).padStart( 2, '0' );
const seconds = ( `${String( duration.seconds )}` ).padStart( 2, '0' );
return `${hours}:${minutes}:${seconds}`;
};
export const moveArray = <T> ( arr: T[], fromIndex: number, toIndex: number ): T[] => {
const element = arr[ fromIndex ];
const deleteCountOne = 1;
arr.splice( fromIndex, deleteCountOne );
const deleteCountZero = 0;
arr.splice( toIndex, deleteCountZero, element );
return arr;
};
export const exists = ( filePath: string ): boolean => {
return fs.existsSync( filePath );
};
export const initializePath = ( reportPath?: string | undefined ): string => {
let reportPathFixed = reportPath;
const resultCheckReportCreationPath = checkFolder( reportPath );
if ( !resultCheckReportCreationPath && typeof reportPath === 'undefined' ) {
const date = format( new Date(), 'yyyy-MM-dd__HH-mm-ss' );
reportPathFixed = path.join( os.tmpdir(), 'cucumber-html-report-generator', date );
fs.mkdirSync( reportPathFixed, { recursive: true } );
}
return reportPathFixed!;
};
export const pathExists = async ( folderPath: string ): Promise<boolean> => {
return new Promise( ( resolve ) => {
fs.access( folderPath, fs.constants.F_OK, error => {
resolve( !error );
} );
} );
};
export const emptyFolder = async ( folderPath: string ): Promise<void> => {
/* istanbul ignore next */
if ( await pathExists( folderPath ) ) {
await fsp.rm( folderPath, { recursive: true } );
}
await fsp.mkdir( folderPath, { recursive: true } );
};