Repository: vercel/next.js
Pull request: [test] Avoid needless start/stop from using `createSandbox`

`createSandbox` fully isolates the returned session from the existing Next instance by restarting the server.

This is wasteful if the existing instance is already started. Some tests called `nextTestSetup()` without `skipStart` which got fixed in this PR.

Some tests can just use `patchFile` nowadays instead of `createSandbox`.

Changed files:
filename: test/development/acceptance-app/app-hmr-changes.test.ts
patch: @@ -19,6 +19,7 @@ describe('Error overlay - RSC build errors', () => {
       'image-size': '^1.0.2',
       autoprefixer: '^10.4.13',
     },
+    skipStart: true,
   })
 
   // TODO: The error overlay is not closed when restoring the working code.

filename: test/development/app-dir/cache-components-dev-errors/cache-components-dev-errors.test.ts
patch: @@ -6,7 +6,6 @@ import {
   hasErrorToast,
   retry,
 } from 'next-test-utils'
-import { createSandbox } from 'development-sandbox'
 import { outdent } from 'outdent'
 
 describe('Cache Components Dev Errors', () => {
@@ -128,65 +127,52 @@ describe('Cache Components Dev Errors', () => {
   })
 
   it('should clear segment errors after correcting them', async () => {
-    await using sandbox = await createSandbox(
-      next,
-      new Map([
-        [
-          'app/page.tsx',
-          outdent`
-          export const revalidate = 10
-          export default function Page() {
-            return (
-              <div>Hello World</div>
-            );
-          }
-        `,
-        ],
-      ])
-    )
-    const { browser, session } = sandbox
-    if (isTurbopack) {
-      await expect(browser).toDisplayRedbox(`
-       {
-         "description": "Ecmascript file had an error",
-         "environmentLabel": null,
-         "label": "Build Error",
-         "source": "./app/page.tsx (1:14)
-       Ecmascript file had an error
-       > 1 | export const revalidate = 10
-           |              ^^^^^^^^^^",
-         "stack": [],
-       }
-      `)
-    } else {
-      await expect(browser).toDisplayRedbox(`
-       {
-         "description": "  x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.",
-         "environmentLabel": null,
-         "label": "Build Error",
-         "source": "./app/page.tsx
-       Error:   x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.
-          ,-[1:1]
-        1 | export const revalidate = 10
-          :              ^^^^^^^^^^
-        2 | export default function Page() {
-        3 |   return (
-        4 |     <div>Hello World</div>
-          \`----",
-         "stack": [],
-       }
-      `)
-    }
-
-    await session.patch(
+    let browser: any
+    await next.patchFile(
       'app/page.tsx',
       outdent`
+      export const revalidate = 10
       export default function Page() {
         return (
           <div>Hello World</div>
         );
       }
-    `
+    `,
+      async () => {
+        browser = await next.browser('/')
+        if (isTurbopack) {
+          await expect(browser).toDisplayRedbox(`
+           {
+             "description": "Ecmascript file had an error",
+             "environmentLabel": null,
+             "label": "Build Error",
+             "source": "./app/page.tsx (1:14)
+           Ecmascript file had an error
+           > 1 | export const revalidate = 10
+               |              ^^^^^^^^^^",
+             "stack": [],
+           }
+          `)
+        } else {
+          await expect(browser).toDisplayRedbox(`
+           {
+             "description": "  x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.",
+             "environmentLabel": null,
+             "label": "Build Error",
+             "source": "./app/page.tsx
+           Error:   x Route segment config "revalidate" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.
+              ,-[1:1]
+            1 | export const revalidate = 10
+              :              ^^^^^^^^^^
+            2 | export default function Page() {
+            3 |   return (
+            4 |     <div>Hello World</div>
+              \`----",
+             "stack": [],
+           }
+          `)
+        }
+      }
     )
 
     await assertNoRedbox(browser)

filename: test/e2e/app-dir/app-root-params-getters/fixtures/use-cache-runtime/app/[lang]/[locale]/unstable_cache/page.tsx
patch: @@ -29,6 +29,6 @@ async function Runtime() {
   )
 }
 
-const getCachedParams = unstable_cache(async () => {
+const getCachedParams = unstable_cache(async function uncachedGetParams() {
   return { lang: await lang(), locale: await locale() }
 })

filename: test/e2e/app-dir/app-root-params-getters/use-cache.test.ts
patch: @@ -1,6 +1,6 @@
 import { nextTestSetup } from 'e2e-utils'
 import { join } from 'path'
-import { createSandbox } from 'development-sandbox'
+import { assertNoRedbox } from 'next-test-utils'
 
 describe('app-root-param-getters - cache - at runtime', () => {
   const { next, isNextDev, skipped } = nextTestSetup({
@@ -13,29 +13,38 @@ describe('app-root-param-getters - cache - at runtime', () => {
 
   if (isNextDev) {
     it('should error when using root params within a "use cache" - dev', async () => {
-      await using sandbox = await createSandbox(
-        next,
-        undefined,
-        '/en/us/use-cache'
-      )
-      const { session } = sandbox
-      await session.assertHasRedbox()
-      expect(await session.getRedboxDescription()).toInclude(
-        'Route /[lang]/[locale]/use-cache used `import(\'next/root-params\').lang()` inside `"use cache"` or `unstable_cache`'
-      )
+      const browser = await next.browser('/en/us/use-cache')
+      await expect(browser).toDisplayRedbox(`
+       {
+         "description": "Route /[lang]/[locale]/use-cache used \`import('next/root-params').lang()\` inside \`"use cache"\` or \`unstable_cache\`. Support for this API inside cache scopes is planned for a future version of Next.js.",
+         "environmentLabel": "Cache",
+         "label": "Runtime Error",
+         "source": "app/[lang]/[locale]/use-cache/page.tsx (33:28) @ getCachedParams
+       > 33 |   return { lang: await lang(), locale: await locale() }
+            |                            ^",
+         "stack": [
+           "getCachedParams app/[lang]/[locale]/use-cache/page.tsx (33:28)",
+         ],
+       }
+      `)
     })
 
     it('should error when using root params within `unstable_cache` - dev', async () => {
-      await using sandbox = await createSandbox(
-        next,
-        undefined,
-        '/en/us/unstable_cache'
-      )
-      const { session } = sandbox
-      await session.assertHasRedbox()
-      expect(await session.getRedboxDescription()).toInclude(
-        'Route /[lang]/[locale]/unstable_cache used `import(\'next/root-params\').lang()` inside `"use cache"` or `unstable_cache`'
-      )
+      const browser = await next.browser('/en/us/unstable_cache')
+      await expect(browser).toDisplayRedbox(`
+       {
+         "description": "Route /[lang]/[locale]/unstable_cache used \`import('next/root-params').lang()\` inside \`"use cache"\` or \`unstable_cache\`. Support for this API inside cache scopes is planned for a future version of Next.js.",
+         "environmentLabel": "Server",
+         "label": "Runtime Error",
+         "source": "app/[lang]/[locale]/unstable_cache/page.tsx (33:28) @ uncachedGetParams
+       > 33 |   return { lang: await lang(), locale: await locale() }
+            |                            ^",
+         "stack": [
+           "uncachedGetParams app/[lang]/[locale]/unstable_cache/page.tsx (33:28)",
+           "Runtime app/[lang]/[locale]/unstable_cache/page.tsx (17:22)",
+         ],
+       }
+      `)
     })
   } else {
     it('should error when using root params within a "use cache" - start', async () => {
@@ -61,13 +70,9 @@ describe('app-root-param-getters - private cache', () => {
 
   if (isNextDev) {
     it('should allow using root params within a "use cache: private" - dev', async () => {
-      await using sandbox = await createSandbox(
-        next,
-        undefined,
-        '/en/us/use-cache-private'
-      )
-      const { session, browser } = sandbox
-      await session.assertNoRedbox()
+      const browser = await next.browser('/en/us/use-cache-private')
+
+      await assertNoRedbox(browser)
       expect(await browser.elementById('param').text()).toBe('en us')
     })
   } else {

filename: test/e2e/app-dir/logging/fetch-logging.test.ts
patch: @@ -3,7 +3,6 @@ import fs from 'fs'
 import stripAnsi from 'strip-ansi'
 import { retry } from 'next-test-utils'
 import { nextTestSetup } from 'e2e-utils'
-import { createSandbox } from 'development-sandbox'
 
 const cacheReasonRegex = /Cache (missed|skipped) reason: /
 
@@ -52,30 +51,26 @@ describe('app-dir - fetch logging', () => {
 
   isNextDev &&
     it('should not log requests for HMR refreshes', async () => {
-      await using sandbox = await createSandbox(
-        next,
-        undefined,
-        '/fetch-no-store'
-      )
-
-      const { browser, session } = sandbox
+      const browser = await next.browser('/fetch-no-store')
 
       let headline = await browser.waitForElementByCss('h1').text()
       expect(headline).toBe('Hello World!')
       const outputIndex = next.cliOutput.length
 
-      await session.patch('app/fetch-no-store/page.js', (content) =>
-        content.replace('Hello World!', 'Hello Test!')
+      await next.patchFile(
+        'app/fetch-no-store/page.js',
+        (content) => content.replace('Hello World!', 'Hello Test!'),
+        async () => {
+          await retry(async () => {
+            headline = await browser.waitForElementByCss('h1').text()
+            expect(headline).toBe('Hello Test!')
+            const logs = stripAnsi(next.cliOutput.slice(outputIndex))
+            expect(logs).toInclude(' GET /fetch-no-store')
+            expect(logs).not.toInclude(` │ GET `)
+            // TODO: remove custom duration in case we increase the default.
+          }, 5000)
+        }
       )
-
-      await retry(async () => {
-        headline = await browser.waitForElementByCss('h1').text()
-        expect(headline).toBe('Hello Test!')
-        const logs = stripAnsi(next.cliOutput.slice(outputIndex))
-        expect(logs).toInclude(' GET /fetch-no-store')
-        expect(logs).not.toInclude(` │ GET `)
-        // TODO: remove custom duration in case we increase the default.
-      }, 5000)
     })
 
   // TODO: remove when there is a test for isNextDev === false