Repository navigation
fix(e2e): stabilize test suite and fix blog revalidation regression - #420
Merged
Merged
Conversation
Parallel loaders (standalone segments keyed to parent shortCode) were grouped with the layout in segmentTreeWalk. When the layout has no loading(), renderSegments awaited them, blocking SSR for the full loader duration. Fix at the rendering layer with two small changes: 1. resolveParallelEntry: tag loader segments with parallelLoading (the owning parallel's loading fallback) when the parallel has loading() set. 2. renderSegments: in the "has loaders but no loading" branch, split loaders into layout-owned (await as before) and parallel-owned (attach as loaderDataPromise to the parallel segment so ParallelOutlet wraps in LoaderBoundary for streaming). No changes to resolution paths, cache-hit paths, segment IDs, partial navigation, or the segment tree walk. The fix is purely in how renderSegments handles the tagged loaders.
Inflight prefetches buffer the entire response body (arrayBuffer) before resolving. For routes with slow streaming loaders (e.g., parallel sidebar with 5.5s loader), this blocks SPA navigation for the full loader duration even though createFromFetch can consume streams incrementally. When an inflight prefetch exists, skip it and do a fresh fetch instead. The fresh fetch gives createFromFetch a streaming Response for incremental consumption. The inflight prefetch completes in the background and populates the cache for future navigations.
…h fetch" This reverts commit 9699109.
ctx.url is stripped of _rsc* params by stripInternalParams() in the middleware pipeline, so _rsc_partial, _rsc_segments, _rsc_action, and _rsc_loader checks on ctx.url were always false in production. This caused: - No segment hash in cache keys (different partial navigations to the same URL with different client segments shared a cache slot) - _rsc_action and _rsc_loader skip guards never firing (loader requests going through the document cache instead of being bypassed) - mayNeedSSR() receiving stripped URL, falling back to Accept header heuristic instead of definitive _rsc_partial detection Fix: derive _rsc* detection and segment hashing from ctx.request.url (the original, unstripped URL). ctx.url is still used for pathname matching and user-facing query params. Also update test mock to simulate production behavior: ctx.url has _rsc* params stripped, ctx.request.url has them intact.
Prefetch fetch was calling response.arrayBuffer() to fully buffer the body before resolving the inflight promise. This blocked navigation for the entire stream duration when reusing inflight prefetches for routes with slow streaming loaders. Remove the arrayBuffer() call — return the streaming Response directly. Cache storage uses response.clone() so the original body stays consumable by navigation's createFromFetch for incremental streaming.
…d actions - Add parallelLoaderSources to ResolvedSegment for stable aggregate promise identity across renders - Fix segment reconciler to preserve parallel loader fields during partial updates - Add restoreParallelLoaderMarkers for cache-hit/revalidation paths where parallelLoading tag is lost after serialization - Add e2e blog test covering action revalidation with parallel sidebar
…euse Prefetch fetch now tees the response body instead of buffering with arrayBuffer(). One branch goes to cache storage, the other resolves the inflight promise for navigation consumption. This lets navigation reuse inflight prefetches without waiting for the full body to download. Restores the inflight prefetch reuse path in navigation-client that was previously removed — it now works correctly because the inflight promise resolves with a streaming Response (not a buffered one).
… combined/parallel-loader-streaming
…n' into combined/parallel-loader-streaming
…aming' into combined/parallel-loader-streaming
…ching, docs Parallel entries are now keyed by slot name (Record<@slot, ParallelEntryData>) instead of a flat array. This makes slot override semantics natural (record key assignment = last-write-wins) and gives the resolution layer direct slot→entry mapping. - server/context.ts: ParallelEntries type (Partial<Record<@slot, ParallelEntryData>>), helper functions (getParallelEntries, getParallelSlotEntries, getParallelSlotCount) - dsl-helpers.ts: parallel() creates one entry per slot, assigns to parent.parallel[slot] - fresh.ts + revalidation.ts: iterate via getParallelSlotEntries(), dedup by entry ID - segment-system.tsx: namespace-based restoreParallelLoaderMarkers and per-parallel loader isolation in renderSegments - Fix stale-propagation test mock to include actual context exports Docs: - skills/parallel/SKILL.md: streaming behavior, slot override semantics - execution-model.md: parallel streaming across all render paths - feature-map.md: parallel slot streaming and override dedup - feature-file-map.md: add segment-system.tsx and segment-reconciler.ts ownership - semantic-change-checklist.md: parallel slots section with reconstruction invariant
…uning - Split single `hmr` project into `hmr-loader` → `hmr-routes` → `hmr-intercept` chain to prevent concurrent file modifications (route-types-hmr was modifying route files while intercept-hmr expected them intact) - Decouple HMR/webkit-smoke from dev/production dependencies — flaky dev tests no longer block 44+ HMR/webkit tests from running - Set retries: 2 for both local and CI (was 0 locally) - Set workers: 3 for both local and CI (was 6 locally) to reduce dev server contention that caused random test failures
…errides
- Set global expect timeout to 10s (was Playwright default 5s) to handle
slower dev server responses under concurrent worker load
- Increase helper function default timeouts (waitForElement, clickAndWaitFor,
waitForTextChange, waitForNumericChange) from 5s to 10s
- Remove explicit `{ timeout: 5000 }` from 43 positive assertions across
8 flaky test files so they inherit the higher global default
- Increase pending-actions wait from 600ms to 1500ms for action settlement
- Increase handler-first cache write wait from 500ms to 1000ms
- Increase handle-breadcrumbs action timeout from 15s to 20s
- Increase timeout.test.ts waitForOnError default from 5s to 15s
Reduces flaky test count from ~17 to ~12 per run.
…d retries - Increase waitForClientAgeToExceed polling timeout from 5s to 15s to handle slower timer increments under load - Set retries: 2 for both local and CI (was 0 locally) to handle the flaky blog post query-param revalidation test
…ions The blog probe action feature changed postRevalidation to use `isBlogProbeAction ?? defaultShouldRevalidate` which falls through to defaultShouldRevalidate (true) for non-action GET navigations with search param changes. This caused the blog post to re-render on every ?tab=N navigation, resetting the client timer. Fix: explicitly check for probe action OR slug change, preserving the original behavior of skipping revalidation on search-only changes.
commit: |
- Remove unused waitForCacheHit function from cache-status.test.ts - Blog postRevalidation: defer to defaultShouldRevalidate for ALL actions (not just probe), only skip revalidation for GET navigations with same slug. This handles future non-probe actions correctly.
The hmr project was split into hmr-loader, hmr-routes, hmr-intercept but the CI workflow still referenced the old 'hmr' project name, causing CI to fail with "Project hmr not found".
…ze timing - Remove clearLocalViteCache() and its call sites — isolated servers now use RANGO_E2E_VITE_CACHE_DIR env var instead of deleting shared cache - Add default cacheDir fallbacks to vite configs (e2e-basic, e2e-timeout, test-app) so each app uses its own cache dir without the env var - Add .vite* to .gitignore for isolated cache directories - Small timing stabilization: waitForTimeout after hydration in cache and loader-behavior tests, expectNoReload guard in skipSSR test, increase prerender-intercept retry attempts from 2 to 5
After production eviction, Static() handler stubs lack a .handler property, causing parallel slot resolution to skip (fresh path) or crash with an invariant (revalidation path) before tryStaticSlot could look up the pre-rendered component from __STATIC_MANIFEST. Move tryStaticSlot before the handler undefined guard in fresh.ts, and replace the invariant with a graceful fallback in revalidation.ts.
After `rm -rf node_modules/.vite`, the first HMR file change triggers Vite dep re-optimization → full page reload, which destroys the console event listener tests rely on. Add warmup tests that absorb this initial reload so subsequent HMR tests get clean rsc:update events.
Isolated dev servers (use-cache, timeout tests) skip the shared dev-warmup setup, so their first SSR request triggers Vite dep optimization → module re-evaluation → in-memory cache loss. Add warmupDevServer() that makes two consecutive SSR requests to absorb the dep optimization cycle before tests run. Also switch Unix kill() to process group kill (-pid) so Vite's child processes (workerd, etc.) are cleaned up properly on teardown.
This was referenced Mar 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
route-types-hmrandintercept-hmrmodified the same files simultaneouslypostRevalidationreturnedtruefor search-only navigationsChanges
Playwright config (router):
hmrproject →hmr-loader→hmr-routes→hmr-interceptchain to prevent concurrent file modificationsTest file improvements:
{ timeout: 5000 }overrides that were lower than the new global defaultBlog revalidation fix (
tests/vite-rsc-demo/src/pages/blog.tsx):isBlogProbeAction ?? defaultShouldRevalidatereturnedtruefor GET navigations with search param changes (undefined ?? true = true)Test plan