refactor e2e test fail reporting process

This commit is contained in:
mahula 2025-10-06 15:34:04 +02:00
parent b5966d549f
commit 3a86aac3d6
10 changed files with 300 additions and 101 deletions

View File

@ -138,7 +138,7 @@ jobs:
# Disable individual cypress-split summaries to avoid conflicts
SPLIT_SUMMARY: false
- name: Upload raw test artifacts on failure
- name: Upload test artifacts on failure
if: failure()
uses: actions/upload-artifact@2848b2cda0e5190984587ec6bb1f36730ca78d50 # v4.6.2
with:
@ -146,7 +146,6 @@ jobs:
path: |
cypress/results/
cypress/screenshots/
cypress/videos/
retention-days: 7
if-no-files-found: warn
@ -176,37 +175,38 @@ jobs:
name: cypress-test-results-${{ github.run_id }}
path: ./cypress
- name: Merge test reports
run: |
if [ -d "results" ] && [ "$(find results/ -name "*.json" 2>/dev/null)" ]; then
echo "Found JSON reports, merging..."
npm run report:merge
echo "=== Merge result ==="
ls -la results/merged-report.json || echo "Merge failed"
else
echo "No JSON reports found to merge"
echo "=== Debug: results directory ==="
ls -la results/ || echo "Directory does not exist"
fi
- name: Merge JSON reports into one consolidated report
run: ./scripts/merge-reports.sh
working-directory: ./cypress
- name: Generate HTML report
run: |
if [ -f "results/merged-report.json" ]; then
echo "Generating HTML report..."
npm run report:generate
echo "=== HTML generation result ==="
ls -la results/html/ || echo "HTML generation failed"
else
echo "No merged report found, skipping HTML generation"
fi
- name: Generate HTML report with screenshots
run: ./scripts/generate-html-report.sh
working-directory: ./cypress
- name: Upload processed reports
- name: Create simple index page
run: ./scripts/create-index-page.sh
working-directory: ./cypress
env:
GITHUB_RUN_ID: ${{ github.run_id }}
GITHUB_SHA: ${{ github.sha }}
- name: Upload consolidated test report
uses: actions/upload-artifact@2848b2cda0e5190984587ec6bb1f36730ca78d50 # v4.6.2
with:
name: e2e-test-reports-${{ github.run_id }}
name: e2e-test-report-${{ github.run_id }}
path: |
cypress/results/
cypress/results/html/
retention-days: 14
if-no-files-found: warn
- name: Upload raw test data (for debugging)
if: failure()
uses: actions/upload-artifact@2848b2cda0e5190984587ec6bb1f36730ca78d50 # v4.6.2
with:
name: e2e-raw-data-${{ github.run_id }}
path: |
cypress/results/
cypress/screenshots/
cypress/videos/
retention-days: 7
if-no-files-found: warn

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
.claude/
data/
cypress/node_modules/
cypress/reports/
cypress/results/
cypress/runner-results/
cypress/screenshots/

View File

