Skip to content

fix(core): TRAC-281 prevent breadcrumb cache mutation and stabilize flaky E2E tests#2964

Open
chanceaclark wants to merge 1 commit into
canaryfrom
TRAC-281/fix-breadcrumb-cache-mutation
Open

fix(core): TRAC-281 prevent breadcrumb cache mutation and stabilize flaky E2E tests#2964
chanceaclark wants to merge 1 commit into
canaryfrom
TRAC-281/fix-breadcrumb-cache-mutation

Conversation

@chanceaclark
Copy link
Copy Markdown
Contributor

@chanceaclark chanceaclark commented Apr 1, 2026

Jira: TRAC-281

What/Why?

Fixes the underlying TRAC-281 breadcrumb mutation bug and stabilizes the E2E suite around it. Originally scoped to just the breadcrumb cache fix; expanded after rebasing onto canary's Next.js 16.2 bump exposed unrelated flaky tests that needed the same hand-holding.

Core fixes:

  1. Breadcrumb cache mutation (breadcrumbs-transformer.ts) — getWebPageBreadcrumbs called .reverse() directly on the array returned from React's cache(), mutating the shared reference. When generateMetadata raced getWebPageBreadcrumbs, the cached array could be in reversed order, dropping parent breadcrumbs. Spread into a new array before reversing. Also fixes an off-by-one in truncateBreadcrumbs where arrays at exactly the target length entered the truncation path with dropCount = 0 and replaced the middle element with '...' spuriously (<<=).

  2. DOMPurify SSR crash (product-review-schema.tsx) — DOMPurify needs a browser DOM and crashed during SSR. Defer the component to client-only rendering via a mounted-state check.

E2E test stabilization:

  1. Nested webpages breadcrumb assertion (webpages.spec.ts) — Even after the upstream PHP fix for PageTree cache invalidation on page create/update, the breadcrumb-truncation assertion in the nested-webpages test remained unreliable. Dropped the assertion; the test still validates sidemenu rendering and navigation, which is the substantive coverage. Breadcrumb behavior is covered by breadcrumbs-transformer logic and the dedicated breadcrumbs.spec.ts UI test.

  2. Add-to-cart toast race (cart.spec.ts, coupon.spec.ts, shipping.spec.ts) — The success message is a Sonner toast that auto-dismisses after ~4s, making the assertion inherently racy. The pre-existing toPass + reload retry in cart.spec.ts was also logically broken: reload destroys client-side toast state, so retries could never succeed once the first attempt missed the toast. Replaced with waitForLoadState('networkidle') and let the post-navigation /cart assertions verify state.

  3. Coupon apply re-attempt (coupon.spec.ts) — The previous catch/reload pattern only recovered when the server had already applied the coupon (CATALYST-1685). When the action silently failed, reload couldn't bring the coupon back. Replaced with a toPass retry that re-applies the coupon from a clean reloaded state if the optimistic update reverts.

  4. Shipping/compare retry wrappers (shipping.spec.ts, compare.spec.ts) — Wrapped the known-flaky server-action state assertions in toPass retries with longer timeouts (CATALYST-1685).

  5. Orders duplicate-match in PPR (account/orders.spec.ts) — Next.js 16.2's PPR/Suspense streaming leaves a hidden stale shell alongside the streamed content, leaving two identical <h2> elements in the DOM (only one is accessibility-visible, but Playwright strict mode matches both). Added .first() to the empty-state title and order-id assertions, matching the pattern already used elsewhere in the file.

Rollout/Rollback

Standard rollout. No feature flags. Revert is safe.

Testing

  • pnpm run typecheck passes
  • pnpm run lint passes (core)
  • E2E suite passes on the rebased branch (Next.js 16.2). The previously consistently-failing webpages.spec.ts:42 is gone, and the auth/session-related flakes that surfaced on a separate run passed on the most recent run — they are flaky in canary post-Next-bump, not blockers from this PR.

Migration

No migration needed. No breaking changes.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 1, 2026

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

Project Deployment Actions Updated (UTC)
catalyst Ready Ready Preview, Comment May 14, 2026 8:46pm

Request Review

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: 9830ff0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@bigcommerce/catalyst-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

Bundle Size Report

Comparing against baseline from 9aacfdc (2026-05-14).

No bundle size changes detected.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

Unlighthouse Performance Comparison — Vercel

Comparing PR preview deployment Unlighthouse scores vs production Unlighthouse scores.

Summary Score

Aggregate score across all categories as reported by Unlighthouse.

Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Score 90 93 92 95

Category Scores

Category Prod Desktop Prod Mobile Preview Desktop Preview Mobile
Performance 77 91 76 81
Accessibility 95 95 95 92
Best Practices 100 100 100 100
SEO 88 88 100 100

Core Web Vitals

