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
- Check out the example project under
examples/cypressfolder in the reporter repository. - You can also refer to the GitHub Actions workflow for Cypress along with the generated reports attached.
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 thedistand.runfolderspnpm build- Compiles the TypeScript code forcucumber-html-report.tspnpm test- Run the tests in the browserpnpm report- Generate the HTML report
Generate the HTML reporter
Once the test execution is done, generate the HTML report using the CLI:
pnpm reportLast updated on