multipleCucumberHTMLreporter

Cypress

How to use Multiple Cucumber HTML Reporter with Cypress.

Here you will learn how to configure the Cypress Cucumber project to generate Cucumber JSON reports and the beautiful HTML report using Multiple Cucumber HTML Reporter.

Tip

Steps to generate Multiple Cucumber HTML reporter

Generate Cucumber JSON reports

The multiple cucumber HTML reporter requires the Cucumber JSON report files in order to generate the beautiful HTML reports.

We have used @badeball/cypress-cucumber-preprocessor and @bahmutov/cypress-esbuild-preprocessor dependencies to generate the Cucumber JSON report files.

Cypress Configuration

Update the Cypress Configuration file as shown below to save the result in JSON format. This data will be used later.

// cypress.config.ts
import { writeFileSync } from 'node:fs';
import { addCucumberPreprocessorPlugin, afterRunHandler } from '@badeball/cypress-cucumber-preprocessor';
import { createEsbuildPlugin } from '@badeball/cypress-cucumber-preprocessor/esbuild';
import createBundler from '@bahmutov/cypress-esbuild-preprocessor';
import { defineConfig } from 'cypress';

async function setupNodeEvents(
  on: Cypress.PluginEvents,
  config: Cypress.PluginConfigOptions,
): Promise<Cypress.PluginConfigOptions> {
  await addCucumberPreprocessorPlugin(on, config);

  on(
    'file:preprocessor',
    createBundler({
      plugins: [createEsbuildPlugin(config)],
    }),
  );
  on(
    'after:run',
    async (results: CypressCommandLine.CypressRunResult | CypressCommandLine.CypressFailedRunResult): Promise<void> => {
      if (results) {
        await afterRunHandler(config, results);
        writeFileSync('.run/results.json', JSON.stringify(results));  
      }
    },
  );

  return config;
}

export default defineConfig({
  e2e: {
    specPattern: 'cypress/e2e/**/*.feature',
    setupNodeEvents,  
    defaultCommandTimeout: 60000,
    pageLoadTimeout: 60000,
    video: false,
    experimentalInteractiveRunEvents: true,
    downloadsFolder: './cypress/.run/downloads',
    fixturesFolder: './cypress/.run/fixtures',
    screenshotsFolder: './cypress/.run/screenshots',
    videosFolder: './cypress/.run/videos',
  },
});

Cucumber pre-processor configuration

Add the following configuration to your .cypress-cucumber-preprocessorrc.json file to generate the Cucumber JSON report files.

// .cypress-cucumber-preprocessorrc.json
{
  "json": {
    "enabled": true,  
    "output": ".run/reports/json/cucumber-report.json"
  },
  "messages": {
    "enabled": true,  
    "output": ".run/reports/messages/cucumber-report.ndjson"
  },
  "stepDefinitions": [
    "cypress/e2e/[filepath].step.{js,ts}",
    "cypress/e2e/[filepath]/*.step.{js,ts}"
  ]
}

Reporter config file

Create a TypeScript reporter config file at the root of the project as shown below. It supports dynamically targeting either the legacy JSON directory (.run/reports/json/) or the Cucumber Messages directory (.run/reports/messages/) depending on whether MESSAGES environment variable is defined:

// .multiple-cucumber-html-reporter.ts
import type { Options } from 'multiple-cucumber-html-reporter';

const reportFolder = process.env.MESSAGES ?? 'json';

const config: Options = {
  jsonDir: `./.run/reports/${reportFolder}/`, 
  reportPath: './.run/html-report/',
  openReportInBrowser: true,
  useCDN: true,
  emailReport: true,
  humanReadableDuration: true,
  metadataFilePath: './.run/reports/metadata.json', 
  customData: {
    projectName: 'Cypress sample project',
    release: '1.2.0',
    testCycle: process.env.GITHUB_RUN_ID || 'Cycle 1',
    buildNumber: process.env.GITHUB_RUN_NUMBER || 'Build 1',
    environment: 'production',
    ciPipeline: 'GitHub Actions',
  },
  pageTitle: 'Cypress Sample',
  reportName: 'Cypress Sample',
  displayDuration: true,
  displayReportTime: true,
};

export default config;

Custom Cypress Runner Script

When running tests across multiple browsers, create a runner script (such as cypress-runner.ts) that runs Cypress per browser, updates the feature URIs with the browser suffix in both JSON and Cucumber Messages (.ndjson), saves metadata.json, and renames the report per browser:

// cypress-runner.ts
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import cypress from 'cypress';
import type { Metadata } from 'multiple-cucumber-html-reporter';

