Skip to content

Conversation

@acdlite
Copy link
Contributor

@acdlite acdlite commented Feb 12, 2026

Based on:


Adds a companion test suite that verifies the Instant Navigation Testing API works with the legacy prefetch model (no Cache Components / PPR). Without Cache Components, default prefetch renders up to the first loading.tsx boundary, and prefetch={true} eagerly fetches the full page.

Tests cover: loading shell rendered instantly during navigation, full prefetch content rendered instantly, nested instant scope error, and normal navigation after scope exits.

MPA tests are intentionally excluded — without PPR there is no static shell concept for MPA navigations, so those tests have no analog here.

The Instant Navigation Testing API lets e2e tests assert on the
prefetched UI state before dynamic data streams in. This simulates
what happens on a slow network or slow database: the CDN instantly
serves a cached static shell, but dynamic data takes an indefinite
amount of time to arrive.

Previously, MPA navigations (page reload, plain anchor links) within
an instant test scope served the static shell without hydration data,
so the page couldn't hydrate. This adds proper hydration support: on
initial page load, the static shell renders and hydration proceeds
normally, but dynamic data is held back until the test scope ends.
The Instant Navigation Testing API already uses a cookie to persist lock
state across MPA navigations (page reloads, plain anchor links), since
window globals don't survive navigation. Rather than maintaining two
parallel mechanisms — a window global for SPA and a cookie for MPA —
this makes the cookie the single source of truth for all cases.

Playwright sets/clears the cookie directly via the browser context, and
the client reacts to changes through the CookieStore API's change event.
This removes the window.__EXPERIMENTAL_NEXT_TESTING__ global and the
page.evaluate() calls that went with it.
Adds a companion test suite that verifies the Instant Navigation
Testing API works with the legacy prefetch model (no Cache Components /
PPR). Without Cache Components, default prefetch renders up to the
first loading.tsx boundary, and prefetch={true} eagerly fetches the
full page.

Tests cover: loading shell rendered instantly during navigation, full
prefetch content rendered instantly, nested instant scope error, and
normal navigation after scope exits.

MPA tests are intentionally excluded — without PPR there is no static
shell concept for MPA navigations, so those tests have no analog here.
@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Feb 12, 2026

Failing test suites

Commit: 63fc988 | About building and testing Next.js

pnpm test-start test/e2e/app-dir/segment-cache/prefetch-runtime/prefetch-runtime.test.ts (job)

  • runtime prefetching > passed to a public cache > can completely prefetch a page that uses cookies and no uncached IO (DD)
Expand output

● runtime prefetching › passed to a public cache › can completely prefetch a page that uses cookies and no uncached IO

