Repository: vercel/next.js
Pull request: test: de-flake client-cache deployment tests

These tests aren't resilient to changes in router behavior after extensive refactors. What looks to be happening is a race between the prefetch being issued and the click firing:
1. Cache expires -> refetch prefetch is triggered
2. Click happens while prefetch is in-flight -> route entry is Pending
3. Since no fulfilled cache entry exists, `navigate()` falls through to `navigateDynamicallyWithNoPrefetch()`
4. This issues a full dynamic request
5. The full dynamic response is favored and skips the loading state

This ensures that we are waiting for network activity to finish before navigating. 

Changed files:
filename: test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts
patch: @@ -268,6 +268,9 @@ describe('app dir client cache semantics (default semantics)', () => {
       })
 
       it('should refetch the full page after 5 mins', async () => {
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         const randomLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
           .click()
@@ -285,6 +288,10 @@ describe('app dir client cache semantics (default semantics)', () => {
           .click()
           .waitForElementByCss('[href="/1?timeout=1000"]')
 
+        // Wait for prefetch requests to complete before clicking, otherwise
+        // clicking during an in-flight prefetch aborts it and skips loading state
+        await browser.waitForIdleNetwork()
+
         const newLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
           .click()

filename: test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts
patch: @@ -20,6 +20,9 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
       it('should trigger a loading state before fetching the page, followed by fresh data on every subsequent navigation', async () => {
         const browser = await next.browser('/', browserConfigWithFixedTime)
 
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         // this test introduces an artificial delay in rendering the requested page, so we verify a loading state is rendered
         await browser
           .elementByCss('[href="/1?timeout=1000"]')
@@ -124,6 +127,9 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
       it('should trigger a loading state before fetching the page, followed by fresh data on every subsequent navigation', async () => {
         const browser = await next.browser('/', browserConfigWithFixedTime)
 
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         // this test introduces an artificial delay in rendering the requested page, so we verify a loading state is rendered
         await browser
           .elementByCss('[href="/2?timeout=1000"]')
@@ -176,6 +182,9 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
       it('should trigger a loading state before fetching the page, followed by fresh data on every subsequent navigation', async () => {
         const browser = await next.browser('/', browserConfigWithFixedTime)
 
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         // this test introduces an artificial delay in rendering the requested page, so we verify a loading state is rendered
         await browser
           .elementByCss('[href="/1?timeout=1000"]')
@@ -317,15 +326,21 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
       it('should re-use the loading boundary for the custom static override time (3 minutes)', async () => {
         const browser = await next.browser('/', browserConfigWithFixedTime)
 
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         const loadingRandomNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
           .click()
           .waitForElementByCss('#loading')
           .text()
 
+        await browser.eval(fastForwardTo, 2 * 60 * 1000) // fast forward 2 minutes
+
         await browser.elementByCss('[href="/"]').click()
 
-        await browser.eval(fastForwardTo, 2 * 60 * 1000) // fast forward 2 minutes
+        await browser.waitForElementByCss('[href="/1?timeout=1000"]')
+        await browser.waitForIdleNetwork()
 
         let newLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
@@ -335,9 +350,13 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
 
         expect(loadingRandomNumber).toBe(newLoadingNumber)
 
+        await browser.eval(fastForwardTo, 2 * 60 * 1000) // fast forward 2 minutes
+
         await browser.elementByCss('[href="/"]').click()
 
-        await browser.eval(fastForwardTo, 2 * 60 * 1000) // fast forward 2 minutes
+        // Wait for link to be visible (triggers prefetch), then wait for prefetch to complete
+        await browser.waitForElementByCss('[href="/1?timeout=1000"]')
+        await browser.waitForIdleNetwork()
 
         newLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
@@ -384,6 +403,9 @@ describe('app dir client cache semantics (experimental staleTimes)', () => {
     it('should trigger a loading state before fetching the page, followed by fresh data on every subsequent navigation', async () => {
       const browser = await next.browser('/', browserConfigWithFixedTime)
 
+      // Wait for initial prefetch to complete before clicking
+      await browser.waitForIdleNetwork()
+
       // this test introduces an artificial delay in rendering the requested page, so we verify a loading state is rendered
       await browser
         .elementByCss('[href="/1?timeout=1000"]')

filename: test/e2e/app-dir/app-client-cache/client-cache.original.test.ts
patch: @@ -360,6 +360,9 @@ describe('app dir client cache semantics (30s/5min)', () => {
         expect(newNumber).not.toBe(randomNumber)
       })
       it('should refetch the full page after 5 mins', async () => {
+        // Wait for initial prefetch to complete before clicking
+        await browser.waitForIdleNetwork()
+
         const randomLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
           .click()
@@ -377,6 +380,10 @@ describe('app dir client cache semantics (30s/5min)', () => {
           .click()
           .waitForElementByCss('[href="/1?timeout=1000"]')
 
+        // Wait for prefetch requests to complete before clicking, otherwise
+        // clicking during an in-flight prefetch aborts it and skips loading state
+        await browser.waitForIdleNetwork()
+
         const newLoadingNumber = await browser
           .elementByCss('[href="/1?timeout=1000"]')
           .click()