UNPKG

cucumber-html-report-generator

Version:

Generate beautiful cucumberjs html reports for multiple instances (browsers / devices)

262 lines (242 loc) 12.3 kB
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' ) ); } }