Test reporting turns raw pass/fail output into signals humans and platforms can act on: a machine-readable results file (JUnit/JSON) the CI ingests, a status check that appears green/red on the pull request, and human-readable reports or inline annotations pointing at failures.
A test run that only prints to a log is nearly useless at scale. Reporting operates at three levels. First, machine-readable output (JUnit XML, JSON) that dashboards and the CI platform parse to show pass/fail counts, durations and trends. Second, status checks: each job reports a commit status, and these surface directly on the PR as required checks that gate merge. Third, developer-facing detail: an HTML report with traces, or inline PR annotations that point to the exact failing assertion and line. Good reporting shortens the loop from 'the build is red' to 'this specific test failed here for this reason' without the developer digging through logs. It's also what makes quality gates enforceable, because a gate needs a concrete signal to block on.
A PR shows three status checks: lint ✓, unit ✓, e2e ✗. Clicking the red e2e check opens inline annotations naming the failed test and assertion, plus a link to the uploaded HTML report with the trace — the reviewer sees exactly what broke without opening a single raw log.
# Emit JUnit for the platform + a rich report for humans
- run: npx playwright test --reporter=junit,html
env:
PLAYWRIGHT_JUNIT_OUTPUT_NAME: results/junit.xml
# Surface failures inline on the PR (GitHub reporter or an action)
# playwright.config: reporter: [['github'], ['junit', { outputFile: 'results/junit.xml' }], ['html']]
- name: Publish test results to the PR
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
with: { name: junit, path: results/junit.xml }Scenario: Developers complain that when CI is red, they have to scroll through thousands of log lines to find which test failed and why, so they often just re-run the build hoping it goes green. How do you improve reporting to fix this behavior?