#!/usr/bin/env node const fs = require('fs'); const [, , resultsPath] = process.argv; // This runs with `if: always()`, so it also fires when an earlier step failed and // no results file was ever written. Report that plainly and exit 0 — the job still // fails on the real step, and a stack trace here would only bury it. if (!fs.existsSync(resultsPath)) { report([`## Playwright E2E Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']); console.log(`Playwright: no results file at ${resultsPath} — the test step failed before writing one`); process.exit(0); } const raw = fs.readFileSync(resultsPath, 'utf-8'); const data = JSON.parse(raw); const stats = data.stats || {}; const lines = []; lines.push('## Playwright E2E Results'); lines.push(''); lines.push('| | Count |'); lines.push('|---|---|'); lines.push(`| ✅ Passed | ${stats.expected || 0} |`); lines.push(`| ❌ Failed | ${stats.unexpected || 0} |`); lines.push(`| ⚠️ Flaky | ${stats.flaky || 0} |`); lines.push(`| ⏭️ Skipped | ${stats.skipped || 0} |`); lines.push(''); function collectFailures(suites, path) { path = path || []; let failures = []; for (const suite of suites || []) { const currentPath = path.concat(suite.title).filter(Boolean); for (const spec of suite.specs || []) { for (const test of spec.tests || []) { for (const result of test.results || []) { if (result.status !== 'passed' && result.status !== 'skipped') { failures.push(`${currentPath.join(' > ')} > ${spec.title}: ${result.status}`); } } } } if (suite.suites) failures = failures.concat(collectFailures(suite.suites, currentPath)); } return failures; } const failures = collectFailures(data.suites); if (failures.length) { lines.push('### Failures'); lines.push(''); for (const f of failures) lines.push(`- ${f}`); lines.push(''); } report(lines); headline(); // Exits 0 whatever it found. Summarising and gating are two jobs, and this // script doing both meant the workflow failed at a step named "Summarize // end-to-end tests", which reads as a broken summary script rather than as // failing tests. The workflow's own `Fail if either suite failed` step fails the // job now. It was written for exactly that and was being skipped, because a step // whose `if:` omits always() still requires its predecessors to have succeeded, // and this script had already failed the job before it could run. process.exit(0); /** * One line of counts on stdout, alongside the markdown. * * `report` writes to GITEA_STEP_SUMMARY when it is set, which under Actions is * always — so the table and the failure list landed in the Summary tab while the * log showed a bare exit with no output at all. This puts the numbers in the log * too, at the point the run stops. */ function headline() { const failed = stats.unexpected || 0; const counts = `Playwright: ${stats.expected || 0} passed, ${failed} failed, ` + `${stats.flaky || 0} flaky, ${stats.skipped || 0} skipped`; console.log(failed > 0 ? `${counts} — see the job summary for which` : counts); } function report(out) { const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY; if (summaryPath) { fs.appendFileSync(summaryPath, out.join('\n') + '\n'); } else { console.log(out.join('\n')); } }