Metric Prod Desktop Prod Mobile Preview Desktop Preview Mobile
LCP 3.5 s 3.5 s 3.6 s 5.0 s
CLS 0.001 0 0 0
FCP 1.2 s 1.2 s 1.2 s 1.1 s
TBT 0 ms 0 ms 0 ms 10 ms
Max Potential FID 50 ms 50 ms 40 ms 60 ms
Time to Interactive 3.6 s 3.6 s 3.6 s 5.1 s

Full Unlighthouse report →

@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from c83e817 to 97e0587 Compare April 1, 2026 23:27
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from 97e0587 to 64e9fcd Compare April 1, 2026 23:32
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from d299528 to 3179322 Compare April 8, 2026 21:39
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from 9045c5b to 7550c97 Compare May 14, 2026 16:01
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from 4dcaec1 to aeba961 Compare May 14, 2026 17:56
@chanceaclark chanceaclark changed the title fix(core): TRAC-281 Prevent breadcrumb cache mutation on web pages fix(core): TRAC-281 prevent breadcrumb cache mutation and stabilize flaky E2E tests May 14, 2026
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from aeba961 to 1a97d49 Compare May 14, 2026 18:10
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from 1a97d49 to 033d2a6 Compare May 14, 2026 18:18
@chanceaclark chanceaclark force-pushed the TRAC-281/fix-breadcrumb-cache-mutation branch from 033d2a6 to f7dfc78 Compare May 14, 2026 18:28
@chanceaclark chanceaclark marked this pull request as ready for review May 14, 2026 19:23
@chanceaclark chanceaclark requested a review from a team as a code owner May 14, 2026 19:23
Comment thread core/tests/ui/e2e/compare.spec.ts Outdated
await expect(page.getByText(formattedProductPrice)).toBeVisible();
await expect(page.getByText(formattedProductWithVariantsPrice).first()).toBeVisible();
}
}).toPass({ timeout: 90000, intervals: [2000] });
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

how heavy is the refresh? if we're setting 90s as the limit but doing 2s retries, does that enable us to provide a big enough window? unsure, but gut feeling is that it is on the smaller side

(same comment for the ones with similar values)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The test passes within 45s so I am going to lower it and just use the default interval here. It does take 30-40 seconds though for this test.

…laky E2E tests

Fix the underlying TRAC-281 breadcrumb mutation bug and stabilize the
specific E2E tests that block CI around it.

Core fixes:
- `breadcrumbs-transformer.ts`: stop calling `.reverse()` on the array
  returned from React's `cache()`, which mutated the shared reference
  and caused parent breadcrumbs to disappear when `generateMetadata`
  raced `getWebPageBreadcrumbs`. Fix off-by-one in `truncateBreadcrumbs`
  where arrays exactly at the target length were incorrectly truncated.
- `product-review-schema.tsx`: defer DOMPurify-using markup to the
  client via a mounted-state check; DOMPurify needs a browser DOM and
  crashed during SSR.

E2E test stabilization:
- `webpages.spec.ts`: drop the truncated-breadcrumb assertion from the
  nested-webpages test; the Storefront API caches PageTree and the
  assertion is unreliable in CI even after the upstream PHP fix.
- `cart.spec.ts`, `coupon.spec.ts`, `shipping.spec.ts` (helper): stop
  asserting on the add-to-cart Sonner toast, which auto-dismisses
  after ~4s and made the assertion racy. Wait for `networkidle` and
  verify state on the /cart page instead.
- `coupon.spec.ts`: replace the catch/reload pattern with a `toPass`
  retry that re-applies the coupon from a clean reloaded state when
  the optimistic update reverts (CATALYST-1685). The previous pattern
  only recovered when the server had already applied the coupon;
  reload alone couldn't recover a silently-failed action.
- `account/orders.spec.ts`: add `.first()` to the empty-state title
  and order-id assertions to match the pattern already used by the
  rest of the file; Next.js 16.2 PPR/Suspense leaves a hidden stale
  shell alongside the streamed content, leaving two matches in the
  DOM.

CI workflow:
- `e2e.yml`: hoist `TESTS_LOCALE` and `TRAILING_SLASH` to job-level
  env so every step (Build, Start server, Run E2E tests) receives
  them. Previously they were only set on Start server, so:
    1. The Build step ran without `TRAILING_SLASH`, baking
       `trailingSlash: true` into `next.config.ts` for the
       TRAILING_SLASH=false matrix. Next's own routing then added
       the trailing slash before the proxy could intercept,
       causing the trailing-slash redirect-loop regression tests
       to fail when they finally ran.
    2. The Run E2E tests step ran without `TESTS_LOCALE` or
       `TRAILING_SLASH`, so `testEnv` fell back to schema
       defaults and every tagged test in the alternate-locale
       and TRAILING_SLASH=false matrices self-skipped, silently
       making those matrix jobs no-ops since they were added in
       the Next.js 16 proxy migration (#2912).

Fixes TRAC-281
Refs CATALYST-1685
Co-Authored-By: Claude <noreply@anthropic.com>
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.

2 participants