next.js/test/e2e/app-dir/use-cache-hanging/use-cache-hanging.test.ts
use-cache-hanging.test.ts179 lines6.5 KB
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'
import stripAnsi from 'strip-ansi'

const expectedTimeoutErrorMessage =
  'Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".'

describe('use-cache-hanging', () => {
  const { next, isNextDev, skipped, isTurbopack } = nextTestSetup({
    files: __dirname,
    skipDeployment: true,
    skipStart: process.env.NEXT_TEST_MODE !== 'dev',
  })

  if (skipped) {
    return
  }

  if (isNextDev) {
    describe('when a "use cache" fill hangs in the static stage', () => {
      it('should show an error after a timeout', async () => {
        const outputIndex = next.cliOutput.length
        const browser = await next.browser('/static')

        await expect(browser).toDisplayRedbox(`
         {
           "code": "E236",
           "description": "Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".",
           "environmentLabel": null,
           "label": "Runtime Error",
           "source": "app/static/page.tsx (6:1) @ getCachedData
         > 6 | async function getCachedData(): Promise<string> {
             | ^",
           "stack": [
             "getCachedData app/static/page.tsx (6:1)",
             "Cached app/static/page.tsx (18:24)",
             "Page app/static/page.tsx (32:10)",
           ],
         }
        `)

        const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))

        expect(cliOutput).toContain(`Error: ${expectedTimeoutErrorMessage}
    at getCachedData (app/static/page.tsx:6:1)`)
      })
    })

    describe('when a "use cache" fill hangs in the runtime stage', () => {
      it('should show an error after a timeout', async () => {
        const outputIndex = next.cliOutput.length
        const browser = await next.browser('/runtime')

        await expect(browser).toDisplayRedbox(`
         {
           "code": "E236",
           "description": "Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".",
           "environmentLabel": null,
           "label": "Runtime Error",
           "source": "app/runtime/page.tsx (8:1) @ getCachedData
         >  8 | async function getCachedData(): Promise<string> {
              | ^",
           "stack": [
             "getCachedData app/runtime/page.tsx (8:1)",
             "Cached app/runtime/page.tsx (20:24)",
             "Page app/runtime/page.tsx (42:7)",
           ],
         }
        `)

        const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))

        expect(cliOutput).toContain(`Error: ${expectedTimeoutErrorMessage}
    at getCachedData (app/runtime/page.tsx:8:1)`)
      })
    })

    describe('when navigating to the static page', () => {
      it('should show an error toast after a timeout', async () => {
        const outputIndex = next.cliOutput.length
        const browser = await next.browser('/')

        await browser.elementByCss('a[href="/static"]').click()

        await retry(() => {
          const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))

          expect(cliOutput).toContain(`Error: ${expectedTimeoutErrorMessage}
    at getCachedData (app/static/page.tsx:6:1)`)
        }, 20_000)

        await expect(browser).toDisplayCollapsedRedbox(`
         {
           "code": "E236",
           "description": "Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".",
           "environmentLabel": "Server",
           "label": "Console Error",
           "source": "app/static/page.tsx (6:1) @ getCachedData
         > 6 | async function getCachedData(): Promise<string> {
             | ^",
           "stack": [
             "getCachedData app/static/page.tsx (6:1)",
             "Cached app/static/page.tsx (18:24)",
             "Page app/static/page.tsx (32:10)",
           ],
         }
        `)
      })
    })

    describe('when navigating to the runtime page', () => {
      it('should show an error toast after a timeout', async () => {
        const outputIndex = next.cliOutput.length
        const browser = await next.browser('/')

        await browser.elementByCss('a[href="/runtime"]').click()

        await retry(() => {
          const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))

          expect(cliOutput).toContain(`Error: ${expectedTimeoutErrorMessage}
    at getCachedData (app/runtime/page.tsx:8:1)`)
        }, 20_000)

        await expect(browser).toDisplayCollapsedRedbox(`
         {
           "code": "E236",
           "description": "Filling a cache during prerender timed out, likely because request-specific arguments such as params, searchParams, cookies() or dynamic data were used inside "use cache".",
           "environmentLabel": "Server",
           "label": "Console Error",
           "source": "app/runtime/page.tsx (8:1) @ getCachedData
         >  8 | async function getCachedData(): Promise<string> {
              | ^",
           "stack": [
             "getCachedData app/runtime/page.tsx (8:1)",
             "Cached app/runtime/page.tsx (20:24)",
             "Page app/runtime/page.tsx (42:7)",
           ],
         }
        `)
      })
    })

    describe('when a "use cache" performs long-running I/O in the dynamic stage', () => {
      it('should not time out', async () => {
        const outputIndex = next.cliOutput.length
        const browser = await next.browser('/dynamic')

        await expect(browser.elementByCss('#cached').text()).resolves.toBe(
          'cached'
        )

        const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))

        expect(cliOutput).not.toContain(expectedTimeoutErrorMessage)
      })
    })
  } else {
    describe('when a "use cache" fill hangs during prerendering', () => {
      it('should fail the build with a timeout error', async () => {
        try {
          await next.start()
        } catch {
          // expected
        }

        if (isTurbopack) {
          expect(next.cliOutput)
            .toContain(`Error: ${expectedTimeoutErrorMessage}
    at <unknown> (app/static/page.tsx:6:1)`)
        } else {
          // Webpack production builds don't have source maps by default.
          expect(next.cliOutput).toContain(expectedTimeoutErrorMessage)
        }
      })
    })
  }
})
Quest for Codev2.0.0
/
SIGN IN