Skip to content

test(e2e): add Cypress custom HTML reporter with multi-reporters #3166

Open
kithwa wants to merge 1 commit intomainfrom
cypress-custom-reporter
Open

test(e2e): add Cypress custom HTML reporter with multi-reporters #3166
kithwa wants to merge 1 commit intomainfrom
cypress-custom-reporter

Conversation

@kithwa
Copy link

@kithwa kithwa commented Mar 4, 2026

PR Checklist

  • Unit Tests have been added for new changes
  • API tests have been updated if applicable
  • All commented code has been removed
  • If you've added a dependency, you've ensured license is compatible with Apache 2.0 and clearly outlined the added dependency.

What are you changing?

  • added the custom HTML reporter using Mocha multi-reporters
  • retaining the original Junit XML cypress reports as they were

Anything the reviewer should know when reviewing this PR?

If the there are associated PRs in other repositories, please link them here (i.e. device-management-toolkit/repo#365 )

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Cypress custom reporter setup using mocha-multi-reporters to generate an HTML (and companion JSON/CSV/text) report while preserving the existing JUnit XML output used by CI.

Changes:

  • Switch Cypress to mocha-multi-reporters and introduce a multi-reporter config.
  • Add a custom Mocha reporter that writes per-run and consolidated reports to cypress/reports.
  • Add mocha-multi-reporters and mocha-junit-reporter dependencies (and lockfile updates).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
package.json Adds Mocha multi-reporter dependencies needed for Cypress reporting.
package-lock.json Locks transitive deps for the new reporter packages (including Mocha peer install).
cypress/support/multi-reporter-config.json Configures multi reporters (custom HTML reporter + JUnit XML).
cypress/support/custom-reporter.js Implements custom tabular reporter that outputs HTML/JSON/CSV/text + consolidated report.
cypress.config.ts Switches Cypress reporter to mocha-multi-reporters and points at config file.
Comments suppressed due to low confidence (6)

cypress/support/custom-reporter.js:286

  • generateConsolidatedReport() re-reads every prior JSON report under baseReportDir and rewrites consolidated outputs on every run. Over time this can become slow and produce very large consolidated files in CI/workspaces. Consider scoping consolidation to the current execution (e.g., pass the current run’s folder list), or add a retention/cleanup strategy (e.g., only last N runs) or a flag to disable consolidation by default.
  generateConsolidatedReport(baseReportDir) {
    try {
      // Find all JSON report files in subdirectories
      const allReports = []
      const entries = fs.readdirSync(baseReportDir, { withFileTypes: true })

      for (const entry of entries) {
        if (entry.isDirectory()) {
          const jsonPath = path.join(baseReportDir, entry.name, 'report.json')
          if (fs.existsSync(jsonPath)) {
            const reportData = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))
            allReports.push({
              folder: entry.name,
              data: reportData,
              timestamp: new Date(reportData.startTime)
            })
          }
        }
      }

      if (allReports.length === 0) return

      // Sort reports by execution time (chronological order)
      allReports.sort((a, b) => a.timestamp - b.timestamp)

      // Combine all reports into one
      const consolidatedData = {
        suites: [],
        totals: {
          suites: 0,
          tests: 0,
          passes: 0,
          failures: 0,
          pending: 0,
          skipped: 0
        },
        startTime: new Date(Math.min(...allReports.map((r) => new Date(r.data.startTime)))),
        endTime: new Date(Math.max(...allReports.map((r) => new Date(r.data.endTime)))),
        reports: allReports.map((r) => r.folder)
      }

      // Aggregate all suites and totals
      for (const report of allReports) {
        consolidatedData.suites.push(...report.data.suites)
        consolidatedData.totals.suites += report.data.totals.suites
        consolidatedData.totals.tests += report.data.totals.tests
        consolidatedData.totals.passes += report.data.totals.passes
        consolidatedData.totals.failures += report.data.totals.failures
        consolidatedData.totals.pending += report.data.totals.pending
        consolidatedData.totals.skipped += report.data.totals.skipped
      }

      // Generate consolidated HTML report
      const consolidatedHTML = this.generateConsolidatedHTMLReport(consolidatedData)
      fs.writeFileSync(path.join(baseReportDir, 'consolidated-report.html'), consolidatedHTML)

      // Generate consolidated JSON
      fs.writeFileSync(path.join(baseReportDir, 'consolidated-report.json'), JSON.stringify(consolidatedData, null, 2))

cypress/support/custom-reporter.js:332

  • The HTML/CSS template is duplicated in generateConsolidatedHTMLReport() and generateHTMLReport() (large repeated <style>/layout blocks). This makes future UI tweaks error-prone. Consider extracting the shared CSS/template pieces into helper functions or separate static assets so both report types share the same base styling.
    const html = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Consolidated Cypress Test Report</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      padding: 20px;
      min-height: 100vh;
    }
    
    .container {
      max-width: 1400px;
      margin: 0 auto;
      background: white;
      border-radius: 12px;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3);
      overflow: hidden;
    }
    
    .header {
      background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);

cypress/support/multi-reporter-config.json:6

  • reportName is configured here, but the custom reporter currently hardcodes the output base name to report and doesn’t read this option. Either remove reportName from the config or update the reporter to honor it so the config file reflects actual behavior.
  "reporterEnabled": "cypress/support/custom-reporter.js, mocha-junit-reporter",
  "cypressSupportCustomReporterJsReporterOptions": {
    "reportDir": "cypress/reports",
    "reportName": "cypress-test-report"
  },

cypress.config.ts:12

  • Cypress CI currently uploads only the JUnit XML and screenshots; the new HTML/JSON/CSV/text reports written under cypress/reports won’t be preserved as artifacts, which makes them hard to access from CI runs. Consider updating the CI workflow/artifact uploads to include cypress/reports/** (or adjust reportDir to a path that is already collected).
  reporter: 'mocha-multi-reporters',
  reporterOptions: {
    configFile: 'cypress/support/multi-reporter-config.json'
  },

cypress/support/custom-reporter.js:195

  • reportName from reporter options isn’t used: multi-reporter-config.json sets reportName: "cypress-test-report", but the reporter hardcodes const reportName = 'report', so the config has no effect. Either read options.reporterOptions.reportName (with a default) when choosing output filenames, or remove reportName from the config to avoid confusion.
    const baseReportDir = options.reporterOptions?.reportDir || 'cypress/reports'

    // Get spec file name from first suite or use default
    const specName =
      data.suites.length > 0
        ? data.suites[0].name
            .split(/[\/\\]/)
            .pop()
            .replace(/[^a-zA-Z0-9]/g, '-')
            .toLowerCase()
        : 'test'

    // Create subfolder for this spec file run
    const reportSubDir = path.join(baseReportDir, `${specName}-${timestamp}`)
    const reportName = 'report'

    // Create report subdirectory if it doesn't exist

cypress/support/custom-reporter.js:985

  • successRate is computed with data.totals.tests as the divisor without guarding for 0 tests; if a run produces 0 tests (e.g., filtered specs), the report will show Infinity%/NaN%. Use the same tests > 0 ? ... : '0.00' guard you already use in the consolidated report.
  generateHTMLReport(data) {
    const duration = ((data.endTime - data.startTime) / 1000).toFixed(2)
    const successRate = ((data.totals.passes / data.totals.tests) * 100).toFixed(2)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants