From 152d456571442dae078437e99bf0280dcce2b3c0 Mon Sep 17 00:00:00 2001 From: snomiao Date: Mon, 20 Oct 2025 18:49:37 +0000 Subject: [PATCH] feat: integrate Playwright E2E test reports into GitHub Pages deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for downloading and displaying Playwright E2E test reports in the GitHub Pages deployment workflow. The reports are organized by browser (chromium, chromium-2x, chromium-0.5x, mobile-chrome) and presented via an interactive index page with test statistics. Changes: - Download Playwright reports from CI test runs (both triggered and latest) - Organize reports into browser-specific directories - Create interactive index page showing test stats (passed/failed/skipped/flaky) - Integrate with existing .pages landing page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/release-pages.yml | 46 +++++ scripts/create-playwright-index.js | 290 ++++++++++++++++++++++++++++ 2 files changed, 336 insertions(+) create mode 100755 scripts/create-playwright-index.js diff --git a/.github/workflows/release-pages.yml b/.github/workflows/release-pages.yml index e3c52f96f..79e8a97b6 100644 --- a/.github/workflows/release-pages.yml +++ b/.github/workflows/release-pages.yml @@ -96,6 +96,52 @@ jobs: workflow_conclusion: success path: ./.pages/vitest-reports + - name: Download Playwright E2E reports (source run) + id: fetch_playwright_trigger + continue-on-error: true + if: github.event_name == 'workflow_run' && github.event.workflow_run.name == 'Tests CI' + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: ci-tests-e2e.yaml + name_is_regexp: true + name: playwright-report-.* + run_id: ${{ github.event.workflow_run.id }} + path: ./playwright-reports-temp + + - name: Download Playwright E2E reports (latest successful run on main) + continue-on-error: true + if: steps.fetch_playwright_trigger.outcome != 'success' + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: ci-tests-e2e.yaml + name_is_regexp: true + name: playwright-report-.* + branch: main + workflow_conclusion: success + path: ./playwright-reports-temp + + - name: Organize Playwright reports by browser + if: always() + run: | + mkdir -p ./.pages/playwright-reports + + # Move each browser report to its own directory + if [ -d "./playwright-reports-temp" ]; then + for dir in ./playwright-reports-temp/playwright-report-*; do + if [ -d "$dir" ]; then + browser_name=$(basename "$dir" | sed 's/playwright-report-//') + mkdir -p "./.pages/playwright-reports/${browser_name}" + cp -r "$dir"/* "./.pages/playwright-reports/${browser_name}/" + fi + done + fi + + - name: Create Playwright reports index page + if: always() + run: node scripts/create-playwright-index.js + - name: Build static assets (with artifact reuse) run: ./scripts/build-pages.sh diff --git a/scripts/create-playwright-index.js b/scripts/create-playwright-index.js new file mode 100755 index 000000000..de8ac433a --- /dev/null +++ b/scripts/create-playwright-index.js @@ -0,0 +1,290 @@ +#!/usr/bin/env node +/** + * Creates an index page for Playwright test reports with test statistics + * Reads JSON reports from each browser and creates a landing page with cards + */ +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const reportsDir = path.join(__dirname, '..', '.pages', 'playwright-reports') + +function getTestStats(reportPath) { + try { + const reportJsonPath = path.join(reportPath, 'report.json') + if (!fs.existsSync(reportJsonPath)) { + console.warn(`No report.json found at ${reportJsonPath}`) + return null + } + + const reportData = JSON.parse(fs.readFileSync(reportJsonPath, 'utf-8')) + + let passed = 0 + let failed = 0 + let skipped = 0 + let flaky = 0 + + // Parse Playwright JSON report format + if (reportData.suites) { + const countResults = (suites) => { + for (const suite of suites) { + if (suite.specs) { + for (const spec of suite.specs) { + if (!spec.tests || spec.tests.length === 0) continue + + const test = spec.tests[0] + const results = test.results || [] + + // Check if test is flaky (has both pass and fail results) + const hasPass = results.some((r) => r.status === 'passed') + const hasFail = results.some((r) => r.status === 'failed') + + if (hasPass && hasFail) { + flaky++ + } else if (results.some((r) => r.status === 'passed')) { + passed++ + } else if (results.some((r) => r.status === 'failed')) { + failed++ + } else if (results.some((r) => r.status === 'skipped')) { + skipped++ + } + } + } + if (suite.suites) { + countResults(suite.suites) + } + } + } + + countResults(reportData.suites) + } + + return { passed, failed, skipped, flaky } + } catch (error) { + console.error(`Error reading report at ${reportPath}:`, error.message) + return null + } +} + +function generateIndexHtml(browsers) { + const cards = browsers + .map((browser) => { + const { name, stats } = browser + if (!stats) return '' + + const total = stats.passed + stats.failed + stats.skipped + stats.flaky + const passRate = total > 0 ? ((stats.passed / total) * 100).toFixed(1) : 0 + + return ` + +
+

${name}

+ ${passRate}% +
+
+
+ Passed + ${stats.passed} +
+
+ Failed + ${stats.failed} +
+
+ Skipped + ${stats.skipped} +
+
+ Flaky + ${stats.flaky} +
+
+
+ ` + }) + .join('') + + return ` + + + + + Playwright E2E Test Reports + + + +
+

🎭 Playwright E2E Test Reports

+
+ ${cards} +
+
+ +` +} + +function main() { + if (!fs.existsSync(reportsDir)) { + console.log( + 'No playwright reports directory found, skipping index creation' + ) + return + } + + const browsers = [] + const browserDirs = fs.readdirSync(reportsDir, { withFileTypes: true }) + + for (const dirent of browserDirs) { + if (dirent.isDirectory()) { + const browserName = dirent.name + const browserPath = path.join(reportsDir, browserName) + const stats = getTestStats(browserPath) + + if (stats) { + browsers.push({ name: browserName, stats }) + console.log(`✓ Found report for ${browserName}:`, stats) + } + } + } + + if (browsers.length === 0) { + console.warn('No valid browser reports found') + return + } + + const html = generateIndexHtml(browsers) + const indexPath = path.join(reportsDir, 'index.html') + + fs.writeFileSync(indexPath, html, 'utf-8') + console.log(`✓ Created index page at ${indexPath}`) +} + +main()