ci: let the summarisers summarise and the gate do the failing (#142) #155

Merged
bermudalamb merged 1 commits from feature/142-ci-failure-reporting into main 2026-08-24 09:51:51 -05:00
3 changed files with 51 additions and 3 deletions
Showing only changes of commit 491c2652f3 - Show all commits
+9 -1
View File
@@ -178,6 +178,14 @@ jobs:
# summaries first. Without this step continue-on-error above would turn a # summaries first. Without this step continue-on-error above would turn a
# failing suite into a passing job, which is the one way this change could # failing suite into a passing job, which is the one way this change could
# do real damage. # do real damage.
#
# always() is load-bearing. A step whose `if:` omits it still implicitly
# requires every previous step to have succeeded, so this was skipped in
# exactly the case it exists for: the summarise step above used to exit 1
# on a failing suite, which failed the job first and left this gate as dead
# code. The job then reported its failure under a name that describes
# summarising rather than testing. The summarisers exit 0 now; this is what
# fails the run. See #142.
- name: Fail if either suite failed - name: Fail if either suite failed
if: steps.unit.outcome == 'failure' || steps.e2e.outcome == 'failure' if: always() && (steps.unit.outcome == 'failure' || steps.e2e.outcome == 'failure')
run: exit 1 run: exit 1
+15 -1
View File
@@ -8,6 +8,7 @@ const [, , resultsPath, label] = process.argv;
// fails on the real step, and a stack trace here would only bury it. // fails on the real step, and a stack trace here would only bury it.
if (!fs.existsSync(resultsPath)) { if (!fs.existsSync(resultsPath)) {
report([`## ${label || 'Test'} Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']); 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); process.exit(0);
} }
@@ -44,7 +45,20 @@ if (data.numFailedTests > 0) {
report(lines); report(lines);
process.exit(data.numFailedTests > 0 ? 1 : 0); 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) { function report(out) {
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY; const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY;
+27 -1
View File
@@ -8,6 +8,7 @@ const [, , resultsPath] = process.argv;
// fails on the real step, and a stack trace here would only bury it. // fails on the real step, and a stack trace here would only bury it.
if (!fs.existsSync(resultsPath)) { if (!fs.existsSync(resultsPath)) {
report([`## Playwright E2E Results`, '', `No results file at \`${resultsPath}\` — the test step failed before writing one.`, '']); 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); process.exit(0);
} }
@@ -55,7 +56,32 @@ if (failures.length) {
report(lines); report(lines);
process.exit((stats.unexpected || 0) > 0 ? 1 : 0); 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) { function report(out) {
const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY; const summaryPath = process.env.GITEA_STEP_SUMMARY || process.env.GITHUB_STEP_SUMMARY;