const browsers = ['chrome', 'firefox'];
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const formatterFolder = process.env.MESSAGES || 'json';
const formatterExt = formatterFolder === 'messages' ? '.ndjson' : '.json';
const rawReportDir = join(__dirname, `.run/reports/${formatterFolder}`);

function updateFeatureFileNameByBrowser(filePath: string, browser: string): Record<string, Metadata> {
  const metadata: Metadata = {
    device: 'Local Test Machine',
  };
  const customMetadata: Record<string, Metadata> = {};

  if (!existsSync(filePath)) return customMetadata;
  const rawData = readFileSync(filePath, 'utf-8');
  if (!rawData.trim()) return customMetadata;

  // Handle Cucumber Messages NDJSON format
  if (formatterFolder === 'messages') {
    const lines = rawData.split('\n');
    const updatedLines = lines.map((line) => {
      if (!line.trim()) return line;
      try {
        const envelope = JSON.parse(line);
        const updateUri = (uri: string): string => uri.replace('.feature', `-${browser}.feature`);

        if (envelope.source?.uri) {
          envelope.source.uri = updateUri(envelope.source.uri);
        }
        if (envelope.gherkinDocument) {
          if (envelope.gherkinDocument.uri) {
            envelope.gherkinDocument.uri = updateUri(envelope.gherkinDocument.uri);
          }
          if (envelope.gherkinDocument.feature) {
            const originalName = envelope.gherkinDocument.feature.name;
            envelope.gherkinDocument.feature.name = `${originalName} [${browser.toUpperCase()}]`;
          }
          const metaKey = envelope.gherkinDocument.uri?.split('/').pop();
          if (metaKey) {
            customMetadata[metaKey] = {
              ...metadata,
              browser: { name: browser, version: 'latest' },
            };
          }
        }
        if (envelope.pickle?.uri) {
          envelope.pickle.uri = updateUri(envelope.pickle.uri);
        }
        return JSON.stringify(envelope);
      } catch {
        return line;
      }
    });

    writeFileSync(filePath, updatedLines.join('\n'));
    return customMetadata;
  }

  // Handle Legacy Cucumber JSON format
  const suiteData = JSON.parse(rawData);
  suiteData.forEach((feature: any) => {
    const originalName = feature.name;
    feature.name = `${originalName} [${browser.toUpperCase()}]`;
    feature.id = `${feature.id}-${browser}`;
    const fileName = feature.uri as string;
    feature.uri = fileName.replace('.feature', `-${browser}.feature`);

    const metaKey = (feature.uri as string).split('/').pop();
    if (metaKey) {
      customMetadata[metaKey] = {
        ...metadata,
        browser: { name: browser, version: 'latest' },
      };
    }
  });

  writeFileSync(filePath, JSON.stringify(suiteData, null, 2));
  return customMetadata;
}

async function executeCrossBrowserTests() {
  if (existsSync(rawReportDir)) {
    rmSync(rawReportDir, { recursive: true, force: true });
  }
  mkdirSync(rawReportDir, { recursive: true });

  let metadata: Record<string, Metadata> = {};

  for (const browser of browsers) {
    console.log(`Running Cypress tests on browser: ${browser}...`);
    await cypress.run({ browser });

    const defaultReportPath = join(rawReportDir, `cucumber-report${formatterExt}`);
    const targetedReportPath = join(rawReportDir, `cucumber_${browser}${formatterExt}`);

    const customMetadata = updateFeatureFileNameByBrowser(defaultReportPath, browser);
    metadata = { ...metadata, ...customMetadata };

    if (existsSync(defaultReportPath)) {
      renameSync(defaultReportPath, targetedReportPath);
      console.log(`Saved browser report to: ${targetedReportPath}`);
    }
  }
  writeFileSync(join(__dirname, '.run/reports/metadata.json'), JSON.stringify(metadata, null, 2), 'utf8');
}

executeCrossBrowserTests().catch(console.error);

Configure the scripts in package.json

// package.json
{
  "scripts": {
    "clean": "rm -rf dist .run",
    "build": "pnpm clean && tsc",
    "test": "pnpm build && tsx cypress-runner.ts",
    "report": "mchr --silent"
  },
}
  • pnpm clean - Remove the dist and .run folders
  • pnpm build - Compiles the TypeScript code for cucumber-html-report.ts
  • pnpm test - Run the tests in the browser
  • pnpm report - Generate the HTML report

Install the dependencies

Install the dependencies by running the following command:

pnpm install

Run the Tests

Execute your test cases by running the following command in the terminal:

pnpm test

Generate the HTML reporter

Once the test execution is done, generate the HTML report using the CLI:

pnpm report

Last updated on

On this page