@ -16,13 +16,14 @@ export default defineConfig({
reporter: 'mochawesome',
reporterOptions: {
useInlineDiffs: true,
embeddedScreenshots: true,
reportDir: 'results',
reportFilename: '[name].html',
reportFilename: '[name]',
overwrite: false,
html: true,
json: true,
html: false, // Only generate JSON during test runs for merging
json: true, // Generate JSON for merging
embeddedScreenshots: true,
useInlineDiffs: true,
screenshotOnRunFailure: true
},
defaultCommandTimeout: 10000,
@ -49,6 +50,10 @@ export default defineConfig({
// Load cypress-split plugin
cypressSplit(on, config)
// Load parallel reporter plugin
const parallelReporter = require('./plugins/parallel-reporter')
config = parallelReporter(on, config)
on('task', {
log(message) {
console.log(message)

View File

@ -9,13 +9,13 @@
"version": "1.0.0",
"devDependencies": {
"@eslint/js": "^9.36.0",
"@types/mochawesome": "^6.2.4",
"@types/node": "^24.5.2",
"cypress": "^15.3.0",
"cypress-mochawesome-reporter": "^3.8.2",
"cypress-split": "^1.24.23",
"eslint": "^9.36.0",
"eslint-plugin-cypress": "^5.1.1",
"mochawesome": "^7.1.3",
"mochawesome": "^7.1.4",
"mochawesome-merge": "^4.3.0",
"mochawesome-report-generator": "^6.2.0",
"typescript": "^5.9.2",
@ -737,17 +737,6 @@
"node": ">= 8"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@types/estree": {
"version": "1.0.8",
"dev": true,
@ -758,6 +747,23 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/mocha": {
"version": "10.0.10",
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
"integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/mochawesome": {
"version": "6.2.4",
"resolved": "https://registry.npmjs.org/@types/mochawesome/-/mochawesome-6.2.4.tgz",
"integrity": "sha512-tWnTmoWX1bpxutRYyrrgVtLeOUvaLxmoPsO+sMw8pvVFQ90UT2TkOQwu8usUGgGbEajMOIIarSW3hpaA7AavOQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/mocha": "*"
}
},
"node_modules/@types/node": {
"version": "24.5.2",
"dev": true,
@ -1835,54 +1841,6 @@
"node": "^20.1.0 || ^22.0.0 || >=24.0.0"
}
},
"node_modules/cypress-mochawesome-reporter": {
"version": "3.8.4",
"resolved": "https://registry.npmjs.org/cypress-mochawesome-reporter/-/cypress-mochawesome-reporter-3.8.4.tgz",
"integrity": "sha512-ytn8emXyR5nz2+uqqgwqEwpeR9oILEIFSWl2lt2eyHICb2d0s/Hu7bPPo02bEf8UkqJohwg00yZ+jDH6oUqmzw==",
"dev": true,
"dependencies": {
"commander": "^10.0.1",
"fs-extra": "^10.0.1",
"mochawesome": "^7.1.3",
"mochawesome-merge": "^4.2.1",
"mochawesome-report-generator": "^6.2.0"
},
"bin": {
"generate-mochawesome-report": "cli.js"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/LironEr"
},
"peerDependencies": {
"cypress": ">=6.2.0"
}
},
"node_modules/cypress-mochawesome-reporter/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/cypress-mochawesome-reporter/node_modules/fs-extra": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
"dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/cypress-split": {
"version": "1.24.23",
"dev": true,

View File

@ -7,9 +7,10 @@
"test": "cypress run --e2e --browser chromium",
"test:open": "cypress open --e2e",
"test:split:auto": "SPEC_COUNT=$(find e2e -name '*.cy.ts' | wc -l) && echo \"Running $SPEC_COUNT chunks in parallel\" && for i in $(seq 0 $((SPEC_COUNT-1))); do SPLIT=$SPEC_COUNT SPLIT_INDEX=$i cypress run --e2e --browser chromium & done; wait",
"report:merge": "mochawesome-merge results/*.json > results/merged-report.json",
"report:generate": "marge results/merged-report.json --reportDir results/html --reportTitle 'Utopia Map E2E Tests' --reportPageTitle 'Utopia Map E2E Test Report' --inline --charts",
"report:clean": "rm -rf results screenshots videos",
"report:merge": "mochawesome-merge 'results/*.json' -o results/merged-report.json",
"report:generate": "marge results/merged-report.json --reportDir results/html --reportTitle 'Utopia Map E2E Tests' --reportPageTitle 'Utopia Map E2E Test Report' --inline --charts --showPassed --showFailed --showPending --showSkipped",
"report:copy-screenshots": "if [ -d screenshots ]; then cp -r screenshots results/html/ 2>/dev/null || true; fi",
"report:full": "npm run report:merge && npm run report:generate && npm run report:copy-screenshots",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
@ -17,18 +18,19 @@
"cypress",
"cypress-split",
"e2e",
"mochawesome",
"testing",
"utopia-map"
],
"devDependencies": {
"@eslint/js": "^9.36.0",
"@types/mochawesome": "^6.2.4",
"@types/node": "^24.5.2",
"cypress": "^15.3.0",
"cypress-mochawesome-reporter": "^3.8.2",
"cypress-split": "^1.24.23",
"eslint": "^9.36.0",
"eslint-plugin-cypress": "^5.1.1",
"mochawesome": "^7.1.3",
"mochawesome": "^7.1.4",
"mochawesome-merge": "^4.3.0",
"mochawesome-report-generator": "^6.2.0",
"typescript": "^5.9.2",

View File

@ -0,0 +1,71 @@
/**
* Cypress Plugin for Enhanced Parallel Test Reporting
* Handles mochawesome report generation for parallel test execution
*/
const path = require('path')
const fs = require('fs')
module.exports = (on, config) => {
// Ensure results directory exists
const resultsDir = path.join(config.projectRoot, 'results')
if (!fs.existsSync(resultsDir)) {
fs.mkdirSync(resultsDir, { recursive: true })
}
// Configure mochawesome for parallel execution - JSON only for merging
const splitIndex = process.env.SPLIT_INDEX || '0'
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
config.reporterOptions = {
...config.reporterOptions,
reportFilename: `split-${splitIndex}-${timestamp}-[name]`,
reportDir: resultsDir,
overwrite: false,
html: false, // No individual HTML files
json: true, // Only JSON for merging into one report
embeddedScreenshots: true,
useInlineDiffs: true
}
// Task for logging parallel execution info
on('task', {
log(message) {
console.log(`[Parallel ${splitIndex}] ${message}`)
return null
},
logReportInfo() {
console.log(`[Parallel ${splitIndex}] Report will be saved as: report-${splitIndex}-${timestamp}.json`)
return null
}
})
// Before run hook
on('before:run', (details) => {
console.log(`[Parallel ${splitIndex}] Starting test execution`)
console.log(`[Parallel ${splitIndex}] Browser: ${details.browser.name}`)
console.log(`[Parallel ${splitIndex}] Specs: ${details.specs.length}`)
return details
})
// After run hook
on('after:run', (results) => {
console.log(`[Parallel ${splitIndex}] Test execution completed`)
console.log(`[Parallel ${splitIndex}] Total tests: ${results.totalTests}`)
console.log(`[Parallel ${splitIndex}] Passed: ${results.totalPassed}`)
console.log(`[Parallel ${splitIndex}] Failed: ${results.totalFailed}`)
// Ensure the report file was created
const reportFile = path.join(resultsDir, `report-${splitIndex}-${timestamp}.json`)
if (fs.existsSync(reportFile)) {
console.log(`[Parallel ${splitIndex}] ✅ Report saved: ${reportFile}`)
} else {
console.log(`[Parallel ${splitIndex}] ❌ Report not found: ${reportFile}`)
}
return results
})
return config
}

View File

@ -0,0 +1,71 @@
#!/bin/bash
set -e
echo "=== Creating index page for consolidated report ==="
cat > results/html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Utopia Map E2E Test Report</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f8f9fa; }
.header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px #00000020; }
.report-section { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px #00000020; }
.report-link { display: block; padding: 16px 20px; margin: 12px 0; background: #e3f2fd; border-radius: 8px; text-decoration: none; color: #1976d2; border-left: 4px solid #2196f3; font-size: 18px; font-weight: 500; }
.report-link:hover { background: #bbdefb; }
.meta { color: #666; font-size: 14px; margin: 4px 0; }
.status { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.status.failed { background: #ffebee; color: #c62828; }
.status.passed { background: #e8f5e8; color: #2e7d32; }
</style>
</head>
<body>
<div class="header">
<h1>Utopia Map E2E Test Report</h1>
<div class="meta">Generated: $(date)</div>
<div class="meta">Run ID: ${GITHUB_RUN_ID:-unknown}</div>
<div class="meta">Commit: ${GITHUB_SHA:-unknown}</div>
<div class="meta">Status: <span class="status failed">Tests Failed</span></div>
</div>
<div class="report-section">
<h2>📊 Consolidated Test Report</h2>
<p>This report contains all test results from the parallel test execution, merged into one comprehensive view with screenshots embedded directly in failing test cases.</p>
<a href="merged-report.html" class="report-link">
📈 View Complete Test Report (All Specs)
</a>
</div>
<div class="report-section">
<h2>📸 Test Screenshots</h2>
<p>Screenshots are automatically embedded within their respective failing test cases in the main report. No separate screenshot viewing required.</p>
</div>
<script>
setTimeout(() => {
window.location.href = 'merged-report.html';
}, 3000);
let countdown = 3;
const countdownEl = document.createElement('div');
countdownEl.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #2196f3; color: white; padding: 10px 15px; border-radius: 4px; font-size: 14px;';
countdownEl.textContent = `Auto-redirecting in ${countdown}s...`;
document.body.appendChild(countdownEl);
const timer = setInterval(() => {
countdown--;
if (countdown > 0) {
countdownEl.textContent = `Auto-redirecting in ${countdown}s...`;
} else {
clearInterval(timer);
countdownEl.textContent = 'Redirecting...';
}
}, 1000);
</script>
</body>
</html>
EOF
echo "✅ Simple index page created with auto-redirect to consolidated report"

View File

@ -0,0 +1,25 @@
#!/bin/bash
set -e
echo "=== Generating HTML report with embedded screenshots ==="
if [ ! -f "results/merged-report.json" ]; then
echo "❌ No merged JSON found, cannot generate consolidated report"
exit 1
fi
echo "Generating comprehensive HTML report from merged JSON..."
npm run report:generate
if [ ! -f "results/html/merged-report.html" ]; then
echo "❌ HTML generation failed"
exit 1
fi
echo "✅ Consolidated HTML report generated successfully"
echo "Report size: $(wc -c < results/html/merged-report.html) bytes"
npm run report:copy-screenshots
echo "=== Final consolidated report ready ==="
ls -la results/html/

View File

@ -0,0 +1,35 @@
#!/bin/bash
set -e
echo "=== Merging all JSON reports into one consolidated report ==="
json_count=$(find results/ -name "*.json" -type f 2>/dev/null | wc -l)
echo "Found $json_count JSON report files from parallel test execution"
if [ "$json_count" -gt 0 ]; then
echo "=== JSON files found ==="
find results/ -name "*.json" -type f | sort
echo "=== Merging all reports into one ==="
npm run report:merge
if [ ! -f "results/merged-report.json" ]; then
echo "❌ Merge failed - no merged-report.json created"
exit 1
fi
echo "✅ Successfully merged $json_count JSON reports into one"
echo "Merged report size: $(wc -c < results/merged-report.json) bytes"
report=$(cat results/merged-report.json)
echo "Consolidated report stats:"
echo " - Total tests: $(echo "$report" | node -pe 'JSON.parse(require("fs").readFileSync(0)).stats?.tests || 0')"
echo " - Passed: $(echo "$report" | node -pe 'JSON.parse(require("fs").readFileSync(0)).stats?.passes || 0')"
echo " - Failed: $(echo "$report" | node -pe 'JSON.parse(require("fs").readFileSync(0)).stats?.failures || 0')"
echo " - Duration: $(echo "$report" | node -pe 'JSON.parse(require("fs").readFileSync(0)).stats?.duration || 0')ms"
else
echo "❌ No JSON reports found to merge"
echo "Creating empty report structure..."
mkdir -p results
echo '{"stats":{"tests":0,"passes":0,"failures":0},"results":[]}' > results/merged-report.json
fi

View File

@ -2,8 +2,8 @@
import './commands'
// This file is processed and loaded automatically before your test files.
// This is a great place to put global configuration and behavior that modifies Cypress.
// for screenshot embedding
import addContext from 'mochawesome/addContext'
// Global exception handler
Cypress.on('uncaught:exception', (err) => {
@ -11,3 +11,34 @@ Cypress.on('uncaught:exception', (err) => {
// returning false here prevents Cypress from failing the test
return false
})
// Add screenshots of failed tests to mochawesome report
Cypress.on('test:after:run', (test, runnable) => {
if (test.state === 'failed') {
const adjustedSpecPath = Cypress.spec.relative.replace(/^e2e\//, '')
// Build the full test hierarchy title like Cypress does for screenshot naming
const titles: string[] = []
let current = runnable
while (current && current.parent) {
if (current.title) {
titles.unshift(current.title)
}
current = current.parent
}
const fullTitle = titles.join(' -- ').replace(/:/g, '')
const screenshot = `screenshots/${adjustedSpecPath}/${fullTitle} (failed).png`
addContext({ test }, screenshot)
// Also add any retry screenshots if they exist
const screenshot2 = `screenshots/${adjustedSpecPath}/${fullTitle} (failed) (attempt 2).png`
const screenshot3 = `screenshots/${adjustedSpecPath}/${fullTitle} (failed) (attempt 3).png`
// Add retry screenshots (mochawesome will handle non-existent files gracefully)
addContext({ test }, screenshot2)
addContext({ test }, screenshot3)
}
})