cucumber-html-report-generator
Version:
Generate beautiful cucumberjs html reports for multiple instances (browsers / devices)
262 lines (242 loc) • 12.3 kB
text/typescript
import * as CommonFunctions from '../../common-functions/common-functions';
import * as ConsoleMessages from '../../helpers/console-messages';
import type * as Models from '../../models/models';
import * as fse from 'fs-extra';
import * as lodash from 'lodash';
import * as path from 'path';
import * as scripts from './html-charts-scripts-functions';
import fs, { promises as fsp } from 'fs';
import { ChartsData } from './charts-data';
import chalk from 'chalk';
import open from 'open';
const featureOverviewScriptsTemplate = '/resources/templates/scripts/feature-overview-scripts.tmpl';
const featuresOverviewScriptsTemplate = '/resources/templates/scripts/features-overview-scripts.tmpl';
const reportStylesheetDarkThemeTemplate = '/resources/templates/css/style-dark-theme.css';
const reportStylesheetLightThemeTemplate = '/resources/templates/css/style-light-theme.css';
const featuresOverviewIndexTemplate = '/resources/templates/components/features-overview/features-overview-index.tmpl';
const featuresOverviewTableTemplate = '/resources/templates/components/features-overview/features-overview-table.tmpl';
const pieChartTemplate = '/resources/templates/components/charts/pie-chart.tmpl';
const metadataTemplate = '/resources/templates/components/charts/metadata.tmpl';
const featureOverviewIndexTemplate = '/resources/templates/components/feature-overview/feature-overview-index.tmpl';
const scenarioStepsTemplate = '/resources/templates/components/feature-overview/scenario-elements/steps.tmpl';
const scenarioBeforeTemplate = '/resources/templates/components/feature-overview/scenario-elements/before.tmpl';
const scenarioAfterTemplate = '/resources/templates/components/feature-overview/scenario-elements/after.tmpl';
const tagsTemplate = '/resources/templates/components/feature-overview/scenario-elements/tags.tmpl';
const scenarioNameAndResultsTemplate = '/resources/templates/components/feature-overview/scenario-elements/name-and-results.tmpl';
const scenarioResultsTemplate = '/resources/templates/components/feature-overview/scenario-elements/results.tmpl';
const simpleScenarioTemplate = '/resources/templates/components/feature-overview/scenarios/simple-scenario.tmpl';
const outlineScenarioTemplate = '/resources/templates/components/feature-overview/scenarios/outline-scenario.tmpl';
const outlineScenarioChildTemplate = '/resources/templates/components/feature-overview/scenarios/outline-scenario-child.tmpl';
const resourcesFolder = '/resources/dependencies/';
export class GenerateHtml {
private readonly suite: Models.ExtendedReport;
private readonly scriptsFunctions: scripts.HtmlScriptsFunctions;
private readonly chartsData: ChartsData;
private readonly reportConfiguration: Models.ReportDisplay;
private readonly featuresOverviewIndex: string;
private readonly rootFolder: string;
public constructor( suite: Models.ExtendedReport, reportConfiguration: Models.ReportDisplay ) {
this.suite = suite;
this.chartsData = new ChartsData( this.suite );
this.reportConfiguration = reportConfiguration;
this.scriptsFunctions = new scripts.HtmlScriptsFunctions( this.reportConfiguration.theme! );
this.featuresOverviewIndex = path.join( this.reportConfiguration.reportPath!, 'index.html' );
const scriptName = path.basename( __filename );
this.rootFolder = path.join( path.dirname( require.resolve( `./${scriptName}` ) ), '../../..' );
}
public async createHtmlPages (): Promise<void> {
this.createFeaturesOverviewIndexPage();
await this.createFeatureIndexPages();
if ( !this.reportConfiguration.useCDN ) {
await this.copyResourcesToTargetFolder();
}
await this.openReportInBrowser();
}
private async openReportInBrowser (): Promise<void> {
/* istanbul ignore else */
if ( this.reportConfiguration.disableLog ) {
console.log( chalk.blue( ConsoleMessages.reportCreated( this.featuresOverviewIndex ) ) );
}
/* istanbul ignore next */
if ( this.reportConfiguration.openReportInBrowser ) {
const dir = path.join( this.reportConfiguration.reportPath!, 'features' );
const files = await fsp.readdir( dir );
/* istanbul ignore next */
if ( files.length === 1 && this.reportConfiguration.navigateToFeatureIfThereIsOnlyOne ) {
await open( path.join( dir, files[ 0 ] ) );
} else {
const result = await open( this.featuresOverviewIndex );
console.log( result );
}
}
}
private createFeaturesOverviewIndexPage (): void {
fs.writeFileSync(
this.featuresOverviewIndex,
this.generateTemplate( featuresOverviewIndexTemplate, {
featuresOverview: this.generateTemplate( featuresOverviewTableTemplate, {
suite: this.suite
} ),
featuresChart: this.generateTemplate( pieChartTemplate, {
results: this.suite.results.features,
dataBsTarget: '#Features-Charts, #Scenarios-Charts,#Metadata-Properties',
chartData: 'Features'
} ),
scenariosChart: this.generateTemplate( pieChartTemplate, {
results: this.suite.results.features,
dataBsTarget: '#Features-Charts, #Scenarios-Charts,#Metadata-Properties',
chartData: 'Scenarios'
} ),
metadata: this.generateTemplate( metadataTemplate, {
dataBsTarget: '#Features-Charts, #Scenarios-Charts,#Metadata-Properties',
metadata: this.suite.metadata,
metadataTitle: this.suite.metadataTitle
} ),
featuresOverviewScripts: this.generateTemplate( featuresOverviewScriptsTemplate, {
config: this.reportConfiguration,
chartsData: this.chartsData,
scriptsFunctions: this.scriptsFunctions,
suite: this.suite
} ),
config: this.reportConfiguration,
projectName: this.suite.reportTitle,
styles: this.generateTemplate( this.reportConfiguration.overrideStyle ?? ( this.reportConfiguration.theme === 'Dark' ? reportStylesheetDarkThemeTemplate : reportStylesheetLightThemeTemplate ) ),
customStyle: this.reportConfiguration.customStyle ? this.generateTemplate( this.reportConfiguration.customStyle ) : '',
suite: this.suite
} )
);
}
private async createFeatureIndexPages (): Promise<void> {
const featuresPath = path.resolve( this.reportConfiguration.reportPath!, 'features/' );
if ( !CommonFunctions.exists( featuresPath ) ) {
fs.mkdirSync( featuresPath );
}
await Promise.all( this.suite.features.map( feature => {
const featurePage = `${featuresPath}/${feature.id!.toString().replace( '/', '_' )}.html`;
fs.writeFileSync(
featurePage,
this.generateTemplate( featureOverviewIndexTemplate, {
feature,
featureOverviewScripts: this.generateTemplate( featureOverviewScriptsTemplate, {
feature,
chartsData: this.chartsData,
scriptsFunctions: this.scriptsFunctions,
suite: this.suite
} ),
config: this.reportConfiguration,
scenariosChart: this.generateTemplate( pieChartTemplate, {
results: this.suite.results.features,
dataBsTarget: '#scenarios-charts,#steps-charts,#metadata-properties',
chartData: 'Scenarios'
} ),
stepsChart: this.generateTemplate( pieChartTemplate, {
results: this.suite.results.features,
dataBsTarget: '#scenarios-charts,#steps-charts,#metadata-properties',
chartData: 'Steps'
} ),
metadata: this.generateTemplate( metadataTemplate, {
dataBsTarget: '#scenarios-charts,#steps-charts,#metadata-properties',
metadata: feature.metadata,
metadataTitle: feature.metadataTitle
} ),
scenariosTemplate: this.generateScenariosTemplate( feature ),
styles: this.generateTemplate( this.reportConfiguration.theme === 'Dark' || this.reportConfiguration.theme !== 'Light' ? reportStylesheetDarkThemeTemplate : reportStylesheetLightThemeTemplate ),
customStyle: this.reportConfiguration.customStyle ? this.generateTemplate( this.reportConfiguration.customStyle ) : '',
suite: this.suite,
tags: this.generateTemplate( tagsTemplate, {
index: feature.id,
tags: feature.tags
} ),
} )
);
return feature;
} ) );
}
private generateScenariosTemplate ( feature: Models.Feature ): string {
let scenariosTemplates = '';
for ( let scenarioIndex = 0; scenarioIndex < feature.elements!.length; scenarioIndex++ ) {
const scenario = ( feature.elements! )[ scenarioIndex ];
const templates = this.getScenarioElementsTemplates( scenario, scenarioIndex );
if ( scenario.isFirstScenarioOutline && scenario.examples?.length ) {
let scenarioOutlineChildsTemplates = '';
for ( let scenarioExamplesIndex = 1; scenarioExamplesIndex < scenario.examples.length; scenarioExamplesIndex++ ) {
const scenarioTemplates = this.getScenarioElementsTemplates( ( feature.elements! )[ scenarioIndex + scenarioExamplesIndex ], scenarioIndex + scenarioExamplesIndex );
scenarioOutlineChildsTemplates += this.generateTemplate( outlineScenarioChildTemplate, {
templates: scenarioTemplates,
scenarioExamplesIndex,
scenarioExamples: ( feature.elements! )[ scenarioIndex ].examples
} );
}
const scenarioOutline = this.generateTemplate( outlineScenarioTemplate, {
templates,
scenarioOutlineChilds: scenarioOutlineChildsTemplates
} );
scenariosTemplates += scenarioOutline;
scenarioIndex += scenario.examples.length - 1;
} else {
const simpleScenario = this.generateTemplate( simpleScenarioTemplate, {
templates
} );
scenariosTemplates += simpleScenario;
}
}
return scenariosTemplates;
}
private getScenarioElementsTemplates ( scenario: Models.Scenario, scenarioIndex: number ): Models.ScenariosTemplates {
return <Models.ScenariosTemplates>{
config: this.reportConfiguration,
scenarioIndex,
scenario,
scenarioId: scenario.id,
tags: this.generateTemplate( tagsTemplate, { scenario } ),
nameAndResults: this.generateTemplate( scenarioNameAndResultsTemplate, {
config: this.reportConfiguration,
scenario,
scenarioIndex
} ),
results: this.generateTemplate( scenarioResultsTemplate, {
scenario,
scenarioIndex
} ),
steps: this.generateTemplate( scenarioStepsTemplate, {
config: this.reportConfiguration,
isFirstScenarioOutline: scenario.isFirstScenarioOutline,
scenarioIndex,
steps: scenario.steps,
} ),
before: this.generateTemplate( scenarioBeforeTemplate, {
config: this.reportConfiguration,
scenario,
scenarioIndex,
steps: this.generateTemplate( scenarioStepsTemplate, {
config: this.reportConfiguration,
isFirstScenarioOutline: scenario.isFirstScenarioOutline,
scenarioIndex,
steps: scenario.before?.steps
} )
} ),
after: this.generateTemplate( scenarioAfterTemplate, {
config: this.reportConfiguration,
scenario,
scenarioIndex,
steps: this.generateTemplate( scenarioStepsTemplate, {
config: this.reportConfiguration,
isFirstScenarioOutline: scenario.isFirstScenarioOutline,
scenarioIndex,
steps: scenario.after?.steps
} )
} )
};
}
private readTemplateFile ( fileName: string ): string {
return fs.readFileSync( CommonFunctions.exists( fileName ) ? fileName : path.join( this.rootFolder, fileName ), 'utf-8' );
}
private generateTemplate ( templateName: string, parameters?: Record<string, any> ): string {
const compiledTemplate = lodash.template( this.readTemplateFile( templateName ) );
lodash.extend( 'scriptFunctions', this.scriptsFunctions );
return compiledTemplate( parameters );
}
private async copyResourcesToTargetFolder (): Promise<void> {
await fse.copy( path.join( this.rootFolder, resourcesFolder ), path.resolve( this.reportConfiguration.reportPath!, 'resources' ) );
}
}