#!/usr/bin/env node const fs = require('fs'); const [, , resultsPath, label] = 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([`## ${label || 'Test'} Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']); console.log(`${label || 'Test'}: no results file at ${resultsPath} — the test step failed before writing one`); process.exit(0); } // Existing but unreadable is a separate case from absent, and it was the one // that could still fail the job. `--forceExit` can end the process around the // write and leave a partial file, and a suite that fails to *run* rather than // to assert produces a shape the renderer below did not expect. Either crashed // the script, which failed a step named for summarising and buried the real // failure — the exact misdirection #142 fixed once already. See #178. // // The reason is reported rather than swallowed: "could not read the results // file" plus the parse error is diagnostic, where a silent empty summary is not. let data; try { data = JSON.parse(fs.readFileSync(resultsPath, 'utf-8')); } catch (err) { const why = `Could not read \`${resultsPath}\` — ${err.message}`; report([`## ${label || 'Test'} Results`, '', why, '']); console.log(`${label || 'Test'}: ${why}`); process.exit(0); } const lines = []; lines.push(`## ${label || 'Test'} Results`); lines.push(''); lines.push('| | Count |'); lines.push('|---|---|'); lines.push(`| ✅ Passed | ${data.numPassedTests} |`); lines.push(`| ❌ Failed | ${data.numFailedTests} |`); lines.push(`| ⏭️ Skipped | ${data.numPendingTests} |`); lines.push(`| **Total** | **${data.numTotalTests}** |`); lines.push(''); if (data.numFailedTests > 0) { lines.push('### Failures'); lines.push(''); // Defensive at every level, like summarize-playwright.js's traversal: a suite // that failed to run can arrive without the array this walks. for (const suite of data.testResults || []) { for (const t of suite.testResults || []) { if (t.status === 'failed') { lines.push(`- **${t.fullName}**`); const msg = t.failureMessages && t.failureMessages[0] ? t.failureMessages[0].split('\n')[0] : 'see job log for details'; lines.push(` \`${msg}\``); } } } lines.push(''); } report(lines); headline(); // Exits 0 whatever it found, for the same reason as summarize-playwright.js: // summarising and gating are two jobs, and the workflow's `Fail if either suite // failed` step is the one that should fail a run. process.exit(0); /** One line of counts on stdout, so the log says what happened, not only the Summary tab. */ function headline() { const counts = `${label || 'Test'}: ${data.numPassedTests} passed, ${data.numFailedTests} failed, ` + `${data.numPendingTests} skipped`; console.log(data.numFailedTests > 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')); } }