Repository: vercel/next.js
Pull request: [test]: add ability to run test-deploy with pre-existing deployment

When digging into failing deployment tests, I often will re-run the test itself without wanting to change anything about Next.js or the test application, especially when I suspect a logic bug in the test itself or need to figure out what's causing a flake.

This adds an environment variable that can be used to skip the build/deploy step and will use an existing deployment URL to run the tests against. 

I suggest reviewing this [without whitespace](https://github.com/vercel/next.js/pull/88829/changes?w=1). 

Changed files:
filename: contributing/core/testing.md
patch: @@ -92,6 +92,8 @@ these can be leveraged by prefixing the `pnpm test` command.
   it can be used when not using `pnpm test-dev` or `pnpm test-start` directly.
   Valid test modes can be seen here:
   https://github.com/vercel/next.js/blob/aa664868c102ddc5adc618415162d124503ad12e/test/lib/e2e-utils.ts#L46
+- Use `NEXT_TEST_DEPLOY_URL` with `pnpm test-deploy` to skip the Vercel deploy step and run
+  deploy-mode assertions against an existing deployment URL.
 - You can use `NEXT_TEST_PREFER_OFFLINE=1` while testing to configure the package manager to include the
   [`--prefer-offline`](https://pnpm.io/cli/install#--prefer-offline) argument during test setup.
   This is helpful when running tests in internet-restricted environments such as planes or public Wi-Fi.

filename: test/lib/next-modes/next-deploy.ts
patch: @@ -42,6 +42,8 @@ export class NextDeployInstance extends NextInstance {
     super.setup(parentSpan)
     await super.createTestDir({ parentSpan, skipInstall: true })
 
+    const existingDeployUrl = process.env.NEXT_TEST_DEPLOY_URL?.trim()
+
     // ensure Vercel CLI is installed
     try {
       const res = await execa('vercel', ['--version'])
@@ -84,89 +86,101 @@ export class NextDeployInstance extends NextInstance {
       )
       vercelFlags.push('--global-config', vcConfigDir)
     }
-    require('console').log(`Linking project at ${this.testDir}`)
-
-    // link the project
-    const linkRes = await execa(
-      'vercel',
-      ['link', '-p', TEST_PROJECT_NAME, '--yes', ...vercelFlags],
-      {
-        cwd: this.testDir,
-        env: vercelEnv,
-        reject: false,
+    if (existingDeployUrl) {
+      try {
+        this._url = new URL(existingDeployUrl).toString()
+      } catch (err) {
+        throw new Error(
+          `Invalid NEXT_TEST_DEPLOY_URL value: ${existingDeployUrl}`,
+          { cause: err }
+        )
       }
-    )
-
-    if (linkRes.exitCode !== 0) {
-      throw new Error(
-        `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
+      require('console').log(`Using existing deployment URL: ${this._url}`)
+    } else {
+      require('console').log(`Linking project at ${this.testDir}`)
+
+      // link the project
+      const linkRes = await execa(
+        'vercel',
+        ['link', '-p', TEST_PROJECT_NAME, '--yes', ...vercelFlags],
+        {
+          cwd: this.testDir,
+          env: vercelEnv,
+          reject: false,
+        }
       )
-    }
-    require('console').log(`Deploying project at ${this.testDir}`)
-
-    const additionalEnv: string[] = []
 
-    for (const key of Object.keys(this.env || {})) {
-      additionalEnv.push(`${key}=${this.env[key]}`)
-    }
+      if (linkRes.exitCode !== 0) {
+        throw new Error(
+          `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
+        )
+      }
+      require('console').log(`Deploying project at ${this.testDir}`)
 
-    additionalEnv.push(
-      `VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION || 'vercel@latest'}`
-    )
+      const additionalEnv: string[] = []
 
-    // Add experimental feature flags
+      for (const key of Object.keys(this.env || {})) {
+        additionalEnv.push(`${key}=${this.env[key]}`)
+      }
 
-    if (process.env.__NEXT_CACHE_COMPONENTS) {
       additionalEnv.push(
-        `NEXT_PRIVATE_EXPERIMENTAL_CACHE_COMPONENTS=${process.env.__NEXT_CACHE_COMPONENTS}`
+        `VERCEL_CLI_VERSION=${process.env.VERCEL_CLI_VERSION || 'vercel@latest'}`
       )
-    }
 
-    if (process.env.IS_TURBOPACK_TEST) {
-      additionalEnv.push(`IS_TURBOPACK_TEST=1`)
-    }
-    if (process.env.IS_WEBPACK_TEST) {
-      additionalEnv.push(`IS_WEBPACK_TEST=1`)
-    }
+      // Add experimental feature flags
 
-    const deployRes = await execa(
-      'vercel',
-      [
-        'deploy',
-        '--build-env',
-        'NEXT_PRIVATE_TEST_MODE=e2e',
-        '--build-env',
-        'NEXT_TELEMETRY_DISABLED=1',
-        '--build-env',
-        'VERCEL_NEXT_BUNDLED_SERVER=1',
-        ...additionalEnv.flatMap((pair) => [
-          '--env',
-          pair,
-          '--build-env',
-          pair,
-        ]),
-        '--force',
-        ...vercelFlags,
-      ],
-      {
-        cwd: this.testDir,
-        env: vercelEnv,
-        reject: false,
-        // This will print deployment information earlier to the console so we
-        // don't have to wait until the deployment is complete to get the
-        // inspect URL.
-        stderr: 'inherit',
+      if (process.env.__NEXT_CACHE_COMPONENTS) {
+        additionalEnv.push(
+          `NEXT_PRIVATE_EXPERIMENTAL_CACHE_COMPONENTS=${process.env.__NEXT_CACHE_COMPONENTS}`
+        )
+      }
+
+      if (process.env.IS_TURBOPACK_TEST) {
+        additionalEnv.push(`IS_TURBOPACK_TEST=1`)
+      }
+      if (process.env.IS_WEBPACK_TEST) {
+        additionalEnv.push(`IS_WEBPACK_TEST=1`)
       }
-    )
 
-    if (deployRes.exitCode !== 0) {
-      throw new Error(
-        `Failed to deploy project ${deployRes.stdout} ${deployRes.stderr} (${deployRes.exitCode})`
+      const deployRes = await execa(
+        'vercel',
+        [
+          'deploy',
+          '--build-env',
+          'NEXT_PRIVATE_TEST_MODE=e2e',
+          '--build-env',
+          'NEXT_TELEMETRY_DISABLED=1',
+          '--build-env',
+          'VERCEL_NEXT_BUNDLED_SERVER=1',
+          ...additionalEnv.flatMap((pair) => [
+            '--env',
+            pair,
+            '--build-env',
+            pair,
+          ]),
+          '--force',
+          ...vercelFlags,
+        ],
+        {
+          cwd: this.testDir,
+          env: vercelEnv,
+          reject: false,
+          // This will print deployment information earlier to the console so we
+          // don't have to wait until the deployment is complete to get the
+          // inspect URL.
+          stderr: 'inherit',
+        }
       )
-    }
 
-    // the CLI gives just the deployment URL back when not a TTY
-    this._url = deployRes.stdout
+      if (deployRes.exitCode !== 0) {
+        throw new Error(
+          `Failed to deploy project ${deployRes.stdout} ${deployRes.stderr} (${deployRes.exitCode})`
+        )
+      }
+
+      // the CLI gives just the deployment URL back when not a TTY
+      this._url = deployRes.stdout
+    }
     this._parsedUrl = new URL(this._url)
 
     // If configured, we should configure the `/etc/hosts` file to point the