apiRequestContext.fetch: socket hang up
Call log:
  - → GET http://localhost:46239/passed-to-public-cache/cookies-only?_rsc=gtqjj
  -   user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/130.0.6723.31 Safari/537.36
  -   accept: */*
  -   accept-encoding: gzip,deflate,br
  -   cookie: testCookie=initialValue
  -   next-test-fetch-priority: low
  -   referer: http://localhost:46239/
  -   next-router-prefetch: 1
  -   next-router-segment-prefetch: /!KGRlZmF1bHQp/passed-to-public-cache
  -   next-url: /
  -   rsc: 1
  -   sec-ch-ua: "Chromium";v="130", "HeadlessChrome";v="130", "Not?A_Brand";v="99"
  -   sec-ch-ua-mobile: ?0
  -   sec-ch-ua-platform: "Linux"

  225 |             // server; we pass the request to the server the immediately.
  226 |             result: (async () => {
> 227 |               const originalResponse = await page.request.fetch(request, {
      |                                                           ^
  228 |                 maxRedirects: 0,
  229 |               })
  230 |

  at fetch (lib/router-act.ts:227:59)
  at lib/router-act.ts:245:13
  at routeHandler (lib/router-act.ts:257:7)

● runtime prefetching › passed to a public cache › can completely prefetch a page that uses cookies and no uncached IO

apiRequestContext.fetch: socket hang up
Call log:
  - → GET http://localhost:46239/passed-to-public-cache/cookies-only?_rsc=o863q
  -   user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/130.0.6723.31 Safari/537.36
  -   accept: */*
  -   accept-encoding: gzip,deflate,br
  -   cookie: testCookie=initialValue
  -   next-test-fetch-priority: low
  -   referer: http://localhost:46239/
  -   next-router-prefetch: 1
  -   next-router-segment-prefetch: /!KGRlZmF1bHQp
  -   next-url: /
  -   rsc: 1
  -   sec-ch-ua: "Chromium";v="130", "HeadlessChrome";v="130", "Not?A_Brand";v="99"
  -   sec-ch-ua-mobile: ?0
  -   sec-ch-ua-platform: "Linux"

  225 |             // server; we pass the request to the server the immediately.
  226 |             result: (async () => {
> 227 |               const originalResponse = await page.request.fetch(request, {
      |                                                           ^
  228 |                 maxRedirects: 0,
  229 |               })
  230 |

  at fetch (lib/router-act.ts:227:59)
  at lib/router-act.ts:245:13
  at routeHandler (lib/router-act.ts:257:7)

● runtime prefetching › passed to a public cache › can completely prefetch a page that uses cookies and no uncached IO

apiRequestContext.fetch: socket hang up
Call log:
  - → GET http://localhost:46239/passed-to-public-cache/cookies-only?_rsc=nn07o
  -   user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/130.0.6723.31 Safari/537.36
  -   accept: */*
  -   accept-encoding: gzip,deflate,br
  -   cookie: testCookie=initialValue
  -   next-test-fetch-priority: low
  -   referer: http://localhost:46239/
  -   next-router-prefetch: 1
  -   next-router-segment-prefetch: /_index
  -   next-url: /
  -   rsc: 1
  -   sec-ch-ua: "Chromium";v="130", "HeadlessChrome";v="130", "Not?A_Brand";v="99"
  -   sec-ch-ua-mobile: ?0
  -   sec-ch-ua-platform: "Linux"

  225 |             // server; we pass the request to the server the immediately.
  226 |             result: (async () => {
> 227 |               const originalResponse = await page.request.fetch(request, {
      |                                                           ^
  228 |                 maxRedirects: 0,
  229 |               })
  230 |

  at fetch (lib/router-act.ts:227:59)
  at lib/router-act.ts:245:13
  at routeHandler (lib/router-act.ts:257:7)

● runtime prefetching › passed to a public cache › can completely prefetch a page that uses cookies and no uncached IO

apiRequestContext.fetch: socket hang up
Call log:
  - → GET http://localhost:46239/passed-to-public-cache/cookies-only?_rsc=nn07o
  -   user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/130.0.6723.31 Safari/537.36
  -   accept: */*
  -   accept-encoding: gzip,deflate,br
  -   cookie: testCookie=initialValue
  -   next-test-fetch-priority: low
  -   referer: http://localhost:46239/
  -   next-router-prefetch: 1
  -   next-router-segment-prefetch: /_index
  -   next-url: /
  -   rsc: 1
  -   sec-ch-ua: "Chromium";v="130", "HeadlessChrome";v="130", "Not?A_Brand";v="99"
  -   sec-ch-ua-mobile: ?0
  -   sec-ch-ua-platform: "Linux"

  225 |             // server; we pass the request to the server the immediately.
  226 |             result: (async () => {
> 227 |               const originalResponse = await page.request.fetch(request, {
      |                                                           ^
  228 |                 maxRedirects: 0,
  229 |               })
  230 |

  at fetch (lib/router-act.ts:227:59)
  at lib/router-act.ts:245:13
  at routeHandler (lib/router-act.ts:257:7)

pnpm test-start test/e2e/app-dir/static-generation-status/index.test.ts (job)

  • app-dir static-generation-status > should render the page using notFound with status 404 (DD)
  • app-dir static-generation-status > should render the page using redirect with status 307 (DD)
  • app-dir static-generation-status > should render the client page using redirect with status 307 (DD)
  • app-dir static-generation-status > should respond with 308 status code if permanent flag is set (DD)
  • app-dir static-generation-status > should render the non existed route redirect with status 404 (DD)
Expand output

● app-dir static-generation-status › should render the page using notFound with status 404

next build failed with code/signal 1

  77 |             if (code || signal)
  78 |               reject(
> 79 |                 new Error(
     |                 ^
  80 |                   `next build failed with code/signal ${code || signal}`
  81 |                 )
  82 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:79:17)

● app-dir static-generation-status › should render the page using redirect with status 307

next build failed with code/signal 1

  77 |             if (code || signal)
  78 |               reject(
> 79 |                 new Error(
     |                 ^
  80 |                   `next build failed with code/signal ${code || signal}`
  81 |                 )
  82 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:79:17)

● app-dir static-generation-status › should render the client page using redirect with status 307

next build failed with code/signal 1

  77 |             if (code || signal)
  78 |               reject(
> 79 |                 new Error(
     |                 ^
  80 |                   `next build failed with code/signal ${code || signal}`
  81 |                 )
  82 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:79:17)

● app-dir static-generation-status › should respond with 308 status code if permanent flag is set

next build failed with code/signal 1

  77 |             if (code || signal)
  78 |               reject(
> 79 |                 new Error(
     |                 ^
  80 |                   `next build failed with code/signal ${code || signal}`
  81 |                 )
  82 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:79:17)

● app-dir static-generation-status › should render the non existed route redirect with status 404

next build failed with code/signal 1

  77 |             if (code || signal)
  78 |               reject(
> 79 |                 new Error(
     |                 ^
  80 |                   `next build failed with code/signal ${code || signal}`
  81 |                 )
  82 |               )

  at ChildProcess.<anonymous> (lib/next-modes/next-start.ts:79:17)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants