test(e2e): add Cypress custom HTML reporter with multi-reporters #3166
Open
test(e2e): add Cypress custom HTML reporter with multi-reporters #3166
Conversation
a723651 to
d03c63d
Compare
d03c63d to
ac1a9f5
Compare
There was a problem hiding this comment.
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-reportersand introduce a multi-reporter config. - Add a custom Mocha reporter that writes per-run and consolidated reports to
cypress/reports. - Add
mocha-multi-reportersandmocha-junit-reporterdependencies (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 underbaseReportDirand 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()andgenerateHTMLReport()(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
reportNameis configured here, but the custom reporter currently hardcodes the output base name toreportand doesn’t read this option. Either removereportNamefrom 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/reportswon’t be preserved as artifacts, which makes them hard to access from CI runs. Consider updating the CI workflow/artifact uploads to includecypress/reports/**(or adjustreportDirto a path that is already collected).
reporter: 'mocha-multi-reporters',
reporterOptions: {
configFile: 'cypress/support/multi-reporter-config.json'
},
cypress/support/custom-reporter.js:195
reportNamefrom reporter options isn’t used:multi-reporter-config.jsonsetsreportName: "cypress-test-report", but the reporter hardcodesconst reportName = 'report', so the config has no effect. Either readoptions.reporterOptions.reportName(with a default) when choosing output filenames, or removereportNamefrom 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
successRateis computed withdata.totals.testsas the divisor without guarding for 0 tests; if a run produces 0 tests (e.g., filtered specs), the report will showInfinity%/NaN%. Use the sametests > 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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Checklist
What are you changing?
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 )