multipleCucumberHTMLreporter

WebdriverIO

How to use Multiple Cucumber HTML Reporter with WebdriverIO.

Integrating with WebdriverIO allows you to automatically generate beautiful reports and attach screenshots on failure.

Tip

WebdriverIO Support

There are two approaches which you can use to generate beautiful reports using multiple Cucumber HTML reporter.

  • WebDriverIO Cucumber JSON reporter
  • WebDriverIO Cucumber JSON native formatter

Using WebDriverIO Cucumber JSON Reporter

Idea

When you are setting up WebDriverIO or you already have an existing project for WebDriverIO, you need to use their community reporter i.e., wdio-cucumberjs-json-reporter to generate the JSON report files, which will be used by the multiple-cucumber-html-reporter to generate the HTML report.

Checkout WebdriverIO Cucumber JSON reporter.

Add WebDriverIO Reporter Dependency

First, install wdio-cucumberjs-json-reporter as a dev dependency in your project:

npm install -D wdio-cucumberjs-json-reporter

Configure WebDriverIO Reporters

Following is the key configuration changes required for generating Cucumber JSON reports and the beautiful HTML report:

// wdio.conf.ts
export const config: WebdriverIO.Config = {
  // . . .
  framework: 'cucumber',
  reporters: [
    'spec',
    [
      'cucumberjs-json',  
      {
        jsonFolder: 'reports/json', 
      },
    ],
  ],
  // . . .
};

Reporter Options

Check out all the supported options for the reporter here.

Delete Reports Folder

In the onPrepare hook, we delete the reports folder if it exists. This helps in generating a fresh report every time the tests are run.

This can be done as shown in the below example:

// wdio.conf.ts
import { existsSync } from 'node:fs';
import { rm } from 'node:fs/promises';

export const config: WebdriverIO.Config = {
  //. . .
  onPrepare: async () => {
    if (existsSync('reports')) {
      await rm('reports', { recursive: true });
    }
  },
  //. . .
};

Handle Failures

To automatically capture screenshots and attach them on failure globally:

// wdio.conf.ts
import cucumberJson from 'wdio-cucumberjs-json-reporter';

export const config: WebdriverIO.Config = {
  //. . .
  afterScenario: async (_world, result, _context) => {
    if (!result.passed) {
      cucumberJson.attach(await browser.takeScreenshot(), 'image/png');  
    }
  },
  //. . .
};

Generate Report after Test execution

Add the report logic to the onComplete hook as shown in the below example:

// wdio.conf.ts
import { generate } from 'multiple-cucumber-html-reporter';

export const config: WebdriverIO.Config = {
  // ... other configs ...
  onComplete: async (_exitCode, _config, _capabilities, _results) => {
    await generate({
      jsonDir: 'reports/json/', 
      reportPath: 'reports/report/', 
      useCDN: false,
      openReportInBrowser: true,
      saveCollectedJSON: true,
      displayReportTime: true,
      durationInMS: false,
      displayDuration: true,
      pageTitle: 'My WDIO Typescript Sample',
      reportName: 'Cucumber JS Report',
      customData: {
        projectName: 'WebDriverIO 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',
      },
    });
  },
};

Info

Note that the jsonDir points to a directory, inside which there will be JSON result files created by the Cucumber JSON reporter as configured earlier.

The HTML report will be generated inside a timestamped folder within the reportPath directory.

Check out all the supported options here.

If you want to add your test specific metadata, you can do that by passing the metadata from the cjson:metadata capability object for each capabilities as shown below:

// wdio.conf.ts
import { generate, type Metadata } from 'multiple-cucumber-html-reporter';

export const config: WebdriverIO.Config = {
  // ... other configs ...
  capabilities: [
    {
      browserName: 'chrome',
      browserVersion: '148',
      'cjson:metadata': { 
        browser: {  
          name: 'chrome', 
          version: '148', 
        },  
        platform: { 
          name: os.platform().trim(), 
          version: os.release().trim(), 
        },  
      },  
    } as WebdriverIO.Capabilities & { 'cjson:metadata': Metadata },
  ],

  onComplete: async (
    _exitCode: number,
    _config: WebdriverIO.Config,
    capabilities: WebdriverIO.Capabilities[],
    _results: any,
  ) => {
    const capability = capabilities[0] as WebdriverIO.Capabilities & { 'cjson:metadata': Metadata };
    const reportMetadata: Metadata = capability['cjson:metadata'];

    await generate({
      jsonDir: 'reports/json/',
      reportPath: 'reports/report/',
      useCDN: false,
      openReportInBrowser: true,
      saveCollectedJSON: true,
      displayReportTime: true,
      durationInMS: false,
      displayDuration: true,
      displayChartPercentages: true,
      pageTitle: 'My WDIO Typescript Sample',
      reportName: 'WDIO Cucumber JS Report',
      metadata: reportMetadata,
      customData: {
        projectName: 'WebDriverIO 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',
      },
    });
  },
};

Tip

Before we generate the report, we need to capture the metadata defined in the capabilities, and pass it to the metadata option of the generate method.

Configure package.json for test script

// package.json
"scripts": {
  "test:reporter": "wdio run ./wdio-cc-reporter.conf.ts"
}

Run the Tests and generate HTML report

Execute the following command to run the tests and generate the HTML report:

pnpm test:reporter

Using WebDriverIO Cucumber in-built JSON formatter

Cucumber framework has an in-built JSON formatter which can be used to generate JSON reports. In WebDriverIO, it has @wdio/cucumber-framework integration for Cucumber.

Add dependency

Install the Cucumber framework for WebdriverIO:

npm install -D @wdio/cucumber-framework

Configure tsconfig.json

The types section in tsconfig.json must include the types for the Cucumber framework.

// tsconfig.json (in your project root)
{
  "compilerOptions": {
    // ...
    "types": [
      "node",
      "@wdio/globals/types",
      "expect-webdriverio",
      "@wdio/cucumber-framework"
    ]
    // ...
  }
}

Configure Cucumber Options

Add Cucumber configuration in the cucumberOpts section as shown in the below example. You can configure formatters for both JSON and Cucumber Messages (.ndjson):

// wdio.conf.ts
export const config: WebdriverIO.Config = {
  // ... other options
  cucumberOpts: {
    // ... other Cucumber options
    format: [
      'progress',
      `json:reports/json/cucumber-report-${process.env.WDIO_WORKER_ID || '0-0'}.json`,  
      `message:reports/messages/cucumber-message-${process.env.WDIO_WORKER_ID || '0-0'}.ndjson`,  
    ],
  },
};

Tip

The process.env.WDIO_WORKER_ID is used to generate a unique report file for each worker capability, which is then processed to generate the HTML report. This is particularly useful when running tests across multiple browsers.

Delete Reports Folder

In the onPrepare hook, we delete the reports folder if it exists. This helps in generating a fresh report every time the tests are run.

This can be done as shown in the below example:

// wdio.conf.ts
import { existsSync } from 'node:fs';
import { rm } from 'node:fs/promises';

export const config: WebdriverIO.Config = {
  //. . .
  onPrepare: async () => {
    if (existsSync('reports')) {
      await rm('reports', { recursive: true });
    }
  },
  //. . .
};

Handle Failures

To automatically capture screenshots and attach them on failure globally:

// steps.ts
import { After } from '@wdio/cucumber-framework';
import { browser } from '@wdio/globals';

// ... Other Hooks

After(async function ({ result }) {
  if (result?.status !== 'PASSED') {
    const screenshot = await browser.takeScreenshot();
    this.attach(Buffer.from(screenshot, 'base64'), 'image/png');
  }
});

// ... Other steps definition

Parse Metadata and Update Feature URIs

When running parallel or cross-browser tests, create a metadata helper (e.g., src/libs/metadata-parser.ts) to update feature URIs and associate capability metadata with both JSON and Cucumber Messages NDJSON files:

// src/libs/metadata-parser.ts
import { readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import type { Metadata } from 'multiple-cucumber-html-reporter';

export function saveMetadata(capabilities: (WebdriverIO.Capabilities & { 'cjson:metadata': Metadata })[]): void {
  const formatterFolder = process.env.MESSAGES || 'json';
  const formatterExt = formatterFolder === 'messages' ? '.ndjson' : '.json';
  const jsonDir = `./reports/${formatterFolder}/`;
  const files = readdirSync(jsonDir).filter((file) => file.endsWith(formatterExt));

  const customMetadata: Record<string, Metadata> = {};

  files.forEach((file, index) => {
    const filePath = join(jsonDir, file);
    const rawData = readFileSync(filePath, 'utf-8');
    const capability = capabilities[index];
    if (!capability) return;
    const metadata = capability['cjson:metadata'];
    const browserName = capability.browserName || 'chrome';

    if (!rawData.trim()) return;

    // 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', `-${browserName}.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} [${browserName.toUpperCase()}]`;
            }
            const metaKey = envelope.gherkinDocument.uri?.split('/').pop();
            if (metaKey) {
              customMetadata[metaKey] = metadata;
            }
          }
          if (envelope.pickle?.uri) {
            envelope.pickle.uri = updateUri(envelope.pickle.uri);
          }
          return JSON.stringify(envelope);
        } catch {
          return line;
        }
      });

      writeFileSync(filePath, updatedLines.join('\n'));
    } else {
      // Handle legacy JSON format
      const suiteData = JSON.parse(rawData);

      suiteData.forEach((feature: any) => {
        const originalName = feature.name;
        feature.name = `${originalName} [${browserName.toUpperCase()}]`;
        feature.id = `${feature.id}-${browserName}`;
        const fileName = feature.uri as string;
        feature.uri = fileName.replace('.feature', `-${browserName}.feature`);

        const metaKey = (feature.uri as string).split('/').pop();
        if (metaKey) {
          customMetadata[metaKey] = metadata;
        }
      });

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

  writeFileSync(join(process.cwd(), 'reports', 'metadata.json'), JSON.stringify(customMetadata, null, 2), 'utf8');
}

In your wdio.conf.ts onComplete hook, invoke this parser:

// wdio.conf.ts
import type { Metadata } from 'multiple-cucumber-html-reporter';
import { saveMetadata } from './src/libs/metadata-parser';

export const config: WebdriverIO.Config = {
  // ...
  onComplete: async (_exitCode, _config, capabilities: WebdriverIO.Capabilities[], _results) => {
    saveMetadata(capabilities as (WebdriverIO.Capabilities & { 'cjson:metadata': Metadata })[]);
  },
};

Reporter Configuration File

Create .multiple-cucumber-html-reporter.yml in your project root using ${MESSAGES:'json'} to support both formats:

# .multiple-cucumber-html-reporter.yml
jsonDir: ./reports/${MESSAGES:'json'}
reportPath: ./reports/html
emailReport: true
openReportInBrowser: true
displayDuration: true
humanReadableDuration: true
displayReportTime: true
durationInMS: true
pageTitle: 'My WDIO Typescript Sample'
reportName: 'WDIO Cucumber JS Report'
metadataFilePath: ./reports/metadata.json
customData: 
  projectName: 'WebDriverIO sample project'
  release: '1.2.0'
  testCycle: ${GITHUB_RUN_ID:'Cycle 1'}
  buildNumber: ${GITHUB_RUN_NUMBER:'Build 1'}
  environment: 'production'
  ciPipeline: 'GitHub Actions'

Configure package.json for test script

// package.json
"scripts": {
  "test:formatter": "wdio run ./wdio-cc-formatter.conf.ts",
  "report": "mchr"
}

Run the Tests and generate HTML report

Run the tests and generate the report using either legacy JSON or Cucumber Messages:

pnpm test:formatter
pnpm report

Last updated on

On this page