Skip to content

ci: parallel jobs + PR-gate + jest-junit + report (mirror of API #672)#1908

Merged
Hugo0 merged 4 commits intodevfrom
devx/test-overhaul-ui
Apr 28, 2026
Merged

ci: parallel jobs + PR-gate + jest-junit + report (mirror of API #672)#1908
Hugo0 merged 4 commits intodevfrom
devx/test-overhaul-ui

Conversation

@Hugo0
Copy link
Copy Markdown
Contributor

@Hugo0 Hugo0 commented Apr 28, 2026

Companion to peanut-api-ts #672 (devx/test-overhaul). Same structural CI improvements applied to peanut-ui.

Why

peanut-ui's Tests workflow had the same shape the API had before #672:

  • Single serial job (lint + typecheck + unit + e2e all in one)
  • on: push: branches: ['**'] — fired on every push to every branch, even ones without an open PR
  • No cache: 'pnpm' on setup-node — paid full install cost every run
  • pnpm install (not --frozen-lockfile) — non-deterministic CI installs
  • Node 21.1.0 (drift from API's 20)
  • No PR-comment surface for failing tests / coverage

Changes

File Change
.github/workflows/tests.yml Split into 5 parallel jobs: lint, typecheck, unit, e2e, report. PR-gate trigger. cache: 'pnpm'. --frozen-lockfile. Node 20. dorny/test-reporter for inline annotations. New report job posts an idempotent PR comment with status, failing tests, coverage, slowest cases.
package.json New test:unit:ci script (jest --coverage + jest-junit reporter). jest-junit@^16.0.0 devDep. jest-junit reporter config block.
.gitignore test-results/ + coverage/

Wall-clock estimate

Before After
Trigger every push to every branch PR only
Wall-clock (p50) ~167s (single job) ~max(lint, typecheck, unit, e2e) ≈ ~70s
Failing-test signal buried in raw logs inline PR-diff annotations + auto-comment with file paths + 🔴/✅ status emoji

What's NOT in this PR

  • e2e is still advisory (continue-on-error: true). Wiring TEST_HARNESS_SECRET to make it blocking is a separate decision.
  • Bundle-size regression check, Lighthouse CI, visual regression — backlog (see engineering/projects/test-infra-overhaul/TODO.md in the mono companion PR).

Test plan

  • All 5 jobs (lint, typecheck, unit, e2e, report) appear and run in parallel
  • report job posts a PR comment with non-zero coverage + suite results
  • Push to a feature branch with no PR open: Tests workflow does NOT fire (was firing on every push before)
  • On a failing unit test: PR-diff annotations appear via dorny + the failing test is listed by file path in the auto-comment

Mirror the structure peanut-api-ts shipped in PR #672 (devx/test-overhaul).
Same wins, smaller absolute speedup because UI suite is smaller (38 jest
files, 17 Playwright specs vs API's 1100+ tests).

Changes to .github/workflows/tests.yml:

- **Trigger**: `on: push: branches: ['**']` → `on: pull_request` against
  main/dev/develop. Stops firing the full suite on every feature-branch
  push that doesn't have a PR open.
- **Split** the single serial job into 5 parallel jobs:
  - `lint` — prettier check + advisory validate-links
  - `typecheck` — `next typegen` then `tsc --noEmit`
  - `unit` — `pnpm test:unit:ci` (with --coverage, jest-junit reporter)
  - `e2e` — Playwright (advisory; same continue-on-error as before)
  - `report` — depends on unit; downloads coverage + JUnit artifacts,
    merges via nyc, posts an idempotent PR comment with status emoji,
    failing-tests-by-file, top-10 slowest cases, and coverage totals.
    Same `<!-- ui-test-report -->` tag pattern as code-analysis.yml.
- **Add `cache: 'pnpm'`** to setup-node — was missing; saved time per
  job.
- **Switch `pnpm install` → `pnpm install --frozen-lockfile`** for
  determinism in CI.
- **Align Node 21.1.0 → 20** with peanut-api-ts.
- **`permissions: checks: write`** so dorny/test-reporter can publish
  per-test inline annotations.

package.json:
- New `test:unit:ci` script (`jest --coverage` + jest-junit reporter).
- `jest-junit@^16.0.0` to devDependencies.
- `jest-junit` reporter config block (output to `test-results/junit.xml`).

.gitignore:
- `test-results/` and `coverage/` (generated on every run).

Wins (estimated):
- p50 wall-clock: ~167s → ~max(lint ~45s, typecheck ~70s, unit ~50s,
  e2e advisory) ≈ 70s. ~58% faster.
- Stops wasted runs on non-PR pushes.
- Failing tests now surface as inline PR-diff annotations + a clear
  status emoji at the top of the auto-comment.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Apr 28, 2026 4:45pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

CI workflow re-scoped to run on pull requests, split into lint/typecheck/unit/e2e/report/ci-success jobs with Node 20 and pnpm caching; unit job emits JUnit and coverage artifacts, e2e runs continue-on-error; report merges coverage and posts an idempotent PR comment. Added CI Jest config and extra ignore patterns.

Changes

Cohort / File(s) Summary
CI Workflow Restructuring
.github/workflows/tests.yml
Switches trigger from push to pull_request for main, dev, develop; adds checks: write permission and PR-scoped concurrency. Replaces single test job with lint, typecheck, unit, e2e, report, and ci-success jobs. All jobs use Node 20 and pnpm install --frozen-lockfile with PNPM cache. Unit job runs pnpm test:unit:ci, publishes JUnit via dorny/test-reporter, uploads coverage-final.json and unit JUnit XML artifacts. E2E runs pnpm test:e2e (installs Playwright), marked continue-on-error: true, and uploads Playwright report. report downloads artifacts, merges coverage with nyc, parses/decodes unit JUnit XML to compute failures and top slow tests, writes payload JSON, and posts/updates an idempotent PR comment. ci-success aggregates outcomes and uses if: always() while failing on upstream failure/cancelled.
Ignore & CI test config
.gitignore, package.json
Adds test-results/ and coverage/ to .gitignore (non-root patterns). Adds test:unit:ci script to package.json, adds jest-junit devDependency, and a top-level jest-junit config block to emit JUnit XML for CI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: CI refactoring with parallel jobs, PR-gating, jest-junit integration, and a new report job.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the why, what, and testing approach for the CI improvements.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

Code-analysis diff

Painscore total: 5402.52 → 5402.52 (0)
Findings: +1 net (+13 new, -12 resolved)

🆕 New findings (13)

  • low unused-dep — package.json:47 — unused dependency: @capacitor/core
  • low unused-dep — package.json:66 — unused dependency: @sumsub/cordova-idensic-mobile-sdk-plugin
  • low unused-dep — package.json:74 — unused dependency: @zerodev/webauthn-key
  • low unused-dep — package.json:77 — unused dependency: circle-flags
  • low unused-dep — package.json:81 — unused dependency: ethers
  • low unused-dep — package.json:93 — unused dependency: posthog-node
  • low unused-dep — package.json:105 — unused dependency: redux
  • low unused-dep — package.json:108 — unused dependency: siwe
  • info unused-dep — package.json:121 — unused devDependency: @testing-library/dom
  • info unused-dep — package.json:138 — unused devDependency: prettier
  • info unused-dep — package.json:141 — unused devDependency: size-limit
  • info unused-dep — package.json:144 — unused devDependency: tsx
  • info unused-dep — package.json:146 — unused devDependency: jest-junit

✅ Resolved (12)

  • package.json:46 — unused dependency: @capacitor/core
  • package.json:65 — unused dependency: @sumsub/cordova-idensic-mobile-sdk-plugin
  • package.json:73 — unused dependency: @zerodev/webauthn-key
  • package.json:76 — unused dependency: circle-flags
  • package.json:80 — unused dependency: ethers
  • package.json:92 — unused dependency: posthog-node
  • package.json:104 — unused dependency: redux
  • package.json:107 — unused dependency: siwe
  • package.json:120 — unused devDependency: @testing-library/dom
  • package.json:137 — unused devDependency: prettier
  • package.json:140 — unused devDependency: size-limit
  • package.json:143 — unused devDependency: tsx

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

🧪 UI test report — ✅ all green

Suites

  • unit: 932 ran, 0 failed, 0 skipped, 16.1s

📊 Coverage (unit)

metric %
statements 43.0%
branches 22.8%
functions 22.7%
lines 42.6%
⏱ 10 slowest test cases
time test
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.2s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › SimpleFi WebSocket waiting shows processing indicator
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid German IBAN
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address with surrounding spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle unresolvable ENS name
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (missing 0x prefix)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid US account with spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle too long for US account
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tests.yml:
- Around line 172-175: The report job currently lists only needs: [unit] but
posts a misleading "✅ all green" status; update the report job (named report) so
its needs array includes lint and typecheck (needs: [unit, lint, typecheck]) to
ensure it waits for those results before posting, OR change the posted
status/message to clearly state it's reporting unit-only results (e.g., "✅ unit
tests passed") instead of "all green"; adjust the workflow step that emits the
message to reference the new wording if you choose the latter.
- Around line 307-313: The current lookup uses github.rest.issues.listComments
which only returns the first page (30) so the tagged report can be missed;
update the code that builds comments (the call around
github.rest.issues.listComments and the variables tag/existing) to fetch all
pages using Octokit's pagination (e.g. github.paginate with
github.rest.issues.listComments and the same params: owner, repo, issue_number)
and then search the resulting full list for the tag to set existing, ensuring
the follow-up update is idempotent instead of creating duplicates.
- Around line 215-246: The report job currently depends only on unit (needs:
[unit]) so update the report job's needs to include lint and typecheck (needs:
[unit, lint, typecheck]) so the PR comment reflects all checks; also fix the
comment-update logic that uses listComments by adding per_page and implementing
pagination (loop through pages until no more) when fetching existing comments so
the code can locate and update an existing report instead of creating
duplicates—refer to the report job's needs declaration and the listComments
usage in the comment-update routine when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0a139784-2efd-4294-ac10-82518cadf070

📥 Commits

Reviewing files that changed from the base of the PR and between 785620f and 0c765df.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • .github/workflows/tests.yml
  • .gitignore
  • package.json

Comment thread .github/workflows/tests.yml
Comment thread .github/workflows/tests.yml
Comment thread .github/workflows/tests.yml
Same pattern as peanut-api-ts #672. Branch protection requires only
`ci-success` instead of listing every job — auto-gates new jobs added
to `needs:` here, no ruleset edits required.

`if: always()` + explicit `failure || cancelled` check handles the
edge case where an upstream `skipped` would otherwise let the umbrella
silently pass (GitHub treats `skipped` as neutral).
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
.github/workflows/tests.yml (2)

172-175: ⚠️ Potential issue | 🟡 Minor

Report job claims "all green" but only checks unit tests.

The needs: [unit] dependency means the report runs after only unit tests complete. However, line 260 displays "✅ all green" which is misleading since lint and typecheck may still be failing. Either expand needs to include lint and typecheck, or clarify the message to reflect unit-only results.

Option A: Expand needs array
     report:
         runs-on: ubuntu-latest
-        needs: [unit]
+        needs: [unit, lint, typecheck]
         if: always()
Option B: Clarify message as unit-only
-                     const status = p.failed.length === 0 ? '✅ all green' : `🔴 ${p.failed.length} failing`;
+                     const status = p.failed.length === 0 ? '✅ unit green' : `🔴 ${p.failed.length} unit failing`;

Also applies to: 260-260

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 172 - 175, The report job's
dependency only lists "unit" causing the "✅ all green" message (line with that
text) to be misleading; either expand the report job's needs array to include
lint and typecheck (so the job waits for unit, lint, and typecheck before
reporting) or change the report job's success message to explicitly state it
reflects only unit test results; locate the `report:` job block and update the
`needs:` array to `needs: [unit, lint, typecheck]` or edit the displayed message
text ("✅ all green") to "✅ unit tests passed" (or similar) to accurately reflect
scope.

307-313: ⚠️ Potential issue | 🟡 Minor

Paginate PR comments to avoid duplicates on busy PRs.

listComments returns only the first page (30 items by default). On PRs with many comments, the tagged report may not be found, causing duplicates instead of updates.

🔁 Proposed fix using Octokit pagination
                      const tag = '<!-- ui-test-report -->';
-                     const { data: comments } = await github.rest.issues.listComments({
-                         issue_number: context.issue.number,
-                         owner: context.repo.owner,
-                         repo: context.repo.repo,
-                     });
+                     const comments = await github.paginate(
+                         github.rest.issues.listComments,
+                         {
+                             issue_number: context.issue.number,
+                             owner: context.repo.owner,
+                             repo: context.repo.repo,
+                             per_page: 100,
+                         }
+                     );
                      const existing = comments.find(c => c.body && c.body.includes(tag));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 307 - 313, The code uses
github.rest.issues.listComments which only returns the first page, causing
missed tagged reports; replace that single-page call with Octokit pagination
(use github.paginate) to retrieve all comments before searching for the tag
variable, i.e. call github.paginate(github.rest.issues.listComments, {
issue_number: context.issue.number, owner: context.repo.owner, repo:
context.repo.repo }) to populate the comments array and then run the existing
find (existing = comments.find(...)) so duplicates are avoided.
🧹 Nitpick comments (1)
.github/workflows/tests.yml (1)

155-156: Consider using pnpm exec instead of npx for Playwright browser install.

After pnpm install, using npx playwright install may resolve to a globally cached version rather than the project's locked version. Using pnpm exec ensures the locally installed Playwright is used.

♻️ Suggested change
             - name: Install Playwright browsers
-              run: npx playwright install --with-deps chromium
+              run: pnpm exec playwright install --with-deps chromium
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 155 - 156, The GitHub Actions step
"Install Playwright browsers" currently uses npx which can pick a global binary;
update the step to invoke the project's Playwright via pnpm by replacing the run
command "npx playwright install --with-deps chromium" with a pnpm-based
invocation (e.g., "pnpm exec playwright install --with-deps chromium") so the
workflow uses the locked local Playwright package version after pnpm install.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tests.yml:
- Around line 220-222: The decode function's replacement for &apos; is wrong:
change the `.replaceAll('&apos;','\\'')` occurrence inside function decode to
replace &apos; with a single-quote character instead of a backslash-escaped
string; update the replacement argument so it is a single-quote (e.g., use a
literal "'" as the replacement) ensuring proper quoting in the workflow/YAML
context so the resulting JavaScript uses .replaceAll('&apos;', "'").

---

Duplicate comments:
In @.github/workflows/tests.yml:
- Around line 172-175: The report job's dependency only lists "unit" causing the
"✅ all green" message (line with that text) to be misleading; either expand the
report job's needs array to include lint and typecheck (so the job waits for
unit, lint, and typecheck before reporting) or change the report job's success
message to explicitly state it reflects only unit test results; locate the
`report:` job block and update the `needs:` array to `needs: [unit, lint,
typecheck]` or edit the displayed message text ("✅ all green") to "✅ unit tests
passed" (or similar) to accurately reflect scope.
- Around line 307-313: The code uses github.rest.issues.listComments which only
returns the first page, causing missed tagged reports; replace that single-page
call with Octokit pagination (use github.paginate) to retrieve all comments
before searching for the tag variable, i.e. call
github.paginate(github.rest.issues.listComments, { issue_number:
context.issue.number, owner: context.repo.owner, repo: context.repo.repo }) to
populate the comments array and then run the existing find (existing =
comments.find(...)) so duplicates are avoided.

---

Nitpick comments:
In @.github/workflows/tests.yml:
- Around line 155-156: The GitHub Actions step "Install Playwright browsers"
currently uses npx which can pick a global binary; update the step to invoke the
project's Playwright via pnpm by replacing the run command "npx playwright
install --with-deps chromium" with a pnpm-based invocation (e.g., "pnpm exec
playwright install --with-deps chromium") so the workflow uses the locked local
Playwright package version after pnpm install.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee9b5f7e-849a-4dc6-97d6-de637e00d672

📥 Commits

Reviewing files that changed from the base of the PR and between 5c34751 and 608cbdf.

📒 Files selected for processing (1)
  • .github/workflows/tests.yml

Comment on lines +220 to +222
function decode(s) {
return s.replaceAll('&amp;','&').replaceAll('&lt;','<').replaceAll('&gt;','>').replaceAll('&quot;','\"').replaceAll('&apos;','\\'');
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the current escaping behavior vs the fix
echo "=== Current escaping (produces backslash) ==="
node -e "console.log('test'.replaceAll('t','\\\\'))"

echo ""
echo "=== Correct escaping for single quote ==="
node -e "console.log('test'.replaceAll('t',\"'\"))"

Repository: peanutprotocol/peanut-ui

Length of output: 167


🏁 Script executed:

cd .github/workflows && head -n 225 tests.yml | tail -n 15

Repository: peanutprotocol/peanut-ui

Length of output: 877


Escaping bug in decode function: &apos; replacement produces backslash instead of single quote.

The current code .replaceAll('&apos;','\\'') replaces XML &apos; entities with a backslash character (\), not a single quote ('). In JavaScript, '\\' is a string containing one backslash.

Fix by using "'" inside the double-quoted shell context:

🐛 Proposed fix
                  function decode(s) {
-                     return s.replaceAll('&amp;','&').replaceAll('&lt;','<').replaceAll('&gt;','>').replaceAll('&quot;','\"').replaceAll('&apos;','\\'');
+                     return s.replaceAll('&amp;','&').replaceAll('&lt;','<').replaceAll('&gt;','>').replaceAll('&quot;','\"').replaceAll('&apos;',"'");
                  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 220 - 222, The decode function's
replacement for &apos; is wrong: change the `.replaceAll('&apos;','\\'')`
occurrence inside function decode to replace &apos; with a single-quote
character instead of a backslash-escaped string; update the replacement argument
so it is a single-quote (e.g., use a literal "'" as the replacement) ensuring
proper quoting in the workflow/YAML context so the resulting JavaScript uses
.replaceAll('&apos;', "'").

…back)

Same fix as peanut-api-ts/devx/test-overhaul commit 9dc1b2dc:

1. report's needs was [unit] only — header "✅ all green" misled when
   lint/typecheck/e2e statuses weren't represented. Now
   needs: [lint, typecheck, unit, e2e].

2. listComments wasn't paginated — would silently start posting
   duplicates once PR has >30 comments. Switched to github.paginate
   with per_page=100.
@Hugo0 Hugo0 merged commit 9b490c9 into dev Apr 28, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant