diff --git a/.github/workflows/releasetest_sep.yaml b/.github/workflows/releasetest_sep.yaml index d902be41..d3b404e3 100644 --- a/.github/workflows/releasetest_sep.yaml +++ b/.github/workflows/releasetest_sep.yaml @@ -6,7 +6,7 @@ on: - main # ─────────────────────────────────────────────────────────────── -# 1. Playwright tests → artifacts +# 1. Playwright tests → artifacts # ─────────────────────────────────────────────────────────────── jobs: test: @@ -137,16 +137,6 @@ jobs: name: playwright-metrics path: dl - - name: Prepare metrics file - run: | - if [ -f dl/playwright-metrics-pr.json ]; then - cp dl/playwright-metrics-pr.json dl/playwright-metrics.json - echo "✅ Copied playwright-metrics-pr.json to playwright-metrics.json" - else - echo "⚠️ Warning: playwright-metrics-pr.json not found" - ls -la dl/ - fi - - name: Download PR HTML report uses: actions/download-artifact@v4 with: @@ -165,6 +155,26 @@ jobs: name: lint-summary path: dl + # CRITICAL: Ensure PR metrics are used for visualization + - name: Prepare artifact files + run: | + echo "📁 Downloaded artifacts:" + ls -la dl/ + + # Ensure the PR metrics file is properly named + if [ -f dl/playwright-metrics-pr.json ]; then + cp dl/playwright-metrics-pr.json dl/playwright-metrics.json + echo "✅ Using PR metrics for visualization" + else + echo "❌ PR metrics file not found!" + fi + + # Ensure PR summary is properly named + if [ -f dl/playwright-summary-pr.json ] && [ ! -f dl/playwright-summary.json ]; then + cp dl/playwright-summary-pr.json dl/playwright-summary.json + echo "✅ Using PR summary" + fi + # Build dashboard, post comment, deploy Pages - id: review name: Dashboard / PR comment / Pages @@ -174,4 +184,4 @@ jobs: mode: dashboard-only custom-artifacts-path: dl enable-github-pages: 'true' - enable-visual-comparison: 'true' + enable-visual-comparison: 'true' \ No newline at end of file diff --git a/action.yml b/action.yml index aa0fa761..b59fe4ce 100644 --- a/action.yml +++ b/action.yml @@ -444,14 +444,17 @@ runs: ' artifacts/playwright-metrics.json > artifacts/playwright-summary-pr.json fi - cp artifacts/playwright-metrics-pr.json artifacts/playwright-metrics.json || true + # IMPORTANT: Keep a copy of PR metrics before main branch tests + if [ -f artifacts/playwright-metrics.json ]; then + cp artifacts/playwright-metrics.json artifacts/playwright-metrics-pr.json + echo "✅ Preserved PR metrics" + fi - name: Alias summary for checklist if: steps.modes.outputs.playwright == 'true' shell: bash run: | cp artifacts/playwright-summary-pr.json artifacts/playwright-summary.json || true - cp artifacts/playwright-metrics.json artifacts/playwright-metrics-pr.json || true # 3b ── Playwright on main branch with progress indicators - name: Run Playwright on main diff --git a/scripts/generate-test-city-3d.js b/scripts/generate-test-city-3d.js index a71c4515..02e4aa4c 100644 --- a/scripts/generate-test-city-3d.js +++ b/scripts/generate-test-city-3d.js @@ -3,18 +3,15 @@ * generate-test-city-3d.js * Creates an interactive 3D city visualization from Playwright test results. * Each building represents a test, grouped by test suite. + * FIXED: Proper Three.js loading and data handling */ -/* eslint-disable no-console */ - const fs = require('fs'); const path = require('path'); -// ──────────────────────────────────────────────────────────── -// Constants / helpers -// ──────────────────────────────────────────────────────────── -const ART = 'artifacts'; // dashboard output folder -const HISTORY_FILE = path.join(ART, 'test-history-insights.json'); // output from track-test-history.js +// Constants +const ART = 'artifacts'; +const HISTORY_FILE = path.join(ART, 'test-history-insights.json'); // Safe JSON reader function readJSON (filepath, def = null) { @@ -28,26 +25,19 @@ function readJSON (filepath, def = null) { } } -// ──────────────────────────────────────────────────────────── -// Extract all test data we have (metrics + history) -// ──────────────────────────────────────────────────────────── +// Extract all test data function extractTestData () { - // Prefer the rich metrics file emitted by Playwright reporter + // Try multiple locations for metrics const candidatePaths = [ - // Prefer PR‑branch detailed metrics first path.join(ART, 'playwright-metrics-pr.json'), - path.join(ART, 'playwright-summary-pr.json'), // minimal but PR‑scoped - // Fallbacks (may contain main‑branch data if workflow overwrote generic files) - 'playwright-metrics.json', path.join(ART, 'playwright-metrics.json'), + 'playwright-metrics.json', + path.join(ART, 'playwright-summary-pr.json'), path.join(ART, 'playwright-summary.json') ]; - // ★ PATCH: keep looking until we find a file **with suites**. - // If we only find summaries, remember the first one - // and fall back to it later. - let metrics = null; // detailed (has suites) - let summaryMetrics = null; // first summary found + let metrics = null; + let summaryMetrics = null; for (const p of candidatePaths) { if (!fs.existsSync(p)) continue; @@ -55,91 +45,105 @@ function extractTestData () { const data = readJSON(p); if (!data) continue; + console.log(`📁 Checking ${p}...`); + if (data.suites) { - metrics = data; // detailed ⇒ stop - console.log(`Found detailed metrics at: ${p}`); + metrics = data; + console.log(`✅ Found detailed metrics at: ${p}`); + console.log(` - Total suites: ${data.suites.length}`); + console.log(` - Stats:`, data.stats); break; } if (!summaryMetrics && data.total) { - summaryMetrics = data; // remember first summary - console.log(`Found summary metrics at: ${p} (continuing search for detailed)`); + summaryMetrics = data; + console.log(`📊 Found summary metrics at: ${p}`); } } - if (!metrics) metrics = summaryMetrics; // fall back if needed + if (!metrics) metrics = summaryMetrics; - // Fall‑back: if we only have a summary, return one pseudo‑suite so the UI still works + // Fallback if only summary exists if (!metrics || !metrics.suites) { - const summary = readJSON(path.join(ART, 'playwright-summary-pr.json')); + console.log('⚠️ No detailed metrics found, using summary fallback'); + const summary = summaryMetrics || readJSON(path.join(ART, 'playwright-summary-pr.json')); if (summary && summary.total) { - console.log('Using summary data for visualization'); + console.log('📊 Using summary data for visualization:', summary); return [{ - id: 'summary-tests', - suite: 'All Tests', - describe: 'Summary', - name: `${summary.passed} passed, ${summary.failed} failed`, - duration: summary.duration || 0, - passRate: summary.pass_rate || 0, - passed: summary.passed || 0, - failed: summary.failed || 0, - total: summary.total || 0, - lastStatus: summary.failed > 0 ? 'failed' : 'passed', - priority: 1 + id: 'summary-tests', + suite: 'All Tests', + describe: 'Summary', + name: `${summary.passed} passed, ${summary.failed} failed`, + duration: summary.duration || 0, + passRate: summary.pass_rate || 0, + passed: summary.passed || 0, + failed: summary.failed || 0, + total: summary.total || 0, + lastStatus: summary.failed > 0 ? 'failed' : 'passed', + priority: 1 }]; } - // absolutely nothing to show return []; } - // Load history if it exists (for flakiness calc) + // Load history const historyData = readJSON(HISTORY_FILE, { tests: {} }); const historyMap = historyData.tests || {}; const testData = []; + // Process detailed test data metrics.suites.forEach((suite, sIdx) => { const suiteName = path.basename(suite.file || `suite-${sIdx}`) .replace(/\.(spec|test)\.(jsx?|tsx?)$/, ''); + console.log(`Processing suite: ${suiteName}`); + suite.suites.forEach((describe, dIdx) => { describe.specs.forEach((spec, tIdx) => { const fullName = `${suiteName} > ${describe.title} > ${spec.title}`; - const attempts = spec.tests || []; + const attempts = spec.tests || []; let durationTotal = 0; let passed = 0, failed = 0, skipped = 0, lastStatus = 'unknown'; const errors = []; attempts.forEach(attempt => { - attempt.results.forEach(r => { + (attempt.results || []).forEach(r => { durationTotal += r.duration || 0; - if (r.status === 'passed' || r.status === 'expected') { passed++; lastStatus = 'passed'; } - else if (r.status === 'failed' || r.status === 'unexpected') { failed++; lastStatus = 'failed'; } - else if (r.status === 'skipped') { skipped++; lastStatus = 'skipped'; } + if (r.status === 'passed' || r.status === 'expected') { + passed++; + lastStatus = 'passed'; + } else if (r.status === 'failed' || r.status === 'unexpected') { + failed++; + lastStatus = 'failed'; + } else if (r.status === 'skipped') { + skipped++; + lastStatus = 'skipped'; + } if (r.error) errors.push({ message: r.error.message, stack: r.error.stack }); }); }); - const runs = passed + failed + skipped; - const avgDur = runs ? durationTotal / runs : 0; - const passRate = runs ? (passed / runs) * 100 : 0; + const runs = passed + failed + skipped; + const avgDur = runs ? durationTotal / runs : 0; + const passRate = runs ? (passed / runs) * 100 : 0; - // flakiness: prefer history, else derive from current attempts + // Flakiness calculation let flakiness = 0; if (historyMap[fullName]) { flakiness = historyMap[fullName].flakiness || 0; } else if (runs > 1 && passed > 0 && failed > 0) { - flakiness = 50; // simplistic + flakiness = 50; } - // tag category + // Category detection let category = 'standard'; const loTitle = spec.title.toLowerCase(); - const loDesc = describe.title.toLowerCase(); - if (loTitle.includes('critical') || loDesc.includes('critical')) category = 'critical'; - else if (loTitle.includes('smoke') || loDesc.includes('smoke')) category = 'smoke'; - else if (loTitle.includes('regression')) category = 'regression'; + const loDesc = describe.title.toLowerCase(); + if (loTitle.includes('critical') || loDesc.includes('critical')) category = 'critical'; + else if (loTitle.includes('smoke') || loDesc.includes('smoke')) category = 'smoke'; + else if (loTitle.includes('regression')) category = 'regression'; testData.push({ id: `${suiteName}-${dIdx}-${tIdx}`, @@ -166,6 +170,11 @@ function extractTestData () { }); }); + console.log(`📊 Extracted ${testData.length} tests`); + if (testData.length > 0) { + console.log('📊 Sample test:', JSON.stringify(testData[0], null, 2)); + } + return testData; } @@ -174,13 +183,13 @@ function calcPriority (duration, passRate, flakiness, category) { if (category === 'critical') p *= 2; else if (category === 'smoke') p *= 1.5; - p *= 1 + duration / 5_000; // slower → taller - p *= 2 - passRate / 100; // failing → taller - if (flakiness > 30) p *= 1.5; // flaky → taller + p *= 1 + duration / 5000; + p *= 2 - passRate / 100; + if (flakiness > 30) p *= 1.5; return p; } -// Generate the 3D city HTML +// Generate the 3D city HTML with fixed Three.js loading function generate3DCityHTML(testData) { // Group tests by suite const suites = {}; @@ -198,10 +207,12 @@ function generate3DCityHTML(testData) { failed: testData.filter(t => t.lastStatus === 'failed').length, skipped: testData.filter(t => t.lastStatus === 'skipped').length, flaky: testData.filter(t => t.flakiness > 30).length, - avgDuration: testData.reduce((sum, t) => sum + t.duration, 0) / testData.length, + avgDuration: testData.length > 0 ? testData.reduce((sum, t) => sum + t.duration, 0) / testData.length : 0, totalDuration: testData.reduce((sum, t) => sum + t.totalDuration, 0) }; + console.log('📊 Stats for visualization:', stats); + return ` @@ -234,6 +245,7 @@ body { border-radius: 12px; border: 1px solid #334155; max-width: 350px; + z-index: 100; } #info h1 { @@ -300,6 +312,7 @@ body { border: 1px solid #334155; display: none; max-width: 400px; + z-index: 100; } #hover-info.visible { @@ -332,6 +345,7 @@ body { padding: 15px; border-radius: 8px; border: 1px solid #334155; + z-index: 100; } .control-btn { @@ -359,6 +373,7 @@ body { left: 50%; transform: translate(-50%, -50%); text-align: center; + z-index: 200; } .spinner { @@ -375,9 +390,28 @@ body { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } + +#error-message { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(239, 68, 68, 0.1); + border: 1px solid #ef4444; + color: #ef4444; + padding: 20px; + border-radius: 8px; + display: none; + z-index: 200; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + -
@@ -386,6 +420,11 @@ body {