diff --git a/.github/workflows/test.e2e.yml b/.github/workflows/test.e2e.yml index af848558..964d2360 100644 --- a/.github/workflows/test.e2e.yml +++ b/.github/workflows/test.e2e.yml @@ -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 diff --git a/.gitignore b/.gitignore index 568c04c3..0b0b7030 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .claude/ data/ cypress/node_modules/ -cypress/reports/ +cypress/results/ cypress/runner-results/ +cypress/screenshots/ diff --git a/cypress/cypress.config.ts b/cypress/cypress.config.ts index 0164c50c..0e744cfb 100644 --- a/cypress/cypress.config.ts +++ b/cypress/cypress.config.ts @@ -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) diff --git a/cypress/package-lock.json b/cypress/package-lock.json index 5b475d93..be2827b4 100644 --- a/cypress/package-lock.json +++ b/cypress/package-lock.json @@ -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, diff --git a/cypress/package.json b/cypress/package.json index c377e703..0c4602e9 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -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", diff --git a/cypress/plugins/parallel-reporter.js b/cypress/plugins/parallel-reporter.js new file mode 100644 index 00000000..b858a274 --- /dev/null +++ b/cypress/plugins/parallel-reporter.js @@ -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 +} diff --git a/cypress/scripts/create-index-page.sh b/cypress/scripts/create-index-page.sh new file mode 100755 index 00000000..9e39b59a --- /dev/null +++ b/cypress/scripts/create-index-page.sh @@ -0,0 +1,71 @@ +#!/bin/bash +set -e + +echo "=== Creating index page for consolidated report ===" + +cat > results/html/index.html << 'EOF' + + +
+This report contains all test results from the parallel test execution, merged into one comprehensive view with screenshots embedded directly in failing test cases.
+ + 📈 View Complete Test Report (All Specs) + +Screenshots are automatically embedded within their respective failing test cases in the main report. No separate screenshot viewing required.
+