Post reports as a single PR comment#151
Conversation
This reverts commit 8904eb5.
There was a problem hiding this comment.
Pull request overview
This PR updates the CI pipelines to generate unit-test coverage + mutation-test artifacts per workflow and aggregate them into a single, continuously-updated PR comment (one section per package/workflow), while also adding workflow concurrency controls and bumping multiple GitHub Actions versions.
Changes:
- Generate/upload Jest JSON + coverage summary artifacts (head + base) and Stryker JSON artifacts for later aggregation.
- Add an
aggregate-reportcomposite action plus scripts to assemble Markdown and update a single PR comment. - Add
concurrencyto multiple workflows and introduce a newreportjob that posts/updates the aggregated comment.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sdk/jest.config.ts | Adds coverage reporters needed for summary extraction. |
| packages/ot-server/package.json | Updates coverage script to emit Jest JSON output. |
| packages/ot-server/jest.config.ts | Adds coverage reporters for JSON summary + text summary. |
| packages/model/stryker.conf.mjs | Adds JSON reporter for mutation results. |
| packages/model/package.json | Updates coverage script to emit Jest JSON output. |
| packages/model/jest.config.ts | Adds coverage reporters for JSON summary + text summary. |
| packages/dom-adapters/package.json | Updates coverage script to emit Jest JSON output (currently with trailing whitespace bug). |
| packages/dom-adapters/jest.config.ts | Adds coverage reporters for JSON summary + text summary. |
| packages/core/stryker.conf.mjs | Adds JSON reporter for mutation results. |
| packages/core/package.json | Updates coverage script to emit Jest JSON output. |
| packages/core/jest.config.ts | Adds coverage reporters for JSON summary + text summary. |
| packages/collaboration-manager/package.json | Updates coverage script to emit Jest JSON output. |
| packages/collaboration-manager/jest.config.ts | Adds coverage reporters for JSON summary + text summary. |
| .gitignore | Ignores generated jest-report.json. |
| .github/workflows/sdk.yml | Adds concurrency + bumps checkout action version. |
| .github/workflows/playground.yml | Adds concurrency + bumps checkout action version. |
| .github/workflows/ot-server.yml | Adds concurrency, bumps checkout version, adds report job (needs PR-only gating). |
| .github/workflows/npm-publish.yml | Bumps checkout/setup-node action versions. |
| .github/workflows/model.yml | Adds concurrency, bumps checkout version, adds report job (needs PR-only gating). |
| .github/workflows/dom-adapters.yml | Adds concurrency, bumps checkout version, adds report job (needs PR-only gating; mutation JSON reporter mismatch). |
| .github/workflows/core.yml | Adds concurrency, bumps checkout version, adds report job. |
| .github/workflows/collaboration-manager.yml | Adds concurrency, bumps checkout version, adds report job (needs PR-only gating). |
| .github/workflows/build-and-push-docker-image.yml | Bumps checkout action version. |
| .github/scripts/update-aggregated-comment.js | Implements “create/update single aggregated PR comment” logic (export format currently incompatible with require). |
| .github/scripts/process-mutation.js | Parses mutation JSON + changed-files list into Markdown (module format + logic issues). |
| .github/scripts/generate-report.js | Generates per-package Markdown section (export format currently incompatible with require). |
| .github/scripts/compare-coverage.js | Compares head/base coverage + extracts Jest counts into Markdown (module format + formatting issues). |
| .github/actions/unit-tests/action.yml | Runs coverage, uploads artifacts, triggers base coverage (needs PR-only gating). |
| .github/actions/setup/action.yml | Bumps cache action version. |
| .github/actions/mutation-tests-changed-files/action.yml | Uploads mutation artifacts and changed-files list for aggregation. |
| .github/actions/mutation-tests-all-files/action.yml | Bumps setup-node/changed-files versions. |
| .github/actions/lint/action.yml | Bumps setup-node version. |
| .github/actions/build/action.yml | Bumps setup-node version. |
| .github/actions/base-coverage/action.yml | New composite action to run coverage on base ref and upload artifacts. |
| .github/actions/aggregate-report/action.yml | New composite action to download artifacts, build Markdown, and update the aggregated PR comment (coverage path handling needed). |
Comments suppressed due to low confidence (1)
.github/scripts/compare-coverage.js:118
- After removing the ESM
exportkeyword,processReportsstill needs to be exported for therequire(...).processReportscall site. Add a CommonJS export at the end of the file.
return message;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "lint:fix": "yarn lint --fix", | ||
| "test": "jest", | ||
| "test:coverage": "yarn test --coverage=true", | ||
| "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json ", |
| const score = (killed + timedOut) / (killed + timedOut + notCovered + survived).toFixed(2); | ||
|
|
||
| return { | ||
| total, | ||
| score, | ||
| survived, | ||
| notCovered, | ||
| thresholds: obj.thresholds, | ||
| }; |
| - name: Run base coverage action | ||
| uses: ./.github/actions/base-coverage | ||
| with: | ||
| package-name: ${{ inputs.package-name }} | ||
| working-directory: ${{ inputs.working-directory }} |
| let message = `${tests.passedTests} tests passed in ${tests.passedSuites} suites.\n` | ||
|
|
||
| message += `Branches coverage: ${categories.branches.head}%\n`; | ||
|
|
||
| let warning = ''; | ||
|
|
||
| for (const cat in categories) { | ||
| if (categories[cat].delta < 0) { | ||
| warning += `Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; | ||
| } | ||
| } | ||
|
|
||
| if (warning.length > 0) { | ||
| message += `> [!WARNING]\n> ${message}\n`; | ||
| } |
| if (!fs.existsSync(reportFile) && !fs.existsSync(changedFilesFile)) { | ||
| return ''; | ||
| } | ||
| const changedFiles = JSON.parse(fs.readFileSync(changedFilesFile, 'utf8')); | ||
|
|
||
| if (changedFiles.length === 0) { | ||
| return 'No files to mutate found.'; | ||
| } | ||
|
|
||
| const raw = fs.readFileSync(reportFile, 'utf8'); | ||
| const obj = JSON.parse(raw); | ||
|
|
| let message = `Mutation tests run with mutation score ${(metrics.score * 100).toFixed(2)}%.\n`; | ||
|
|
||
| if (metrics.survived) { | ||
| message += `Survived mutants: ${metrics.survived}\n`; | ||
| } | ||
|
|
||
| if (metrics.notCovered) { | ||
| message += `Not covered mutants: ${metrics.notCovered}\n`; | ||
| } | ||
|
|
||
| if (metrics.score < metrics.thresholds.low) { | ||
| message += `> [!WARNING]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; | ||
| } else if (metrics.score < metrics.thresholds.high) { | ||
| message += `> [!CAUTION]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; | ||
| } |
|
|
maybe it would be better to display the result as a simple table
|
|
|
||
| - name: Find current PR's number | ||
| uses: jwalton/gh-find-current-pr@v1 | ||
| id: findPr |
There was a problem hiding this comment.
| id: findPr | |
| id: find_pr |
for ids consistency if possible
| if (fs.existsSync(report)) { | ||
| try { | ||
| return JSON.parse(fs.readFileSync(report, 'utf8')); | ||
| } catch (report) { | ||
| // continue | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
would be nice if eslint checked this scripts too, but for now i would suggest logging of the report or explicitly dismissing with void report
| } catch (e) { | ||
| // ignore parse errors | ||
| } |
| // prefer head counts; if missing, fallback to base counts; otherwise null | ||
| const tests = { |
There was a problem hiding this comment.
| // prefer head counts; if missing, fallback to base counts; otherwise null | |
| const tests = { | |
| // prefer head counts; if missing, fallback to base counts; otherwise null | |
| const tests = { |
|
|
||
| for (const cat in categories) { | ||
| if (categories[cat].delta < 0) { | ||
| warning += `\n> Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; |
There was a problem hiding this comment.
if delta is 'N/A' — it has no toFixed() method, otherwise delta is already .toFixed(2)
| elif git rev-parse --verify "origin/master" >/dev/null 2>&1; then | ||
| RESOLVED_BASE_REF="origin/master" | ||
| debug "Resolved base ref as: origin/master" | ||
| elif git rev-parse --verify "master" >/dev/null 2>&1; then | ||
| RESOLVED_BASE_REF="master" | ||
| debug "Resolved base ref as: master" |
There was a problem hiding this comment.
redundant
| elif git rev-parse --verify "origin/master" >/dev/null 2>&1; then | |
| RESOLVED_BASE_REF="origin/master" | |
| debug "Resolved base ref as: origin/master" | |
| elif git rev-parse --verify "master" >/dev/null 2>&1; then | |
| RESOLVED_BASE_REF="master" | |
| debug "Resolved base ref as: master" |
| let md = ''; | ||
|
|
||
| md += `<!-- ${pkg} REPORT -->\n`; | ||
| md += `## ${pkg}\n\n`; |
There was a problem hiding this comment.
lets start with separator
| md += `## ${pkg}\n\n`; | |
| md += '---' | |
| md += `## ${pkg}\n\n`; |
Unit Tests
Mutation Tests
|
Now only a single comment with test reports generated and updated for all workflows. Some minor workflow changes as well. Coverage comparison would be enabled after merge as base branch doesn't generate summary at the moment
Suggestions on the format are welcome.
Current format for a package section (warning are displayed conditionally):
<package-name>
Unit tests report
<number> tests passed in <number> suites.
Branches coverage: <number>%
Caution
Coverage for <category> dropped by <number>%
Coverage for <another-category> dropped by <number>%
Mutation tests report
Mutation tests run with mutation score <number>%.
Survived mutants: <number>
Not covered mutants: <number>
Caution
Mutation score is below the low threshold of <number>%
Warning
Mutation score is below the low threshold of <number>%
Dashboard URL: <URL>