Commit Graph

35736 Commits

Author SHA1 Message Date
Jimmy Miller 6cfca45f1d Pin tasks during construction (#98688)
I got a failure in CI for the stress test that pointed to this as the
cause. During construction, since the task has no references to it, it
can be collected. Setting the transient ref prevents this bad case from
happening.

@lukesandberg I'm going to work on the gc at each suspend point in a
follow up PR.
2026-09-16 06:50:59 -07:00
Arya f1a7e1db62 fix(use-cache): track root params read in "use cache" as vary params (#98674)
### What?

A `"use cache"` segment that reads a root param via `next/root-params`
is reused after a client-side navigation that changes that param. With
`cacheComponents`, a production build and `<Link prefetch={false}>`:
open `/en`, click `de`. URL, `usePathname()` and layout say `de`; the
page segment still says `en`.

### Why?

Inside a `"use cache"` scope, `getRootParam` only records the name in
`readRootParamNames`, which keys the server cache entry. Nothing
forwards it to the response's root vary params, so the client re-keys
the segment as `Fallback` and reuses it for every locale. `await params`
masks the bug because serializing the cache key touches the tracked
params object.

### How?

`propagateCacheEntryMetadata` adds `readRootParamNames` to the outer
store's root vary accumulator when one exists. The client already unions
root vary params into every segment.

Not covered: `"use cache: private"` root-param reads are only tracked in
dev, so the same reuse can still happen there in production.

Review order: `use-cache-wrapper.ts` is the whole change (one block
after the propagation switch, plus the doc note above
`maybePropagateCacheEntryMetadata`). The fixture adds a `[locale]` route
under the existing `vary-params` app.

Test: `vary-params.test.ts`, "does not reuse a "use cache" segment
across root param values", fails with `Received: "Locale: en"` before
the fix. Reporter's repro renders `de` after navigation on the patched
package.

Fixes #98493

cc @unstubbable @lubieowoce (use cache vary params)

<!-- NEXT_JS_LLM -->
2026-09-16 11:58:06 +00:00
Hendrik Liebau c92542c99c [test] Fix Webpack deployment failures in PostCSS tests (#98710)
#98527 enabled deployment coverage for a Turbopack-only fixture, but
`skipStart` did not prevent deployment during `setup()`. The suite now
uses `@force-gate turbopack` to skip setup for other bundlers while
preserving Turbopack deployment coverage.

This fixes the failure in
https://github.com/vercel/next.js/actions/runs/35037213782/job/104625995824.
2026-09-16 12:53:23 +02:00
Tim Neutkens 52788bdfe1 Revert "Fix App Router locale path matching with Pages i18n" (#98715)
Reverts vercel/next.js#98095

A stale merge of #98095 caused failures on canary:
https://github.com/vercel/next.js/actions/runs/35075051322

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
2026-09-16 10:14:59 +00:00
next-js-bot[bot] c709412355 v16.4.0-canary.33 v16.4.0-canary.33 2026-09-16 08:41:55 +00:00
Tim Neutkens 1d5dd67200 Fix App Router locale path matching with Pages i18n (#98095)
## Summary

Fix hybrid App Router and Pages Router projects using Pages i18n so
explicit or rewritten locale segments remain available to App Router
route matching, while Pages routes continue using locale-normalized
paths.

The built-in filesystem matcher now carries each route owner through
`RouteKind`, avoids duplicate App routes being treated as Pages routes
in production, and preserves literal App path parameters through the
router server, direct route-module invocation, and static export paths.
The same ownership information is maintained by the development bundler.

Adds regression coverage for domain locales, proxy rewrites, exact and
dynamic App pages, catch-all parameters, route handlers, inferred
default locales, and Pages routes. Adapter-generated routing metadata is
intentionally handled in the follow-up layers #97366 and #97500.

Closes #86048

## Verification

- `pnpm build-all`
- `NEXT_TEST_PREFER_OFFLINE=1 pnpm test-start-webpack
test/e2e/app-dir/i18n-app-pages-domain-routing/i18n-app-pages-domain-routing.test.ts`
(9 passed)
- Prettier and ESLint checks on all changed files

<!-- NEXT_JS_LLM -->
2026-09-16 10:40:09 +02:00
next-js-bot[bot] 65e29e4abe Upgrade React from 019019be-20260911 to ff8f88fc-20260915 (#98693) 2026-09-16 10:14:47 +02:00
next-js-bot[bot] 90515901d6 v16.4.0-canary.32 v16.4.0-canary.32 2026-09-15 23:22:42 +00:00
Tobias Koppers 2a3bf9ae21 fix(turbopack): trace cyclic modules to explicit entries (#98661)
### What?

Makes Turbopack import tracing recognize the module graph's explicit
entries as roots, including when an entry participates in an import
cycle. Adds focused graph-level coverage and a Next.js production
fixture that exercises issue formatting through a cyclic graph.

If a malformed graph still has no path to any explicit entry, tracing
now returns a minimal trace and emits a bug-severity issue instead of
panicking.

### Why?

The import tracer inferred roots from nodes with no incoming edges. A
valid cycle that points back to an entry gives every node an incoming
edge, so the tracer could not find a root and panicked while formatting
another diagnostic. The graph already records its entries explicitly,
making them the authoritative and cycle-safe definition of a root.

The same topology-based assumption affected entry membership checks, so
those now use the explicit entry list as well.

### How?

- Resolve import-trace paths against node indices derived from
`GraphEntries`.
- Preserve a defensive fallback for malformed graphs and classify that
invariant violation as an implementation bug.
- Cover cyclic entry paths, malformed rootless graphs, absent paths, and
the product-level issue-formatting path.

Fixes #98205

<!-- NEXT_JS_LLM -->


<!-- fleet 25b26044-3397-4642-a77f-c565b7577046 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 23:18:37 +00:00
Tobias Koppers f1285cec5f Use default pnpm package import method (#98703)
### What?

Use pnpm's default package import method in the two hoisted
isolated-test configurations added by #98425.

### Why?

`package-import-method` controls how regular package files are
materialized from pnpm's store: by reflink, hardlink, or copy. Those
choices do not affect `realpath`, so forcing copies is unrelated to the
Node.js symlink-resolution workaround and unnecessarily disables pnpm's
more efficient defaults.

### How?

Keep `node-linker=hoisted`, which is the setting responsible for
producing npm-style real package directories, while removing the
independent copy policy. The Node-version gate, local-tarball
validation, release-age policy, and fixture behavior remain unchanged.

### Verification

- Hoisted-only scratch install produced real package directories, no
package symlinks outside `.bin`, and a metadata-only `.pnpm`
- Production Turbopack: non-hoisted SWC helper and warm-restart task
stats passed (2/2)
- `pnpm build`
- `pnpm types`
- Prettier, ESLint, and `git diff --check`

Follow-up to #98425 and
https://github.com/vercel/next.js/pull/98425#discussion_r4020168942.

<!-- NEXT_JS_LLM -->


<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 23:15:46 +00:00
Tobias Koppers 69d11874f6 Fix constant inlining in shorthand properties (#98662)
### What?

Fixes constant replacement in object shorthand properties and adds
focused Turbopack execution coverage for the existing analyzer-aware
cross-module constants path.

### Why?

This is a pre-existing bug in `ConstantValueCodeGen`, which is shared by
the current analyzer-aware/opt-in constants implementation and the newer
codegen-only export-inlining work. When an imported constant is used as
`{ VALUE }`, the AST path ends at `Prop::Shorthand`; the old
expression-only visitor could replace the enclosing object literal,
collapsing `{ FIRST, SECOND }` to a single primitive.

This fix is intentionally independent of the export-inlining feature
stack so existing `turbopackCrossModuleConstants` users receive and
review the correction separately.

### How?

When the code-generation path ends at a shorthand property, it now
mirrors `EsmBinding`: the shorthand is expanded to a key/value property,
preserving the original key and generating the compile-time constant
only for the value. Other expression paths are unchanged, and generated
values retain the standard compile-time marker.

The execution fixture uses only analyzer-aware constants. Without the
fix it returns `"second"` instead of `{ FIRST: "first", SECOND: "second"
}`; with the fix it verifies the runtime object, both generated keys,
and both marked inlined values.

### Verification

- `NODE_PATH=/vercel/sandbox/test-deps/node_modules cargo test -p
turbopack-tests --test execution cross_module_constants_shorthand --
--nocapture`
- `cargo test -p turbopack-tests --test snapshot cross_module --
--nocapture`
- `cargo check -p turbopack-ecmascript -p turbopack-tests --tests`
- `cargo fmt --all -- --check`

<!-- NEXT_JS_LLM -->


<!-- fleet 29433e1c-e26e-435a-b516-45108f629d79 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 23:36:07 +02:00
Luke Sandberg 9487264fda test(turbopack): add strong read operation roots (#98248)
## Summary

Replace top-level consistency suppression in read-ref and trait-ref cell
tests with explicit operation roots. The operations separately cover
counter creation, counter reads, and trait upcasts while preserving the
tests’ warm-cache and snapshot behavior.

## Verification

- `cargo test -p turbo-tasks-backend --test read_ref_cell --test
trait_ref_cell`
- `cargo fmt --all -- --check`

<!-- NEXT_JS_LLM -->

<!-- fleet 475180cd-06a7-4669-89b5-a2029a3a18c9 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-15 14:15:45 -07:00
Tobias Koppers 357b2b3c4c Use pnpm for isolated test installs (#98425)
### What?

Migrate the five local isolated test installs that explicitly used npm
to pnpm. The Nx fixture now has pnpm workspace metadata, while
filesystem-layout-sensitive fixtures use pnpm's hoisted linker with
copied package files.

### Why?

The npm-based Nx install bypassed the repository's centralized
supply-chain protections and could select a package immediately after
publication, including temporarily incomplete multi-package releases.
Using pnpm makes isolated installs inherit the repository's
`minimumReleaseAge`, exclusions, and exotic-subdependency policy.

The other npm installs depended on npm-style real package directories.
On Node versions affected by nodejs/node#65113, hoisted/copy mode
preserves that layout without leaving these fixtures outside the shared
pnpm security configuration; fixed Node releases use normal pnpm
linking.

### How?

- Use normal pnpm workspace resolution for the Nx fixture.
- Use `node-linker=hoisted` and `package-import-method=copy` for
filesystem tests only on affected Node releases; Node 24.21+ and 26.8+
use normal linking. Node 20 CI keeps the workaround because no fixed
Node 20 release exists.
- Validate local `@next/env` tarballs through the lockfile when hoisted
installs do not expose pnpm's virtual-store path marker.
- Keep the deployment-environment npm install unchanged.

### Verification

- `pnpm build-all`
- `pnpm types`
- A 9-version throwaway assertion verified the affected/fixed Node
release matrix
- `pnpm test-dev-turbo test/e2e/app-dir/nx-handling/nx-handling.test.ts
test/e2e/handle-non-hoisted-swc-helpers/index.test.ts
test/e2e/filesystem-cache/filesystem-cache.test.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts
test/e2e/filesystem-cache/evict-after-snapshot.test.ts` — all 25 tests
passed after installing the sandbox's missing Playwright browser
- Production Turbopack: Nx, non-hoisted SWC helper, build-cache-default,
and warm restart passed (9/9)
- `filesystem-cache.test.ts` production baseline: 15/17 passed; the same
two cache-growth bounds fail under both the unchanged npm fixture and
the pnpm fixture at nearly identical percentages, so they are
pre-existing sandbox-specific failures
- Generated-layout inspection: no package symlinks outside expected
`.bin` command shims; package files are copied; `node_modules/.pnpm` is
metadata-only

<!-- NEXT_JS_LLM -->


<!-- fleet 81cd457d-6956-4cf9-b6f6-9ebf9d95f285 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 20:19:48 +00:00
Sam Poder 4bb6fc4b90 [turbopack] Forward usage information across re-exports (#96282)
## What?

This PR improves tree-shaking across whole-namespace re-exports. It is
the middle PR in the stack: #98680 provides the independently reviewable
`EsmAssetReferenceOptions` constructor refactor requested during review,
and #96396 builds on this PR to narrow static member reads on
namespace-valued named exports.

A representative case is a CommonJS re-export followed by an ESM star
barrel:

```js
// index.js
const { m } = require('./cjs')

// cjs.js
module.exports = require('./barrel') // CJS re-export

// barrel.js
export * from './base' // ESM star barrel

// base.js
export const m = 1
export const n = 2 // nobody ever uses this
```

Only `m` is observed by the entrypoint, so that usage can now flow
through both re-export layers to `base.js` and allow `n` to be removed.

## Why?

Turbopack now has a generic `ExportUsage::Passthrough` mechanism,
introduced on `canary` for client-component and Next.js module proxies.
It carries the referencing module's accumulated used-export set into a
transparent target.

However, CommonJS whole-module forwarding and ordinary ESM star barrels
do not opt into that mechanism. Applying this PR's tests to plain
`canary` showed that all runtime values are already correct, but unused
exports remain retained in four important shapes:

- an ESM named consumer through `module.exports = require(ESM)`;
- a deep CommonJS forwarding chain;
- CommonJS forwarding into an ESM `export *` barrel;
- a CommonJS diamond with disjoint used exports.

This PR therefore remains a tree-shaking precision change rather than
introducing a second forwarding model.

There is also an independent name-observability concern. A CommonJS
forwarder exposes a raw namespace object's properties under their
original names, while named consumers of an ESM `export *` can be
rewritten if an export is mangled. Namespace provenance must survive
multiple passthrough hops without unnecessarily disabling mangling for
ordinary ESM named forwarding.

## How?

### Configure forwarding at construction time

#98680 replaces `EsmAssetReference`'s positional behavior arguments with
`EsmAssetReferenceOptions`. This PR extends that options object with an
optional passthrough mode. ESM star references receive `Some(false)`
directly when constructed; free-variable references receive `None`; and
the existing annotation-driven client-proxy path still contributes
`Some(true)`.

The two sources merge conservatively: an annotation that exposes the
namespace's original names cannot be weakened by a syntax-driven
`Some(false)` on the same reference. The former mutating
`mark_namespace_reexport()` API is removed.

### Reuse the generic passthrough fixed point

The implementation uses canary's existing model:

```rust
ExportUsage::Passthrough {
    namespace_object_may_escape: bool,
}
```

The boolean describes whether the edge itself exposes the target
namespace's original property names:

- `module.exports = require('…')` records `Passthrough {
namespace_object_may_escape: true }` directly in the existing CommonJS
import-usage map;
- `export * from '…'` records `Passthrough {
namespace_object_may_escape: false }`, because statically known ESM
consumers can be rewritten when target exports are mangled.

No parallel `TargetExportUsage` type, graph field, call parameter, or
second fixed-point implementation is added.

Detection is limited to a simple assignment using the unresolved/global
`module` and `require` bindings. Shadowed bindings, member requires, and
compound assignments retain their existing behavior.

### Propagate keys and namespace provenance independently

A passthrough edge merges the parent module's resolved `Evaluation`,
`Exports`, or `All` state into its target. The merge remains monotonic
and idempotent, so chains, cycles, and diamonds converge.

Namespace provenance is carried independently of the used key set:

- an edge with `namespace_object_may_escape: true` makes the target's
original names observable;
- every passthrough also carries namespace provenance that already
reached its parent;
- a newly propagated provenance bit retriggers traversal even when the
used key set did not change.

This preserves original names through multi-hop namespace reads while
keeping unrelated ordinary named exports mangleable. Passthrough
references are also exposed correctly to scope-hoisted merged modules.

## Testing

Enabled execution coverage includes:

- deep CommonJS chains, CommonJS → ESM star barrels, cycles, diamonds,
and overwrite behavior;
- ESM named/default/namespace import interop through `module.exports =
require(ESM)`;
- opaque whole-namespace and evaluation-only consumption, including
single evaluation;
- native `export *`, default/local-shadow filtering, and the
optimization-disabled mode;
- namespace provenance across forwarding edges and a multi-hop diamond;
- both mangling safety and non-pessimization for ordinary named
forwarding;
- shadowed `module`/`require`, member-require, and non-simple assignment
boundaries;
- runtime counterparts for every nested precision case that remains
skipped.

Known optimization-only limitations remain under
`__skipped__/deep-reexports`: nested `export * as` through CJS,
namespace-property unwrapping in a CJS assignment, depth-two nested
namespaces, and unwrapping after a forwarding hop. Their runtime
semantics are covered by the enabled
`reexport-forwarding-nested-runtime` fixture.

Verification on the rebased stack:

- plain-canary reassessment: 7/9 fixtures, with only four expected
precision failures;
- integrated bottom layer: 9/9 forwarding fixtures;
- passthrough unit tests: 2/2, plus the six-case construction-option
merge lattice;
- combined stack: 14/14 focused execution/snapshot tests and 60/60
analyzer graph tests;
- affected Cargo checks, `next-core` check, `build-all`, and the
forwarding/namespace HMR test pass.

<!-- NEXT_JS_LLM -->


<!-- fleet 993ef5d6-cc80-4082-8e14-ff9ac871ce33 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-15 21:49:14 +02:00
Tobias Koppers 7c87468aa3 [turbopack] Use options object for ESM references (#98680)
### What?

Introduce `EsmAssetReferenceOptions`, an explicit options object for ESM
asset references.

### Why?

`EsmAssetReference::new` and `new_pure` accepted a long positional list
of behavioral settings. Call sites were difficult to scan and easy to
misconfigure as the reference grows new behavior.

This is intentionally a behavior-neutral, independently reviewable base
for the two existing tree-shaking PRs above it:

1. This PR centralizes constructor configuration.
2. #96282 uses the options object to configure re-export usage
forwarding.
3. #96396 tracks static member reads through namespace-valued
re-exports.

### How?

Keep the three reference-identity inputs (`module`, `origin`, and
`request`) positional, and move behavioral settings—source location,
annotations, target part, import usage, externals, module-fragment mode,
and resolve override—into a named options struct.

The two constructor call sites now declare their settings by field name.
The refactor removes the `too_many_arguments` allowances without
changing resolution or binding-usage behavior.

### Testing

- `cargo check -p turbopack-ecmascript`
- Analyzer graph tests: 60/60
- The stacked focused execution/snapshot tests, 2/2 usage-lattice units,
`build-all`, and HMR regression are verified on the upper PRs.

<!-- NEXT_JS_LLM -->


<!-- fleet 993ef5d6-cc80-4082-8e14-ff9ac871ce33 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-15 21:49:13 +02:00
Jamiboy Mohammad 3dbf921905 test: enable verified runtime deploy tests (#98457)
## Summary

Enable the same 9 previously selected deployment-test scopes across 9
runtime test files, now in a stack rooted on canary. Remove 8
`skipDeployment` options and their obsolete skip guards. Enable only the
selected middleware cookie assertion; the neighboring query assertion
remains excluded. Other mode, bundler, middleware, and Cache Components
exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

The worker-react-refresh scope (ID 411) remains deployment-excluded
pending fixture dependency changes: React 19.3.0 conflicts with
@react-three/fiber 9.7.0’s React <19.3 peer range. Both deploy variants
fail during npm installation, before the assertion executes. It is
tracked as a fixable candidate, not a permanent local-only test.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (9)</summary>

- ID 350: `test/e2e/app-dir/app-edge-root-layout/index.test.ts` —
`describe('app-dir edge runtime root layout', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  it('should not emit metadata files into bad paths', async () => {
    await next.fetch('/favicon.ico')
// issue: If metadata files are not filter out properly with
image-loader,
    // an incorrect static/media folder will be generated

    // Check that the static folder is not generated
    const incorrectGeneratedStaticFolder = await next.hasFile('static')
    expect(incorrectGeneratedStaticFolder).toBe(false)
  })

  if (isNextStart) {
it('should mark static contain metadata routes as edge functions', async
() => {
      const middlewareManifest = await next.readFile(
        '.next/server/middleware-manifest.json'
      )
      expect(middlewareManifest).not.toContain('/favicon')
    })
  }
})`
- ID 353: `test/e2e/app-dir/binary/rsc-binary.test.ts` — `describe('RSC
binary serialization', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      'server-only': 'latest',
    },
  })

  afterEach(async () => {
    await next.stop()
  })

it('should correctly encode/decode binaries and hydrate', async function
() {
    const browser = await next.browser('/')
    await check(async () => {
      const content = await browser.elementByCss('body').text()

      return content.includes('utf8 binary: hello') &&
        content.includes('arbitrary binary: 255,0,1,2,3') &&
        content.includes('hydrated: true')
        ? 'success'
        : 'fail'
    }, 'success')
  })
})`
- ID 355:
`test/e2e/app-dir/interception-middleware-rewrite/interception-middleware-rewrite.test.ts`
— `describe('interception-middleware-rewrite', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should support intercepting routes with a middleware rewrite', async
() => {
    const browser = await next.browser('/')

    await check(() => browser.elementByCss('#children').text(), 'root')

    await check(
      () =>
        browser
          .elementByCss('[href="/feed"]')
          .click()
          .elementByCss('#modal')
          .text(),
      'intercepted'
    )

    await check(
      () => browser.refresh().elementByCss('#children').text(),
      'not intercepted'
    )

    await check(() => browser.elementByCss('#modal').text(), 'default')
  })

it('should continue to work after using browser back button and
following another intercepting route', async () => {
    const browser = await next.browser('/')
    await check(() => browser.elementById('children').text(), 'root')

    await browser.elementByCss('[href="/photos/1"]').click()
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )
    await browser.back()
    await browser.elementByCss('[href="/photos/2"]').click()
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 2'
    )
  })

it('should continue to show the intercepted page when revisiting it',
async () => {
    const browser = await next.browser('/')
    await check(() => browser.elementById('children').text(), 'root')

    await browser.elementByCss('[href="/photos/1"]').click()

    // we should be showing the modal and not the page
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )

    await browser.refresh()

    // page should show after reloading the browser
    await check(
      () => browser.elementById('children').text(),
      'Page Photo ID: 1'
    )

    // modal should no longer be showing
    await check(() => browser.elementById('modal').text(), 'default')

    await browser.back()

    // revisit the same page that was intercepted
    await browser.elementByCss('[href="/photos/1"]').click()

    // ensure that we're still showing the modal and not the page
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )

    // page content should not have changed
    await check(() => browser.elementById('children').text(), 'root')
  })
})`
- ID 360: `test/e2e/app-dir/middleware-matching/index.test.ts` —
`describe('app dir - middleware with custom matcher', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should match /:id (without asterisk)', async () => {
    const browser = await next.browser('/chat/123')
    expect(await browser.elementByCss('p').text()).toBe('Home')
  })
})`
- ID 361:
`test/e2e/app-dir/node-extensions/node-extensions.random.test.ts` —
`describe('Cache Components', () => {
      const { next } = nextTestSetup({
        files: __dirname + '/fixtures/random/cache-components',
      })

it('should not error when accessing middlware that use Math.random()',
async () => {
        let res: Awaited<ReturnType<typeof next.fetch>>,
          $: Awaited<ReturnType<typeof next.render$>>

        res = await next.fetch('/rewrite')
        expect(res.status).toBe(200)
        $ = await next.render$('/rewrite')
        expect($('[data-testid="content"]').text()).toBe('rewritten')
      })

it('should not error when accessing pages that use Math.random() in App
Router', async () => {
        let res, $

        res = await next.fetch('/app/prerendered/unstable-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/prerendered/unstable-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/prerendered/use-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/prerendered/use-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/uncached')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/uncached')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/unstable-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/unstable-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/use-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/use-cache')
        expect($('li').length).toBe(2)
      })

it('should not error when accessing routes that use Math.random() in App
Router', async () => {
        let res, body

        res = await next.fetch('/app/prerendered/uncached/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/prerendered/unstable-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/prerendered/use-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/uncached/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/unstable-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/use-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })
      })

it('should not error when accessing pages that use Math.random() in
Pages Router', async () => {
        let res, $

        res = await next.fetch('/pages/gip/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gip/random')
        expect($('li').length).toBe(2)

        res = await next.fetch('/pages/gssp/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gssp/random')
        expect($('li').length).toBe(2)

        res = await next.fetch('/pages/gsp/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gsp/random')
        expect($('li').length).toBe(2)
      })

it('should not error when accessing routes that use Math.random() in
Pages Router', async () => {
        let res, body

        res = await next.fetch('/api/random')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        expect(body.rand1).not.toBe(body.rand2)

        const first1 = body.rand1
        const first2 = body.rand2

        res = await next.fetch('/api/random')
        body = await res.json()
        expect(body.rand1).not.toBe(body.rand2)
        expect(body.rand1).not.toBe(first1)
        expect(body.rand2).not.toBe(first2)
      })
    })`
- ID 370:
`test/e2e/app-dir/webpack-loader-binary/webpack-loader-binary.test.ts` —
`describe('webpack-loader-ts-transform', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should allow passing binary assets to and from a Webpack loader',
async () => {
    const $ = await next.render$('/')
    expect($('#text').text()).toBe('Got a buffer of 18 bytes')
    expect($('#binary').text()).toBe('Got a buffer of 6765 bytes')
  })
})`
- ID 386:
`test/e2e/edge-runtime-uses-edge-light-import-specifier-for-packages/edge-runtime-uses-edge-light-import-specifier-for-packages.test.ts`
— `describe('edge-runtime uses edge-light import specifier for
packages', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    packageJson: {
      scripts: {
        build: 'next build',
        dev: 'next dev',
        start: 'next start',
      },
    },
    installCommand: 'pnpm i',
    startCommand: (global as any).isNextDev ? 'pnpm dev' : 'pnpm start',
    buildCommand: 'pnpm build',
  })

  // In case you need to test the response object
  it('pages/api endpoints import the correct module', async () => {
    const res = await next.fetch('/api/edge')
    const html = await res.json()
    expect(html).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })

  it('pages import the correct module', async () => {
    const $ = await next.render$('/')
    const text = JSON.parse($('pre#result').text())
    expect(text).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })

  it('app-dir imports the correct module', async () => {
    const $ = await next.render$('/app-dir')
    const text = JSON.parse($('pre#result').text())
    expect(text).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })
})`
- ID 392: `test/e2e/middleware-custom-matchers/test/index.test.ts` —
`it('should match has cookie on client routing', async () => {
      const browser = await next.browser('/routes')
      await browser.addCookie({ name: 'loggedIn', value: 'true' })
      await browser.refresh()
      await browser.eval('window.__TEST_NO_RELOAD = true')
      await browser.elementById('has-match-3').click()
const fromMiddleware = await
browser.elementById('from-middleware').text()
      expect(fromMiddleware).toBe('true')
      const noReload = await browser.eval('window.__TEST_NO_RELOAD')
      expect(noReload).toBe(true)
    })`
- ID 402:
`test/e2e/on-request-error/skip-next-internal-error/skip-next-internal-error.test.ts`
— `describe('on-request-error - skip-next-internal-error', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  async function assertNoNextjsInternalErrors() {
    const output = next.cliOutput
    // No navigation errors
    expect(output).not.toContain('NEXT_REDIRECT')
    expect(output).not.toContain('NEXT_NOT_FOUND')
    expect(output).not.toContain('BAILOUT_TO_CLIENT_SIDE_RENDERING')
    // No dynamic usage errors
    expect(output).not.toContain('DYNAMIC_SERVER_USAGE')
    // No react postpone errors
    // TODO: cover PPR errors later
    expect(output).not.toContain('react.postpone')
  }

  describe('app router render', () => {
    // Server navigation errors
it('should not catch server component not-found errors', async () => {
      await next.fetch('/server/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch server component redirect errors', async () => {
      await next.render('/server/redirect')
      await assertNoNextjsInternalErrors()
    })

    // Client navigation errors
it('should not catch client component not-found errors', async () => {
      await next.fetch('/server/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch client component redirect errors', async () => {
      await next.render('/client/redirect')
      await assertNoNextjsInternalErrors()
    })

    // Dynamic usage
it('should not catch server component dynamic usage errors', async () =>
{
      await next.fetch('/server/dynamic-fetch')
      await assertNoNextjsInternalErrors()
    })

it('should not catch client component dynamic usage errors', async () =>
{
      await next.fetch('/client/dynamic-fetch')
      await assertNoNextjsInternalErrors()
    })

    // No SSR
    it('should not catch next dynamic no-ssr errors', async () => {
      await next.fetch('/client/no-ssr')
      await assertNoNextjsInternalErrors()
    })

    // Server Actions navigation
    it('should not catch server action not-found errors', async () => {
      await next.fetch('/form/not-found')
      await assertNoNextjsInternalErrors()
    })

    it('should not catch server action redirect errors', async () => {
      await next.fetch('/form/redirect')
      await assertNoNextjsInternalErrors()
    })
  })

  describe('app router API', () => {
    // API routes navigation errors
it('should not catch server component not-found errors', async () => {
      await next.render('/app-route/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch server component redirect errors', async () => {
      await next.render('/app-route/redirect')
      await assertNoNextjsInternalErrors()
    })
  })
})`


</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/middleware-custom-matchers/test/index.test.ts` — `it('should
match has cookie on client routing', async () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785013/job/102715655249),
[cache](https://github.com/vercel/next.js/actions/runs/34426785013/job/102715655202).

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:07 -07:00
Jamiboy Mohammad 2ad16804ac test: enable verified assets deploy tests (#98527)
## Summary

Enable the same 11 previously selected deployment-test scopes across 11
assets test files, now in a stack rooted on canary. Remove 11
`skipDeployment` options and their obsolete skip guards. Other mode,
bundler, middleware, and Cache Components exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (11)</summary>

- ID 286: `test/e2e/app-dir/app-css-pageextensions/index.test.ts` —
`describe('app dir - css with pageextensions', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      '@picocss/pico': '1.5.7',
      sass: 'latest',
    },
  })

  describe('css support with pageextensions', () => {
describe('page in app directory with pageextention, css should work', ()
=> {
      it('should support global css inside layout', async () => {
        const browser = await next.browser('/css-pageextensions')
        expect(
          await browser.eval(
`window.getComputedStyle(document.querySelector('h1')).color`
          )
        ).toBe('rgb(255, 0, 0)')
      })
    })
  })
})`
- ID 292: `test/e2e/app-dir/dynamic-css/index.test.ts` — `describe('app
dir - dynamic css', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should preload all chunks of dynamic component during SSR', async ()
=> {
    const $ = await next.render$('/ssr')
const cssLinks = $('link[rel="stylesheet"][data-precedence="dynamic"]')
    expect(cssLinks.attr('href')).toContain('.css')

    const preloadJsChunks = $('link[rel="preload"]')
    expect(preloadJsChunks.attr('as')).toBe('script')
    expect(preloadJsChunks.attr('fetchpriority')).toContain(`low`)
  })

it('should only apply corresponding css for page loaded that /ssr',
async () => {
    const browser = await next.browser('/ssr')
    await retry(async () => {
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).color`
        )
      ).toBe('rgb(255, 0, 0)')
// Default border width, which is not effected by bar.css that is not
loaded in /ssr
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).borderWidth`
        )
      ).toBe('0px')
    })
  })

it('should only apply corresponding css for page loaded in edge
runtime', async () => {
    const browser = await next.browser('/ssr/edge')
    await retry(async () => {
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).color`
        )
      ).toBe('rgb(255, 0, 0)')
// Default border width, which is not effected by bar.css that is not
loaded in /ssr
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).borderWidth`
        )
      ).toBe('0px')
    })
  })

it('should only apply corresponding css for page loaded that /another',
async () => {
    const browser = await next.browser('/another')
    await retry(async () => {
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).color`
        )
      ).not.toBe('rgb(255, 0, 0)')
// Default border width, which is not effected by bar.css that is not
loaded in /ssr
      expect(
        await browser.eval(
`window.getComputedStyle(document.querySelector('.text')).borderWidth`
        )
      ).toBe('1px')
    })
  })

it('should not throw with accessing to ALS in preload css', async () =>
{
    const output = next.cliOutput
    expect(output).not.toContain('was called outside a request scope')
  })
})`
- ID 293: `test/e2e/app-dir/emotion-js/index.test.ts` — `describe('app
dir - emotion-js', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      '@emotion/react': 'latest',
      '@emotion/cache': 'latest',
    },
  })

it('should render emotion-js css with compiler.emotion option
correctly', async () => {
    const browser = await next.browser('/')
    const el = browser.elementByCss('h1')
    expect(await el.text()).toBe('Blue')
    await check(
      async () =>
        await browser.eval(
          `window.getComputedStyle(document.querySelector('h1')).color`
        ),
      'rgb(0, 0, 255)'
    )

    const el2 = browser.elementByCss('p')
    expect(await el2.text()).toBe('Red')
    await check(
      async () =>
        await browser.eval(
          `window.getComputedStyle(document.querySelector('p')).color`
        ),
      'rgb(255, 0, 0)'
    )
  })
})`
- ID 294:
`test/e2e/app-dir/global-error/with-style-import/index.test.ts` —
`describe('app dir - global error - with style import', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should render global error with correct styles', async () => {
    const browser = await next.browser('/')

    if (isNextDev) {
      await testDev(browser, /Root Layout Error/)
      return
    }

    const h2 = await browser.elementByCss('h2')
expect(await h2.getComputedCss('color')).toBe('rgb(255, 255, 0)') //
yellow
  })
})`
- ID 301: `test/e2e/app-dir/not-found/css-precedence/index.test.ts` —
`describe('not-found app dir css', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      sass: 'latest',
    },
  })

it('should load css while navigation between not-found and page', async
() => {
    const browser = await next.browser('/')
    await check(
      async () =>
        await browser.eval(

`window.getComputedStyle(document.querySelector('#go-to-404')).backgroundColor`
        ),
      'rgb(0, 128, 0)'
    )
    await browser.elementByCss('#go-to-404').click()
    await browser.waitForElementByCss('#go-to-index')
    await check(
      async () =>
        await browser.eval(

`window.getComputedStyle(document.querySelector('#go-to-index')).backgroundColor`
        ),
      'rgb(0, 128, 0)'
    )
    await browser.elementByCss('#go-to-index').click()
    await browser.waitForElementByCss('#go-to-404')
    await check(
      async () =>
        await browser.eval(

`window.getComputedStyle(document.querySelector('#go-to-404')).backgroundColor`
        ),
      'rgb(0, 128, 0)'
    )
  })
})`
- ID 308:
`test/e2e/app-dir/turbopack-postcss-multiple-configs/turbopack-postcss-multiple-configs.test.ts`
— `describe('turbopack-postcss-multiple-configs', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
// Per-directory PostCSS config resolution is a Turbopack-only feature
// (turbopackLocalPostcssConfig). Webpack does not support this feature
and
// does not accept function-valued PostCSS plugins, so skip
non-Turbopack runs.
    skipStart: true,
  })

  if (!isTurbopack) {
    it('should only run with Turbopack', () => {})
    return
  }

  beforeAll(async () => {
    await next.start()
  })

// Each directory's postcss.config.js passes a unique color option to
the
  // shared plugin, which replaces `color: red` with the given color.
  // In production mode the CSS minifier may shorten named colors to hex
  // (e.g. blue → #00f), so we match on patterns that cover both forms.
  const DIR_COLORS: Record<number, string | RegExp> = {
    1: /blue|#00f/,
    2: /purple|#800080/,
    3: /orange|#ffa500/,
    4: /cyan|#0ff/,
    5: /magenta|#f0f/,
  }

  const DIRS = 5
  const FILES_PER_DIR = 3

it('should render all elements with CSS module classes applied', async
() => {
    const $ = await next.render$('/')

    for (let dir = 1; dir <= DIRS; dir++) {
      for (let file = 1; file <= FILES_PER_DIR; file++) {
        const padded = String(file).padStart(2, '0')
        const id = `dir${dir}-file${padded}`
        const el = $(`#${id}`)
        expect(el.length).toBe(1)
        expect(el.text().trim()).toBe(`dir${dir} file${padded}`)
        expect(el.attr('class')).toBeTruthy()
      }
    }
  })

it('should apply per-directory PostCSS transforms with distinct colors',
async () => {
    const cssContent = await collectCss(next)

    // Each directory's PostCSS config passes a unique color option.
    // Verify every expected color appears in the output.
    for (const [, pattern] of Object.entries(DIR_COLORS)) {
      expect(cssContent).toMatch(pattern)
    }

    // No original `color: red` should remain — all were transformed.
    expect(cssContent).not.toMatch(/color\s*:\s*red/)

// The old hardcoded green should NOT appear, proving options are used.
    expect(cssContent).not.toMatch(/green|#0f0|#008000/)
  })
})`
- ID 317: `test/e2e/image-optimizer/image-optimizer.test.ts` —
`describe('Server support for trailingSlash in next.config.js', () => {
    const { next } = nextTestSetup({
      files: join(__dirname, 'app'),
      nextConfig: {
        trailingSlash: true,
        images: {
          imageSizes: [8, 16, 32, 48, 64, 96, 128, 256, 384],
          qualities: [70, 75],
        },
      },
    })

it('should return successful response for original loader', async () =>
{
      const query = { url: '/test.png', w: 8, q: 70 }
const res = await next.fetch(`/_next/image/?${toQueryString(query)}`)
      expect(res.status).toBe(200)
    })
  })`
- ID 329: `test/e2e/next-image-legacy/default/default-static.test.ts` —
`describe('Static Image Component Tests', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

  let browser: Playwright
  let html: string

  beforeAll(async () => {
    html = await next.render('/static-img')
    browser = await next.browser('/static-img')
  })

it('Should allow an image with a static src to omit height and width',
async () => {
    expect(await browser.elementById('basic-static')).toBeTruthy()
    expect(await browser.elementById('blur-png')).toBeTruthy()
    expect(await browser.elementById('blur-webp')).toBeTruthy()
    expect(await browser.elementById('blur-avif')).toBeTruthy()
    expect(await browser.elementById('blur-jpg')).toBeTruthy()
    expect(await browser.elementById('static-svg')).toBeTruthy()
    expect(await browser.elementById('static-gif')).toBeTruthy()
    expect(await browser.elementById('static-bmp')).toBeTruthy()
    expect(await browser.elementById('static-ico')).toBeTruthy()
    expect(await browser.elementById('static-unoptimized')).toBeTruthy()
  })
  ;(isNextStart ? it : it.skip)(
    'Should use immutable cache-control header for static import',
    async () => {
      await browser.eval(
        `document.getElementById("basic-static").scrollIntoView()`
      )
      await new Promise((resolve) => setTimeout(resolve, 1000))
      const url = await browser.eval(
        `document.getElementById("basic-static").src`
      )
      const res = await fetch(url)
      expect(res.headers.get('cache-control')).toBe(
        'public, max-age=315360000, immutable'
      )
    }
  )
  ;(isNextStart ? it : it.skip)(
    'Should use immutable cache-control header even when unoptimized',
    async () => {
      await browser.eval(
        `document.getElementById("static-unoptimized").scrollIntoView()`
      )
      await new Promise((resolve) => setTimeout(resolve, 1000))
      const url = await browser.eval(
        `document.getElementById("static-unoptimized").src`
      )
      const res = await fetch(url)
      expect(res.headers.get('cache-control')).toBe(
        'public, max-age=31536000, immutable'
      )
    }
  )

it('Should automatically provide an image height and width', async () =>
{
    expect(html).toContain('width:400px;height:300px')
  })

it('Should allow provided width and height to override intrinsic', async
() => {
    expect(html).toContain('width:200px;height:200px')
    expect(html).not.toContain('width:400px;height:400px')
  })

it('Should add a blur placeholder to statically imported jpg', async ()
=> {
    const $ = cheerio.load(html)
    const style = $('#basic-static').attr('style')
    if (isNextDev && !isTurbopack) {
// In webpack dev, `next/legacy/image` emits a dynamic blur URL via the
// image optimizer route instead of an inlined base64 data URL, to avoid
      // slowing down the dev server (see
// `packages/next/src/build/webpack/loaders/next-image-loader/blur.ts`).
      expect(replaceBlurUrl(style)).toMatchInlineSnapshot(

`"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-position:0%
0%;filter:blur(20px);background-image:url("<REPLACED_BLUR_URL>")"`
      )
    } else {
      expect(replaceDataUrl(style)).toMatchInlineSnapshot(

`"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-position:0%
0%;filter:blur(20px);background-image:url("data:<REPLACED>")"`
      )
    }
  })

it('Should add a blur placeholder to statically imported png', async ()
=> {
    const $ = cheerio.load(html)
    const style = $('#basic-static')[2].attribs.style
    if (isTurbopack) {
      expect(style).toMatchInlineSnapshot(

`"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-position:0%
0%;filter:blur(20px);background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAICAYAAAA870V8AAAARUlEQVR42l3MoQ0AQQhE0XG7xWwIJSBIKBRJOZRBEXOWnPjimQ8AXC3ce+nuPOcQEcHuppkRVcWZYWYSIkJV5XvvN9j4AFZHJTnjDHb/AAAAAElFTkSuQmCC")"`
      )
    } else if (isNextDev) {
// In webpack dev, `next/legacy/image` emits a dynamic blur URL via the
// image optimizer route instead of an inlined base64 data URL, to avoid
      // slowing down the dev server (see
// `packages/next/src/build/webpack/loaders/next-image-loader/blur.ts`).
      expect(replaceBlurUrl(style)).toMatchInlineSnapshot(

`"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-position:0%
0%;filter:blur(20px);background-image:url("<REPLACED_BLUR_URL>")"`
      )
    } else {
// In webpack start, the exact base64 output of the blur placeholder
// depends on the environment's sharp/libvips version, so normalize the
      // data URL contents to only assert the data URL prefix.
      expect(replaceDataUrl(style)).toMatchInlineSnapshot(

`"position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%;background-size:cover;background-position:0%
0%;filter:blur(20px);background-image:url("data:<REPLACED>")"`
      )
    }
  })

  it('should load direct imported image', async () => {
const src = await
browser.elementById('basic-static').getAttribute('src')
    expect(src).toMatch(

/_next\/image\?url=%2F_next%2Fstatic%2F(immutable%2F)?media%2Ftest-rect(.+)\.jpg&w=828&q=75/
    )
    const fullSrc = new URL(src, next.url)
    const res = await fetch(fullSrc)
    expect(res.status).toBe(200)
  })

  it('should load staticprops imported image', async () => {
    const src = await browser
      .elementById('basic-staticprop')
      .getAttribute('src')
    expect(src).toMatch(

/_next\/image\?url=%2F_next%2Fstatic%2F(immutable%2F)?media%2Fexif-rotation(.+)\.jpg&w=256&q=75/
    )
    const fullSrc = new URL(src, next.url)
    const res = await fetch(fullSrc)
    expect(res.status).toBe(200)
  })
})`
- ID 345: `test/e2e/next-image-svgo-webpack/svgo-webpack.test.ts` —
`describe('svgo-webpack loader', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      '@svgr/webpack': '8.1.0',
    },
  })

it('should render an SVG that is transformed by @svgr/webpack into a
React component (pages router)', async () => {
    const browser = await next.browser('/pages')
    expect(await browser.elementByCss('svg')).toBeDefined()
  })

it('should render an SVG that is transformed by @svgr/webpack into a
React component (app router)', async () => {
    const browser = await next.browser('/')
    expect(await browser.elementByCss('svg')).toBeDefined()
  })
})`
- ID 347: `test/e2e/styled-jsx-dynamic/index.test.ts` —
`describe('styled-jsx dynamic styles SSR', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

// Dynamic styled-jsx (with interpolated expressions) produces numeric
class
// names at runtime via the DJB2 hash in styled-jsx's computeId
function.
// This pattern matches production deployments where all jsx class names
// are numeric (e.g. jsx-2267428885) rather than hex
(jsx-f36313d9f07883b7).
it('should contain dynamic styled-jsx styles during SSR', async () => {
    const html = await next.render('/')

    // Dynamic styled-jsx produces numeric class names at runtime
    const numericClasses = html.match(/\bjsx-\d+\b/g) || []
    console.log('Numeric jsx classes:', [...new Set(numericClasses)])
    expect(numericClasses.length).toBeGreaterThan(0)

    // All dynamic styles should be present as inline <style> tags
    expect(html).toMatch(/color:.*?green/) // main page
    expect(html).toMatch(/color:.*?blue/) // DynamicStyled
    expect(html).toMatch(/background-color:.*?navy/) // header
    expect(html).toMatch(/color:.*?purple/) // footer
  })
})`
- ID 348: `test/e2e/styled-jsx/index.test.ts` — `describe('styled-jsx',
() => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      'styled-jsx': '5.0.0', // styled-jsx on user side
    },
  })

  it('should contain styled-jsx styles during SSR', async () => {
    const html = await next.render('/')
    expect(html).toMatch(/color:.*?red/)
    expect(html).toMatch(/color:.*?cyan/)
  })

  it('should render styles during CSR', async () => {
    const browser = await next.browser('/')
    const color = await browser.eval(
      `getComputedStyle(document.querySelector('button')).color`
    )

    expect(color).toMatch('0, 255, 255')
  })

  it('should render styles inside TypeScript', async () => {
    const browser = await next.browser('/typescript')
    const color = await browser.eval(
      `getComputedStyle(document.querySelector('button')).color`
    )

    expect(color).toMatch('255, 0, 0')
  })
})`

</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/image-optimizer/image-optimizer.test.ts` — `describe('Server
support for trailingSlash in next.config.js', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784928/job/102715630953),
[cache](https://github.com/vercel/next.js/actions/runs/34426784928/job/102715630856).
- `test/e2e/next-image-legacy/default/default-static.test.ts` —
`describe('Static Image Component Tests', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784928/job/102715630923),
[cache](https://github.com/vercel/next.js/actions/runs/34426784928/job/102715630827).

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:07 -07:00
Jamiboy Mohammad 77fa82756a test: enable verified pages-router deploy tests (#98526)
## Summary

Enable the same 12 previously selected deployment-test scopes across 12
pages-router test files, now in a stack rooted on canary. Remove 11
`skipDeployment` options and their obsolete skip guards. Remove the
selected suite’s deploy-only placeholder return. Other mode, bundler,
middleware, and Cache Components exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (12)</summary>

- ID 222: `test/e2e/404-page-custom-error/404-page-custom-error.test.ts`
— `describe('Default 404 Page with custom _error', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should respond to 404 correctly', async () => {
    const res = await next.fetch('/404')
    expect(res.status).toBe(404)
    expect(await res.text()).toContain('This page could not be found')
  })

  it('should render error correctly', async () => {
    const text = await next.render('/err')
    expect(text).toContain(isNextDev ? 'oops' : 'Internal Server Error')
  })

  it('should render index page normal', async () => {
    const html = await next.render('/')
    expect(html).toContain('hello from index')
  })
  ;(isNextStart ? it : it.skip)(
    'should set pages404 in routes-manifest correctly',
    async () => {
const data = JSON.parse(await
next.readFile('.next/routes-manifest.json'))
      expect(data.pages404).toBe(true)
    }
  )
;(isNextStart ? it : it.skip)('should have output 404.html', async () =>
{
    const pagesManifest = await next.readJSON(
      '.next/server/pages-manifest.json'
    )
    const page = pagesManifest['/404']
    expect(page.endsWith('.html')).toBe(true)
  })
})`
- ID 223: `test/e2e/404-page/404-page.test.ts` — `describe('404 Page
Support', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  const gip404Err =
    /`pages\/404` can not have getInitialProps\/getServerSideProps/

  it('should use pages/404', async () => {
    const html = await next.render('/abc')
    expect(html).toContain('custom 404 page')
  })

  it('should set correct status code with pages/404', async () => {
    const res = await next.fetch('/abc')
    expect(res.status).toBe(404)
  })

  it('should use pages/404 for .d.ts file', async () => {
    const html = await next.render('/invalidExtension')
    expect(html).toContain('custom 404 page')
  })

  it('should not error when visited directly', async () => {
    const res = await next.fetch('/404')
    expect(res.status).toBe(404)
    expect(await res.text()).toContain('custom 404 page')
  })

  it('should render _error for a 500 error still', async () => {
    const html = await next.render('/err')
    expect(html).not.toContain('custom 404 page')
    expect(html).toContain(isNextDev ? 'oops' : 'Internal Server Error')
  })

  if (isNextStart) {
    it('should output 404.html during build', async () => {
const manifest = await next.readJSON('.next/server/pages-manifest.json')
      const page = manifest['/404']
      expect(page.endsWith('.html')).toBe(true)
    })

    it('should still output 404.js anyway', async () => {
      expect(await next.hasFile('.next/server/pages/404.js')).toBe(true)
    })

    it('should add /404 to pages-manifest correctly', async () => {
const manifest = await next.readJSON('.next/server/pages-manifest.json')
      expect('/404' in manifest).toBe(true)
    })
  }

  if (isNextDev) {
    it('falls back to _error correctly without pages/404', async () => {
      const original404 = await next.readFile('pages/404.js')
      try {
        await next.deleteFile('pages/404.js')
        await retry(async () => {
          const res = await next.fetch('/abc')
          expect(res.status).toBe(404)
expect(await res.text()).toContain('This page could not be found')
        })
      } finally {
        await next.patchFile('pages/404.js', original404)
      }
    })

it('shows error with getInitialProps in pages/404 dev', async () => {
      const original404 = await next.readFile('pages/404.js')
      try {
        await next.patchFile(
          'pages/404.js',
          `
          const page = () => 'custom 404 page'
          page.getInitialProps = () => ({ a: 'b' })
          export default page
        `
        )
        await next.render('/abc')
        await retry(async () => {
          expect(next.cliOutput).toMatch(gip404Err)
        })
      } finally {
        await next.patchFile('pages/404.js', original404)
      }
    })

it('does not show error with getStaticProps in pages/404 dev', async ()
=> {
      const original404 = await next.readFile('pages/404.js')
      const getOutput = next.getCliOutputFromHere()
      try {
        await next.patchFile(
          'pages/404.js',
          `
          const page = () => 'custom 404 page'
          export const getStaticProps = () => ({ props: { a: 'b' } })
          export default page
        `
        )
        await next.render('/abc')
        await retry(async () => {
          const html = await next.render('/abc')
          expect(html).toContain('custom 404 page')
        })
        expect(getOutput()).not.toMatch(gip404Err)
      } finally {
        await next.patchFile('pages/404.js', original404)
      }
    })

it('shows error with getServerSideProps in pages/404 dev', async () => {
      const original404 = await next.readFile('pages/404.js')
      try {
        await next.patchFile(
          'pages/404.js',
          `
          const page = () => 'custom 404 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
          export default page
        `
        )
        await next.render('/abc')
        await retry(async () => {
          expect(next.cliOutput).toMatch(gip404Err)
        })
      } finally {
        await next.patchFile('pages/404.js', original404)
      }
    })
  }
})`
- ID 225: `test/e2e/500-page/500-page.test.ts` — `describe('500 Page
Support', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should use pages/500', async () => {
    const html = await next.render('/500')
    expect(html).toContain('custom 500 page')
  })

  it('should set correct status code with pages/500', async () => {
    const res = await next.fetch('/500')
    expect(res.status).toBe(500)
  })

  it('should not error when visited directly', async () => {
    const res = await next.fetch('/500')
    expect(res.status).toBe(500)
    expect(await res.text()).toContain('custom 500 page')
  })

  if (isNextStart) {
    it('should output 500.html during build', async () => {
const manifest = await next.readJSON('.next/server/pages-manifest.json')
      const page = manifest['/500']
      expect(page.endsWith('.html')).toBe(true)
    })

    it('should add /500 to pages-manifest correctly', async () => {
const manifest = await next.readJSON('.next/server/pages-manifest.json')
      expect('/500' in manifest).toBe(true)
    })
  }

  if (isNextDev) {
it('shows error with getInitialProps in pages/500 dev', async () => {
      const original500 = await next.readFile('pages/500.js')
      try {
        await next.patchFile(
          'pages/500.js',
          `
          const page = () => 'custom 500 page'
          page.getInitialProps = () => ({ a: 'b' })
          export default page
        `
        )
        await next.render('/500')
        await retry(async () => {
          expect(next.cliOutput).toMatch(
/`pages\/500` can not have getInitialProps\/getServerSideProps/
          )
        })
      } finally {
        await next.patchFile('pages/500.js', original500)
      }
    })

it('does not show error with getStaticProps in pages/500 dev', async ()
=> {
      const original500 = await next.readFile('pages/500.js')
      const outputBefore = next.cliOutput.length
      try {
        await next.patchFile(
          'pages/500.js',
          `
          const page = () => 'custom 500 page'
          export const getStaticProps = () => ({ props: { a: 'b' } })
          export default page
        `
        )
        await next.render('/abc')
        await retry(async () => {
          expect(next.cliOutput.slice(outputBefore)).not.toMatch(
/`pages\/500` can not have getInitialProps\/getServerSideProps/
          )
        })
      } finally {
        await next.patchFile('pages/500.js', original500)
      }
    })

it('shows error with getServerSideProps in pages/500 dev', async () => {
      const original500 = await next.readFile('pages/500.js')
      try {
        await next.patchFile(
          'pages/500.js',
          `
          const page = () => 'custom 500 page'
export const getServerSideProps = () => ({ props: { a: 'b' } })
          export default page
        `
        )
        await next.render('/500')
        await retry(async () => {
          expect(next.cliOutput).toMatch(
/`pages\/500` can not have getInitialProps\/getServerSideProps/
          )
        })
      } finally {
        await next.patchFile('pages/500.js', original500)
      }
    })
  }
})`
- ID 227:
`test/e2e/api-resolver-query-writeable/api-resolver-query-writeable.test.ts`
— `describe('api-resolver-query-writeable', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    startCommand: 'node server.js',
    serverReadyPattern: /Next mode: (production|development)/,
    dependencies: {
      'get-port': '5.1.1',
      express: '5.1.0',
    },
  })

it('should allow req.query to be writable and reflect changes made in
the API handler', async () => {
    const res = await next.fetch('/api?hello=yes', {
      headers: {
        'Content-Type': 'application/json; charset=utf-8',
      },
    })
    if (!res.ok) {
      throw new Error('Fetch failed')
    }
    const data = await res.json()
    expect(data).toEqual({ query: { hello: 'yes', changed: 'yes' } })
  })
})`
- ID 230: `test/e2e/app-document/client.test.ts` — `describe('Document
and App - Client side', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should share module state with pages', async () => {
    const browser = await next.browser('/shared')

    const text = await browser.elementByCss('#currentstate').text()
    expect(text).toBe('UPDATED CLIENT')
  })

  if (isNextDev) {
it('should detect the changes to pages/_app.js and display it', async ()
=> {
      const appPath = 'pages/_app.js'
      const originalContent = await next.readFile(appPath)
      try {
        const browser = await next.browser('/')
        const text = await browser.elementByCss('#hello-hmr').text()
        expect(text).toBe('Hello HMR')

        // change the content
const editedContent = originalContent.replace('Hello HMR', 'Hi HMR')
        await next.patchFile(appPath, editedContent)

        await retry(async () =>
expect(await browser.elementByCss('body').text()).toContain('Hi HMR')
        )

        // add the original content
        await next.patchFile(appPath, originalContent)

        await retry(async () =>
          expect(await browser.elementByCss('body').text()).toContain(
            'Hello HMR'
          )
        )
      } finally {
        await next.patchFile(appPath, originalContent)
      }
    })

it('should detect the changes to pages/_document.js and display it',
async () => {
      const appPath = 'pages/_document.js'
      const originalContent = await next.readFile(appPath)
      try {
        const browser = await next.browser('/')
        const text = await browser.elementByCss('#hello-hmr').text()
        expect(text).toBe('Hello HMR')

        const editedContent = originalContent.replace(
          'Hello Document HMR',
          'Hi Document HMR'
        )

        // change the content
        await next.patchFile(appPath, editedContent)

        await retry(async () =>
          expect(await browser.elementByCss('body').text()).toContain(
            'Hi Document HMR'
          )
        )

        // add the original content
        await next.patchFile(appPath, originalContent)

        await retry(async () =>
          expect(await browser.elementByCss('body').text()).toContain(
            'Hello Document HMR'
          )
        )
      } finally {
        await next.patchFile(appPath, originalContent)
      }
    })

    it('should keep state between page navigations', async () => {
      const browser = await next.browser('/')

const randomNumber = await browser.elementByCss('#random-number').text()

      const switchedRandomNumer = await browser
        .elementByCss('#about-link')
        .click()
        .waitForElementByCss('.page-about')
        .elementByCss('#random-number')
        .text()

      expect(switchedRandomNumer).toBe(randomNumber)
      await browser.close()
    })
  }
})`
- ID 250: `test/e2e/disable-js/disable-js.test.ts` — `describe('disabled
runtime JS', () => {
  const { next, isNextDev, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  it('should render the page', async () => {
    const html = await next.render('/')
    expect(html).toMatch(/Hello World/)
  })

  it('should not have __NEXT_DATA__ script', async () => {
    const html = await next.render('/')

    const $ = cheerio.load(html)
    if (isNextStart) {
      expect($('script#__NEXT_DATA__').length).toBe(0)
    }
    if (isNextDev) {
      expect($('script#__NEXT_DATA__').length).toBe(1)
    }
  })

  if (isNextStart) {
    it('should not have scripts', async () => {
      const html = await next.render('/')
      const $ = cheerio.load(html)
      expect($('script[src]').length).toBe(0)
    })

    it('should not have preload links', async () => {
      const html = await next.render('/')
      const $ = cheerio.load(html)
      expect($('link[rel=preload]').length).toBe(0)
    })
  }

  if (isNextDev) {
    it('should have a script for each preload link', async () => {
      const html = await next.render('/')
      const $ = cheerio.load(html)
      const preloadLinks = $('link[rel=preload]')
      preloadLinks.each((idx, element) => {
        const url = $(element).attr('href')
        expect($(`script[src="${url}"]`).length).toBe(1)
      })
    })
  }
})`
- ID 255: `test/e2e/gip-identifier/gip-identifier.test.ts` —
`describe('gip identifiers', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  const getNextData = async () => {
    const html = await next.render('/')
    const $ = cheerio.load(html)
    return JSON.parse($('#__NEXT_DATA__').text())
  }

it('should not have gip or appGip in NEXT_DATA for page without
getInitialProps', async () => {
    const data = await getNextData()
    expect(data.gip).toBe(undefined)
    expect(data.appGip).toBe(undefined)
  })

  if (isNextDev) {
it('should have gip in NEXT_DATA for page with getInitialProps', async
() => {
      await next.patchFile(
        'pages/index.js',
        `
        const Page = () => 'hi'
        Page.getInitialProps = () => ({ hello: 'world' })
        export default Page
      `
      )
      await retry(async () => {
        const data = await getNextData()
        expect(data.gip).toBe(true)
      })
    })

it('should have gip and appGip in NEXT_DATA for page with
getInitialProps and _app with getInitialProps', async () => {
      await next.patchFile(
        'pages/_app.js',
        `
const App = ({ Component, pageProps }) => <Component {...pageProps} />
        App.getInitialProps = async (ctx) => {
          let pageProps = {}
          if (ctx.Component.getInitialProps) {
            pageProps = await ctx.Component.getInitialProps(ctx.ctx)
          }
          return { pageProps }
        }
        export default App
      `
      )
      await retry(async () => {
        const data = await getNextData()
        expect(data.gip).toBe(true)
        expect(data.appGip).toBe(true)
      })
    })

it('should only have appGip in NEXT_DATA for page without
getInitialProps and _app with getInitialProps', async () => {
await next.patchFile('pages/index.js', `export default () => 'hi'\n`)
      await retry(async () => {
        const data = await getNextData()
        expect(data.gip).toBe(undefined)
        expect(data.appGip).toBe(true)
      })
    })
  }
})`
- ID 258:
`test/e2e/i18n-data-fetching-redirect/redirect-from-context.test.ts` —
`describe('i18n-data-fetching-redirect', () => {
  const { next } = nextTestSetup({
    files: {
      pages: new FileRef(join(__dirname, 'app/pages')),
'next.config.js': new FileRef(join(__dirname, 'app/next.config.js')),
    },
    dependencies: {},
  })

  describe('Redirect to locale from context', () => {
    test.each`
      path                       | locale
      ${'gssp-redirect'}         | ${'en'}
      ${'gssp-redirect'}         | ${'sv'}
      ${'gsp-blocking-redirect'} | ${'en'}
      ${'gsp-blocking-redirect'} | ${'sv'}
      ${'gsp-fallback-redirect'} | ${'en'}
      ${'gsp-fallback-redirect'} | ${'sv'}
    `('$path $locale', async ({ path, locale }) => {
      const browser = await next.browser(`/${locale}/${path}/from-ctx`)

      await check(
        () => browser.eval('window.location.pathname'),
        `/${locale}/home`
      )
expect(await browser.elementByCss('#router-locale').text()).toBe(locale)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
        '/home'
      )
expect(await
browser.elementByCss('#router-as-path').text()).toBe('/home')
    })

    test.each`
      path                       | locale
      ${'gssp-redirect'}         | ${'en'}
      ${'gssp-redirect'}         | ${'sv'}
      ${'gsp-blocking-redirect'} | ${'en'}
      ${'gsp-blocking-redirect'} | ${'sv'}
      ${'gsp-fallback-redirect'} | ${'en'}
      ${'gsp-fallback-redirect'} | ${'sv'}
    `('next/link $path $locale', async ({ path, locale }) => {
      const browser = await next.browser(`/${locale}`)
      await browser.eval('window.beforeNav = 1')

      await browser.elementByCss(`#to-${path}-from-ctx`).click()

      await check(
        () => browser.eval('window.location.pathname'),
        `/${locale}/home`
      )

      expect(await browser.eval('window.beforeNav')).toBe(1)
expect(await browser.elementByCss('#router-locale').text()).toBe(locale)
expect(await browser.elementByCss('#router-pathname').text()).toBe(
        '/home'
      )
expect(await
browser.elementByCss('#router-as-path').text()).toBe('/home')
    })
  })
})`
- ID 261:
`test/e2e/i18n-ignore-rewrite-source-locale/rewrites-with-basepath.test.ts`
— `describe('i18n-ignore-rewrite-source-locale with basepath', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  test.each(locales)(
    'get public file by skipping locale in rewrite, locale: %s',
    async (locale) => {
      const res = await renderViaHTTP(
        next.url,
        `/basepath${locale}/rewrite-files/file.txt`
      )
      expect(res).toContain('hello from file.txt')
    }
  )

  test.each(locales)(
    'call api by skipping locale in rewrite, locale: %s',
    async (locale) => {
      const res = await renderViaHTTP(
        next.url,
        `/basepath${locale}/rewrite-api/hello`
      )
      expect(res).toContain('hello from api')
    }
  )

  // build artifacts aren't available on deploy
  if (!(global as any).isNextDeploy) {
    // chunks are not written to disk with TURBOPACK
    ;(process.env.IS_TURBOPACK_TEST ? it.skip.each : it.each)(locales)(
'get _next/static/ files by skipping locale in rewrite, locale: %s',
      async (locale) => {
        const chunks = (
          await fs.readdir(
            path.join(next.testDir, next.distDir, 'static', 'chunks')
          )
        ).filter((f) => f.endsWith('.js'))

        await Promise.all(
          chunks.map(async (file) => {
            const res = await fetchViaHTTP(
              next.url,
`/basepath${locale}/rewrite-files/_next/static/chunks/${file}`
            )
            // eslint-disable-next-line jest/no-standalone-expect
            expect(res.status).toBe(200)
          })
        )
      }
    )
  }
})`
- ID 267: `test/e2e/legacy-link-behavior/index.test.ts` —
`describe('Link with legacyBehavior', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  describe('if the child is an <a> tag', () => {
    it('forwards the href attribute', async () => {
      const $ = await next.render$('/')
      const $a = $('a[href="/about"]')

      expect($a.text()).toBe('About')
      expect($a.attr('href')).toBe('/about')
    })

    it('navigates correctly', async () => {
      const browser = await next.browser('/')
      await browser.elementByCss('a[href="/about"]').click()
      const title = await browser.elementByCss('#about-page').text()

      expect(title).toBe('About Page')
    })
  })

  it('works if the child is a number', async () => {
    const browser = await next.browser('/child-is-a-number')
    await browser.elementByCss('a[href="/about"]').click()
    const title = await browser.elementByCss('h1').text()

    expect(title).toBe('About Page')
  })

  it('works if the child is a string', async () => {
    const browser = await next.browser('/child-is-a-string')
    await browser.elementByCss('a[href="/about"]').click()
    const title = await browser.elementByCss('h1').text()

    expect(title).toBe('About Page')
  })

  it('errors when calling onClick without the event', async () => {
    const browser = await next.browser('/invalid-onclick')
    expect(await browser.elementByCss('#errors').text()).toBe('0')
    await browser.elementByCss('#custom-button').click()
    expect(await browser.elementByCss('#errors').text()).toBe('1')
  })

  it('should show a deprecation warning', async () => {
    const browser = await next.browser('/')

    await retry(async () => {
      const logs = await browser.log()
      const errors = logs.filter((log) => log.source === 'error')

      if (isNextDev) {
        expect(errors).toEqual([
          {
            message:
'`legacyBehavior` is deprecated and will be removed in a future release.
A codemod is available to upgrade your components:\n\n' +
              'npx @next/codemod@latest new-link .\n\n' +
'Learn more:
https://nextjs.org/docs/app/building-your-application/upgrading/codemods#remove-a-tags-from-link-components',
            source: 'error',
          },
        ])
      } else {
        expect(errors).toEqual([])
      }
    })
  })

  describe('passHref', () => {
    const expectHrefToBeForwardedInSSR = async (path: string) => {
      const $ = await next.render$(path)
      const $a = $('a[href="/about"]')
      expect($a.text()).toBe('About')
      expect($a.attr('href')).toBe('/about')
    }

    const expectLinkClickToNavigate = async (path: string) => {
      const browser = await next.browser(path)

      if (isNextDev) {
// We expect a deprecation warning (in a collapsed redbox), but no other
errors (e.g. no errors thrown by Link)
        await openRedbox(browser)
        expect(await createRedboxSnapshot(browser, next)).toEqual(
          expect.objectContaining<Partial<ErrorSnapshot>>({
            label: 'Console Error',
            description: expect.stringContaining(
`\`legacyBehavior\` is deprecated and will be removed in a future
release.`
            ),
          })
        )
await browser.locateRedbox().press('Escape') // Close redbox so we can
click the link
      }

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

      const title = await browser.elementByCss('h1').text()
      expect(title).toBe('About Page')
    }

    describe('with no prefech config', () => {
      it('forwards the href attribute', async () => {
        await expectHrefToBeForwardedInSSR('/passHref/default')
      })

      it('navigates correctly (failing)', async () => {
        if (isNextDev) {
// FIXME(NAR-876): false positive due to debug info blocking the child
          // await expectLinkClickToNavigate('/passHref/default')

          const browser = await next.browser('/passHref/default')
          await expect(browser).toDisplayRedbox(`
           {
"description": "\`<Link legacyBehavior>\` received a direct child that
is either a Server Component, or JSX that was loaded with React.lazy().
This is not supported. Either remove legacyBehavior, or make the direct
child a Client Component that renders the Link's \`<a>\` tag.",
             "environmentLabel": null,
             "label": "Runtime Error",
             "source": "app/passHref/default/page.tsx (7:7) @ Page
           >  7 |       <Link href="/about" legacyBehavior passHref>
                |       ^",
             "stack": [
               "Page app/passHref/default/page.tsx (7:7)",
             ],
           }
          `)
        } else {
          await expectLinkClickToNavigate('/passHref/default')
        }
      })
    })

    describe('with runtime prefetch', () => {
      it('forwards the href attribute', async () => {
        await expectHrefToBeForwardedInSSR('/passHref/runtime')
      })

      it('navigates correctly (failing)', async () => {
        if (isNextDev) {
// FIXME(NAR-876): false positive due to debug info blocking the child
          // await expectLinkClickToNavigate('/passHref/runtime')

          const browser = await next.browser('/passHref/runtime')
          await expect(browser).toDisplayRedbox(`
           {
"description": "\`<Link legacyBehavior>\` received a direct child that
is either a Server Component, or JSX that was loaded with React.lazy().
This is not supported. Either remove legacyBehavior, or make the direct
child a Client Component that renders the Link's \`<a>\` tag.",
             "environmentLabel": null,
             "label": "Runtime Error",
             "source": "app/passHref/runtime/page.tsx (9:7) @ Page
           >  9 |       <Link href="/about" legacyBehavior passHref>
                |       ^",
             "stack": [
               "Page app/passHref/runtime/page.tsx (9:7)",
             ],
           }
          `)
        } else {
          await expectLinkClickToNavigate('/passHref/runtime')
        }
      })
    })

    describe('in dynamic code', () => {
      it('forwards the href attribute', async () => {
        await expectHrefToBeForwardedInSSR('/passHref/dynamic')
      })

      it('navigates correctly', async () => {
        await expectLinkClickToNavigate('/passHref/dynamic')
      })
    })
  })
})`
- ID 270: `test/e2e/next-link-errors/next-link-errors.test.ts` —
`describe('next-link', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('errors on invalid href', async () => {
    const browser = await next.browser('/invalid-href')

    if (isNextDev) {
      await expect(browser).toDisplayRedbox(`
       {
"description": "Failed prop type: The prop \`href\` expects a \`string\`
or \`object\` in \`<Link>\`, but got \`undefined\` instead.
       Open your browser's console to view the Component stack trace.",
         "environmentLabel": null,
         "label": "Runtime Error",
         "source": "app/invalid-href/page.js (6:10) @ Hello
       > 6 |   return <Link>Hello, Dave!</Link>
           |          ^",
         "stack": [
           "Hello app/invalid-href/page.js (6:10)",
         ],
       }
      `)
    }
    // Client errors show "This page couldn\u2019t load"
    expect(await browser.elementByCss('body').text()).toContain(
      'This page couldn\u2019t load'
    )
  })

  it('invalid `prefetch` causes runtime error (dev-only)', async () => {
    const browser = await next.browser('/invalid-prefetch')

    if (isNextDev) {
      await expect(browser).toDisplayRedbox(`
       {
"description": "Failed prop type: The prop \`prefetch\` expects a
\`boolean | "auto"\` in \`<Link>\`, but got \`string\` instead.
       Open your browser's console to view the Component stack trace.",
         "environmentLabel": null,
         "label": "Runtime Error",
         "source": "app/invalid-prefetch/page.js (7:5) @ Hello
       >  7 |     <Link prefetch="unknown" href="https://nextjs.org/">
            |     ^",
         "stack": [
           "Hello app/invalid-prefetch/page.js (7:5)",
         ],
       }
      `)
      // Client errors show "This page couldn\u2019t load"
      expect(await browser.elementByCss('body').text()).toContain(
        'This page couldn\u2019t load'
      )
    } else {
expect(await browser.elementByCss('body').text()).toMatchInlineSnapshot(
        `"Link with unknown \`prefetch\` renders in prod."`
      )
    }
  })
})`
- ID 273: `test/e2e/pages-performance-mark/index.test.ts` —
`describe('pages performance mark', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render the page correctly without crashing with performance
mark', async () => {
    const browser = await next.browser('/')
    expect(await browser.elementByCss('h1').text()).toBe('home')
  })
})`

</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/404-page-custom-error/404-page-custom-error.test.ts` —
`describe('Default 404 Page with custom _error', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905641),
[cache](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905524).
- `test/e2e/404-page/404-page.test.ts` — `describe('404 Page Support',
() => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905631),
[cache](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905599).
-
`test/e2e/i18n-ignore-rewrite-source-locale/rewrites-with-basepath.test.ts`
— `describe('i18n-ignore-rewrite-source-locale with basepath', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905696),
[cache](https://github.com/vercel/next.js/actions/runs/34426785205/job/102715905562).

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:06 -07:00
Jamiboy Mohammad f825d34b75 test: enable verified tooling deploy tests (#98525)
## Summary

Enable the same 26 previously selected deployment-test scopes across 25
tooling test files, now in a stack rooted on canary. Remove 26
`skipDeployment` options and their obsolete skip guards. Other mode,
bundler, middleware, and Cache Components exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (26)</summary>

- ID 146: `test/e2e/app-dir/app-config-crossorigin/index.test.ts` —
`describe('app dir - crossOrigin config', () => {
    const { next } = nextTestSetup({
      files: __dirname,
    })

    it('should render correctly with assetPrefix: "/"', async () => {
      const $ = await next.render$('/')
// Only potential external (assetPrefix) <script /> and <link /> should
have crossorigin attribute
      $(
'script[src*="https://example.vercel.sh"],
link[href*="https://example.vercel.sh"]'
      ).each((_, el) => {
        const crossOrigin = $(el).attr('crossorigin')
        expect(crossOrigin).toBe('use-credentials')
      })

// Inline <script /> (including RSC payload) and <link /> should not
have crossorigin attribute
      $('script:not([src]), link:not([href])').each((_, el) => {
        const crossOrigin = $(el).attr('crossorigin')
        expect(crossOrigin).toBeUndefined()
      })

// Same origin <script /> and <link /> should not have crossorigin
attribute either
      $('script[src^="/"], link[href^="/"]').each((_, el) => {
        const crossOrigin = $(el).attr('crossorigin')
        expect(crossOrigin).toBeUndefined()
      })
    })
  })`
- ID 148:
`test/e2e/app-dir/import-meta-glob-text-type/import-meta-glob-text-type.test.ts`
— `describe('turbopack `text` / `raw` module types', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should load matched files as strings through a `?raw` rule', async
() => {
    const $ = await next.render$('/raw')
    const items = $('li')
      .map((_, el) => $(el).text())
      .get()

    expect(items).toEqual([
      './content/delta.txt: delta contents',
      './content/gamma.txt: gamma contents',
    ])
  })

it('should treat `raw` and `text` the same in a `?raw` rule', async ()
=> {
    const $ = await next.render$('/raw-alias')
    const items = $('li')
      .map((_, el) => $(el).text())
      .get()

    expect(items).toEqual([
      './content/delta.rst: delta contents',
      './content/gamma.rst: gamma contents',
    ])
  })

it('should treat `raw` and `text` the same for a plain import', async ()
=> {
    const $ = await next.render$('/alias')

expect(JSON.parse($('#raw').text())).toBe('# alpha\n\nsome markdown\n')
    expect($('#equal').text()).toBe('true')
  })
})`
- ID 153:
`test/e2e/app-dir/multiple-lockfiles/multiple-lockfiles-with-output-file-tracing-root.test.ts`
— `describe('multiple-lockfiles - has-output-file-tracing-root', () => {
  const { next } = nextTestSetup({
    files: {
      app: new FileRef(join(__dirname, 'app')),
      // This will silence the multiple lockfiles warning.
'next.config.js': `module.exports = { outputFileTracingRoot: __dirname
}`,
// Write a package-lock.json file to the parent directory to simulate
      // multiple lockfiles.
      '../package.json': JSON.stringify({
        name: 'parent-workspace',
        version: '1.0.0',
      }),
      '../package-lock.json': JSON.stringify({
        name: 'parent-workspace',
        version: '1.0.0',
        lockfileVersion: 3,
packages: { '': { name: 'parent-workspace', version: '1.0.0' } },
      }),
    },
    // So that ../package-lock.json doesn't leave the isolated testDir
    subDir: 'test',
    // The workspace file would suppress the warning itself, so the test
    // wouldn't be exercising `outputFileTracingRoot`.
    deleteWorkspaceFile: true,
  })

  it('should not have multiple lockfiles warnings', async () => {
    expect(next.cliOutput).not.toMatch(
/We detected multiple lockfiles and selected the directory of .+ as the
root directory\./
    )
  })
})`
- ID 154:
`test/e2e/app-dir/multiple-lockfiles/multiple-lockfiles-with-turbo-root.test.ts`
— `describe('multiple-lockfiles - has-turbo-root', () => {
  const { next } = nextTestSetup({
    files: {
      app: new FileRef(join(__dirname, 'app')),
      // This will silence the multiple lockfiles warning.
'next.config.js': `module.exports = { turbopack: { root: __dirname } }`,
// Write a package-lock.json file to the parent directory to simulate
      // multiple lockfiles.
      '../package.json': JSON.stringify({
        name: 'parent-workspace',
        version: '1.0.0',
      }),
      '../package-lock.json': JSON.stringify({
        name: 'parent-workspace',
        version: '1.0.0',
        lockfileVersion: 3,
packages: { '': { name: 'parent-workspace', version: '1.0.0' } },
      }),
    },
    // So that ../package-lock.json doesn't leave the isolated testDir
    subDir: 'test',
    // The workspace file would suppress the warning itself, so the test
    // wouldn't be exercising `turbopack.root`.
    deleteWorkspaceFile: true,
  })

  it('should not have multiple lockfiles warnings', async () => {
    expect(next.cliOutput).not.toMatch(
/We detected multiple lockfiles and selected the directory of .+ as the
root directory\./
    )
  })
})`
- ID 170: `test/e2e/app-dir/segment-config-ts/segment-config-ts.test.ts`
— `describe('TypeScript type expressions in route segment config', () =>
{
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  describe('app directory', () => {
it('should pick up maxDuration declared with `as` type assertion', async
() => {
      const $ = await next.render$('/as')
      expect($('main').text()).toBe('hello')
    })

it('should pick up maxDuration declared with `as const` assertion',
async () => {
      const $ = await next.render$('/as-const')
      expect($('main').text()).toBe('hello')
    })

it('should pick up maxDuration declared with `satisfies`', async () => {
      const $ = await next.render$('/satisfies')
      expect($('main').text()).toBe('hello')
    })
  })

  describe('pages directory', () => {
it('should pick up maxDuration from config object declared with `as`',
async () => {
      const $ = await next.render$('/config-as')
      expect($('main').text()).toBe('hello')
    })

it('should pick up maxDuration from config object declared with `as
const`', async () => {
      const $ = await next.render$('/config-as-const')
      expect($('main').text()).toBe('hello')
    })

it('should pick up maxDuration from config object declared with
`satisfies`', async () => {
      const $ = await next.render$('/config-satisfies')
      expect($('main').text()).toBe('hello')
    })
  })

  if (isNextStart) {
    it('should parse the config correctly', async () => {
      const config = await next.readJSON(
        '.next/server/functions-config-manifest.json'
      )
      expect(config).toMatchInlineSnapshot(`
       {
         "functions": {
           "/as": {
             "maxDuration": 1000,
           },
           "/as-const": {
             "maxDuration": 1000,
           },
           "/config-as": {
             "maxDuration": 1000,
           },
           "/config-as-const": {
             "maxDuration": 1000,
           },
           "/config-satisfies": {
             "maxDuration": 1000,
           },
           "/satisfies": {
             "maxDuration": 1000,
           },
         },
         "version": 1,
       }
      `)
    })
  }
})`
- ID 172: `test/e2e/app-dir/trace-build-file/trace-build-file.test.ts` —
`describe('trace-build-file', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    skipStart: !isNextDev,
    env: {
// Enable persistent caching even when the git working directory is
      // dirty (e.g. when developing Next.js itself). Without this, the
      // cache falls back to a temp directory and persistence/compaction
      // spans are not emitted.
      TURBO_ENGINE_IGNORE_DIRTY: '1',
    },
  })

  if (isNextStart) {
it('should create .next/trace-build file during production build', async
() => {
      // Build the app to trigger trace generation
      await next.build()

      // Check that trace-build file exists
      const traceBuildPath = join(next.testDir, '.next/trace-build')
      expect(existsSync(traceBuildPath)).toBe(true)
    })

    it('should contain high-level build trace events', async () => {
      // Ensure we have a fresh build
      await next.build()

      const traceBuildPath = join(next.testDir, '.next/trace-build')
      expect(existsSync(traceBuildPath)).toBe(true)

      const traceStructure = parseTraceFile(traceBuildPath)

      // Should have events
      expect(traceStructure.events.length).toBeGreaterThan(0)

      // Should contain the main next-build event
const nextBuildEvents = traceStructure.eventsByName.get('next-build')
      expect(nextBuildEvents).toBeDefined()
      expect(nextBuildEvents.length).toBe(1)

      const nextBuildEvent = nextBuildEvents[0]
      expect(nextBuildEvent).toHaveProperty('name', 'next-build')
      expect(nextBuildEvent).toHaveProperty('traceId')
      expect(nextBuildEvent).toHaveProperty('id')
      expect(nextBuildEvent).toHaveProperty('duration')
      expect(typeof nextBuildEvent.duration).toBe('number')
      expect(typeof nextBuildEvent.traceId).toBe('string')
      expect(typeof nextBuildEvent.id).toBe('number')
    })

    it('should only contain allowlisted events', async () => {
      await next.build()

      const traceBuildPath = join(next.testDir, '.next/trace-build')
      const traceStructure = parseTraceFile(traceBuildPath)

      // const allowlistedEvents = new Set([
      //   'next-build',
      //   'run-turbopack',
      //   'run-webpack',
      //   'run-typescript',
      //   'run-eslint',
      //   'static-check',
      //   'static-generation',
      //   'output-export-full-static-export',
      // ])

      const foundEvents = new Set<string>()

      for (const event of traceStructure.events) {
        foundEvents.add(event.name)
      }

      if (process.env.IS_TURBOPACK_TEST) {
// Compaction only runs when it is due, so it may or may not appear.
        foundEvents.delete('turbopack-compaction')

        expect([...foundEvents].sort()).toMatchInlineSnapshot(`
                [
                  "next-build",
                  "run-turbopack",
                  "run-typescript",
                  "static-check",
                  "static-generation",
                  "telemetry-flush",
                  "turbopack-persistence",
                ]
              `)
      } else {
        expect([...foundEvents].sort()).toMatchInlineSnapshot(`
         [
           "collect-build-traces",
           "next-build",
           "run-typescript",
           "run-webpack",
           "static-check",
           "static-generation",
           "telemetry-flush",
         ]
        `)
      }
    })

it('should have next-build as root span with proper hierarchy', async ()
=> {
      await next.build()

      const traceBuildPath = join(next.testDir, '.next/trace-build')
      const traceStructure = parseTraceFile(traceBuildPath)

// Should have no orphaned events (all events should have valid parent
references)
      expect(traceStructure.orphanedEvents).toHaveLength(0)

      // Should have at one root event
      expect(traceStructure.rootEvents.length).toBe(1)

      // next-build should be the main root event
const nextBuildEvents = traceStructure.eventsByName.get('next-build')
      expect(nextBuildEvents).toBeDefined()
      expect(nextBuildEvents.length).toBe(1)

      const nextBuildEvent = nextBuildEvents[0]
      expect(nextBuildEvent.parentId).toBeUndefined() // Should be root
      expect(traceStructure.rootEvents).toContain(nextBuildEvent)

// Other build events should be children of next-build or have valid
parent references
const buildEvents = ['run-webpack', 'run-typescript', 'run-eslint']
      for (const eventName of buildEvents) {
        const events = traceStructure.eventsByName.get(eventName)
        if (events && events.length > 0) {
          for (const event of events) {
            if (event.parentId) {
              // Should have a valid parent
              expect(
                traceStructure.eventsById.has(event.parentId.toString())
              ).toBe(true)
              const parent = traceStructure.eventsById.get(
                event.parentId.toString()
              )

// Parent should either be next-build or another valid event
              expect(parent).toBeDefined()
              expect(parent.traceId).toBe(event.traceId) // Same trace
            }
          }
        }
      }
    })

    it('should have consistent traceId across all events', async () => {
      await next.build()

      const traceBuildPath = join(next.testDir, '.next/trace-build')
      const traceStructure = parseTraceFile(traceBuildPath)

      expect(traceStructure.events.length).toBeGreaterThan(0)

      const firstEvent = traceStructure.events[0]
      expect(firstEvent.traceId).toBeDefined()
      expect(typeof firstEvent.traceId).toBe('string')
      expect(firstEvent.traceId.length).toBeGreaterThan(0)

      // All events should have the same traceId
      for (const event of traceStructure.events) {
        expect(event.traceId).toBe(firstEvent.traceId)
      }
    })
  }

  if (isNextDev) {
it('should not create trace-build file in development mode', async () =>
{
      // Make a request to trigger some activity
      await next.render('/')

      // Check that trace-build file does not exist
      const traceBuildPath = join(next.testDir, '.next/trace-build')
      expect(existsSync(traceBuildPath)).toBe(false)
    })
  }

  it('should work with basic page rendering', async () => {
    if (isNextStart) {
      await next.start()
    }
    const $ = await next.render$('/')
    expect($('p').text()).toBe('hello world')
  })
})`
- ID 173:
`test/e2e/app-dir/turbopack-loader-content-type/turbopack-loader-content-type.test.ts`
— `describe('turbopack-loader-content-type', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should apply loader based on contentType glob pattern', async () =>
{
    const $ = await next.render$('/')
    const text = $('#text').text()
    expect(text).toBe('TEXT:Hello World')
  })

it('should apply loader based on contentType for text/javascript', async
() => {
    const $ = await next.render$('/')
    const text = $('#js').text()
    expect(text).toBe('Hello from loader')
  })

  it('should apply loader based on contentType regex', async () => {
    const $ = await next.render$('/')
    const text = $('#image').text()
    expect(text).toMatch(/^IMAGE:\d+ bytes$/)
  })
})`
- ID 181:
`test/e2e/app-dir/webpack-loader-conditions/webpack-loader-conditions.test.ts`
— `describe('webpack-loader-conditions', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should render correctly on server site', async () => {
    const res = await next.fetch('/')
    const html = (await res.text()).replaceAll(/<!-- -->/g, '')
    expect(html).toContain(`server: {&quot;default&quot;:true}`)
    expect(html).toContain(`client: {&quot;default&quot;:true}`)
    expect(html).toContain(`foreignClient: {}`)
  })

  it('should render correctly on client side', async () => {
    const browser = await next.browser('/')
    const text = await browser.elementByCss('body').text()
expect(text).toContain(`server: ${JSON.stringify({ default: true })}`)
expect(text).toContain(`client: ${JSON.stringify({ browser: true })}`)
    expect(text).toContain(
`foreignClient: ${JSON.stringify({ browser: true, foreign: true })}`
    )
  })
})`
- ID 183: `test/e2e/app-dir/webpack-loader-fs/webpack-loader-fs.test.ts`
— `describe('webpack-loader-fs', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should allow reading the input FS', async () => {
    const $ = await next.render$('/')
    expect($('#test').text()).toBe(
"Buffer read: 18, string read: 'this is some data', binary read: 6765,
glob read: 'one.txt'"
    )
  })
})`
- ID 184:
`test/e2e/app-dir/webpack-loader-import-module/webpack-loader-import-module.test.ts`
— `describe('webpack-loader-import-module', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

it('should support this.importModule() in a webpack loader', async () =>
{
    const $ = await next.render$('/')
    expect($('#title').text()).toBe('Import Module Works')
    expect($('#items').text()).toBe('apple, banana, cherry')
    // CJS dependency that itself requires a JSON file
    expect($('#cjs-greeting').text()).toBe('hello from cjs')
    expect($('#version').text()).toBe('1.0.0')
    // ESM dependency imported from config-data.ts
    expect($('#esm-label').text()).toBe('hello from esm')
    // ESM .mjs module (config-data.mjs)
    expect($('#mjs-title').text()).toBe('ESM Config Works')
    expect($('#mjs-esm-label').text()).toBe('hello from esm')

    // resolveAlias: importModule with alias as request
    expect($('#alias-value').text()).toBe('resolved via alias')
    // resolveAlias: dependency of importModule target uses alias
    expect($('#alias-dep-label').text()).toBe('hello from esm')
    // loader rules: importModule on file requiring custom loader
expect($('#custom-data-value').text()).toBe('hello from custom loader')
// loader rules: dependency of importModule target needs custom loader
    expect($('#consumed-value').text()).toBe('hello from custom loader')

    if (isTurbopack) {
      // new URL('./image.png', import.meta.url) in url-wasm-data.ts
      expect($('#image-url').text()).toContain('image')
      expect($('#image-url').text()).toMatch(/\.png/)
      // WebAssembly add(1, 2) from add.wasm in url-wasm-data.ts
      expect($('#wasm-add-result').text()).toBe('3')
      // Dynamic import('./module.js') in url-wasm-data.ts
      expect($('#dynamic-value').text()).toBe('loaded dynamically')
      // new URL('./image.png', import.meta.url) in url-wasm-data.mjs
      expect($('#mjs-image-url').text()).toContain('image')
      expect($('#mjs-image-url').text()).toMatch(/\.png/)
      // WebAssembly add(10, 20) from add.wasm in url-wasm-data.mjs
      expect($('#mjs-wasm-add-result').text()).toBe('30')
      // Dynamic import('./module.js') in url-wasm-data.mjs
      expect($('#mjs-dynamic-value').text()).toBe('loaded dynamically')
    }
  })
})`
- ID 185:
`test/e2e/app-dir/webpack-loader-module-type/webpack-loader-module-type.test.ts`
— `describe('webpack-loader-module-type', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

// bytes type is Turbopack-only, webpack doesn't have a direct
equivalent
  const itTurbopackOnly = isTurbopack ? it : it.skip

  it('should load svg as asset/resource and return URL', async () => {
    const $ = await next.render$('/')
    const src = $('#svg-url').text()
    // asset/resource should emit the file and return URL path
    expect(src).toMatch(
      /\/_next\/static\/(immutable\/)?media\/test\.[0-9a-z_-]+\.svg$/
    )
  })

  itTurbopackOnly(
    'should load data file as bytes and return Uint8Array',
    async () => {
      const $ = await next.render$('/')
      const bytesType = $('#bytes-type').text()
      const bytesLength = $('#bytes-length').text()
      const bytesText = $('#bytes-text').text()

      // eslint-disable-next-line jest/no-standalone-expect
      expect(bytesType).toBe('Uint8Array')
      // eslint-disable-next-line jest/no-standalone-expect
      expect(bytesLength).toBe('11')
      // eslint-disable-next-line jest/no-standalone-expect
      expect(bytesText).toBe('hello world')
    }
  )
})`
- ID 186:
`test/e2e/app-dir/webpack-loader-resolve/webpack-loader-resolve.test.ts`
— `describe('webpack-loader-resolve', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should support resolving absolute path via loader getResolve', async
() => {
    const $ = await next.render$('/')
    expect($('#absolute').text()).toBe('abc')
    expect($('#relative').text()).toBe('xyz')
  })

  it('should support loader getResolve without options', async () => {
    const $ = await next.render$('/no-options')
    expect($('#no-options').text()).toBe('xyz')
  })

  it('should support callback-style loader resolve', async () => {
    const $ = await next.render$('/callback')
    expect($('#resolved').text()).toBe('resolved-value.js')
  })
})`
- ID 187:
`test/e2e/app-dir/webpack-loader-resource-query/webpack-loader-resource-query.test.js`
— `describe('webpack-loader-resource-query', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should pass query to loader', async () => {
    await next.render$('/')

    expect(next.cliOutput).toContain('resource query:  ?test=hi')
  })

  it('should apply loader based on resourceQuery', async () => {
    const $ = await next.render$('/')
    const text = $('#reversed').text()
    expect(text).toBe('dlroW olleH')
  })

  it('should apply loader based on resourceQuery regex', async () => {
    const $ = await next.render$('/')
    const text = $('#upper').text()
    expect(text).toBe('HELLO WORLD')
  })
})`
- ID 188:
`test/e2e/app-dir/webpack-loader-ts-transform/webpack-loader-ts-transform.test.ts`
— `describe('webpack-loader-ts-transform', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should accept Typescript returned from Webpack loaders', async () =>
{
    const $ = await next.render$('/')
    expect($('p').text()).toBe('something')
  })
})`
- ID 189: `test/e2e/app-dir/with-babel/with-babel.test.ts` —
`describe('with babel', () => {
  const { next, isNextStart, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

  it('should support babel in app dir', async () => {
    const $ = await next.render$('/')
    expect($('h1').text()).toBe('hello')
  })

  if (isNextStart) {
// Turbopack always runs SWC, so this shouldn't be an issue, but this
test
    // refers to a webpack-specific output path.
    // https://github.com/vercel/next.js/pull/51067
    ;(isTurbopack ? it.skip : it)(
      'should contain og package files in middleware',
      async () => {
        await retry(async () => {
const middleware = await next.readFile('.next/server/middleware.js')
          // @vercel/og default font should be bundled
          expect(middleware).not.toContain('Geist-Regular.ttf')
        })
      }
    )
  }
})`
- ID 192: `test/e2e/config-schema-check/index.test.ts` —
`describe('next.config.js schema validating - defaultConfig', () => {
  const { next } = nextTestSetup({
    files: {
      'pages/index.js': `
    export default function Page() {
      return <p>hello world</p>
    }
    `,
      'next.config.js': `
    module.exports = (phase, { defaultConfig }) => {
      return defaultConfig
    }
    `,
    },
  })

  it('should validate against defaultConfig', async () => {
    const output = stripAnsi(next.cliOutput)

expect(output).not.toContain('Invalid next.config.js options detected')
  })
})`
- ID 193: `test/e2e/config-schema-check/index.test.ts` —
`describe('next.config.js schema validating - invalid config', () => {
  const { next, isNextStart } = nextTestSetup({
    files: {
      'pages/index.js': `
    export default function Page() {
      return <p>hello world</p>
    }
    `,
      'next.config.js': `
    module.exports = {
      badKey: 'badValue'
    }
    `,
    },
  })

  it('should warn the invalid next config', async () => {
    await check(() => {
      const output = stripAnsi(next.cliOutput)
      const warningTimes = output.split('badKey').length - 1

expect(output).toContain('Invalid next.config.js options detected')
      expect(output).toContain('badKey')
      // for next start and next build we both display the warnings
      expect(warningTimes).toBe(isNextStart ? 2 : 1)

      return 'success'
    }, 'success')
  })
})`
- ID 198: `test/e2e/import-meta-env/import-meta-env.test.ts` —
`describe('import.meta.env', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('exposes built-in environment values on the server and client', async
() => {
    const browser = await next.browser('/docs')
    const expectedMode = isNextDev ? 'development' : 'production'

    expect(
      JSON.parse(await browser.elementByCss('#server-env dd').text())
    ).toEqual({
      DEV: isNextDev,
      PROD: !isNextDev,
      MODE: expectedMode,
      BASE_URL: '/docs/',
      SSR: true,
    })
    expect(
      JSON.parse(await browser.elementByCss('#client-env dd').text())
    ).toEqual({
      DEV: isNextDev,
      PROD: !isNextDev,
      MODE: expectedMode,
      BASE_URL: '/docs/',
      SSR: false,
    })
  })

it('supports static bracket access and unknown properties', async () =>
{
    const browser = await next.browser('/docs')
    const $ = await next.render$('/docs')
    const expectedMode = isNextDev ? 'development' : 'production'

    expect($('#server-env dd').eq(1).text()).toBe(expectedMode)
    expect($('#server-env dd').eq(2).text()).toBe('undefined')
    expect(
      await browser.elementByCss('#client-env dd:nth-of-type(2)').text()
    ).toBe(expectedMode)
    expect(
      await browser.elementByCss('#client-env dd:nth-of-type(3)').text()
    ).toBe('undefined')
  })
})`
- ID 199: `test/e2e/import-meta-glob/import-meta-glob.test.ts` —
`describe('import-meta-glob', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should resolve lazy glob modules', async () => {
    const $ = await next.render$('/')
    const lazyKeys = JSON.parse($('#lazy-keys').text())
    expect(lazyKeys).toEqual([
      './modules/bar.ts',
      './modules/foo.ts',
      './modules/skip.ts',
    ])

    const lazyResults = JSON.parse($('#lazy-results').text())
    expect(lazyResults).toEqual({
      './modules/bar.ts': 'bar',
      './modules/foo.ts': 'foo',
      './modules/skip.ts': 'skip',
    })
  })

  it('should resolve eager glob modules', async () => {
    const $ = await next.render$('/')
    const eagerKeys = JSON.parse($('#eager-keys').text())
    expect(eagerKeys).toEqual([
      './modules/bar.ts',
      './modules/foo.ts',
      './modules/skip.ts',
    ])

    const eagerResults = JSON.parse($('#eager-results').text())
    expect(eagerResults).toEqual({
      './modules/bar.ts': 'bar',
      './modules/foo.ts': 'foo',
      './modules/skip.ts': 'skip',
    })
  })

  it('should resolve named import glob modules', async () => {
    const $ = await next.render$('/')
    const defaultResults = JSON.parse($('#default-results').text())
    expect(defaultResults).toEqual({
      './modules/bar.ts': 'bar-value',
      './modules/foo.ts': 'foo-value',
      './modules/skip.ts': 'skip-value',
    })
  })

  it('should support negative patterns', async () => {
    const $ = await next.render$('/')
    const filteredKeys = JSON.parse($('#filtered-keys').text())
expect(filteredKeys).toEqual(['./modules/bar.ts', './modules/foo.ts'])

    const filteredResults = JSON.parse($('#filtered-results').text())
    expect(filteredResults).toEqual({
      './modules/bar.ts': 'bar',
      './modules/foo.ts': 'foo',
    })
  })

  it('should support multiple patterns', async () => {
    const $ = await next.render$('/')
    const multiKeys = JSON.parse($('#multi-keys').text())
    expect(multiKeys).toEqual([
      './modules/bar.ts',
      './modules/foo.ts',
      './modules/skip.ts',
      './other/baz.ts',
    ])

    const multiResults = JSON.parse($('#multi-results').text())
    expect(multiResults).toEqual({
      './modules/bar.ts': 'bar',
      './modules/foo.ts': 'foo',
      './modules/skip.ts': 'skip',
      './other/baz.ts': 'baz',
    })
  })
})`
- ID 200: `test/e2e/jsconfig-baseurl/jsconfig-baseurl.test.ts` —
`describe('jsconfig.json baseurl', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  describe('default behavior', () => {
    it('should render the page', async () => {
      const $ = await next.render$('/hello')
      expect($('body').text()).toMatch(/World/)
    })

// Integration ran this under `launchApp` only. e2e splits dev vs `next
start` jobs, so
// `it.skip` when !isNextDev is correct: the module-not-found overlay is
dev-only; production
// jobs still cover `should trace correctly` under `should build` below.
    ;(isNextDev ? it : it.skip)(
      'should have correct module not found error',
      async () => {
        const contents = await next.readFile('pages/hello.js')
        try {
          await next.patchFile(
            'pages/hello.js',
            contents.replace('components/world', 'components/worldd')
          )

          await retry(async () => {
            await next.render('/hello').catch(() => {})
            const strippedOutput = stripAnsi(next.cliOutput)
            expect(strippedOutput).toMatch(
              /Module not found: Can't resolve 'components\/worldd'/
            )
          })
        } finally {
          await next.patchFile('pages/hello.js', contents)
        }
      }
    )
  })
  ;(isNextStart ? describe : describe.skip)('should build', () => {
    it('should trace correctly', async () => {
      const helloTrace = JSON.parse(
        await next.readFile('.next/server/pages/hello.js.nft.json')
      )
      expect(
        helloTrace.files.some((file: string) =>
          file.includes('components/world.js')
        )
      ).toBe(false)
      expect(
helloTrace.files.some((file: string) => file.includes('react/index.js'))
      ).toBe(true)
    })
  })
})`
- ID 207: `test/e2e/swc-plugins-env/index.test.ts` —
`describe('swc-plugins-env', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should pass correct environment to swc plugins', async () => {
    const $ = await next.render$('/')
    if (isNextDev) {
expect($('main').text()).toBe('The SWC plugin received env=development')
    } else {
expect($('main').text()).toBe('The SWC plugin received env=production')
    }
  })
})`
- ID 208: `test/e2e/swc-plugins/index.test.ts` — `describe('supports
swcPlugins', () => {
    const { next } = nextTestSetup({
      files: __dirname,
      dependencies: {
        '@swc/plugin-react-remove-properties': '13.0.0',
      },
    })

    it('basic case', async () => {
      const html = await next.render('/')
      expect(html).toContain('Hello World')
      expect(html).not.toContain('data-custom-attribute')
    })
  })`
- ID 212: `test/e2e/transpile-packages-typescript-foreign/index.test.ts`
— `describe('with transpilePackages', () => {
    const { next } = nextTestSetup({
      files: __dirname,
      dependencies: {
        pkg: `file:./pkg`,
      },
      nextConfig: {
        transpilePackages: ['pkg'],
      },
    })

    it('should work', async () => {
      const $ = await next.render$('/')
      expect($('main').text()).toEqual('Hello 123')
    })
  })`
- ID 213: `test/e2e/turbopack-import-with-type/index.test.ts` —
`describe('turbopack-import-with-type', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

// Testing this together on one route ensures we also avoid weird
duplicate module ident things
it('supports import with type: text, type: bytes, and type: json', async
() => {
    const response = JSON.parse(await next.render('/api'))
    expect(response).toEqual({
      text: {
        typeofString: true,
        length: 12,
        content: 'hello world\n',
      },
      jsAsText: {
        typeofString: true,
        content: jsContent,
      },
      bytes: {
        instanceofUint8Array: true,
        length: 18,
        content: 'this is some data\n',
      },
      jsAsBytes: {
        instanceofUint8Array: true,
        content: jsContent,
      },
      configuredAsJsAsBytes: {
        instanceofUint8Array: true,
        content:
"throw new Error('this file is configured as ecmascript but imported as
bytes')\n",
      },
      json: {
        typeofObject: true,
        content: { hello: 'world' },
      },
      jsonAsText: {
        typeofString: true,
        content: '{ "hello": "world" }\n',
      },
    })
  })
})`
- ID 214: `test/e2e/turbopack-loader-config/index.test.ts` —
`describe('turbopack-loader-config', () => {
  const { next, isTurbopack, isNextDev } = nextTestSetup({
    files: __dirname,
// we can't set `nextConfig` inline because it contains regexes that
fail to serialize, it needs
    // to be set in a separate module (`next.config.ts`)
  })

  if (!isTurbopack) {
    it('should only run the test in turbopack', () => {})
    return
  }

it('should replace modules with their loader-generated versions', async
() => {
    const response = JSON.parse(await next.render('/api'))
    expect(response).toEqual({
      foo: 'default return value',
bar: 'has export substring' + (isNextDev ? ' on dev' : ' on prod'),
    })
  })
})`
- ID 217: `test/e2e/typescript/typescript.test.ts` —
`describe('TypeScript Features', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
    dependencies: {
      sass: 'latest',
    },
  })

  it('should render the page', async () => {
    const $ = await next.render$('/hello')
    expect($('body').text()).toMatch(/Hello World/)
    expect($('body').text()).toMatch(/1000000000000/)
  })

  it('should render the cookies page', async () => {
    const $ = await next.render$('/ssr/cookies')
    expect($('#cookies').text()).toBe('{}')
  })

  it('should render the cookies page with cookies', async () => {
    const res = await next.fetch('/ssr/cookies', {
      headers: {
        Cookie: 'key=value;',
      },
    })
    const html = await res.text()
    expect(html).toContain(`{"key":"value"}`)
  })

  it('should render the generics page', async () => {
    const $ = await next.render$('/generics')
    expect($('#value').text()).toBe('Hello World from Generic')
  })

it('should render the angle bracket type assertions page', async () => {
    const $ = await next.render$('/angle-bracket-type-assertions')
    expect($('#value').text()).toBe('test')
  })

// Turbopack prefers `.ts`/`.tsx` over `.js`/`.jsx`, webpack prefers
`.js`/`.jsx`
  ;(isTurbopack ? it.skip : it)(
    'should resolve files in correct order',
    async () => {
      const $ = await next.render$('/hello')
      // eslint-disable-next-line jest/no-standalone-expect
      expect($('#imported-value').text()).toBe('OK')
    }
  )

  // old behavior:
  it.skip('should report type checking to stdout', () => {
    expect(next.cliOutput).toContain('waiting for typecheck results...')
  })

  it('should respond to sync API route correctly', async () => {
    const html = await next.render('/api/sync')
    const data = JSON.parse(html)
    expect(data).toEqual({ code: 'ok' })
  })

  it('should respond to async API route correctly', async () => {
    const html = await next.render('/api/async')
    const data = JSON.parse(html)
    expect(data).toEqual({ code: 'ok' })
  })

  if (isNextDev) {
it('should not fail to render when an inactive page has an error', async
() => {
      await next.patchFile(
        'pages/evil.tsx',
        `import React from 'react'

export default function EvilPage(): JSX.Element {
  return <div notARealProp />
}
`
      )
      try {
        const $ = await next.render$('/hello')
        expect($('body').text()).toMatch(/Hello World/)
      } finally {
        await next.deleteFile('pages/evil.tsx')
      }
    })
  }

  if (isNextStart) {
    it('should build the app successfully', async () => {
      expect(next.cliOutput).toMatch(/Compiled successfully/)
    })

    it('should not inform when using default tsconfig path', () => {
      expect(next.cliOutput).not.toMatch(/Using tsconfig file:/)
    })
  }
})`

</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/app-dir/app-config-crossorigin/index.test.ts` —
`describe('app dir - crossOrigin config', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742650),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742724).
-
`test/e2e/app-dir/webpack-loader-module-type/webpack-loader-module-type.test.ts`
— `describe('webpack-loader-module-type', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742634),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742671).
- `test/e2e/app-dir/with-babel/with-babel.test.ts` — `describe('with
babel', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742666),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742685).
- `test/e2e/jsconfig-baseurl/jsconfig-baseurl.test.ts` —
`describe('jsconfig.json baseurl', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742650),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742724).
- `test/e2e/swc-plugins/index.test.ts` — `describe('supports
swcPlugins', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742599),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742667).
- `test/e2e/transpile-packages-typescript-foreign/index.test.ts` —
`describe('with transpilePackages', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742666),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742685).
- `test/e2e/typescript/typescript.test.ts` — `describe('TypeScript
Features', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742666),
[cache](https://github.com/vercel/next.js/actions/runs/34426785087/job/102715742685).

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:06 -07:00
Jamiboy Mohammad f41cc4dba5 test: enable verified app-router deploy tests (#98524)
## Summary

Enable the same 33 previously selected deployment-test scopes across 32
app-router test files, now in a stack rooted on canary. Remove 33
`skipDeployment` options and their obsolete skip guards. Other mode,
bundler, middleware, and Cache Components exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (33)</summary>

- ID 78:
`test/e2e/app-dir/actions-allowed-origins/app-action-allowed-origins.test.ts`
— `describe('app-dir action allowed origins', () => {
  const { next } = nextTestSetup({
    files: join(__dirname, 'safe-origins'),
    dependencies: {
      'server-only': 'latest',
    },
    // An arbitrary & random port.
    forcedPort: 'random',
  })

it('should pass if localhost is set as a safe origin', async function ()
{
    const browser = await next.browser('/')

    await browser.elementByCss('button').click()

    await check(async () => {
      return await browser.elementByCss('#res').text()
    }, 'hi')
  })
})`
- ID 80:
`test/e2e/app-dir/actions-allowed-origins/app-action-opaque-origin.test.ts`
— `describe('app-dir action allowed from opaque origins', () => {
  const { next } = nextTestSetup({
    files: join(__dirname, 'opaque-origin'),
    env: {
      NEXT_TEST_ALLOW_OPAQUE_ORIGIN: '1',
    },
  })

  it('should succeed on submission', async function () {
    const browser = await next.browser('/sandboxed')

    await browser.elementByCss('input[type="submit"]').click()

    await retry(async () => {
      expect(await browser.elementByCss('output').text()).toEqual(
        'Action Invoked'
      )
    })
  })
})`
- ID 82: `test/e2e/app-dir/app-a11y/index.test.ts` — `describe('app a11y
features', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    packageJson: {},
  })

  describe('route announcer', () => {
    async function getAnnouncerContent(browser: Playwright) {
      return browser.eval(

`document.getElementsByTagName('next-route-announcer')[0]?.shadowRoot.childNodes[0]?.innerHTML`
      )
    }

    it('should not announce the initital title', async () => {
      const browser = await next.browser('/page-with-h1')
      await check(() => getAnnouncerContent(browser), '')
    })

    it('should announce document.title changes', async () => {
      const browser = await next.browser('/page-with-h1')
      await browser.elementById('page-with-title').click()
      await check(() => getAnnouncerContent(browser), 'page-with-title')
    })

    it('should announce h1 changes', async () => {
      const browser = await next.browser('/page-with-h1')
      await browser.elementById('noop-layout-page-1').click()
await check(() => getAnnouncerContent(browser), 'noop-layout/page-1')
    })

it('should announce route changes when h1 changes inside an inner
layout', async () => {
      const browser = await next.browser('/noop-layout/page-1')
      await browser.elementById('noop-layout-page-2').click()
await check(() => getAnnouncerContent(browser), 'noop-layout/page-2')
    })
  })
})`
- ID 84: `test/e2e/app-dir/app-rendering/rendering.test.ts` —
`describe('app dir rendering', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should serve app/page.server.js at /', async () => {
    const html = await next.render('/')
    expect(html).toContain('app/page.server.js')
  })

  describe('SSR only', () => {
    it('should run data in layout and page', async () => {
      const $ = await next.render$('/ssr-only/nested')
      expect($('#layout-message').text()).toBe('hello from layout')
      expect($('#page-message').text()).toBe('hello from page')
    })

    it('should run data fetch in parallel', async () => {
      const startTime = Date.now()
      const $ = await next.render$('/ssr-only/slow')
      const endTime = Date.now()
      const duration = endTime - startTime
      // Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness
don't fail the tests
      expect(duration).toBeLessThan(10_000)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
    })
  })

  describe('static only', () => {
    it('should run data in layout and page', async () => {
      const $ = await next.render$('/static-only/nested')
      expect($('#layout-message').text()).toBe('hello from layout')
      expect($('#page-message').text()).toBe('hello from page')
    })

    it(`should run data in parallel ${
isNextDev ? 'during development' : 'and use cached version for
production'
    }`, async () => {
      // const startTime = Date.now()
      const $ = await next.render$('/static-only/slow')
      // const endTime = Date.now()
      // const duration = endTime - startTime
      // Each part takes 5 seconds so it should be below 10 seconds
// Using 7 seconds to ensure external factors causing slight slowness
don't fail the tests
      // TODO: cache static props in prod
      // expect(duration < (isDev ? 7000 : 2000)).toBe(true)
      // expect(duration < 7000).toBe(true)
expect($('#slow-layout-message').text()).toBe('hello from slow layout')
expect($('#slow-page-message').text()).toBe('hello from slow page')
    })
  })

  describe('ISR', () => {
it('should revalidate the page when revalidate is configured', async ()
=> {
      const getPage = async () => {
        const res = await next.fetch('isr-multiple/nested')
        const html = await res.text()

        return {
          $: cheerio.load(html),
          cacheHeader: res.headers['x-nextjs-cache'],
        }
      }
      const { $ } = await getPage()
      expect($('#layout-message').text()).toBe('hello from layout')
      expect($('#page-message').text()).toBe('hello from page')

      const layoutNow = $('#layout-now').text()
      const pageNow = $('#page-now').text()

      await waitFor(2000)

      // TODO: implement
      // Trigger revalidate
      // const { cacheHeader: revalidateCacheHeader } = await getPage()
      // expect(revalidateCacheHeader).toBe('STALE')

      // TODO: implement
const { $: $revalidated /* cacheHeader: revalidatedCacheHeader */ } =
        await getPage()
      // expect(revalidatedCacheHeader).toBe('REVALIDATED')

      const layoutNowRevalidated = $revalidated('#layout-now').text()
      const pageNowRevalidated = $revalidated('#page-now').text()

// Expect that the `Date.now()` is different as the page have been
regenerated
      expect(layoutNow).not.toBe(layoutNowRevalidated)
      expect(pageNow).not.toBe(pageNowRevalidated)
    })
  })

  // TODO: implement
  describe.skip('mixed static and dynamic', () => {
it('should generate static data during build and use it', async () => {
      const getPage = async () => {
        const $ = await next.render$('isr-ssr-combined/nested')

        return {
          $,
        }
      }
      const { $ } = await getPage()
      expect($('#layout-message').text()).toBe('hello from layout')
      expect($('#page-message').text()).toBe('hello from page')

      const layoutNow = $('#layout-now').text()
      const pageNow = $('#page-now').text()

      const { $: $second } = await getPage()

      const layoutNowSecond = $second('#layout-now').text()
      const pageNowSecond = $second('#page-now').text()

// Expect that the `Date.now()` is different as it came from
getServerSideProps
      expect(layoutNow).not.toBe(layoutNowSecond)
// Expect that the `Date.now()` is the same as it came from
getStaticProps
      expect(pageNow).toBe(pageNowSecond)
    })
  })
})`
- ID 86: `test/e2e/app-dir/app-validation/validation.test.ts` —
`describe('app dir - validation', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should error when passing invalid router state tree', async () => {
    const stateTree1 = JSON.stringify(['', ''])
    const stateTree2 = JSON.stringify(['', {}])

    const headers1 = {
      rsc: '1',
      'next-router-state-tree': stateTree1,
    }

    const headers2 = {
      rsc: '1',
      'next-router-state-tree': stateTree2,
    }

    const url1 = new URL('/', 'http://localhost')
    const url2 = new URL('/', 'http://localhost')

    // Add cache busting search param for both requests
    const cacheBustingParam1 = await computeCacheBustingSearchParam(
      undefined,
      undefined,
      stateTree1,
      undefined
    )
    const cacheBustingParam2 = await computeCacheBustingSearchParam(
      undefined,
      undefined,
      stateTree2,
      undefined
    )

    if (cacheBustingParam1) {
      url1.searchParams.set('_rsc', cacheBustingParam1)
    }
    if (cacheBustingParam2) {
      url2.searchParams.set('_rsc', cacheBustingParam2)
    }

    const res = await next.fetch(url1.toString(), { headers: headers1 })
    expect(res.status).toBe(500)

const res2 = await next.fetch(url2.toString(), { headers: headers2 })
    expect(res2.status).toBe(200)
  })

it('should generate distinct cache-busting params for known colliding
RSC variants', async () => {
    const stateTree = '%5B%22%22%2C%7B%7D%5D'

    const fullRequestHash = await computeCacheBustingSearchParam(
      undefined,
      undefined,
      stateTree,
      undefined
    )
    const prefetchRequestHash = await computeCacheBustingSearchParam(
      '1',
      '/_tree',
      stateTree,
      '/pcsta0'
    )

    expect(fullRequestHash).toHaveLength(16)
    expect(prefetchRequestHash).toHaveLength(16)
    expect(fullRequestHash).not.toBe(prefetchRequestHash)
  })

it('should accept legacy cache-busting params on plain HTTP requests',
async () => {
    const stateTree = '%5B%22%22%2C%7B%7D%5D'
    const url = new URL('/', 'http://localhost')
    const headers = {
      rsc: '1',
      'next-router-state-tree': stateTree,
    }

    url.searchParams.set(
      '_rsc',
      computeLegacyCacheBustingSearchParam(
        undefined,
        undefined,
        stateTree,
        undefined
      )
    )

    const res = await next.fetch(url.toString(), {
      headers,
      redirect: 'manual',
    })

    expect(res.status).toBe(200)
  })
})`
- ID 87:
`test/e2e/app-dir/async-component-preload/async-component-preload.test.ts`
— `describe('async-component-preload', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should handle redirect in an async page', async () => {
    const browser = await next.browser('/')
expect(await
browser.waitForElementByCss('#success').text()).toBe('Success')
  })
})`
- ID 90:
`test/e2e/app-dir/client-reference-side-effects/client-reference-side-effects.test.ts`
— `describe('client-reference-side-effects', () => {
  const { next, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

  it('side effect behavior when only importing', async () => {
    const browser = await next.browser('/imported')

expect(await browser.elementByCss('body').text()).toContain('Server')

    let client = await browser.eval('window.client')
    let client_sideeffect_reexport = await browser.eval(
      'window.client_sideeffect_reexport'
    )
    let client_sideeffect_only = await browser.eval(
      'window.client_sideeffect_only'
    )

    // No client references are rendered, so nothing is executed.
    expect(client).toBeUndefined()
    expect(client_sideeffect_reexport).toBeUndefined()
    expect(client_sideeffect_only).toBeUndefined()
  })

  it('side effect behavior when rendering', async () => {
    const browser = await next.browser('/rendered')

    const body = await browser.elementByCss('body').text()
    expect(body).toContain('Server')
    expect(body).toContain('client component')

    let client = await browser.eval('window.client')
    let client_sideeffect_reexport = await browser.eval(
      'window.client_sideeffect_reexport'
    )
    let client_sideeffect_only = await browser.eval(
      'window.client_sideeffect_only'
    )

    expect(client).toBeTrue()
    expect(client_sideeffect_reexport).toBeTrue()
    if (isTurbopack) {
      expect(client_sideeffect_only).toBeUndefined()
    } else {
// Webpack eagerly initializes all client reference modules once at
least one of them is
      // rendered.
      expect(client_sideeffect_only).toBeTrue()
    }
  })
})`
- ID 92:
`test/e2e/app-dir/duplicate-layout-components/duplicate-layout-components.test.ts`
— `describe('app dir - duplicate layout components', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should not duplicate layout elements when navigating to 404', async
() => {
    const browser = await next.browser('/solutions/404')

    // Verify counts haven't changed - no duplication
    expect((await browser.elementsByCss('body')).length).toBe(1)
    expect((await browser.elementsByCss('#header')).length).toBe(1)
    expect((await browser.elementsByCss('#footer')).length).toBe(1)
  })
})`
- ID 93: `test/e2e/app-dir/dynamic-data/dynamic-data.test.ts` —
`describe('dynamic-data', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname + '/fixtures/main',
  })

it('should render the dynamic apis dynamically when used in a top-level
scope', async () => {
    const $ = await next.render$(
      '/top-level?foo=foosearch',
      {},
      {
        headers: {
          fooheader: 'foo header value',
          cookie: 'foocookie=foo cookie value',
        },
      }
    )
    if (isNextDev) {
      // in dev we expect the entire page to be rendered at runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else if (process.env.__NEXT_CACHE_COMPONENTS) {
// in PPR we expect the shell to be rendered at build and the page to be
rendered at runtime
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
// in static generation we expect the entire page to be rendered at
runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    }

    expect($('#headers .fooheader').text()).toBe('foo header value')
    expect($('#cookies .foocookie').text()).toBe('foo cookie value')
    expect($('#searchparams .foo').text()).toBe('foosearch')
  })

it('should render the dynamic apis dynamically when used in a top-level
scope with force dynamic', async () => {
    const $ = await next.render$(
      '/force-dynamic?foo=foosearch',
      {},
      {
        headers: {
          fooheader: 'foo header value',
          cookie: 'foocookie=foo cookie value',
        },
      }
    )
    if (isNextDev) {
      // in dev we expect the entire page to be rendered at runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else if (process.env.__NEXT_CACHE_COMPONENTS) {
// @TODO this should actually be build but there is a bug in how we do
segment level dynamic in PPR at the moment
      // see note in create-component-tree
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
// in static generation we expect the entire page to be rendered at
runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    }

    expect($('#headers .fooheader').text()).toBe('foo header value')
    expect($('#cookies .foocookie').text()).toBe('foo cookie value')
    expect($('#searchparams .foo').text()).toBe('foosearch')
  })

it('should render empty objects for dynamic APIs when rendering with
force-static', async () => {
    const $ = await next.render$(
      '/force-static?foo=foosearch',
      {},
      {
        headers: {
          fooheader: 'foo header value',
          cookie: 'foocookie=foo cookie value',
        },
      }
    )
    if (isNextDev) {
      // in dev we expect the entire page to be rendered at runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else if (process.env.__NEXT_CACHE_COMPONENTS) {
// in PPR we expect the shell to be rendered at build and the page to be
rendered at runtime
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      // we expect there to be a suspense boundary in fallback state
      expect($('#boundary').html()).toBeNull()
    } else {
// in static generation we expect the entire page to be rendered at
runtime
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      // we expect there to be no suspense boundary in fallback state
      expect($('#boundary').html()).toBeNull()
    }

    expect($('#headers .fooheader').html()).toBeNull()
    expect($('#cookies .foocookie').html()).toBeNull()
    expect($('#searchparams .foo').html()).toBeNull()
  })

it('should track searchParams access as dynamic when the Page is a
client component', async () => {
    const $ = await next.render$(
      '/client-page?foo=foosearch',
      {},
      {
        headers: {
          fooheader: 'foo header value',
          cookie: 'foocookie=foo cookie value',
        },
      }
    )
    if (isNextDev) {
      // in dev we expect the entire page to be rendered at runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
// we don't assert the state of the fallback because it can depend on
the timing
// of when streaming starts and how fast the client references resolve
    } else if (process.env.__NEXT_CACHE_COMPONENTS) {
// in PPR we expect the shell to be rendered at build and the page to be
rendered at runtime
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at runtime')
      // we expect there to be a suspense boundary in fallback state
      expect($('#boundary').html()).not.toBeNull()
    } else {
// in static generation we expect the entire page to be rendered at
runtime
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
// we don't assert the state of the fallback because it can depend on
the timing
// of when streaming starts and how fast the client references resolve
    }

    expect($('#searchparams .foo').text()).toBe('foosearch')
  })

  if (!isNextDev) {
it('should track dynamic apis when rendering app routes', async () => {
      expect(next.cliOutput).toContain(
`Caught Error: Dynamic server usage: Route /routes/url couldn't be
rendered statically because it used \`request.url\`.`
      )
      expect(next.cliOutput).toContain(
`Caught Error: Dynamic server usage: Route /routes/next-url couldn't be
rendered statically because it used \`nextUrl.toString\`.`
      )
    })
  }
})`
- ID 94: `test/e2e/app-dir/dynamic-href/dynamic-href.test.ts` —
`describe('dynamic-href', () => {
  const { isNextDev: isDev, next } = nextTestSetup({
    files: __dirname,
  })

  if (isDev) {
it('should error when using dynamic href.pathname in app dir', async ()
=> {
      const browser = await next.browser('/object')

      await expect(browser).toDisplayRedbox(`
       {
"description": "Dynamic href \`/object/[slug]\` found in <Link> while
using the \`/app\` router, this is not supported. Read more:
https://nextjs.org/docs/messages/app-dir-dynamic-href",
         "environmentLabel": null,
         "label": "Runtime Error",
         "source": "app/object/page.js (5:5) @ HomePage
       > 5 |     <Link
           |     ^",
         "stack": [
           "HomePage app/object/page.js (5:5)",
         ],
       }
      `)

      // Fix error
      const pageContent = await next.readFile('app/object/page.js')
      await next.patchFile(
        'app/object/page.js',
        pageContent.replace(
          "pathname: '/object/[slug]'",
          "pathname: '/object/slug'"
        )
      )
expect(await browser.waitForElementByCss('#link').text()).toBe('to
slug')

      // Navigate to new page
      await browser.elementByCss('#link').click()
expect(await browser.waitForElementByCss('#pathname').text()).toBe(
        '/object/slug'
      )
      expect(await browser.elementByCss('#slug').text()).toBe('1')
    })

    it('should error when using dynamic href in app dir', async () => {
      const browser = await next.browser('/string')

      await expect(browser).toDisplayRedbox(`
       {
"description": "Dynamic href \`/object/[slug]\` found in <Link> while
using the \`/app\` router, this is not supported. Read more:
https://nextjs.org/docs/messages/app-dir-dynamic-href",
         "environmentLabel": null,
         "label": "Runtime Error",
         "source": "app/string/page.js (5:5) @ HomePage
       > 5 |     <Link id="link" href="/object/[slug]">
           |     ^",
         "stack": [
           "HomePage app/string/page.js (5:5)",
         ],
       }
      `)
    })
  } else {
    it('should not error on /object in prod', async () => {
      const browser = await next.browser('/object')
      expect(await browser.elementByCss('#link').text()).toBe('to slug')
    })
    it('should not error on /string in prod', async () => {
      const browser = await next.browser('/string')
      expect(await browser.elementByCss('#link').text()).toBe('to slug')
    })
  }
})`
- ID 95:
`test/e2e/app-dir/dynamic-import-tree-shaking/dynamic-import-tree-shaking.test.ts`
— `describe('dynamic-import-tree-shaking', () => {
  const { next, isNextStart, isTurbopack } = nextTestSetup({
    files: __dirname,
  })

  // Recursively read all .js files in a directory
  function getAllServerFiles(dir: string): string[] {
    const results: string[] = []
    try {
      const entries = fs.readdirSync(dir, { withFileTypes: true })
      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name)
        if (entry.isDirectory()) {
          results.push(...getAllServerFiles(fullPath))
        } else if (entry.name.endsWith('.js')) {
          results.push(fullPath)
        }
      }
    } catch {
      // directory doesn't exist
    }
    return results
  }

  async function getAllServerContent(): Promise<string> {
    const serverDir = path.join(next.testDir, '.next/server')
    const files = getAllServerFiles(serverDir)
    const contents = await Promise.all(
      files.map((f) => fs.promises.readFile(f, 'utf8'))
    )
    return contents.join('\n')
  }

// Verify that each page renders correctly (these should always pass in
both dev and production)
  it('should render const destructure page', async () => {
    const $ = await next.render$('/const-destructure')
    expect($('div').text()).toContain('TREESHAKE_CONST_USED')
  })

  it('should render var destructure page', async () => {
    const $ = await next.render$('/var-destructure')
    expect($('div').text()).toContain('TREESHAKE_VAR_USED')
  })

  it('should render let destructure page', async () => {
    const $ = await next.render$('/let-destructure')
    expect($('div').text()).toContain('TREESHAKE_LET_USED')
  })

  it('should render rename destructure page', async () => {
    const $ = await next.render$('/rename-destructure')
    expect($('div').text()).toContain('TREESHAKE_RENAME_USED')
  })

  it('should render nested destructure page', async () => {
    const $ = await next.render$('/nested-destructure')
    expect($('div').text()).toContain('TREESHAKE_NESTED_USED')
  })

  it('should render default destructure page', async () => {
    const $ = await next.render$('/default-destructure')
    expect($('div').text()).toContain('TREESHAKE_DEFAULT_USED')
  })

  it('should render empty destructure page', async () => {
    const $ = await next.render$('/empty-destructure')
    expect($('div').text()).toContain('TREESHAKE_EMPTY_PAGE')
  })

  it('should render member access page', async () => {
    const $ = await next.render$('/member-access')
    expect($('div').text()).toContain('TREESHAKE_MEMBER_USED')
  })

  it('should render webpack-exports-comment page', async () => {
    const $ = await next.render$('/webpack-exports-comment')
    expect($('div').text()).toContain('TREESHAKE_COMMENT_USED')
  })

  it('should render rest destructure page', async () => {
    const $ = await next.render$('/rest-destructure')
    expect($('div').text()).toContain('TREESHAKE_REST_USED')
  })

  it('should render multiple imports page', async () => {
    const $ = await next.render$('/multiple-imports')
    expect($('div').text()).toContain('TREESHAKE_MULTI_A_USED')
    expect($('div').text()).toContain('TREESHAKE_MULTI_B_USED')
  })

  it('should render reassign page', async () => {
    const $ = await next.render$('/reassign')
    expect($('div').text()).toContain('TREESHAKE_REASSIGN_USED')
  })

  it('should render then-arrow-destructure page', async () => {
    const $ = await next.render$('/then-arrow-destructure')
    expect($('div').text()).toContain('TREESHAKE_THEN_ARROW_USED')
  })

  it('should render then-function-destructure page', async () => {
    const $ = await next.render$('/then-function-destructure')
    expect($('div').text()).toContain('TREESHAKE_THEN_FUNC_USED')
  })

// Tree shaking assertions: unused exports should NOT be in the server
bundle
// Tree shaking is only enabled in production builds, so skip these in
dev mode
  if (isNextStart) {
it('should tree-shake unused export with const destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_CONST_USED')
      expect(content).not.toContain('TREESHAKE_CONST_UNUSED')
    })

it('should tree-shake unused export with var destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_VAR_USED')
      expect(content).not.toContain('TREESHAKE_VAR_UNUSED')
    })

it('should tree-shake unused export with let destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_LET_USED')
      expect(content).not.toContain('TREESHAKE_LET_UNUSED')
    })

it('should tree-shake unused export with renamed destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_RENAME_USED')
      expect(content).not.toContain('TREESHAKE_RENAME_UNUSED')
    })

it('should tree-shake unused export with nested destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_NESTED_USED')
      expect(content).not.toContain('TREESHAKE_NESTED_UNUSED')
    })

it('should tree-shake unused export with default destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_DEFAULT_USED')
      expect(content).not.toContain('TREESHAKE_DEFAULT_UNUSED')
    })

it('should tree-shake all exports with empty destructured dynamic
import', async () => {
      const content = await getAllServerContent()
      // Side effects should still be included
      expect(content).toContain('TREESHAKE_EMPTY_SIDE_EFFECT')
      // But no exports should be included
      expect(content).not.toContain('TREESHAKE_EMPTY_USED')
      expect(content).not.toContain('TREESHAKE_EMPTY_UNUSED')
    })

it('should tree-shake unused export with webpackExports comment', async
() => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_COMMENT_USED')
      expect(content).not.toContain('TREESHAKE_COMMENT_UNUSED')
    })

// Member access on dynamic import is only tree-shaken by Turbopack, not
webpack
    if (isTurbopack) {
it('should tree-shake unused export with member access on dynamic
import', async () => {
        const content = await getAllServerContent()
        expect(content).toContain('TREESHAKE_MEMBER_USED')
        expect(content).not.toContain('TREESHAKE_MEMBER_UNUSED')
      })
    }

it('should NOT tree-shake with rest destructured dynamic import', async
() => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_REST_USED')
// rest elements prevent tree-shaking, so unused exports should still be
present
      expect(content).toContain('TREESHAKE_REST_UNUSED')
    })

it('should tree-shake unused exports with multiple dynamic imports in
one file', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_MULTI_A_USED')
      expect(content).not.toContain('TREESHAKE_MULTI_A_UNUSED')
      expect(content).toContain('TREESHAKE_MULTI_B_USED')
      expect(content).not.toContain('TREESHAKE_MULTI_B_UNUSED')
    })

it('should NOT tree-shake with reassigned dynamic import', async () => {
      const content = await getAllServerContent()
      expect(content).toContain('TREESHAKE_REASSIGN_USED')
// re-assignment prevents destructuring analysis, so unused exports
should remain
      expect(content).toContain('TREESHAKE_REASSIGN_UNUSED')
    })

// .then() callback destructuring is only tree-shaken by Turbopack, not
webpack
    if (isTurbopack) {
it('should tree-shake unused export with .then() arrow destructured
dynamic import', async () => {
        const content = await getAllServerContent()
        expect(content).toContain('TREESHAKE_THEN_ARROW_USED')
        expect(content).not.toContain('TREESHAKE_THEN_ARROW_UNUSED')
      })

it('should tree-shake unused export with .then() function destructured
dynamic import', async () => {
        const content = await getAllServerContent()
        expect(content).toContain('TREESHAKE_THEN_FUNC_USED')
        expect(content).not.toContain('TREESHAKE_THEN_FUNC_UNUSED')
      })
    }
  }
})`
- ID 96: `test/e2e/app-dir/dynamic-in-generate-params/index.test.ts` —
`describe('app-dir - dynamic in generate params', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render sitemap with generateSitemaps in force-dynamic config
dynamically', async () => {
    const firstTime = await getLastModifiedTime(next, 'sitemap/0.xml')
    const secondTime = await getLastModifiedTime(next, 'sitemap/0.xml')

    expect(firstTime).not.toEqual(secondTime)
  })

it('should be able to call while generating multiple dynamic sitemaps',
async () => {
    const res0 = await next.fetch('sitemap/0.xml')
    const res1 = await next.fetch('sitemap/1.xml')
    assertSitemapResponse(res0)
    assertSitemapResponse(res1)
  })

it('should be able to call fetch while generating multiple dynamic
pages', async () => {
    const pageRes0 = await next.fetch('dynamic/0')
    const pageRes1 = await next.fetch('dynamic/1')
    expect(pageRes0.status).toBe(200)
    expect(pageRes1.status).toBe(200)
  })
})`
- ID 97: `test/e2e/app-dir/dynamic/dynamic.test.ts` — `describe('app dir
- next/dynamic', () => {
  const { next, isNextStart, isNextDev } = nextTestSetup({
    files: __dirname,
  })

it('should handle ssr: false in pages when appDir is enabled', async ()
=> {
    const $ = await next.render$('/legacy/no-ssr')
    expect($.html()).not.toContain('navigator')

    const browser = await next.browser('/legacy/no-ssr')
expect(await
browser.waitForElementByCss('#pure-client').text()).toContain(
      'navigator'
    )
  })

  it('should handle next/dynamic in SSR correctly', async () => {
    const $ = await next.render$('/dynamic')
    // filter out the script
    const selector = 'body div'
    const serverContent = $(selector).text()
// should load chunks generated via async import correctly with
React.lazy
    expect(serverContent).toContain('next-dynamic lazy')
    // should support `dynamic` in both server and client components
    expect(serverContent).toContain('next-dynamic dynamic on server')
    expect(serverContent).toContain('next-dynamic dynamic on client')
    expect(serverContent).toContain('next-dynamic server import client')
expect(serverContent).not.toContain('next-dynamic dynamic no ssr on
client')
  })

  it('should handle next/dynamic in hydration correctly', async () => {
    const browser = await next.browser('/dynamic')
    await browser.waitForElementByCss('#css-text-dynamic-no-ssr-client')

    expect(
await browser.elementByCss('#css-text-dynamic-no-ssr-client').text()
    ).toBe('next-dynamic dynamic no ssr on client:suffix')
  })

it('should generate correct client manifest for dynamic chunks', async
() => {
    const $ = await next.render$('/chunk-loading/server')
    expect($('h1').text()).toBe('hello')
  })

it('should render loading by default if loading is specified and loader
is slow', async () => {
    const $ = await next.render$('/default-loading')

// First render in dev should show loading, production build will
resolve the content.
    expect($('body').text()).toContain(
isNextDev ? 'Loading...' : 'This is a dynamically imported component'
    )
  })

  it('should not render loading by default', async () => {
    const $ = await next.render$('/default')
    expect($('#dynamic-component').text()).not.toContain('loading')
  })

  it('should ignore next/dynamic in routes', async () => {
    const response = await next.fetch('/api')
    expect(await response.text()).toEqual('Hello function')
  })

  it('should ignore next/dynamic in sitemap', async () => {
    const response = await next.fetch('/sitemap.xml')
expect(await
response.text()).toInclude('<changefreq>yearly</changefreq>')
  })

  if (isNextDev) {
it('should directly raise error when dynamic component error on server',
async () => {
      const pagePath = 'app/default-loading/dynamic-component.js'
      const page = await next.readFile(pagePath)
      await next.patchFile(
        pagePath,
page.replace('const isDevTest = false', 'const isDevTest = true')
      )
      await retry(async () => {
        const { status } = await next.fetch('/default-loading')
        expect(status).toBe(200)
      })
    })
  }

  describe('no SSR', () => {
it('should not render client component imported through ssr: false in
client components in edge runtime', async () => {
      // noSSR should not show up in html
const $ = await next.render$('/dynamic-mixed-ssr-false/client-edge')
      expect($('#server-false-client-module')).not.toContain(
        'ssr-false-client-module-text'
      )
      // noSSR should not show up in browser
const browser = await
next.browser('/dynamic-mixed-ssr-false/client-edge')
      expect(
        await browser.elementByCss('#ssr-false-client-module').text()
      ).toBe('ssr-false-client-module-text')

// in the server bundle should not contain client component imported
through ssr: false
      if (isNextStart) {
        const middlewareManifest = JSON.parse(
          await next.readFile('.next/server/middleware-manifest.json')
        )

        const uniquePageFiles = [
          ...new Set<string>(
            middlewareManifest.functions[
              '/dynamic-mixed-ssr-false/client-edge/page'
            ].files
          ),
        ]

        for (const file of uniquePageFiles) {
          const contents = await next.readFile(path.join('.next', file))
          expect(contents).not.toContain('ssr-false-client-module-text')
        }
      }
    })

it('should not render client component imported through ssr: false in
client components', async () => {
      // noSSR should not show up in html
      const $ = await next.render$('/dynamic-mixed-ssr-false/client')
      expect($('#client-false-client-module')).not.toContain(
        'ssr-false-client-module-text'
      )
      // noSSR should not show up in browser
const browser = await next.browser('/dynamic-mixed-ssr-false/client')
      expect(
        await browser.elementByCss('#ssr-false-client-module').text()
      ).toBe('ssr-false-client-module-text')

// in the server bundle should not contain both server and client
component imported through ssr: false
      if (isNextStart) {
        const pageServerChunk = await next.readFile(
          '.next/server/app/dynamic-mixed-ssr-false/client/page.js'
        )
expect(pageServerChunk).not.toContain('ssr-false-client-module-text')
      }
    })

it('should support dynamic import with accessing named exports from
client component', async () => {
      const $ = await next.render$('/dynamic/named-export')
      expect($('#client-button').text()).toBe('this is a client button')
    })

it('should support dynamic import with TLA in client components', async
() => {
      const $ = await next.render$('/dynamic/async-client')
      expect($('#client-button').text()).toBe(
        'this is an async client button with SSR'
      )
      expect($('#client-button-no-ssr').text()).toBe('')

      const browser = await next.browser('/dynamic/async-client')
      expect(await browser.elementByCss('#client-button').text()).toBe(
        'this is an async client button with SSR'
      )
expect(await browser.elementByCss('#client-button-no-ssr').text()).toBe(
        'this is an async client button'
      )
    })
  })
})`
- ID 101: `test/e2e/app-dir/forbidden/default/forbidden-default.test.ts`
— `describe('app dir - forbidden with default forbidden boundary', () =>
{
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  // TODO: error forbidden usage in root layout
it.skip('should error on client forbidden from root layout in browser',
async () => {
    const browser = await next.browser('/')

    await browser.elementByCss('#trigger-forbidden').click()

    if (isNextDev) {
      await waitForRedbox(browser)
      expect(await getRedboxDescription(browser)).toMatch(
        /forbidden\(\) is not allowed to use in root layout/
      )
    }
  })

  // TODO: error forbidden usage in root layout
it.skip('should error on server forbidden from root layout on
server-side', async () => {
    const browser = await next.browser('/?root-forbidden=1')

    if (isNextDev) {
      await waitForRedbox(browser)
      expect(await getRedboxDescription(browser)).toBe(
        'Error: forbidden() is not allowed to use in root layout'
      )
    }
  })

it('should be able to navigate to page calling forbidden', async () => {
    const browser = await next.browser('/')

    await browser.elementByCss('#navigate-forbidden').click()
    await browser.waitForElementByCss('.next-error-h1')

    expect(await browser.elementByCss('h1').text()).toBe('403')
    expect(await browser.elementByCss('h2').text()).toBe(
      'This page could not be accessed.'
    )
  })

it('should be able to navigate to page with calling forbidden in
metadata', async () => {
    const browser = await next.browser('/')

    await browser.elementByCss('#metadata-layout-forbidden').click()
    await browser.waitForElementByCss('.next-error-h1')

    expect(await browser.elementByCss('h1').text()).toBe('403')
    expect(await browser.elementByCss('h2').text()).toBe(
      'This page could not be accessed.'
    )
  })

it('should render default forbidden for group routes if forbidden is not
defined', async () => {
    const browser = await next.browser('/group-dynamic/123')
    expect(await browser.elementByCss('#page').text()).toBe(
      'group-dynamic [id]'
    )

    await browser.loadPage(next.url + '/group-dynamic/403')
    await waitForNoRedbox(browser)
    await browser.waitForElementByCss('.group-root-layout')
expect(await browser.elementByCss('.next-error-h1').text()).toBe('403')
  })
})`
- ID 102: `test/e2e/app-dir/global-error/catch-all/index.test.ts` —
`describe('app dir - global error - with catch-all route', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should render catch-all route correctly', async () => {
    expect(await next.render('/en/foo')).toContain('catch-all page')
  })

  it('should render 404 page correctly', async () => {
expect(await next.render('/en')).toContain('This page could not be
found.')
  })

  it('should render global error correctly', async () => {
    const browser = await next.browser('/en/error')

    const text = await browser.elementByCss('#global-error').text()
    expect(text).toMatchInlineSnapshot(`"global-error"`)
  })
})`
- ID 103: `test/e2e/app-dir/global-error/layout-error/index.test.ts` —
`describe('app dir - global error - layout error', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

it('should render global error for error in server components', async ()
=> {
    const browser = await next.browser('/')

    if (isNextDev) {
      await expect(browser).toDisplayRedbox(`
       {
         "description": "layout error",
         "environmentLabel": "Server",
         "label": "Runtime Error",
         "source": "app/layout.js (2:9) @ layout
       > 2 |   throw new Error('layout error')
           |         ^",
         "stack": [
           "layout app/layout.js (2:9)",
         ],
       }
      `)
    }

    expect(await browser.elementByCss('h1').text()).toBe('Global Error')
    expect(await browser.elementByCss('#error').text()).toBe(
      isNextDev
        ? 'Global error: layout error'
: 'Global error: Minified React error #441; visit
https://react.dev/errors/441 for the full message or use the
non-minified dev environment for full errors and additional helpful
warnings.'
    )
    expect(await browser.elementByCss('#digest').text()).toMatch(/\w+/)
  })
})`
- ID 114: `test/e2e/app-dir/io/io.test.ts` — `describe('io with cache
components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname + '/fixtures/cache-components',
  })

it('should make content after io() dynamic during prerender', async ()
=> {
    const $ = await next.render$('/io-boundary')
    if (isNextDev) {
      // In dev mode everything renders at runtime
      expect($('#before').text()).toBe('at runtime')
      expect($('#after-io').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      // In production with cache components, io() creates a dynamic
      // boundary. Content in the static shell is rendered at buildtime.
      // Content after io() is rendered at request time because the
// hanging promise prevented it from executing during the build
prerender.
      expect($('#before').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#after-io').text()).toBe('at runtime')
    }
  })

it('should resolve immediately inside a "use cache" scope', async () =>
{
    const $ = await next.render$('/io-in-cache')
    if (isNextDev) {
      expect($('#cached-value').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      // io() inside "use cache" is a no-op so the cached value is
      // computed at cache-fill time during the build
      expect($('#cached-value').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should work in pages router with getServerSideProps (CC)', async ()
=> {
    const $ = await next.render$('/pages-gssp')
    expect($('#pages-content').text()).toBe('ok')
  })

it('should work in pages router with getStaticProps (CC)', async () => {
    const $ = await next.render$('/pages-gsp')
    expect($('#pages-content').text()).toBe('ok')
  })

  it('should work in pages router with React.use() (CC)', async () => {
    const $ = await next.render$('/pages-use')
    expect($('#pages-content').text()).toBe('ok')
  })
})`
- ID 115: `test/e2e/app-dir/io/io.test.ts` — `describe('io without cache
components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname + '/fixtures/default',
  })

it('should be a no-op during prerender without cache components', async
() => {
    const $ = await next.render$('/io-boundary')
    if (isNextDev) {
      expect($('#before').text()).toBe('at runtime')
      expect($('#after-io').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      // Without cache components, io() resolves immediately during
      // prerendering so the entire page is fully static
      expect($('#before').text()).toBe('at buildtime')
      expect($('#after-io').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should work in pages router with getServerSideProps', async () => {
    const $ = await next.render$('/pages-gssp')
    expect($('#pages-content').text()).toBe('ok')
  })

  it('should work in pages router with getStaticProps', async () => {
    const $ = await next.render$('/pages-gsp')
    expect($('#pages-content').text()).toBe('ok')
  })

  it('should work in pages router with React.use()', async () => {
    const $ = await next.render$('/pages-use')
    expect($('#pages-content').text()).toBe('ok')
  })
})`
- ID 116: `test/e2e/app-dir/metadata-json-manifest/index.test.ts` —
`describe('app-dir metadata-json-manifest', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should support metadata.json manifest', async () => {
    const response = await next.fetch('/manifest.json')
    expect(response.status).toBe(200)
    const json = await response.json()
    expect(json).toEqual({
      name: 'My Next.js Application',
      short_name: 'Next.js App',
      description: 'An application built with Next.js',
      start_url: '/',
    })
  })
})`
- ID 117: `test/e2e/app-dir/metadata-suspense/index.test.ts` —
`describe('app dir - metadata dynamic routes suspense', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render metadata in head when root layout is wrapped with
Suspense for bot requests', async () => {
    const $ = await next.render$('/', undefined, {
      headers: {
        'User-Agent': 'Discordbot/2.0;',
      },
    })
    expect($('head title').text()).toBe('My title')
expect($('head meta[name="application-name"]').attr('content')).toBe(
      'suspense-app'
    )

    // unique title
    expect($('title').length).toBe(1)
  })
})`
- ID 119:
`test/e2e/app-dir/metadata-warnings/metadata-warnings-with-metadatabase.test.ts`
— `describe('app dir - metadata missing metadataBase', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
    overrideFiles: {
      'app/layout.js': `
        export default function Layout({ children }) {
          return (
            <div>
              {children}
            </div>
          )
        }
        
        export const metadata = {
          metadataBase: new URL('https://example.com'),
        }
      `,
    },
  })

// If it's start mode, we get the whole logs since they're from build
process.
  // If it's development mode, we get the logs after request
  function getCliOutput(logStartPosition: number) {
return isNextDev ? next.cliOutput.slice(logStartPosition) :
next.cliOutput
  }

it('should not show warning in output in default build output mode',
async () => {
    const logStartPosition = next.cliOutput.length
    await next.fetch('/og-image-convention')
    const output = getCliOutput(logStartPosition)

    expect(output).not.toInclude(METADATA_BASE_WARN_STRING)
  })

it('should not warn metadataBase is missing and a relative URL is used',
async () => {
    const logStartPosition = next.cliOutput.length
    await next.fetch('/relative-url-og')
    const output = getCliOutput(logStartPosition)

    expect(output).not.toInclude(METADATA_BASE_WARN_STRING)
  })

  it('should warn for unsupported metadata properties', async () => {
    const logStartPosition = next.cliOutput.length
    await next.fetch('/unsupported-metadata')
    const output = getCliOutput(logStartPosition)
    expect(output).toInclude(
'Unsupported metadata themeColor is configured in metadata export in
/unsupported-metadata. Please move it to viewport'
    )
    expect(output).toInclude(
'Read more:
https://nextjs.org/docs/app/api-reference/functions/generate-viewport'
    )
  })

it('should not warn for viewport properties during manually merging
metadata', async () => {
    const outputLength = next.cliOutput.length
    await next.fetch('/merge')
// Should not log the unsupported metadata viewport warning in the
output
    // during merging the metadata, if the value is still nullable.
    const output = next.cliOutput.slice(outputLength)
    expect(output).not.toContain('Unsupported metadata viewport')
  })

it('should warn for deprecated fields in other property', async () => {
    const logStartPosition = next.cliOutput.length
    await next.fetch('/deprecated-other-fields')
    const output = getCliOutput(logStartPosition)
    expect(output).toInclude('Use appleWebApp instead')
    expect(output).toInclude('Use icons.apple instead')
  })
})`
- ID 125:
`test/e2e/app-dir/not-found-with-layout-and-group-not-found/index.test.ts`
— `describe('app dir - not found with nested layouts and custom
not-found', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render the custom not-found page when notFound() is thrown
from a page within the group', async () => {
    const browser = await next.browser('/')
    await waitForNoRedbox(browser)
    const heading = await browser.elementByCss('h1#not-found-heading')
    expect(await heading.text()).toBe('Group Not Found Page')
  })
})`
- ID 126: `test/e2e/app-dir/not-found-with-nested-layouts/index.test.ts`
— `describe('app dir - not found with nested layouts', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render the custom not-found page when notFound() is thrown
from a page', async () => {
    const browser = await next.browser('/')
    await waitForNoRedbox(browser)
    const heading = await browser.elementByCss('h1#not-found-heading')
    expect(await heading.text()).toBe('Custom Not Found Page')
  })
})`
- ID 129: `test/e2e/app-dir/not-found/default/default.test.ts` —
`describe('app dir - not-found - default', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  it('should has noindex in the head html', async () => {
    const $ = await next.render$('/does-not-exist')
expect(await $('meta[name="robots"]').attr('content')).toBe('noindex')
  })

  if (isNextStart) {
    it('should contain noindex contain in the page', async () => {
const html = await next.readFile('.next/server/app/_not-found.html')
      const rsc = isPPREnabled
        ? 'noindex'
        : await next.readFile(`.next/server/app/_not-found.rsc`)

      expect(html).toContain('noindex')
      expect(rsc).toContain('noindex')
    })
  }
})`
- ID 130:
`test/e2e/app-dir/not-found/group-route-root-not-found/index.test.ts` —
`describe('app dir - group routes with root not-found', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should render default 404 with root layout for non-existent page',
async () => {
    const browser = await next.browser('/non-existent')
expect(await browser.elementByCss('p').text()).toBe('Not found
placeholder')
    expect(await browser.elementByCss('h1').text()).toBe('Root layout')
  })

it('should render root not found for group routes if hit 404', async ()
=> {
    const browser = await next.browser('/group-dynamic/123')
expect(await browser.elementByCss('p').text()).toBe('group-dynamic
[id]')

    await browser.loadPage(next.url + '/group-dynamic/404')
expect(await browser.elementByCss('p').text()).toBe('Not found
placeholder')
    expect(await browser.elementByCss('h1').text()).toBe('Root layout')
  })
})`
- ID 132:
`test/e2e/app-dir/parallel-routes-and-interception-nested-dynamic-routes/parallel-routes-and-interception-nested-dynamic-routes.test.ts`
— `describe('parallel-routes-and-interception-nested-dynamic-routes', ()
=> {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should intercept the route for nested dynamic routes', async () => {
    const browser = await next.browser('/1/1')
expect(await browser.elementByCss('h1').text()).toBe('foo id 1, bar id
1')
    await browser.elementByCss('a').click()

    // Should intercept the route.
expect(await
browser.waitForElementByCss('p').text()).toBe('intercepted!')
    // Should preserve the previous component.
expect(await browser.elementByCss('h1').text()).toBe('foo id 1, bar id
1')

    await browser.refresh()
    // Should display the correct /baz_id/1 content.
expect(await browser.waitForElementByCss('p').text()).toBe('baz_id/1')
  })
})`
- ID 133:
`test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts`
— `describe('parallel-routes-and-interception-conflicting-pages', () =>
{
  const { next } = nextTestSetup({
    files: {
      app: new FileRef(path.join(__dirname, 'app')),
      'app/parallel/nested-2/page.js': `
       export default function Page() {
          return 'hello world'
       }
      `,
    },
    nextConfig,
  })

it('should gracefully handle when two page segments match the `children`
parallel slot', async () => {
    const html = await next.render('/parallel/nested-2')

// before adding this file, the page would have matched
`/app/parallel/(new)/@baz/nested-2/page`
// but we've added a more specific page, so it should match that instead
    if (process.env.IS_TURBOPACK_TEST) {
// TODO: this matches differently in Turbopack because the Webpack
loader does some sorting on the paths
// Investigate the discrepancy in a follow-up. For now, since no errors
are being thrown (and since this test was previously ignored in
Turbopack),
// we'll just verify that the page is rendered and some content was
matched.
      expect(html).toContain('parallel/(new)/@baz/nested/page')
    } else {
      expect(html).toContain('hello world')
    }
  })
})`
- ID 134:
`test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts`
— `describe('parallel-routes-and-interception', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  // TODO: revisit the error for missing parallel routes slot
it('should not render the @children slot when the @slot is not found',
async () => {
    const browser = await next.browser('/')
    // we make sure the page is available through navigating
    expect(await browser.elementByCss('body').text()).toMatch(
      /This page could not be found/
    )

    // we also check that the #children-slot id is not present
expect(await
browser.hasElementByCssSelector('#children-slot')).toBe(false)
    await retry(async () => {
      const title = await browser.eval(() => {
        return document.title
      })

// TODO: the fact that the title on the client (in hydration data)
disagrees with the title SSRd
// when cache components is off is a sign we don't have coherent
handling of notFound titles
// This test now asserts the prod client title in next start that would
actually be observed
      // by site visitors post hydration.
      expect(title).toBe('404: This page could not be found.')
    })
  })

it('should render the title once for the non-existed route', async () =>
{
    const browser = await next.browser('/non-existed')
    const titles = await browser.elementsByCss('title')

// FIXME: (metadata), the title should only be rendered once and using
the not-found title
    expect(titles).toHaveLength(3)
  })
})`
- ID 137: `test/e2e/app-dir/root-layout-render-once/index.test.ts` —
`describe('app-dir root layout render once', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should only render root layout once', async () => {
    let $ = await next.render$('/render-once')
    expect($('#counter').text()).toBe('0')
    $ = await next.render$('/render-once')
    expect($('#counter').text()).toBe('1')
    $ = await next.render$('/render-once')
    expect($('#counter').text()).toBe('2')
  })
})`
- ID 138: `test/e2e/app-dir/root-layout/root-layout.test.ts` —
`describe('app-dir root layout', () => {
  const { next, isNextDev: isDev } = nextTestSetup({
    files: __dirname,
  })

  if (isDev) {
    // TODO-APP: re-enable after reworking the error overlay.
    describe.skip('Missing required tags', () => {
      it('should error on page load', async () => {
        const browser = await next.browser('/missing-tags', {
          waitHydration: false,
        })

        await waitForRedbox(browser)
        expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout:
<html>, <body>.

        Missing required root layout tags: html, body"
      `)
      })

      it('should error on page navigation', async () => {
        const browser = await next.browser('/has-tags', {
          waitHydration: false,
        })
        await browser.elementByCss('a').click()

        await waitForRedbox(browser)
        expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout:
<html>, <body>.

        Missing required root layout tags: html, body"
      `)
      })

      it('should error on page load on static generation', async () => {
const browser = await next.browser('/static-missing-tags/slug', {
          waitHydration: false,
        })

        await waitForRedbox(browser)
        expect(await getRedboxSource(browser)).toMatchInlineSnapshot(`
"Please make sure to include the following tags in your root layout:
<html>, <body>.

        Missing required root layout tags: html, body"
      `)
      })
    })
  }

describe('Should do a mpa navigation when switching root layout', () =>
{
    it('should work with basic routes', async () => {
      const browser = await next.browser('/basic-route')

      expect(await browser.elementById('basic-route').text()).toBe(
        'Basic route'
      )
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await browser.elementByCss('a').click()
      expect(
        await browser.waitForElementByCss('#inner-basic-route').text()
      ).toBe('Inner basic route')
      expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()

      // Navigate to page with different root layout
      await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#route-group').text()).toBe(
        'Route group'
      )
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
    })

    it('should work with route groups', async () => {
      const browser = await next.browser('/route-group')

      expect(await browser.elementById('route-group').text()).toBe(
        'Route group'
      )
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await browser.elementByCss('a').click()
      expect(
        await browser.waitForElementByCss('#nested-route-group').text()
      ).toBe('Nested route group')
      expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()

      // Navigate to page with different root layout
      await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#parallel-one').text()).toBe(
        'One'
      )
expect(await browser.waitForElementByCss('#parallel-two').text()).toBe(
        'Two'
      )
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
    })

    it('should work with parallel routes', async () => {
      const browser = await next.browser('/with-parallel-routes')

expect(await browser.elementById('parallel-one').text()).toBe('One')
expect(await browser.elementById('parallel-two').text()).toBe('Two')
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await check(async () => {
        await browser.elementByCss('a').click()
        expect(
await browser.waitForElementByCss('#parallel-one-inner').text()
        ).toBe('One inner')
        expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()
        return 'success'
      }, 'success')

      // Navigate to page with different root layout
      await check(async () => {
        await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#dynamic-hello').text()).toBe(
          'dynamic hello'
        )
        return 'success'
      }, 'success')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
    })

    it('should work with dynamic routes', async () => {
      const browser = await next.browser('/dynamic/first')

      expect(await browser.elementById('dynamic-first').text()).toBe(
        'dynamic first'
      )
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await browser.elementByCss('a').click()
      expect(
await browser.waitForElementByCss('#dynamic-first-second').text()
      ).toBe('dynamic first second')
      expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()

      // Navigate to page with different root layout
      await browser.elementByCss('a').click()
      expect(
        await browser.waitForElementByCss('#inner-basic-route').text()
      ).toBe('Inner basic route')
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
    })

    it('should work with dynamic catchall routes', async () => {
      const browser = await next.browser('/dynamic-catchall/slug')

      expect(await browser.elementById('catchall-slug').text()).toBe(
        'catchall slug'
      )
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await browser.elementById('to-next-url').click()
      expect(
        await browser.waitForElementByCss('#catchall-slug-slug').text()
      ).toBe('catchall slug slug')
      expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()

      // Navigate to page with different root layout
      await browser.elementById('to-dynamic-first').click()
      expect(await browser.elementById('dynamic-first').text()).toBe(
        'dynamic first'
      )
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()
    })

    it('should work with static routes', async () => {
      const browser = await next.browser('/static-mpa-navigation/slug1')

      expect(await browser.elementById('static-slug1').text()).toBe(
        'static slug1'
      )
      await browser.eval('window.__TEST_NO_RELOAD = true')

      // Navigate to page with same root layout
      await browser.elementByCss('a').click()
expect(await browser.waitForElementByCss('#static-slug2').text()).toBe(
        'static slug2'
      )
      expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeTrue()

      // Navigate to page with different root layout
      await browser.elementByCss('a').click()
      expect(await browser.elementById('basic-route').text()).toBe(
        'Basic route'
      )
expect(await browser.eval('window.__TEST_NO_RELOAD')).toBeUndefined()

      const res = await next.fetch(
        `${next.url}/static-mpa-navigation/slug-not-existed`
      )
      expect(res.status).toBe(404)
    })
  })

it('should correctly handle navigation between multiple root layouts',
async () => {
    const browser = await next.browser('/root-layout-a')

    await browser.waitForElementByCss('#root-a')
    expect(await browser.hasElementByCssSelector('#root-b')).toBeFalse()
    await browser
      .elementById('link-to-b')
      .click()
      .waitForElementByCss('#root-b')
    expect(await browser.hasElementByCssSelector('#root-a')).toBeFalse()
  })

it('should correctly handle navigation between multiple root layouts
when redirecting in a server action', async () => {
    const browser = await next.browser('/root-layout-a')

    await browser.waitForElementByCss('#action-redirect-to-b')
    expect(await browser.hasElementByCssSelector('#root-b')).toBeFalse()
    await browser
      .elementById('action-redirect-to-b')
      .click()
      .waitForElementByCss('#root-b')
    expect(await browser.hasElementByCssSelector('#root-a')).toBeFalse()
  })
})`
- ID 139:
`test/e2e/app-dir/root-suspense-dynamic/root-suspense-dynamic.test.ts` —
`describe('Root Suspense Dynamic Rendering', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname + '/fixtures/default',
  })

  // TODO: remove when there is a test for isNextDev === false
it('placeholder to satisfy at least one test when isNextDev is false',
async () => {
    expect(true).toBe(true)
  })

  if (isNextStart) {
it('should handle dynamic content wrapped in Suspense above HTML
structure', async () => {
      try {
        // Should render the page successfully
        const $ = await next.render$('/')
        expect($('body').text()).toContain('Hello World')
      } catch (error) {
        throw new Error(
'Expected build to succeed for Suspense wrapping dynamic content above
HTML',
          { cause: error }
        )
      }
    })

    it('should correctly mark route as dynamic', async () => {
      // The route should be marked as dynamic (ƒ) not static (○)
      expect(next.cliOutput).toContain('ƒ /')
    })
  }
})`
- ID 140:
`test/e2e/app-dir/similar-pages-paths/similar-pages-paths.test.ts` —
`describe('app-dir similar pages paths', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should not have conflicts for similar pattern page paths between app
and pages', async () => {
    // pages/page and app/page
    const res1 = await next.fetch('/')
    expect(res1.status).toBe(200)
    expect(await res1.text()).toContain('(app/page.js)')

    const res2 = await next.fetch('/page')
    expect(res2.status).toBe(200)
    expect(await res2.text()).toContain('(pages/page.js)')
  })
})`
- ID 143:
`test/e2e/app-dir/unauthorized/default/unauthorized-default.test.ts` —
`describe('app dir - unauthorized with default unauthorized boundary',
() => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  // TODO: error unauthorized usage in root layout
it.skip('should error on client unauthorized from root layout in
browser', async () => {
    const browser = await next.browser('/')

    await browser.elementByCss('#trigger-unauthorized').click()

    if (isNextDev) {
      await waitForRedbox(browser)
      expect(await getRedboxDescription(browser)).toMatch(
        /unauthorized\(\) is not allowed to use in root layout/
      )
    }
  })

  // TODO: error unauthorized usage in root layout
it.skip('should error on server unauthorized from root layout on
server-side', async () => {
    const browser = await next.browser('/?root-unauthorized=1')

    if (isNextDev) {
      await waitForRedbox(browser)
      expect(await getRedboxDescription(browser)).toBe(
        'Error: unauthorized() is not allowed to use in root layout'
      )
    }
  })

it('should be able to navigate to page calling unauthorized', async ()
=> {
    const browser = await next.browser('/')

    await browser.elementByCss('#navigate-unauthorized').click()
    await browser.waitForElementByCss('.next-error-h1')

    expect(await browser.elementByCss('h1').text()).toBe('401')
    expect(await browser.elementByCss('h2').text()).toBe(
      `You're not authorized to access this page.`
    )
  })

it('should be able to navigate to page with calling unauthorized in
metadata', async () => {
    const browser = await next.browser('/')

    await browser.elementByCss('#metadata-layout-unauthorized').click()
    await browser.waitForElementByCss('.next-error-h1')

    expect(await browser.elementByCss('h1').text()).toBe('401')
    expect(await browser.elementByCss('h2').text()).toBe(
      `You're not authorized to access this page.`
    )
  })

it('should render default unauthorized for group routes if unauthorized
is not defined', async () => {
    const browser = await next.browser('/group-dynamic/123')
    expect(await browser.elementByCss('#page').text()).toBe(
      'group-dynamic [id]'
    )

    await browser.loadPage(next.url + '/group-dynamic/401')
    await waitForNoRedbox(browser)
    await browser.waitForElementByCss('.group-root-layout')
expect(await browser.elementByCss('.next-error-h1').text()).toBe('401')
  })
})`

</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/app-dir/app-rendering/rendering.test.ts` — `describe('app
dir rendering', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677110);
Cache Components excluded by manifest.
- `test/e2e/app-dir/dynamic-data/dynamic-data.test.ts` —
`describe('dynamic-data', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677056);
Cache Components excluded by manifest.
- `test/e2e/app-dir/forbidden/default/forbidden-default.test.ts` —
`describe('app dir - forbidden with default forbidden boundary', () =>
{`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677074);
Cache Components excluded by manifest.
- `test/e2e/app-dir/root-layout/root-layout.test.ts` —
`describe('app-dir root layout', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677110);
Cache Components excluded by manifest.
- `test/e2e/app-dir/unauthorized/default/unauthorized-default.test.ts` —
`describe('app dir - unauthorized with default unauthorized boundary',
() => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677110);
Cache Components excluded by manifest.
-
`test/e2e/app-dir/actions-allowed-origins/app-action-opaque-origin.test.ts`
— `describe('app-dir action allowed from opaque origins', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677074),
[cache](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677108);
this scope passed although another scope in the file failed.
-
`test/e2e/app-dir/parallel-routes-and-interception/parallel-routes-and-interception.test.ts`
— `describe('parallel-routes-and-interception-conflicting-pages', () =>
{`:
[normal](https://github.com/vercel/next.js/actions/runs/34426784855/job/102715677110);
Cache Components excluded by manifest; this scope passed although
another scope in the file failed.

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:05 -07:00
Jamiboy Mohammad dee811ff1c test: enable verified caching deploy tests (#98523)
## Summary

Enable the same 24 previously selected deployment-test scopes across 21
caching test files, now in a stack rooted on canary. Remove 24
`@force-gate !deploy` directives and their associated TODO comments,
which are already present on canary. Other mode, bundler, middleware,
and Cache Components exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (24)</summary>

- ID 1:
`test/e2e/app-dir/app-client-cache/client-cache.original.test.ts` —
`describe('app dir client cache semantics (30s/5min)', () => {
  const { next, isNextDev } = nextTestSetup({
    files: path.join(__dirname, 'fixtures', 'regular'),
    nextConfig: {
      experimental: { staleTimes: { dynamic: 30, static: 180 } },
    },
  })

  if (isNextDev) {
// dev doesn't support prefetch={true}, so this just performs a basic
test to make sure data is reused for 30s
it('should renew the 30s cache once the data is revalidated', async ()
=> {
      let browser = await next.browser('/', browserConfigWithFixedTime)

      // navigate to prefetch-auto page
      await browser.elementByCss('[href="/1"]').click()
      await browser.waitForElementByCss('#random-number')

let initialNumber = await browser.elementById('random-number').text()

// Navigate back to the index, and then back to the prefetch-auto page
      await browser.elementByCss('[href="/"]').click()
      await browser.waitForElementByCss('[href="/1"]')
      await browser.eval(fastForwardTo, 5 * 1000)
      await browser.elementByCss('[href="/1"]').click()
      await browser.waitForElementByCss('#random-number')

      let newNumber = await browser.elementById('random-number').text()

      // the number should be the same, as we navigated within 30s.
      expect(newNumber).toBe(initialNumber)

      // Fast forward to expire the cache
      await browser.eval(fastForwardTo, 30 * 1000)

// Navigate back to the index, and then back to the prefetch-auto page
      await browser.elementByCss('[href="/"]').click()
      await browser.waitForElementByCss('[href="/1"]')
      await browser.elementByCss('[href="/1"]').click()
      await browser.waitForElementByCss('#random-number')

      newNumber = await browser.elementById('random-number').text()

// ~35s have passed, so the cache should be expired and the number
should be different
      expect(newNumber).not.toBe(initialNumber)

// once the number is updated, we should have a renewed 30s cache for
this entry
      // store this new number so we can check that it stays the same
      initialNumber = newNumber

      await browser.eval(fastForwardTo, 5 * 1000)

// Navigate back to the index, and then back to the prefetch-auto page
      await browser.elementByCss('[href="/"]').click()
      await browser.waitForElementByCss('[href="/1"]')
      await browser.elementByCss('[href="/1"]').click()
      await browser.waitForElementByCss('#random-number')

      newNumber = await browser.elementById('random-number').text()

// the number should be the same, as we navigated within 30s (part 2).
      expect(newNumber).toBe(initialNumber)
    })
  } else {
    describe('prefetch={true}', () => {
      let browser: Playwright

      beforeEach(async () => {
        browser = await next.browser('/', browserConfigWithFixedTime)
      })

      it('should prefetch the full page', async () => {
        const { getRequests, clearRequests } =
          await createRequestsListener(browser)
        await retry(() => {
          expect(
            getRequests().some(
              ([url, didPartialPrefetch]) =>
                getPathname(url) === '/0' && !didPartialPrefetch
            )
          ).toBe(true)
        })

        clearRequests()

        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')

        await retry(() => {
          const requests = getRequests()
expect(requests.every(([url]) => getPathname(url) !== '/0')).toBe(
            true
          )
        })
      })
it('should re-use the cache for the full page, only for 5 mins', async
() => {
        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')
const randomNumber = await browser.elementById('random-number').text()

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/0?timeout=0"]')

        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')
        const number = await browser.elementById('random-number').text()

        expect(number).toBe(randomNumber)

        await browser.eval(fastForwardTo, 5 * 60 * 1000)

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/0?timeout=0"]')

        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')
const newNumber = await browser.elementById('random-number').text()

        expect(newNumber).not.toBe(randomNumber)
      })

it('should prefetch again after 5 mins if the link is visible again',
async () => {
        const { getRequests, clearRequests } =
          await createRequestsListener(browser)

        await retry(() => {
          expect(
            getRequests().some(
              ([url, didPartialPrefetch]) =>
                getPathname(url) === '/0' && !didPartialPrefetch
            )
          ).toBe(true)
        })

        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')
const randomNumber = await browser.elementById('random-number').text()

        await browser.eval(fastForwardTo, 5 * 60 * 1000)
        clearRequests()

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/0?timeout=0"]')

        await retry(() => {
          expect(
            getRequests().some(
              ([url, didPartialPrefetch]) =>
                getPathname(url) === '/0' && !didPartialPrefetch
            )
          ).toBe(true)
        })

        await browser.elementByCss('[href="/0?timeout=0"]').click()
        await browser.waitForElementByCss('#random-number')
        const number = await browser.elementById('random-number').text()

        expect(number).not.toBe(randomNumber)
      })
    })
    describe('prefetch={false}', () => {
      let browser: Playwright

      beforeEach(async () => {
        browser = await next.browser('/', browserConfigWithFixedTime)
      })
      it('should not prefetch the page at all', async () => {
        const { getRequests } = await createRequestsListener(browser)

        await browser.elementByCss('[href="/2"]').click()
        await browser.waitForElementByCss('#random-number')

        await retry(() => {
          const requests = getRequests().filter(
            ([url]) => getPathname(url) === '/2'
          )
          expect(requests.length).toBe(1)
        })

        expect(
          getRequests().some(
            ([url, didPartialPrefetch]) =>
              getPathname(url) === '/2' && didPartialPrefetch
          )
        ).toBe(false)
      })
      it('should re-use the cache only for 30 seconds', async () => {
        await browser.elementByCss('[href="/2"]').click()
        await browser.waitForElementByCss('#random-number')
const randomNumber = await browser.elementById('random-number').text()

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/2"]')

        await browser.elementByCss('[href="/2"]').click()
        await browser.waitForElementByCss('#random-number')
        const number = await browser.elementById('random-number').text()

        expect(number).toBe(randomNumber)

        await browser.eval(fastForwardTo, 30 * 1000)

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/2"]')

        await browser.elementByCss('[href="/2"]').click()
        await browser.waitForElementByCss('#random-number')
const newNumber = await browser.elementById('random-number').text()

        expect(newNumber).not.toBe(randomNumber)
      })
    })
    describe('prefetch={undefined} - default', () => {
      let browser: Playwright

      beforeEach(async () => {
        browser = await next.browser('/', browserConfigWithFixedTime)
      })

      it('should prefetch partially a dynamic page', async () => {
        const { getRequests, clearRequests } =
          await createRequestsListener(browser)

        await retry(() => {
          expect(
            getRequests().some(
              ([url, didPartialPrefetch]) =>
                getPathname(url) === '/1' && didPartialPrefetch
            )
          ).toBe(true)
        })

        clearRequests()

        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')

        await retry(() => {
          expect(
            getRequests().some(
              ([url, didPartialPrefetch]) =>
                getPathname(url) === '/1' && !didPartialPrefetch
            )
          ).toBe(true)
        })
      })
it('should re-use the full cache for only 30 seconds', async () => {
        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')
const randomNumber = await browser.elementById('random-number').text()

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')

        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')
        const number = await browser.elementById('random-number').text()

        expect(number).toBe(randomNumber)

        await browser.eval(fastForwardTo, 5 * 1000)

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')

        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')
const newNumber = await browser.elementById('random-number').text()

        expect(newNumber).toBe(randomNumber)

        await browser.eval(fastForwardTo, 30 * 1000)

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')

        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')
const newNumber2 = await browser.elementById('random-number').text()

        expect(newNumber2).not.toBe(newNumber)
      })

it('should renew the 30s cache once the data is revalidated', async ()
=> {
        // navigate to prefetch-auto page
        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')

let initialNumber = await browser.elementById('random-number').text()

// Navigate back to the index, and then back to the prefetch-auto page
        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')
        await browser.eval(fastForwardTo, 5 * 1000)
        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')

let newNumber = await browser.elementById('random-number').text()

        // the number should be the same, as we navigated within 30s.
        expect(newNumber).toBe(initialNumber)

        // Fast forward to expire the cache
        await browser.eval(fastForwardTo, 30 * 1000)

// Navigate back to the index, and then back to the prefetch-auto page
        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')
        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')

        newNumber = await browser.elementById('random-number').text()

// ~35s have passed, so the cache should be expired and the number
should be different
        expect(newNumber).not.toBe(initialNumber)

// once the number is updated, we should have a renewed 30s cache for
this entry
        // store this new number so we can check that it stays the same
        initialNumber = newNumber

        await browser.eval(fastForwardTo, 5 * 1000)

// Navigate back to the index, and then back to the prefetch-auto page
        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1"]')
        await browser.elementByCss('[href="/1"]').click()
        await browser.waitForElementByCss('#random-number')

        newNumber = await browser.elementById('random-number').text()

// the number should be the same, as we navigated within 30s (part 2).
        expect(newNumber).toBe(initialNumber)
      })

      it('should refetch below the fold after 30 seconds', async () => {
        await browser.elementByCss('[href="/1?timeout=1000"]').click()
        await browser.waitForElementByCss('#random-number')
const randomNumber = await browser.elementById('random-number').text()

        await browser.elementByCss('[href="/"]').click()
        await browser.waitForElementByCss('[href="/1?timeout=1000"]')

        await browser.eval(fastForwardTo, 30 * 1000)

        await browser.elementByCss('[href="/1?timeout=1000"]').click()
        await browser.waitForElementByCss('#random-number')
const newNumber = await browser.elementById('random-number').text()

        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()
          .waitForElementByCss('#loading')
          .text()

        const randomNumber = await browser
          .waitForElementByCss('#random-number')
          .text()

        await browser.eval(fastForwardTo, 5 * 60 * 1000)

        await browser
          .elementByCss('[href="/"]')
          .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()
          .waitForElementByCss('#loading')
          .text()

        const newNumber = await browser
          .waitForElementByCss('#random-number')
          .text()

        expect(newLoadingNumber).not.toBe(randomLoadingNumber)

        expect(newNumber).not.toBe(randomNumber)
      })

it('should respect a loading boundary that returns `null`', async () =>
{
        await browser.elementByCss('[href="/null-loading"]').click()

        // the page content should disappear immediately
        await retry(async () => {
          expect(
await browser.hasElementByCssSelector('[href="/null-loading"]')
          ).toBe(false)
        })

        // the root layout should still be visible
expect(await browser.hasElementByCssSelector('#root-layout')).toBe(true)

        // the dynamic content should eventually appear
        await browser.waitForElementByCss('#random-number')
expect(await browser.hasElementByCssSelector('#random-number')).toBe(
          true
        )
      })
    })

it('should seed the prefetch cache with the fetched page data', async ()
=> {
const browser = await next.browser('/1', browserConfigWithFixedTime)

      await browser.waitForElementByCss('#random-number')
const initialNumber = await browser.elementById('random-number').text()

// Move forward a few seconds, navigate off the page and then back to it
      await browser.eval(fastForwardTo, 5 * 1000)
      await browser.elementByCss('[href="/"]').click()
      await browser.waitForElementByCss('[href="/1"]')

      await browser.waitForIdleNetwork()

      await browser.elementByCss('[href="/1"]').click()
      await browser.waitForElementByCss('#random-number')

const newNumber = await browser.elementById('random-number').text()

// The number should be the same as we've seeded it in the prefetch
cache when we loaded the full page
      expect(newNumber).toBe(initialNumber)
    })

it('should renew the initial seeded data after expiration time', async
() => {
      const browser = await next.browser(
        '/without-loading/1',
        browserConfigWithFixedTime
      )

      await browser.waitForElementByCss('#random-number')
const initialNumber = await browser.elementById('random-number').text()

      // Expire the cache
      await browser.eval(fastForwardTo, 30 * 1000)
      await browser.elementByCss('[href="/without-loading"]').click()
      await browser.waitForElementByCss('[href="/without-loading/1"]')
      await browser.elementByCss('[href="/without-loading/1"]').click()
      await browser.waitForElementByCss('#random-number')

const newNumber = await browser.elementById('random-number').text()

// The number should be different, as the seeded data has expired after
30s
      expect(newNumber).not.toBe(initialNumber)
    })
  }
})`
- ID 4: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` —
`describe('app-dir - custom-cache-handler - cjs', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
    env: {
      CUSTOM_CACHE_HANDLER: 'cache-handler.js',
    },
  })

  runTests('cjs module exports', { next, isNextDev })
})`
- ID 5: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` —
`describe('app-dir - custom-cache-handler - cjs-default-export', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
    env: {
      CUSTOM_CACHE_HANDLER: 'cache-handler-cjs-default-export.js',
    },
  })

  runTests('cjs default export', { next, isNextDev })
})`
- ID 6: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` —
`describe('app-dir - custom-cache-handler - esm', () => {
  const { next, isNextDev } = nextTestSetup({
    files: {
      app: new FileRef(__dirname + '/app'),
'cache-handler-esm.js': new FileRef(__dirname +
'/cache-handler-esm.js'),
      'next.config.js': originalNextConfig.replace(
        'module.exports = ',
        'export default '
      ),
    },
    packageJson: {
      type: 'module',
    },
    env: {
      CUSTOM_CACHE_HANDLER: 'cache-handler-esm.js',
    },
  })

  runTests('esm default export', { next, isNextDev })
})`
- ID 7: `test/e2e/app-dir/app-custom-cache-handler/index.test.ts` —
`describe('app-dir - custom-cache-handler - esm import.meta.resolve', ()
=> {
  const { next, isNextDev } = nextTestSetup({
    files: {
      app: new FileRef(__dirname + '/app'),
'cache-handler-esm.js': new FileRef(__dirname +
'/cache-handler-esm.js'),
      'next.config.js': importMetaResolveNextConfig,
    },
    packageJson: {
      type: 'module',
    },
  })

  runTests('esm default export', { next, isNextDev })
})`
- ID 9: `test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts`
— `describe('app dir - prefetching (custom staleTime)', () => {
  const { next, isNextDev } = nextTestSetup({
    files: {
      app: new FileRef(join(__dirname, 'app')),
    },
    nextConfig: {
      experimental: {
        staleTimes: {
static: 30, // Minimum enforced by clientSegmentCache is 30 seconds
          dynamic: 5,
        },
      },
    },
  })

  if (isNextDev) {
    it('should skip next dev for now', () => {})
    return
  }

it('should not fetch again when a static page was prefetched when
navigating to it twice', async () => {
    let act: ReturnType<typeof createRouterAct>
    const browser = await next.browser('/', {
      beforePageLoad(page) {
        act = createRouterAct(page)
      },
    })

    // Reveal the link to trigger prefetch and wait for it to complete
    const link = await act(
      async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
        await reveal.click()
        return browser.elementByCss('#to-static-page')
      },
      { includes: 'Static Page [prefetch-sentinel]' }
    )

// Navigate to static page - should use prefetched data with no
additional requests
    await act(async () => {
      await link.click()
const staticPageText = await browser.elementByCss('#static-page').text()
      expect(staticPageText).toBe('Static Page [prefetch-sentinel]')
    }, 'no-requests')

    // Reveal the "to-home" link and navigate back
// Note: Not using act() here because behavior differs between cache
models.
// With clientSegmentCache, revealing may trigger a prefetch. Without
it, home is already
// cached so no prefetch occurs. Either way, navigation works with
cached data.
    const reveal = await browser.elementByCss('#accordion-to-home')
    await reveal.click()
    const homeLink = await browser.waitForElementByCss('#to-home')
    await homeLink.click()
    await browser.waitForElementByCss('#accordion-to-static-page')

// Reveal the static page link again since accordion is hidden after
navigation
    await browser.elementByCss('#accordion-to-static-page').click()
    await browser.waitForElementByCss('#to-static-page')

// Navigate to static page again using the accordion - should still use
cached data with no additional requests
    const staticPageText = await act(async () => {
      await browser.elementByCss('#to-static-page').click()
      return browser.elementByCss('#static-page').text()
    }, 'no-requests')

    expect(staticPageText).toBe('Static Page [prefetch-sentinel]')
  })

it('should fetch again when a static page was prefetched when navigating
to it after the stale time has passed', async () => {
    let act: ReturnType<typeof createRouterAct>
    const timeController = createTimeController()
    const browser = await next.browser('/', {
      beforePageLoad(page) {
        act = createRouterAct(page)
      },
    })

    // Install time controller
    await timeController.install(browser)

// Reveal the static-page link to trigger prefetch and wait for it to
complete
    let link = await act(
      async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
        await reveal.click()
        return browser.elementByCss('#to-static-page')
      },
      { includes: 'Static Page [prefetch-sentinel]' }
    )

// Navigate to static page - should use prefetched data with no
additional requests
    await act(async () => {
      await link.click()
      await browser.waitForElementByCss('#static-page')
    }, 'no-requests')

    // Reveal the "to-home" link and navigate back
    const reveal = await browser.elementByCss('#accordion-to-home')
    await reveal.click()
    const homeLink = await browser.waitForElementByCss('#to-home')
    await homeLink.click()
    await browser.waitForElementByCss('#accordion-to-static-page')

    // Advance time past the stale time
    await timeController.advance(browser, 31000)

// Reveal the static-page link to trigger prefetch and wait for it to
complete
    link = await act(
      async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
        await reveal.click()
        return browser.elementByCss('#to-static-page')
      },
      { includes: 'Static Page [prefetch-sentinel]' }
    )

// Navigate to static page - should use prefetched data with no
additional requests
    await act(async () => {
      await link.click()
      await browser.waitForElementByCss('#static-page')
    }, 'no-requests')
  })

  // FIXME: Flaky test - investigate and re-enable
it.skip('should not re-fetch cached data when navigating back to a route
group', async () => {
    let act: ReturnType<typeof createRouterAct>
// Just installing so that the page doesn't automatically move past
dynamic stale time
    createTimeController()
    const browser = await next.browser('/prefetch-auto-route-groups', {
      beforePageLoad(page) {
        act = createRouterAct(page)
      },
    })

// Once the page has loaded, we expect a data fetch (initial page load)
    expect(await browser.elementById('count').text()).toBe('1')

    // Navigate to a sub-page - this will trigger a data fetch
    await act(async () => {
      await browser
        .elementByCss("[href='/prefetch-auto-route-groups/sub/foo']")
        .click()
    })

// Navigate back to the route group page - should use cached data with
no additional fetch
    await act(async () => {
await
browser.elementByCss("[href='/prefetch-auto-route-groups']").click()
// Confirm that the dashboard page is still rendering the stale fetch
count, as it should be cached
    }, 'no-requests')

    expect(await browser.elementById('count').text()).toBe('1')

    // Navigate to a new sub-page - this will trigger another data fetch
    await act(async () => {
      await browser
        .elementByCss("[href='/prefetch-auto-route-groups/sub/bar']")
        .click()
    })

// Finally, go back to the route group page - should use cached data
with no additional fetch
    await act(async () => {
await
browser.elementByCss("[href='/prefetch-auto-route-groups']").click()
    }, 'no-requests')

// Confirm that the dashboard page is still rendering the stale fetch
count, as it should be cached
    expect(await browser.elementById('count').text()).toBe('1')

    // Reload the page to get the accurate total number of fetches
    await browser.refresh()

// The initial fetch, 2 sub-page fetches, and a final fetch when
reloading the page
    expect(await browser.elementById('count').text()).toBe('4')
  })

it('should fetch again when the initially visited static page is visited
after the stale time has passed', async () => {
    let act: ReturnType<typeof createRouterAct>
    const timeController = createTimeController()
    const browser = await next.browser('/static-page-no-prefetch', {
      beforePageLoad(page) {
        act = createRouterAct(page)
      },
    })

    // Install time controller
    await timeController.install(browser)

// Wait for the page to load (initial navigation request happened during
browser load)
    await browser.waitForElementByCss('#static-page-no-prefetch')

// Reveal the home link and wait for prefetch to complete, then navigate
    const homeLink = await act(
      async () => {
        const reveal = await browser.elementByCss('#accordion-to-home')
        await reveal.click()
        return browser.elementByCss('#to-home')
      },
      { includes: 'Home Page [prefetch-sentinel]' }
    )

// Navigate to home - no additional requests since we just prefetched
    await homeLink.click()
    await browser.waitForElementByCss('#accordion-to-static-page')

    // Advance time past the stale time
    await timeController.advance(browser, 31000)

    // Reveal the link to static-page-no-prefetch and wait for prefetch
    const link = await act(
      async () => {
        const reveal = await browser.elementByCss(
          '#accordion-to-static-page-no-prefetch'
        )
        await reveal.click()
        return browser.elementByCss('#to-static-page-no-prefetch')
      },
      { includes: 'Static Page No Prefetch [prefetch-sentinel]' }
    )

// Navigate back to static-page-no-prefetch - should use the fresh
prefetch data
    const staticPageText = await act(async () => {
      await link.click()
      return browser.elementByCss('#static-page-no-prefetch').text()
    }, 'no-requests')
expect(staticPageText).toBe('Static Page No Prefetch
[prefetch-sentinel]')
  })

it('should renew the stale time after refetching expired RSC data',
async () => {
    let act: ReturnType<typeof createRouterAct>
    const timeController = createTimeController()
    const browser = await next.browser('/', {
      beforePageLoad(page) {
        act = createRouterAct(page)
      },
    })

    // Install time controller
    await timeController.install(browser)

// Reveal the static-page link to trigger prefetch and wait for it to
complete
    let link = await act(
      async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
        await reveal.click()
        return browser.elementByCss('#to-static-page')
      },
      { includes: 'Static Page [prefetch-sentinel]' }
    )

// Navigate to static page (should use prefetched data with no
additional requests)
    await act(async () => {
      await link.click()
      await browser.waitForElementByCss('#static-page')
    }, 'no-requests')

    // Reveal the "to-home" link and navigate back
// Note: Not using act() here because behavior differs between cache
models.
// With clientSegmentCache, revealing may trigger a prefetch. Without
it, home is already
// cached so no prefetch occurs. Either way, navigation works with
cached data.
    const reveal = await browser.elementByCss('#accordion-to-home')
    await reveal.click()
    const homeLink = await browser.waitForElementByCss('#to-home')
    await homeLink.click()
    await browser.waitForElementByCss('#accordion-to-static-page')

    // Advance time past the stale time
    await timeController.advance(browser, 31000)

// Reveal the static-page link to trigger prefetch and wait for it to
complete
    link = await act(
      async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
        await reveal.click()
        return browser.elementByCss('#to-static-page')
      },
      { includes: 'Static Page [prefetch-sentinel]' }
    )

// Navigate to static page again (should use freshly prefetched data
with no additional requests)
    await act(async () => {
      await link.click()
      await browser.waitForElementByCss('#static-page')
    }, 'no-requests')

    // Go back to home (reveal the link and navigate)
// Note: Not using act() here because behavior differs between cache
models.
    const reveal2 = await browser.elementByCss('#accordion-to-home')
    await reveal2.click()
    const homeLink2 = await browser.waitForElementByCss('#to-home')
    await homeLink2.click()
    await browser.waitForElementByCss('#accordion-to-static-page')

// Advance time but not past the stale time (20 seconds < 30 second
stale time - should still be fresh)
    await timeController.advance(browser, 20000)

// Reveal the static-page link to trigger prefetch (should use cached
data, not refetch)
    link = await act(async () => {
const reveal = await browser.elementByCss('#accordion-to-static-page')
      await reveal.click()
      return browser.elementByCss('#to-static-page')
    }, 'no-requests')

// Navigate to static page again (should NOT refetch - stale time should
be renewed)
// If this assertion passes, it means the stale time was properly
renewed after the refetch
    const staticPageText = await act(async () => {
      await link.click()
      return browser.elementByCss('#static-page').text()
    }, 'no-requests')
    expect(staticPageText).toBe('Static Page [prefetch-sentinel]')
  })
})`
- ID 10: `test/e2e/app-dir/app-root-params-getters/use-cache.test.ts` —
`describe('app-root-param-getters - cache dedup with root params', () =>
{
  const { next, isNextDev } = nextTestSetup({
    files: join(__dirname, 'fixtures', 'use-cache-dedup'),
  })

it('should dedupe same root params and isolate different root params',
async () => {
    // Three concurrent requests: ca/en, ca/fr, ca/fr.
    const [$en, $fr1, $fr2] = await Promise.all([
      next.render$('/ca/en'),
      next.render$('/ca/fr'),
      next.render$('/ca/fr'),
    ])

    const randomEn = $en('#random').text()
    const randomFr1 = $fr1('#random').text()
    const randomFr2 = $fr2('#random').text()

    expect(randomEn).toBeTruthy()
    expect(randomFr1).toBeTruthy()

    // ca/en and ca/fr should have different results (isolation).
    expect(randomEn).not.toBe(randomFr1)

    // Both ca/fr requests should have the same result (deduped).
    expect(randomFr1).toBe(randomFr2)
  })

it('should dedupe same root params and isolate different root params for
private caches', async () => {
    // Three concurrent requests: ca/en, ca/fr, ca/fr.
    const [$en, $fr1, $fr2] = await Promise.all([
      next.render$('/ca/en/use-cache-private'),
      next.render$('/ca/fr/use-cache-private'),
      next.render$('/ca/fr/use-cache-private'),
    ])

    const randomEn = $en('#random').text()
    const randomFr1 = $fr1('#random').text()
    const randomFr2 = $fr2('#random').text()

    expect(randomEn).toBeTruthy()
    expect(randomFr1).toBeTruthy()

// Different root params produce different entries, in dev and
production.
    expect(randomEn).not.toBe(randomFr1)

    if (isNextDev) {
// In dev, private caches are persisted and participate in cross-request
// deduplication keyed by root params, so the two ca/fr requests join
one
      // in-flight invocation and share a single fill.
      expect(randomFr1).toBe(randomFr2)
    } else {
// In production, private caches are not persisted and are never deduped
      // across requests, so each ca/fr request generates its own value.
      expect(randomFr1).not.toBe(randomFr2)
    }
  })
})`
- ID 21: `test/e2e/app-dir/cache-components-errors/module-scope.test.ts`
— `describe('Lazy Module Init', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname + '/fixtures/lazy-module-init',
    skipStart: true,
  })

  if (isNextDev) {
    it('does not run in dev', () => {})
    return
  }

it('should build statically even if module scope uses sync APIs like
current time and random', async () => {
    try {
      await next.start()
    } catch {
throw new Error('expected build not to fail for fully static project')
    }

    expect(next.cliOutput).toContain('○ /server')
    expect(next.cliOutput).toContain('○ /client')
    expect(next.cliOutput).toContain('○ /client-page')
    expect(next.cliOutput).toContain('◐ /[dyn]')
    let $

    $ = await next.render$('/server')
    expect($('#id').text().length).toBeGreaterThan(0)

    $ = await next.render$('/client')
    expect($('#id').text().length).toBeGreaterThan(0)

    $ = await next.render$('/client-page')
    expect($('#id').text().length).toBeGreaterThan(0)

    $ = await next.render$('/foo')
    expect($('#id').text().length).toBeGreaterThan(0)

    $ = await next.render$('/serial-client-sync-io')
    expect($('#id').text().length).toBeGreaterThan(0)
  })
})`
- ID 27:
`test/e2e/app-dir/cache-components/cache-components.connection.test.ts`
— `describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

it('should partially prerender pages that use connection', async () => {
let $ = await next.render$('/connection/static-behavior/boundary', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#foo').text()).toBe('foo')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#foo').text()).toBe('foo')
    }
  })

it('should be able to pass connection as a promise to another component
and trigger an intermediate Suspense boundary', async () => {
const $ = await next.render$('/connection/static-behavior/pass-deeply')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
// In dev, whether or not the fallback appears in the HTML is unreliable
      // and depends on timing, so we don't assert on its presence
      // (if we want to assert on it, we should use a browser test)
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#fallback').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at runtime')
    }
  })
})`
- ID 28:
`test/e2e/app-dir/cache-components/cache-components.cookies.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should partially prerender pages that use cookies', async () => {
    let $ = await next.render$('/cookies/static-behavior', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#x-sentinel').text()).toBe('hello')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#x-sentinel').text()).toBe('hello')
    }
  })

it('should be able to pass cookies as a promise to another component and
trigger an intermediate Suspense boundary', async () => {
    const $ = await next.render$('/cookies/static-behavior/pass-deeply')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#fallback').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#fallback').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at runtime')
    }
  })

  it('should be able to access cookie properties', async () => {
    let $ = await next.render$('/cookies/exercise', {})
    let cookieWarnings = next.cliOutput
      .split('\n')
      .filter((l) => l.includes('Route "/cookies/exercise'))

    expect(cookieWarnings).toHaveLength(0)

    // For...of iteration
    expect($('#for-of-x-sentinel').text()).toContain('hello')

expect($('#for-of-x-sentinel-path').text()).toContain('/cookies/exercise')

expect($('#for-of-x-sentinel-rand').text()).toContain('x-sentinel-rand')

    // ...spread iteration
    expect($('#spread-x-sentinel').text()).toContain('hello')

expect($('#spread-x-sentinel-path').text()).toContain('/cookies/exercise')

expect($('#spread-x-sentinel-rand').text()).toContain('x-sentinel-rand')

    // cookies().size
expect(parseInt($('#size-cookies').text())).toBeGreaterThanOrEqual(3)

    // cookies().get('...') && cookies().getAll('...')
    expect($('#get-x-sentinel').text()).toContain('hello')
expect($('#get-x-sentinel-path').text()).toContain('/cookies/exercise')
expect($('#get-x-sentinel-rand').text()).toContain('x-sentinel-rand')

    // cookies().has('...')
    expect($('#has-x-sentinel').text()).toContain('true')
    expect($('#has-x-sentinel-foobar').text()).toContain('false')

    // cookies().set('...', '...')
    expect($('#set-result-x-sentinel').text()).toContain(
      'Cookies can only be modified in a Server Action'
    )
    expect($('#set-value-x-sentinel').text()).toContain('hello')

    // cookies().delete('...', '...')
    expect($('#delete-result-x-sentinel').text()).toContain(
      'Cookies can only be modified in a Server Action'
    )
    expect($('#delete-value-x-sentinel').text()).toContain('hello')

    // cookies().clear()
    expect($('#clear-result').text()).toContain(
      'Cookies can only be modified in a Server Action'
    )
    expect($('#clear-value-x-sentinel').text()).toContain('hello')

    // cookies().toString()
    expect($('#toString').text()).toContain('x-sentinel=hello')
    expect($('#toString').text()).toContain('x-sentinel-path')
    expect($('#toString').text()).toContain('x-sentinel-rand=')
  })
})`
- ID 29:
`test/e2e/app-dir/cache-components/cache-components.date.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should not have route specific errors', async () => {
    expect(next.cliOutput).not.toMatch('Error: Route "/')
expect(next.cliOutput).not.toMatch('Error occurred prerendering page')
  })

it('should prerender pages with cached `Date.now()` calls', async () =>
{
    let $ = await next.render$('/date/now/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toMatch(/^\d+$/)
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#value').text()).toMatch(/^\d+$/)
    }
  })

  it('should prerender pages with cached `Date()` calls', async () => {
    let $ = await next.render$('/date/date/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toContain('GMT')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#value').text()).toContain('GMT')
    }
  })

it('should prerender pages with cached `new Date()` calls', async () =>
{
    let $ = await next.render$('/date/new-date/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toContain('GMT')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#value').text()).toContain('GMT')
    }
  })

it('should prerender pages with cached static Date instances like `new
Date(0)`', async () => {
    let $ = await next.render$('/date/static-date/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toContain('GMT')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#value').text()).toContain('GMT')
    }
  })

it('should not prerender pages with uncached static Date instances like
`new Date(0)`', async () => {
    let $ = await next.render$('/date/static-date/uncached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toContain('GMT')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#value').text()).toContain('GMT')
    }
  })
})`
- ID 30:
`test/e2e/app-dir/cache-components/cache-components.draft-mode.test.ts`
— `describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  let cliIndex = 0
  beforeEach(() => {
    cliIndex = next.cliOutput.length
  })
  function getLines(containing: string): Array<string> {
    const warnings = next.cliOutput
      .slice(cliIndex)
      .split('\n')
      .filter((l) => l.includes(containing))

    cliIndex = next.cliOutput.length
    return warnings
  }

  it('should fully prerender pages that use draftMode', async () => {
    expect(getLines('Route "/draftmode')).toEqual([])
    let $ = await next.render$('/draftmode', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#draft-mode').text()).toBe('false')
      expect(getLines('Route "/draftmode')).toEqual([])
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#draft-mode').text()).toBe('false')
      expect(getLines('Route "/draftmode')).toEqual([])
    }
  })

  if (!isNextDev) {
it('should stream Suspense fallbacks when draft mode is enabled', async
() => {
      const draftRes = await next.fetch('/draftmode/toggle')
      const setCookie = draftRes.headers.get('set-cookie')
      const cookieHeader = { Cookie: setCookie?.split(';', 1)[0] }

      expect(cookieHeader.Cookie).toBeTruthy()

      const $ = await next.render$('/draftmode/streaming', undefined, {
        headers: cookieHeader,
      })

      expect($('#draft-mode').text()).toBe('true')
      expect($('#delayed-runtime-fallback').text()).toBe(
        'Loading draft content...'
      )
    })
  }
})`
- ID 32:
`test/e2e/app-dir/cache-components/cache-components.node-crypto.test.ts`
— `describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should not have route specific errors', async () => {
    expect(next.cliOutput).not.toMatch('Error: Route "/')
expect(next.cliOutput).not.toMatch('Error occurred prerendering page')
  })

it("should prerender pages with cached
`require('node:crypto').getRandomValues(...)` calls", async () => {
let $ = await next.render$('/node-crypto/get-random-values/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').randomUUID()` calls", async () => {
    let $ = await next.render$('/node-crypto/random-uuid/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').randomBytes(size)` calls", async () => {
    let $ = await next.render$('/node-crypto/random-bytes/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').randomFillSync(buffer)` calls", async () => {
let $ = await next.render$('/node-crypto/random-fill-sync/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').randomInt(max)` calls", async () => {
let $ = await next.render$('/node-crypto/random-int/up-to/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').randomInt(min, max)` calls", async () => {
let $ = await next.render$('/node-crypto/random-int/between/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').generatePrimeSync(size, options)` calls", async
() => {
let $ = await next.render$('/node-crypto/generate-prime-sync/cached',
{})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').generateKeyPairSync(type, options)` calls",
async () => {
let $ = await next.render$('/node-crypto/generate-key-pair-sync/cached',
{})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it("should prerender pages with cached
`require('node:crypto').generateKeySync(type, options)` calls", async ()
=> {
let $ = await next.render$('/node-crypto/generate-key-sync/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })
})`
- ID 33:
`test/e2e/app-dir/cache-components/cache-components.params.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  let cliIndex = 0
  beforeEach(() => {
    cliIndex = next.cliOutput.length
  })
  function getLines(containing: string): Array<string> {
    const warnings = next.cliOutput
      .slice(cliIndex)
      .split('\n')
      .filter((l) => l.includes(containing))

    cliIndex = next.cliOutput.length
    return warnings
  }

  describe('Params', () => {
it('should partially prerender pages that await params in a server
components', async () => {
      expect(getLines('Route "/params')).toEqual([])

      let $ = await next.render$(
        '/params/semantics/one/build/layout-access/server'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')

        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
      }

$ = await next.render$('/params/semantics/one/run/layout-access/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')

        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-access/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')

        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-access/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')

        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

// Since #85155, we intentionally omit search params from client
segments
    // if the page is otherwise static, and resume using a client fetch
// instead. So it's expected that the value is missing pre-hydration.
// There are separate tests that verify that it is eventually hydrated.
    // TODO: Rewrite or update this test.
it.skip('should partially prerender pages that use params in a client
components', async () => {
      expect(getLines('Route "/params')).toEqual([])

      let $ = await next.render$(
        '/params/semantics/one/build/layout-access/client'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/layout-access/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')

        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-access/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('build')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-access/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-lowcard').text()).toBe('one')
        expect($('#param-highcard').text()).toBe('run')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

it('should fully prerender pages that check individual param keys after
awaiting params in a server component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/semantics/one/build/layout-has/server'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-has/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/layout-has/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
// With PPR fallbacks the first visit is still partially prerendered
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-has/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
// With PPR fallbacks the first visit is still partially prerendered
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

// Since #85155, we intentionally omit search params from client
segments
    // if the page is otherwise static, and resume using a client fetch
// instead. So it's expected that the value is missing pre-hydration.
// There are separate tests that verify that it is eventually hydrated.
    // TODO: Rewrite or update this test.
it.skip('should fully prerender pages that check individual param keys
after `use`ing params in a client component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/semantics/one/build/layout-has/client'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-has/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/layout-has/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
// With PPR fallbacks the first visit is still partially prerendered
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-has/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      } else {
// With PPR fallbacks the first visit is still partially prerendered
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-has-lowcard').text()).toBe('true')
        expect($('#param-has-highcard').text()).toBe('true')
        expect($('#param-has-foo').text()).toBe('false')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

it('should partially prerender pages that spread awaited params in a
server component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/semantics/one/build/layout-spread/server'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-spread/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/layout-spread/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-spread/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

// Since #85155, we intentionally omit search params from client
segments
    // if the page is otherwise static, and resume using a client fetch
// instead. So it's expected that the value is missing pre-hydration.
// There are separate tests that verify that it is eventually hydrated.
    // TODO: Rewrite or update this test.
it.skip('should partially prerender pages that spread `use`ed params in
a client component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/semantics/one/build/layout-spread/client'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/build/page-spread/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at buildtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('build')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/layout-spread/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/semantics/one/run/page-spread/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#lowcard').text()).toBe('at runtime')
        expect($('#highcard').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#lowcard').text()).toBe('at buildtime')
        expect($('#highcard').text()).toBe('at buildtime')
expect($('#highcard-fallback').text()).toBe('loading highcard children')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-copied-lowcard').text()).toBe('one')
        expect($('#param-copied-highcard').text()).toBe('run')
        expect($('#param-key-count').text()).toBe('2')
        expect(getLines('Route "/params')).toEqual([])
      }
    })
  })

  describe('Param Shadowing', () => {
it('should correctly allow param names like then, value, and status when
awaiting params in a server component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/shadowing/foo/bar/baz/qux/layout/server'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/shadowing/foo/bar/baz/qux/page/server')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      }
    })

// Since #85155, we intentionally omit search params from client
segments
    // if the page is otherwise static, and resume using a client fetch
// instead. So it's expected that the value is missing pre-hydration.
// There are separate tests that verify that it is eventually hydrated.
    // TODO: Rewrite or update this test.
it.skip('should correctly allow param names like then, value, and status
when `use`ing params in a client component', async () => {
      expect(getLines('Route "/params')).toEqual([])
      let $ = await next.render$(
        '/params/shadowing/foo/bar/baz/qux/layout/client'
      )
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      }

$ = await next.render$('/params/shadowing/foo/bar/baz/qux/page/client')
      if (isNextDev) {
        expect($('#layout').text()).toBe('at runtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      } else {
        expect($('#layout').text()).toBe('at buildtime')
        expect($('#page').text()).toBe('at runtime')
        expect($('#param-dyn').text()).toBe('foo')
        expect($('#param-then').text()).toBe('bar')
        expect($('#param-value').text()).toBe('baz')
        expect($('#param-status').text()).toBe('qux')
        expect(getLines('Route "/params')).toEqual([])
      }
    })
  })

  if (!isNextDev) {
    describe('generateStaticParams', () => {
// This test is skipped as the previous workaround of using
`fetch-cache` will no longer be supported with DIO.
it.skip('should have cacheComponents semantics inside
generateStaticParams', async () => {
// This test is named what we want but our current implementation is not
actually correct yet.
// We are asserting current behavior and will update the test when we
land the correct behavior

        const lines: Array<string> = next.cliOutput.split('\n')
        let i = 0
        while (true) {
          const line = lines[i++]
          if (typeof line !== 'string') {
            throw new Error(
'Could not find expected route output for
/params/generate-static-params/[slug]/page/...'
            )
          }

          if (
            line.startsWith('├') &&
            line.includes('/params/generate-static-params/[slug]')
          ) {
            let nextLine = lines[i++]
            // we expect the fallback shell first
expect(nextLine).toContain('/params/generate-static-params/[slug]')
            nextLine = lines[i++]

            expect(nextLine).toMatch(
              /\/params\/generate-static-params\/\d+\/page/
            )
            nextLine = lines[i++]
// Because we force-cache we only end up with one prebuilt page.
// When cacheComponents semantics are fully respected we will end up
with two.
            expect(nextLine).not.toMatch(
              /\/params\/generate-static-params\/\d+\/page/
            )
            break
          }
        }
      })
    })
  }
})`
- ID 34:
`test/e2e/app-dir/cache-components/cache-components.random.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should not have route specific errors', async () => {
    expect(next.cliOutput).not.toMatch('Error: Route "/')
expect(next.cliOutput).not.toMatch('Error occurred prerendering page')
  })

it('should prerender pages with cached Math.random() calls', async () =>
{
    let $ = await next.render$('/random/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })
})`
- ID 35:
`test/e2e/app-dir/cache-components/cache-components.routes.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  let cliIndex = 0
  beforeEach(() => {
    cliIndex = next.cliOutput.length
  })
  function getLines(containing: string): Array<string> {
    const warnings = next.cliOutput
      .slice(cliIndex)
      .split('\n')
      .filter((l) => l.includes(containing))

    cliIndex = next.cliOutput.length
    return warnings
  }

it('should not prerender GET route handlers that use dynamic APIs',
async () => {
    let str = await next.render('/routes/dynamic-cookies', {})
    let json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(json.type).toEqual('cookies')

    str = await next.render('/routes/dynamic-headers', {})
    json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(json.type).toEqual('headers')

    str = await next.render('/routes/dynamic-stream', {})
    json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(json.message).toEqual('dynamic stream')

    str = await next.render('/routes/dynamic-url?foo=bar', {})
    json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(json.search).toEqual('?foo=bar')
  })

it('should prerender GET route handlers that have entirely cached io
(fetches)', async () => {
    let str = await next.render('/routes/fetch-cached', {})
    let json = JSON.parse(str)

    let random1 = json.random1
    let random2 = json.random2

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(typeof random1).toBe('string')
      expect(typeof random2).toBe('string')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(typeof random1).toBe('string')
      expect(typeof random2).toBe('string')
    }

    str = await next.render('/routes/fetch-cached', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(random1).toEqual(json.random1)
      expect(random2).toEqual(json.random2)
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(random1).toEqual(json.random1)
      expect(random2).toEqual(json.random2)
    }
  })

it('should not prerender GET route handlers that have some uncached io
(fetches)', async () => {
    let str = await next.render('/routes/fetch-mixed', {})
    let json = JSON.parse(str)

    let random1 = json.random1
    let random2 = json.random2

    expect(json.value).toEqual('at runtime')
    expect(typeof random1).toBe('string')
    expect(typeof random2).toBe('string')

    str = await next.render('/routes/fetch-mixed', {})
    json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(random1).toEqual(json.random1)
    expect(random2).not.toEqual(json.random2)
  })

it('should prerender GET route handlers that have entirely cached io
(unstable_cache)', async () => {
    let str = await next.render('/routes/io-cached', {})
    let json = JSON.parse(str)

    let message1 = json.message1
    let message2 = json.message2

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(typeof message1).toBe('string')
      expect(typeof message2).toBe('string')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(typeof message1).toBe('string')
      expect(typeof message2).toBe('string')
    }

    str = await next.render('/routes/io-cached', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(message1).toEqual(json.message1)
      expect(message2).toEqual(json.message2)
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(message1).toEqual(json.message1)
      expect(message2).toEqual(json.message2)
    }
  })

it('should prerender GET route handlers that have entirely cached io
("use cache")', async () => {
    let str = await next.render('/routes/use_cache-cached', {})
    let json = JSON.parse(str)

    let message1 = json.message1
    let message2 = json.message2

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(typeof message1).toBe('string')
      expect(typeof message2).toBe('string')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(typeof message1).toBe('string')
      expect(typeof message2).toBe('string')
    }

    str = await next.render('/routes/use_cache-cached', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(message1).toEqual(json.message1)
      expect(message2).toEqual(json.message2)
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(message1).toEqual(json.message1)
      expect(message2).toEqual(json.message2)
    }
  })

it('should not prerender GET route handlers that have some uncached io
(unstable_cache)', async () => {
    let str = await next.render('/routes/io-mixed', {})
    let json = JSON.parse(str)

    let message1 = json.message1
    let message2 = json.message2

    expect(json.value).toEqual('at runtime')
    expect(typeof message1).toBe('string')
    expect(typeof message2).toBe('string')

    str = await next.render('/routes/io-mixed', {})
    json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(message1).toEqual(json.message1)
    expect(message2).not.toEqual(json.message2)
  })

it('should prerender GET route handlers that complete synchronously or
in a microtask', async () => {
    let str = await next.render('/routes/microtask', {})
    let json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.message).toBe('microtask')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.message).toBe('microtask')
    }

    str = await next.render('/routes/static-stream-sync', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.message).toBe('stream response')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.message).toBe('stream response')
    }

    str = await next.render('/routes/static-stream-async', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.message).toBe('stream response')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.message).toBe('stream response')
    }

    str = await next.render('/routes/static-string-sync', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.message).toBe('string response')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.message).toBe('string response')
    }

    str = await next.render('/routes/static-string-async', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.message).toBe('string response')
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.message).toBe('string response')
    }
  })

it('should not prerender GET route handlers that complete in a new
Task', async () => {
    let str = await next.render('/routes/task', {})
    let json = JSON.parse(str)

    expect(json.value).toEqual('at runtime')
    expect(json.message).toBe('task')
  })

it('should prerender GET route handlers when accessing params', async ()
=> {
    expect(getLines('Route "/routes/[dyn]')).toEqual([])
    let str = await next.render('/routes/1', {})
    let json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.type).toBe('dynamic params')
      expect(json.param).toBe('1')
      expect(getLines('Route "/routes/[dyn]')).toEqual([])
    } else {
      expect(json.value).toEqual('at buildtime')
      expect(json.type).toBe('dynamic params')
      expect(json.param).toBe('1')
      expect(getLines('Route "/routes/[dyn]')).toEqual([])
    }

    str = await next.render('/routes/2', {})
    json = JSON.parse(str)

    if (isNextDev) {
      expect(json.value).toEqual('at runtime')
      expect(json.type).toBe('dynamic params')
      expect(json.param).toBe('2')
      expect(getLines('Route "/routes/[dyn]')).toEqual([])
    } else {
      expect(json.value).toEqual('at runtime')
      expect(json.type).toBe('dynamic params')
      expect(json.param).toBe('2')
      expect(getLines('Route "/routes/[dyn]')).toEqual([])
    }
  })
})`
- ID 36:
`test/e2e/app-dir/cache-components/cache-components.search.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

it('should partially prerender pages that await searchParams in a server
component', async () => {
    let $ = await next.render$('/search/server/await?sentinel=hello')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#value').text()).toBe('hello')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('main').text()).toContain('inner loading...')
      expect($('main').text()).not.toContain('outer loading...')
      expect($('#value').text()).toBe('hello')
      expect($('#page').text()).toBe('at runtime')
    }
  })

it('should partially prerender pages that `use` searchParams in a server
component', async () => {
    let $ = await next.render$('/search/server/use?sentinel=hello')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#value').text()).toBe('hello')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('main').text()).toContain('inner loading...')
      expect($('main').text()).not.toContain('outer loading...')
      expect($('#value').text()).toBe('hello')
      expect($('#page').text()).toBe('at runtime')
    }
  })

it('should partially prerender pages that `use` searchParams in a client
component', async () => {
    let $ = await next.render$('/search/client/use?sentinel=hello')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#value').text()).toBe('hello')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('main').text()).toContain('inner loading...')
      expect($('main').text()).not.toContain('outer loading...')
// Since #85155, we intentionally omit search params from client
segments
// if the page is otherwise static, and resume using a client fetch
// instead. So it's expected that the value is missing pre-hydration.
// There are separate tests that verify that it is eventually hydrated.
      // TODO: Rewrite or update this test.
      // expect($('#value').text()).toBe('hello')
      // expect($('#page').text()).toBe('at runtime')
      expect($('#value').text()).toBe('')
      expect($('#page').text()).toBe('')
    }
  })
})`
- ID 37: `test/e2e/app-dir/cache-components/cache-components.test.ts` —
`describe('cache-components', () => {
  const { next, isNextDev, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  it('should not have route specific errors', async () => {
    expect(next.cliOutput).not.toMatch('Error: Route "/')
expect(next.cliOutput).not.toMatch('Error occurred prerendering page')
  })

  if (isNextDev) {
    it('should not log not-found errors', async () => {
      const cliOutputLength = next.cliOutput.length
      await next.browser('/cases/not-found')
      const cliOutput = next.cliOutput.slice(cliOutputLength)
expect(cliOutput).not.toMatch('Error: NEXT_HTTP_ERROR_FALLBACK;404')
      expect(cliOutput).not.toMatch('unhandledRejection')
    })
  } else {
it('should not warn about potential memory leak for even listeners on
AbortSignal', async () => {
      expect(next.cliOutput).not.toMatch('MaxListenersExceededWarning')
    })
  }

  it('should prerender fully static pages', async () => {
    let $ = await next.render$('/cases/static', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }

    $ = await next.render$('/cases/static_async', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

  it('should prerender static not-found pages', async () => {
// Using `browser` instead of `render$` because error pages must be
hydrated
    // apparently.
    const browser = await next.browser('/cases/not-found')

    if (isNextDev) {
expect(await browser.elementById('layout').text()).toBe('at runtime')
expect(await browser.elementById('page').text()).toBe('at runtime')
    } else {
expect(await browser.elementById('layout').text()).toBe('at buildtime')
expect(await browser.elementById('page').text()).toBe('at buildtime')
    }
  })

it('should render not-found with Suspense in layout without connection
errors', async () => {
    const browser = await next.browser('/cases/not-found-suspense')

    // The custom not-found component should render
    expect(await browser.elementById('not-found-text').text()).toBe(
      'Custom 404 - Not Found'
    )

    // The async Suspense content in the layout should also render
    expect(await browser.elementById('async-data').text()).toBe(
      'Async Data Loaded'
    )
  })

  it('should prerender pages that render in a microtask', async () => {
    let $ = await next.render$('/cases/microtask', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }

    $ = await next.render$('/cases/microtask_deep_tree', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that take longer than a task to
render', async () => {
    let $ = await next.render$('/cases/task', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      // The inner slot is computed during the prerender but is hidden
      // it gets revealed when the resume happens
      expect($('#inner').text()).toBe('at buildtime')
    }
  })

it('should prerender pages that only use cached fetches', async () => {
    const $ = await next.render$('/cases/fetch_cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that use at least one fetch without
cache', async () => {
    let $ = await next.render$('/cases/fetch_mixed', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
    }
  })

it('should prerender pages that only use cached (unstable_cache) IO',
async () => {
    const $ = await next.render$('/cases/io_cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should prerender pages that only use cached ("use cache") IO', async
() => {
    const $ = await next.render$('/cases/use_cache_cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

  it('should prerender pages that cached the whole page', async () => {
    const $ = await next.render$('/cases/full_cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that do any uncached IO', async ()
=> {
    let $ = await next.render$('/cases/io_mixed', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that do any uncached IO (use
cache)', async () => {
    let $ = await next.render$('/cases/use_cache_mixed', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that use `cookies()`', async () =>
{
    let $ = await next.render$('/cases/dynamic_api_cookies', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
      expect($('#value').text()).toBe('hello')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
      expect($('#value').text()).toBe('hello')
    }
  })

it('should partially prerender pages that use `headers()`', async () =>
{
    let $ = await next.render$('/cases/dynamic_api_headers')
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
      expect($('#value').text()).toBe('hello')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
      expect($('#value').text()).toBe('hello')
    }
  })

it('should fully prerender pages that use `unstable_noStore()`', async
() => {
    let $ = await next.render$('/cases/dynamic_api_no_store', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
    }
  })

it('should partially prerender pages that use `searchParams` in Server
Components', async () => {
    let $ = await next.render$(
      '/cases/dynamic_api_search_params_server?sentinel=my+sentinel',
      {}
    )
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
      expect($('#value').text()).toBe('my sentinel')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#inner').text()).toBe('at buildtime')
      expect($('#value').text()).toBe('my sentinel')
    }
  })

it('should partially prerender pages that use `searchParams` in Client
Components', async () => {
    let $ = await next.render$(
      '/cases/dynamic_api_search_params_client?sentinel=my+sentinel',
      {}
    )
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#inner').text()).toBe('at runtime')
      expect($('#value').text()).toBe('my sentinel')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
// The second component renders before the first one aborts so we end up
      // capturing the static value during buildtime
      expect($('#inner').text()).toBe('at buildtime')
// Since there was no dynamic data access on this page, the search
params
      // are completely ommitted from the HTML document and filled in by
      // the client
      expect($('#value').text()).toBe('')
      expect($('#fallback-component-one-').text()).toBe('loading...')
    }
  })

it('can prerender pages with parallel routes that are static', async ()
=> {
    const $ = await next.render$('/cases/parallel/static', {})

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page-slot').text()).toBe('at buildtime')
      expect($('#page-children').text()).toBe('at buildtime')
    }
  })

it('can prerender pages with parallel routes that resolve in a
microtask', async () => {
    const $ = await next.render$('/cases/parallel/microtask', {})

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page-slot').text()).toBe('at buildtime')
      expect($('#page-children').text()).toBe('at buildtime')
    }
  })

it('does not prerender pages with parallel routes that resolve in a
task', async () => {
    const $ = await next.render$('/cases/parallel/task', {})

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at buildtime')
    }
  })

it('does not prerender pages with parallel routes that uses a dynamic
API', async () => {
    let $ = await next.render$('/cases/parallel/no-store', {})

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page-slot').text()).toBe('at buildtime')
      expect($('#page-children').text()).toBe('at buildtime')
    }

    $ = await next.render$('/cases/parallel/cookies', {})

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at runtime')
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page-slot').text()).toBe('at runtime')
      expect($('#page-children').text()).toBe('at buildtime')
    }
  })

  if (isNextStart) {
it('should ignore late setHeader calls for direct RSC handlers after
headers are sent', async () => {
      const pageModulePath = path.join(
        next.testDir,
        '.next',
        'server',
        'app',
        'cases',
        'static',
        'page.js'
      )
      const previousCwd = process.cwd()
      const port = await findPort()
      let server: http.Server | undefined
      let handlerError: unknown
      let lateHeaderAttempted = false
      let lateHeaderError: unknown
      let resolveHandled: (() => void) | undefined
      const handled = new Promise<void>((resolve) => {
        resolveHandled = resolve
      })

      try {
        process.chdir(next.testDir)

        const { handler } = require(pageModulePath) as {
          handler: (
            req: http.IncomingMessage,
            res: http.ServerResponse,
            ctx: {
              requestMeta?: Record<string, unknown>
              waitUntil?: (promise: Promise<void>) => void
            }
          ) => Promise<void>
        }

        server = http.createServer(async (req, res) => {
          const originalWriteHead = res.writeHead.bind(res)
          res.writeHead = ((...args: any[]) => {
            const result = originalWriteHead(...args)

            if (!lateHeaderAttempted) {
              lateHeaderAttempted = true

              try {
                res.setHeader('x-test-late', '1')
              } catch (error) {
                lateHeaderError = error
              }
            }

            return result
          }) as typeof res.writeHead

          try {
            await handler(req, res, {
              waitUntil: () => {},
              requestMeta: {
                initURL: `https://localhost:${port}${req.url ?? '/'}`,
                minimalMode: true,
                relativeProjectDir: '.',
              },
            })
          } catch (error) {
            handlerError = error

            if (!res.writableEnded) {
              if (!res.headersSent) {
                res.statusCode = 500
              }
              res.end()
            }
          } finally {
            resolveHandled?.()
          }
        })

        await new Promise<void>((resolve, reject) => {
          server.listen(port, () => {
            resolve()
          })
          server.once('error', reject)
        })

        const stateTree = JSON.stringify(['', {}])
const requestUrl = new URL('/cases/static', `http://localhost:${port}`)
        const cacheBustingParam = await computeCacheBustingSearchParam(
          undefined,
          undefined,
          stateTree,
          undefined
        )

        if (cacheBustingParam) {
          requestUrl.searchParams.set('_rsc', cacheBustingParam)
        }

        const res = await fetchViaHTTP(
          port,
          requestUrl.pathname + requestUrl.search,
          undefined,
          {
            headers: {
              rsc: '1',
              'next-router-state-tree': stateTree,
            },
            redirect: 'manual',
          }
        )
        const flight = await res.text()

        expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('text/x-component')
        await handled

        expect(handlerError).toBeUndefined()
        expect(lateHeaderAttempted).toBe(true)
        expect(lateHeaderError).toBeUndefined()
        expect(flight.length).toBeGreaterThan(0)
      } finally {
        process.chdir(previousCwd)

        if (server) {
          await new Promise<void>((resolve, reject) => {
            server.close((error) => {
              if (error) {
                reject(error)
                return
              }

              resolve()
            })
          })
        }
      }
    })
  }

it('should not resume when client components are dynamic but the RSC
render was static', async () => {
    let html = await next.render('/cases/static-rsc-dynamic-client', {})
    const $ = cheerio.load(html)

    // Confirm the HTML document was sent completely
    expect(html).toContain('</body></html>')

    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      // In dev we SSR the time
      expect($('#time').length).toBe(1)
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
// Confirm the time span is not part of the completed HTML document
      expect($('#time').length).toBe(0)
    }

const browser = await next.browser('/cases/static-rsc-dynamic-client')

    const now = new Date()

    if (isNextDev) {
expect(await browser.elementById('layout').text()).toBe('at runtime')
expect(await browser.elementById('page').text()).toBe('at runtime')
      // Assert that we rendered a time within the last couple seconds.
      const inPageDate = new Date(
        await browser.waitForElementByCss('#time').text()
      )
      expect(inPageDate.getTime() - now.getTime()).toBeLessThan(2000)
    } else {
expect(await browser.elementById('layout').text()).toBe('at buildtime')
expect(await browser.elementById('page').text()).toBe('at buildtime')
      // Assert that we rendered a time within the last 2 seconds.
      const inPageDate = new Date(
        await browser.waitForElementByCss('#time').text()
      )
      expect(inPageDate.getTime() - now.getTime()).toBeLessThan(2000)
    }
  })
})`
- ID 38:
`test/e2e/app-dir/cache-components/cache-components.web-crypto.test.ts`
— `describe('cache-components', () => {
  const { next, isNextDev } = nextTestSetup({
    files: __dirname,
  })

  it('should not have route specific errors', async () => {
    expect(next.cliOutput).not.toMatch('Error: Route "/')
expect(next.cliOutput).not.toMatch('Error occurred prerendering page')
  })

it('should prerender pages with cached `crypto.getRandomValues(...)`
calls', async () => {
let $ = await next.render$('/web-crypto/get-random-values/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })

it('should prerender pages with cached `crypto.randomUUID()` calls',
async () => {
    let $ = await next.render$('/web-crypto/random-uuid/cached', {})
    if (isNextDev) {
      expect($('#layout').text()).toBe('at runtime')
      expect($('#page').text()).toBe('at runtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    } else {
      expect($('#layout').text()).toBe('at buildtime')
      expect($('#page').text()).toBe('at buildtime')
      expect($('#first').text()).not.toEqual($('#second').text())
    }
  })
})`
- ID 40:
`test/e2e/app-dir/instant-validation-static-shells/instant-validation-static-shells.test.ts`
— `describe('instant validation - opting out of static shells', () => {
  const { next, isNextDev } = nextTestSetup({
    files: join(__dirname, 'fixtures', 'valid'),
  })

// NOTE: if something's wrong in build, we'll fail before any tests run.
  // Visiting the pages is mostly just a sanity check.

it('does not require a static shell if a root layouts is configured as
blocking', async () => {
    const browser = await next.browser('/blocking-root-layout')
    await browser.elementByCss('main')
    if (isNextDev) await waitForNoErrorToast(browser)
  })
it('does not require a static shell if a layout is configured as
blocking', async () => {
    const browser = await next.browser('/blocking-layout')
    await browser.elementByCss('main')
    if (isNextDev) await waitForNoErrorToast(browser)
  })
it('does not require a static shell if a page is configured as
blocking', async () => {
    const browser = await next.browser('/blocking-page')
    await browser.elementByCss('main')
    if (isNextDev) await waitForNoErrorToast(browser)
  })
})`
- ID 42:
`test/e2e/app-dir/non-rsc-router-prefetch/non-rsc-router-prefetch.test.ts`
— `describe('non-rsc-router-prefetch', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  beforeAll(async () => {
    const res = await next.fetch('/')
    await res.text()
  })

it('ignores the router prefetch header for HTML requests', async () => {
    const res = await next.fetch('/', {
      headers: {
        [NEXT_ROUTER_PREFETCH_HEADER]: '1',
      },
      signal: AbortSignal.timeout(5_000),
    })
    const html = await res.text()

    expect(res.status).toBe(200)
    expect(res.headers.get('content-type')).toContain('text/html')
    expect(html).toContain('hello world')
  })

  it('honors the router prefetch header for RSC requests', async () => {
    const res = await next.fetch('/', {
      headers: {
        [RSC_HEADER]: '1',
        [NEXT_ROUTER_PREFETCH_HEADER]: '1',
      },
    })

    expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('text/x-component')
  })
})`
- ID 56:
`test/e2e/app-dir/use-cache-infinity-profile/use-cache-infinity-profile.test.ts`
— `describe('use-cache-infinity-profile', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
  })

it('caches forever with a configured profile using Infinity revalidate
and expire', async () => {
    const $ = await next.render$('/')
    const initialValue = $('#value').text()
    expect(initialValue).toMatch(uuidRegExp)

// An infinite cache life must not degrade into a dynamic cache life, so
    // the value stays the same across requests instead of regenerating.
    const $second = await next.render$('/')
    expect($second('#value').text()).toBe(initialValue)

    if (isNextStart) {
      // The page must be fully prerendered at build time.
const prerendered = await next.readFile('.next/server/app/index.html')
      expect(prerendered).toContain(initialValue)
    }
  })

it('serves an inline Infinity cache life from a JSON-backed cache
handler across requests', async () => {
    const $ = await next.render$('/inline?key=a')
    const initialValue = $('#value').text()
    expect(initialValue).toMatch(uuidRegExp)

// The second request reads the entry back from the cache handler. If
the
// infinite cache life doesn't survive the handler's JSON round trip,
the
// entry is treated as immediately expired and the value regenerates.
    const $second = await next.render$('/inline?key=a')
    expect($second('#value').text()).toBe(initialValue)
  })
})`
- ID 57:
`test/e2e/app-dir/use-cache-og-image-top-level-await/use-cache-og-image-top-level-await.test.ts`
— `describe('use-cache-og-image-top-level-await', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
    skipStart: true,
  })

  if (isNextStart) {
    beforeAll(async () => {
await next.build({ args: ['--experimental-build-mode', 'compile'] })
    })

it('should prerender a page whose opengraph image uses a top-level
await', async () => {
      const { exitCode, cliOutput } = await next.build({
        args: [
          '--experimental-build-mode',
          'generate',
          '--debug-build-paths',
          'app/[slug]/page.tsx,app/[slug]/opengraph-image.tsx',
        ],
      })

      expect(cliOutput).not.toContain(
        'Unexpected cache miss after cache warming phase'
      )
      expect(cliOutput).not.toContain(
'Next.js encountered uncached or runtime data in `generateMetadata()`'
      )
      expect(exitCode).toBe(0)

// The image route uses generateStaticParams, so the build is expected
      // to prerender it for each param.
      expect(cliOutput).toMatch(/● \/first-post\/opengraph-image/)
      expect(cliOutput).toMatch(/● \/second-post\/opengraph-image/)
    })
  } else {
    beforeAll(async () => {
      await next.start()
    })

it('should render a page whose opengraph image uses a top-level await',
async () => {
      const $ = await next.render$('/first-post')
      expect($('article').text()).toBe('First Post')

      const res = await next.fetch('/first-post/opengraph-image')
      expect(res.status).toBe(200)
      expect(res.headers.get('content-type')).toBe('image/png')
    })
  }
})`
- ID 58:
`test/e2e/app-dir/use-cache-output-export/use-cache-output-export.test.ts`
— `describe('use-cache-output-export', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
    skipStart: process.env.NEXT_TEST_MODE !== 'dev',
  })

  if (process.env.__NEXT_CACHE_COMPONENTS === 'true') {
    return it.skip('for PPR', () => {
      // PPR is not compatible with `output: 'export'`.
    })
  }

  it('should work', async () => {
    let html: string
    let server: Server | undefined

    if (isNextStart) {
      const { cliOutput } = await next.build()

      expect(cliOutput).not.toInclude(
        'Server Actions are not supported with static export.'
      )

      server = await startCleanStaticServer(join(next.testDir, 'out'))
      const { port } = server.address() as AddressInfo
      html = await renderViaHTTP(port, '/')
    } else {
      html = await next.render('/')
    }

    expect(html).toMatch(/<p>[0,1]\.\d+<\/p>/)

    if (server) {
      await new Promise((resolve) => server.close(resolve))
    }
  })
})`

</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/app-dir/app-prefetch/prefetching.stale-times.test.ts` —
`describe('app dir - prefetching (custom staleTime)', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710370);
Cache Components excluded by manifest.
- `test/e2e/app-dir/cache-components-errors/module-scope.test.ts` —
`describe('Lazy Module Init', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710410);
Cache Components excluded by manifest.
- `test/e2e/app-dir/cache-components/cache-components.params.test.ts` —
`describe('cache-components', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710449);
Cache Components excluded by manifest.
-
`test/e2e/app-dir/instant-validation-static-shells/instant-validation-static-shells.test.ts`
— `describe('instant validation - opting out of static shells', () =>
{`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710456),
[cache](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710457).
-
`test/e2e/app-dir/use-cache-output-export/use-cache-output-export.test.ts`
— `describe('use-cache-output-export', () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710496),
[cache](https://github.com/vercel/next.js/actions/runs/34426785098/job/102728710503);
Cache Components explicitly skipped for PPR.

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:05 -07:00
Hendrik Liebau bfcf687f5a Fix unexpected query parameters in adapter deployments (#98584)
The Vercel adapter enables `passQuery: true`, which forwards named route
captures to prerender functions as query parameters instead of using the
legacy route-matches header. When a request bypasses prerendered output,
this also exposes internal path-building captures through application
`searchParams`. With `experimental.collapseAdapterRoutes` disabled, RSC
renders can receive `rscSuffix=.rsc`. With the flag enabled, document
and Server Action renders can also receive `rscSuffix=`, and collapsed
fallback shells can expose values such as `shellPrefix=en`. These
additions can overwrite user query parameters and cause unnecessary
navigation retries.

We fix this in `handleBuildComplete`, where Next.js generates these
path-only helper captures. Unnamed captures and numbered destination
references preserve the rewrite behavior without requiring adapters to
recognize and filter Next.js's internal helper capture names.

When using an adapter, we now emit unnamed captures for these path-only
values. For example, `(?<shellPrefix>de|en)` and its destination
reference `$shellPrefix` become `(de|en)` and `$1` when no locale
capture precedes the group. Likewise, `(?<rscSuffix>...)` and
`$rscSuffix` become `(...)` and a positional reference such as `$2` when
one capture precedes the suffix. The generator accounts for parameter
and locale captures and escapes literal base paths. Destination
substitution runs once, preserving captured text and correctly
distinguishing references such as `$1` and `$10`.

Cache-bypass rules, route counts, and route ordering remain unchanged.
User-supplied `rscSuffix` and `shellPrefix` remain valid query
parameters. Standard `next dev` and `next start` routing, and Vercel's
legacy deployment path, do not use these generated adapter routes, and
thus are not affected.

Verified by running the [full deploy test
matrix](https://github.com/vercel/next.js/actions/runs/34835432358)
against this PR.
2026-09-15 16:26:35 +02:00
Hendrik Liebau 6866b9442c Keep never-prerenderable params out of self-hosted shells (#98612)
With Cache Components enabled and Partial Prefetching disabled, blocking
prerenders in `next start` could cache params that no
`generateStaticParams` result supplies. For `/[top]/[bottom]`, where
only `top` is supplied, this included `bottom` in the static HTML and
created a separate cache entry for each bottom value.

Adapter deployments already enforce this rule. The legacy Vercel builder
has a known limitation that the existing prerender tests explicitly
exclude.

#95872 established that these params must remain dynamic. #96297 later
put self-hosted eligibility-based shell keys and fallback-param
selection behind the `partialPrefetching` flag, alongside automatic
fallback upgrades. #98512 then made resumes honor the parameter set
recorded by the selected prerender. That did not create the invalid
prerenders, but allowed their over-resolved params to enter Cached
Navigations.

The RSC test added in #98512 did not account for that known
legacy-builder limitation and consequently failed in this [canary deploy
run](https://github.com/vercel/next.js/actions/runs/34659100427/job/103469344769).

For `next start`, shell keys and unresolved params now follow GSP
eligibility even when upgrades are disabled. Cache writes, revalidation,
and navigation RDC reads use the same key, including shared shells that
cannot be completed further. Routes whose params are all prerenderable
retain their existing keys when Partial Prefetching is disabled.
Automatic upgrade triggers remain gated.

The aforementioned `cached-navigations` tests now separate cold RSC
navigation from initial HTML and assert eligibility independently of the
observed response. Added coverage checks shell sharing, explicit
revalidation, and generated optional paths. Legacy-builder failures are
documented with expected-failure gates; its implementation remains
unchanged.

Verified by running the [full deploy test
matrix](https://github.com/vercel/next.js/actions/runs/34771777150)
against this PR.
2026-09-15 16:26:34 +02:00
Jimmy Miller c70b96b373 Fix addMissingDependency (#98588)
While before this change we had addMissingDependency, we didn't properly
respond when the file for that missing dep was added.

The way webpack does this internally is not the exact same shape as the
IPC here, it has them as a separate field. I could not find any reason
not to just add them to the filePaths here. So that's what I did to keep
things simple.
2026-09-15 07:21:32 -07:00
Alex Kostyniuk 95d3d8ec81 docs: clarify metadata support in root not-found (#98670)
docs: document metadata/generateMetadata in root `app/not-found.js` as
it works, but docs don't specify it
2026-09-15 13:10:09 +02:00
Benjamin Staneck 4731e968b7 Pages Router: do not return the __appRouter prefetch marker as route info on shallow navigation (#98187)
### What?

`Router.prefetch()` in the Pages Router evaluates the client router
filter (`_bfl`) against the `as` path, but stored the resulting `{
__appRouter: true }` marker in `router.components` under the `href`
pathname. `Router.change()` looked the marker up with the `href`
pathname as well. This PR keys the marker by the normalized `as`
pathname and effective locale, preserves existing cache entries, and
checks for markers before and after route resolution.

### Why?

`href` and `as` are the same URL for most links, so the mismatch was
invisible. They differ for the "route as modal" pattern
(`examples/with-route-as-modal`): `href` stays on the current route
(`router.pathname` + query) and `as` shows the pretty URL. When the
filter matches `as`, including a false positive, the marker could
replace the cached route info of the page the user is currently on:

- On a static route (`/`), the `change()` guard fired for later shallow
navigation and hard-navigated.
- On a dynamic route, the marker was stored under the pattern
(`/players/[name]`) but looked up with the concrete path
(`/players/alice`). The guard could miss it, and shallow navigation
could render without page props, causing missing content or an
application error.

A prefetch must also preserve already-loaded route info when its key
matches the current route. This can happen when the page was reached
through a rewrite and its canonical URL is prefetched. Hash-only
navigation renders that cached entry directly, so preserving it lets the
normal client-side hash update retain the page props and update
`router.asPath`.

Fixes #98180

### How?

- `getAppRouterMarkerKey(router, as, locale)` parses the pathname,
normalizes the trailing slash, and preserves the effective locale. It
expects a path without `basePath` and does not strip that prefix again.
An explicit locale prefix takes precedence over the supplied locale.
- The helper returns `null` for non-local URLs, leaving their existing
navigation handling unchanged.
- `prefetch()` stores the marker under that key only when the
corresponding cache entry is absent. It does not replace loaded route
info.
- `change()` checks both the normalized `as` key and the `href` route.
It checks the resolved route again after config rewrites, before
`getRouteInfo()` reads the cache.
- Non-shallow navigations continue to consult the client router filter
directly. Hash-only navigation retains its normal client-side path.

The complementary change in #98650 (adopts #98187) adds defensive checks
when `getRouteInfo()` reads marker entries.

### Tests

The e2e suites run in production (`next start`) and deploy modes.
`router.prefetch()` is a no-op in development.

`test/e2e/app-dir/pages-prefetch-as-app-route` covers:

- Hard navigation to an App Router destination after prefetching a link
whose `href` and `as` differ.
- Shallow navigation on static and dynamic Pages Router routes without a
reload or lost server-provided props.
- Hard navigation when the `href` route holds a marker and `as` differs,
or a config rewrite resolves to a marked route.
- Client-side hash navigation after an awaited prefetch of the current
route's canonical URL, with preserved props and an updated
`router.asPath`.

`test/e2e/app-dir/pages-prefetch-as-app-route-base-path` covers an
internal `/docs` route with `basePath: '/docs'`, including navigation to
`/docs/docs` and an unaffected shallow update on the index page.

`test/e2e/app-dir/pages-prefetch-as-app-route-i18n` covers a French-only
redirect prefetch without forcing an unrelated English navigation to
reload. Marker-dependent tests wait for the specific key they need,
rather than any marker.

Runtime behaviour was also verified against the reproduction in
https://github.com/Stanzilla/next-pages-router-prefetch-bloom-filter-repro
by patching the compiled `router.js`. The e2e suite was not run locally.

Disclosure: this change and its description were prepared with AI
assistance (Claude Code) on behalf of the author.
2026-09-15 12:53:17 +02:00
Benjamin Staneck 5979942c73 Pages Router: key the client router filter prefetch marker by the as path (#98182)
### What?

`Router.prefetch()` in the Pages Router evaluates the client router
filter (`_bfl`) against the `as` path, but stored the resulting `{
__appRouter: true }` marker in `router.components` under the `href`
pathname. `Router.change()` looked the marker up with the `href`
pathname as well. This PR keys the marker by the normalized `as`
pathname and effective locale, preserves existing cache entries, and
checks for markers before and after route resolution.

### Why?

`href` and `as` are the same URL for most links, so the mismatch was
invisible. They differ for the "route as modal" pattern
(`examples/with-route-as-modal`): `href` stays on the current route
(`router.pathname` + query) and `as` shows the pretty URL. When the
filter matches `as`, including a false positive, the marker could
replace the cached route info of the page the user is currently on:

- On a static route (`/`), the `change()` guard fired for later shallow
navigation and hard-navigated.
- On a dynamic route, the marker was stored under the pattern
(`/players/[name]`) but looked up with the concrete path
(`/players/alice`). The guard could miss it, and shallow navigation
could render without page props, causing missing content or an
application error.

A prefetch must also preserve already-loaded route info when its key
matches the current route. This can happen when the page was reached
through a rewrite and its canonical URL is prefetched. Hash-only
navigation renders that cached entry directly, so preserving it lets the
normal client-side hash update retain the page props and update
`router.asPath`.

Fixes #98180

### How?

- `getAppRouterMarkerKey(router, as, locale)` parses the pathname,
normalizes the trailing slash, and preserves the effective locale. It
expects a path without `basePath` and does not strip that prefix again.
An explicit locale prefix takes precedence over the supplied locale.
- The helper returns `null` for non-local URLs, leaving their existing
navigation handling unchanged.
- `prefetch()` stores the marker under that key only when the
corresponding cache entry is absent. It does not replace loaded route
info.
- `change()` checks both the normalized `as` key and the `href` route.
It checks the resolved route again after config rewrites, before
`getRouteInfo()` reads the cache.
- Non-shallow navigations continue to consult the client router filter
directly. Hash-only navigation retains its normal client-side path.

The complementary change in #98650 (adopts #98187) adds defensive checks
when `getRouteInfo()` reads marker entries.

### Tests

The e2e suites run in production (`next start`) and deploy modes.
`router.prefetch()` is a no-op in development.

`test/e2e/app-dir/pages-prefetch-as-app-route` covers:

- Hard navigation to an App Router destination after prefetching a link
whose `href` and `as` differ.
- Shallow navigation on static and dynamic Pages Router routes without a
reload or lost server-provided props.
- Hard navigation when the `href` route holds a marker and `as` differs,
or a config rewrite resolves to a marked route.
- Client-side hash navigation after an awaited prefetch of the current
route's canonical URL, with preserved props and an updated
`router.asPath`.

`test/e2e/app-dir/pages-prefetch-as-app-route-base-path` covers an
internal `/docs` route with `basePath: '/docs'`, including navigation to
`/docs/docs` and an unaffected shallow update on the index page.

`test/e2e/app-dir/pages-prefetch-as-app-route-i18n` covers a French-only
redirect prefetch without forcing an unrelated English navigation to
reload. Marker-dependent tests wait for the specific key they need,
rather than any marker.

The fix was also verified at runtime against the reproduction in
https://github.com/Stanzilla/next-pages-router-prefetch-bloom-filter-repro
by patching the compiled `router.js` and re-running the scenario. The
e2e suite was not run locally.

Disclosure: this change and its description were prepared with AI
assistance (Claude Code) on behalf of the author.
2026-09-15 12:52:19 +02:00
Tobias Koppers 05c20cd1fb test: remove unsupported deployment ID builder cases (#98668)
### What?

Removes the legacy Vercel builder rows from the immutable-assets
deployment ID determinism test. Direct `.next` output and Vercel adapter
output remain covered, including both the standard and Cache Components
fixtures.

### Why?

Current `vercel@latest` rejects `_next/static/immutable` output when the
Next.js adapter is disabled. Disabling immutable assets is not a valid
replacement for these cases: it adds the deployment ID to asset URLs,
which intentionally changes content-hashed CSS filenames between builds.

This intentionally removes the two legacy-builder deploy-mode cases
rather than skipping or weakening their determinism assertions. The
current legacy builder no longer supports the immutable-output contract
the test verifies.

### How?

The Build Output API matrix now exercises only adapter mode, and
`NEXT_ENABLE_ADAPTER=1` is applied unconditionally. The shared two-build
comparison and all assertions for the retained direct and adapter cases
are unchanged. Builder-only mode branching and timeout handling are
removed.

### Verification

- `pnpm prettier --with-node-modules --ignore-path .prettierignore
--check test/production/deterministic-build/deployment-id.test.ts`
- `pnpm eslint --config eslint.config.mjs
test/production/deterministic-build/deployment-id.test.ts`
- `pnpm test-start-turbo
test/production/deterministic-build/deployment-id.test.ts` (3 tests
passed)

<!-- NEXT_JS_LLM -->

<!-- fleet 00754f3b-4965-4d6d-bc4c-4dbab7b188e4 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-15 09:59:08 +00:00
Joseph e29a96a9ee docs: add an intro paragraph to the glossary (#98654)
The glossary page goes straight from frontmatter to the first entry, `#
A` —  the word "glossary" appears nowhere in its body.

We index docs for search from the rendered content, and the page title
is stored as non-searchable metadata, so searching "glossary" on
nextjs.org does not return the glossary.

This adds a two-sentence intro, which gives the page a body chunk
carrying its own name.

```mdx
The Next.js documentation uses a recurring set of terms for routing, rendering, and caching. This glossary defines them.
```

Also reads better than a page that opens directly to content.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Aurora Scharff <66901228+aurorascharff@users.noreply.github.com>
2026-09-15 08:41:17 +00:00
next-js-bot[bot] 85e574a682 v16.4.0-canary.31 v16.4.0-canary.31 2026-09-14 23:21:48 +00:00
Luke Sandberg be76b0cf0c test(turbopack): strongly read test operations (#98247)
## Summary

Replace top-level consistency suppression in straightforward backend
tests with local operation roots that are read strongly consistently.
Inner operation functions keep each migration scoped to its test.

## Verification

- `cargo test -p turbo-tasks-backend --test filter_unused_args --test
panics --test task_statistics`
- `cargo fmt --all -- --check`

<!-- NEXT_JS_LLM -->

<!-- fleet 475180cd-06a7-4669-89b5-a2029a3a18c9 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Luke Sandberg <210140+lukesandberg@users.noreply.github.com>
2026-09-14 22:55:14 +00:00
Will Binns-Smith 975b9cff16 Add next dev wait for Turbopack shutdown (#98646)
## Summary

Add `NEXT_DEV_WAIT_FOR_TURBOPACK_SHUTDOWN` to make `next dev` wait for
Turbopack's full project shutdown before exiting. This waits for active
TurboTasks work and cache persistence instead of relying on fixed delays
or the parent's normal 100 ms child-exit timeout.

Use the option in the warm-restart task statistics test. Link documented
`Project` interface methods to their native binding documentation.

## Verification

- `pnpm --filter=next types`
- `npx eslint --config eslint.config.mjs
packages/next/src/cli/next-dev.ts
packages/next/src/server/dev/hot-reloader-turbopack.ts
test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`
- Pre-commit lint-staged checks
- CI tests passed

<!-- NEXT_JS_LLM -->
2026-09-14 13:44:01 -07:00
Joseph 25fe6f6f06 docs: document default turbopack.resolveExtensions (#98627)
Fixes: #98609

Editor note: Ensure this lands well into
https://github.com/vercel/next.js/pull/98407 too
2026-09-14 21:15:11 +02:00
Tobias Koppers b421cadefd test: migrate package CSS side-effects coverage (#98635)
### What?

Migrates the package CSS side-effects coverage from #70087 into the
current `css-order` end-to-end suite. The matrix covers `sideEffects`
values of `true`, `false`, broad CSS arrays, and global-CSS-only arrays
across App Router, Pages Router, client components, and server
components with client children.

### Why?

The older coverage never reached `canary`, and most of its Turbopack
cases were skipped as inconsistent. Keeping the scenarios in the current
suite verifies that package CSS ordering remains deterministic in both
Turbopack's default chunking mode and the graph chunker.

### How?

The test uses explicit synthetic package fixtures and reuses the suite's
existing Turbopack mode matrix, including the graph string and object
configurations. The packages are transpiled so Pages Router exercises
bundler CSS behavior rather than externalizing CSS imports to Node.
Invalid entrypoints, selectors, routes, and CSS module references from
the old fixtures were corrected during migration.

This does not include the webpack loader implementation change proposed
by #70087.

### Verification

- `HEADLESS=true pnpm test-dev-turbo
test/e2e/app-dir/css-order/css-order.test.ts` (189 passed, 114 existing
todos)
- `HEADLESS=true pnpm test-start-turbo
test/e2e/app-dir/css-order/css-order.test.ts` (291 passed, 12 existing
todos)
- `HEADLESS=true pnpm test-start-webpack
test/e2e/app-dir/css-order/css-order.test.ts` (192 passed, 8 existing
todos)

<!-- NEXT_JS_LLM -->

<!-- fleet 10f9743e-de76-437c-8fdc-76b7be42a9b9 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 18:11:17 +02:00
Tobias Koppers 3dc93da27e Improve tree shaking through Next.js module wrappers (#98638)
## What?

Extends the export-usage passthrough introduced by #98621 to the
transparent wrappers used for dynamic entries, server components, and
shared server utilities.

The existing reference-tree-shaking production fixture now covers each
wrapper and verifies that unused sibling exports do not remain in
Turbopack's server chunks.

## Why?

These wrappers forward another module's export surface, but previously
marked every target export as used. That kept otherwise unreachable
modules in production output and limited inner-graph tree shaking.

## How?

The dynamic-entry and server-component wrappers now pass their own
resolved export usage to their targets. The custom server-utility
reference reports the same passthrough binding usage while preserving
its shared chunking and merge behavior.

A public Turbopack constructor creates passthrough export-usage values
for references outside `turbopack-core`, consistent with the existing
constructors for other usage modes.

## Verification

- `cargo check -p next-core`
- `pnpm build-all`
- `pnpm test-start-turbo
test/production/app-dir/reference-tree-shaking/reference-tree-shaking.test.ts`
- `pnpm test-start-webpack
test/production/app-dir/reference-tree-shaking/reference-tree-shaking.test.ts`

<!-- NEXT_JS_LLM -->


<!-- fleet df1472be-5cd1-4bd6-9fab-4efc1defa4f9 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 17:53:21 +02:00
Tobias Koppers e3ababa0d5 Trace export usage through client component proxies (#98621)
## Summary

Turbopack previously widened export usage to every export when a Server
Component crossed a client-component proxy. As a result, importing one
named export from a `use client` module retained unrelated
implementations in both browser and SSR bundles.

This extends Turbopack's import annotations with a generic export-usage
passthrough. The server transform for `use client` modules marks its
generated namespace import as passthrough, so the ordinary binding-usage
fixed point carries the Server Component's used export set into
`EcmascriptClientReferenceModule`. Its client-reference edges use the
same signal to forward that set to browser and SSR targets.
Side-effect-only evaluation references retain their evaluation semantics
instead of inheriting the annotation. The forwarded names remain
namespace-observable because React Flight resolves client references by
their original export names, so unused exports can be removed without
changing the protocol-visible identity. An importer whose usage is `All`
still forwards `All`.

The client-component tree-shaking test now checks that unused markers
are absent from every browser chunk and verifies the same behavior in
Turbopack SSR chunks.

## Verification

- `cargo fmt --all -- --check`
- `cargo check -p next-core`
- `cargo test -p turbopack-core module_graph::binding_usage_info::tests
--lib`
- `pnpm build-all`
- `pnpm test-start-turbo
test/production/app-dir/client-components-tree-shaking/index.test.ts`
- `pnpm test-start-webpack
test/production/app-dir/client-components-tree-shaking/index.test.ts`

<!-- NEXT_JS_LLM -->


<!-- fleet 10716dab-2d36-4687-94ae-cd7bdfeac985 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 17:53:21 +02:00
Ben 000390a8fc fix: detect proxy.ts correctly with compound pageExtensions (#93246)
## Maintainer status

- Current with `canary` as of 2026-05-18; this branch includes a clean
merge from latest `origin/canary`.
- Lightweight checks pass; full fork workflows are still
`action_required` until a maintainer approves CI.
- No unresolved review threads or failing jobs are reported after the
refresh.
- Review focus: shared convention-file basename extraction for proxy,
middleware, and instrumentation under compound `pageExtensions`; TS and
Rust paths use the same rule.

---

## Summary

Fixes #85648
Fixes #86303
Fixes #91600
Fixes #85646
Related to #86122
Fixes #92342
Closes #92934

When `pageExtensions` is set to compound extensions like `['page.ts',
'page.tsx']`, proxy files must be named `proxy.page.ts`. However, the
proxy detection logic used `file_stem()` (Rust/Turbopack) and
`path.parse().name` (JS/webpack), both of which only strip the **last**
extension — so `proxy.page.ts` becomes `proxy.page` instead of `proxy`,
and the proxy is never detected.

**Root cause:** `path.parse('proxy.page.ts').name` returns
`'proxy.page'`, not `'proxy'`. Same issue with Rust's `file_stem()`
which uses `rsplit_once('.')`.

**Fix:** Use `file_name().split('.')[0]` (JS) /
`file_name().split('.').next()` (Rust) to extract the first segment
before any dot. This correctly returns `'proxy'` for both `proxy.ts` and
`proxy.page.ts`.

The same `fileBaseName` extraction is also used for `middleware` and
`instrumentation` convention file detection, fixing compound
pageExtensions for those as well.

### Changes

- **Turbopack (Rust):** `crates/next-api/src/project.rs` (2 locations) +
`crates/next-api/src/middleware.rs` (1 location)
- **Webpack (JS):** `packages/next/src/build/index.ts` (build-time
detection) +
`packages/next/src/server/lib/router-utils/setup-dev-bundler.ts`
(dev-time detection)
- **E2e tests:**
- `proxy-page-extensions/` — proxy + instrumentation with compound
extensions
- `middleware-page-extensions/` — middleware with compound extensions
(separate fixture because the build refuses both `proxy.*` and
`middleware.*` simultaneously)

### Verified locally

| Mode | Bundler | Result |
|------|---------|--------|
| Dev | Turbopack | PASS (5/5) |
| Dev | Webpack | PASS (5/5) |
| Production (build+start) | Turbopack | PASS (5/5) |
| Production (build+start) | Webpack | PASS (5/5) |

Existing proxy test suites also verified (proxy-runtime-nodejs,
proxy-with-middleware, proxy-missing-export, proxy-runtime) — no
regressions.

## Test plan

- [x] `proxy-page-extensions.test.ts` covers proxy header injection,
page render through proxy, and `instrumentation.page.ts:register()`
running
- [x] `middleware-page-extensions.test.ts` covers middleware header
injection and page render through middleware
- [x] All 5 cases pass in dev/turbopack, dev/webpack, start/turbopack,
start/webpack
- [x] Existing proxy-runtime-nodejs tests pass (dev/webpack,
dev/turbopack, production/webpack)
- [ ] CI passes on all existing proxy tests

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com>
2026-09-14 17:04:35 +02:00
next-js-bot[bot] 12748456d0 v16.4.0-canary.30 v16.4.0-canary.30 2026-09-14 14:36:31 +00:00
Jiwon Choi 4d925698a8 Expose App Router runtime errors over HMR (#98438)
Ported from #98040 by @marcoshernanz.

> [!TIP]
> Best reviewed commit by commit.

### Why?

External development tools need browser runtime-error state, not just
build errors, to display application failures. Reporting remains opt-in
so applications that do not need it avoid client serialization, HMR
transport, server formatting, buffering, and rebroadcast overhead.

### How?

Add `experimental.exposeRuntimeErrorsToHMR` for App Router development
with Webpack and Turbopack. Internal integrations can also enable the
same behavior without changing `next.config` by setting
`__NEXT_EXPOSE_RUNTIME_ERRORS_TO_HMR` to any non-empty value. When
enabled, the HMR WebSocket emits `runtimeErrors` snapshots containing:

- The current pathname and browser client/document identifiers.
- Error types, names, messages, and source-mapped stacks.
- Fatality and optional catching-boundary details (`default-global`,
`custom-global`, or `custom`).

Snapshots update as errors and navigation change. New or reconnected
observers receive the current state, and disconnecting a browser clears
its reported errors. An empty snapshot means there are no currently
reported errors; it does not guarantee application recovery.

Reporting is disabled by default. Pages Router, MCP `get_errors`, and
production behavior are unchanged.

<!-- NEXT_JS_LLM -->

Co-authored-by: Marcos Hernanz
<96699542+marcoshernanz@users.noreply.github.com>
2026-09-14 16:15:39 +02:00
Tobias Koppers 3c9d1ca77f test: avoid OpenTelemetry collector socket reuse (#98632)
### What?

Prevent the OpenTelemetry instrumentation e2e collector from losing span
exports when an exporter reuses an idle localhost connection. Add
focused coverage for the collector's connection behavior.

### Why?

Keeping the collector listener alive for each application's full
lifetime removed the earlier close-and-rebind gap, but did not eliminate
every socket failure. Node's HTTP server could still time out an idle
keep-alive socket retained by the exporter's Undici pool. A later export
could race that closure, lose spans with `UND_ERR_SOCKET` or
`ECONNRESET`, and leave otherwise unrelated trace assertions with
incomplete trees.

The canary.28 failure was investigated separately and is not this
mechanism: it occurred in the release-enabled Rspack matrix when a
custom server exited during startup. This change is limited to the
residual non-Rspack collector race and does not alter Rspack coverage or
release snapshots.

### How?

Successful collector exports explicitly close their HTTP connection.
Each export therefore uses a fresh local connection instead of retaining
a socket that the server may reclaim while idle. The regression test
asserts this response contract directly, avoiding timing-dependent
coverage of the race.

### Verification

- Node 20.9.0, webpack dev, React 18.3.1: 5 consecutive focused runs, 55
tests passed per run
- Node 20.9.0, webpack start, React 18.3.1: 99 passed, 13 existing skips
- `pnpm test-types`
- Prettier and ESLint on changed files

<!-- NEXT_JS_LLM -->


<!-- fleet e4b8ed15-4a06-4727-a634-fddcc27c11fc -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 13:34:47 +02:00
Ross Sullivan 8a53b57d4b fix: Bump vendored @mswjs/interceptors from 0.42.0 to 0.42.5 (#98607) 2026-09-14 13:14:34 +02:00
Jiwon Choi 996f957c06 Document codemod errors and intentional migration exceptions (#98565)
Stacked on #98560.

### Why?

Users need to distinguish unfinished codemod repairs from intentional
feature-adoption opt-outs.

### How?

Document `@next-codemod-error` and temporary `UnsafeUnwrapped*` casts in
the codemod guide, with examples and a link to the Async Request APIs
migration guide. Explain when an inapplicable suggestion can be marked
`@next-codemod-ignore` with a reason.

Show the ignore comment emitted for Cache Components opt-outs and
explain that it does not block compilation.
2026-09-14 12:36:34 +02:00
Jiwon Choi b0ba92cbc6 Unify TODO markers for codemods and skip adoption-only codemods for upgrade (#98560)
### Why?

Async Request API codemods can leave temporary casts that still require
application-specific repairs. Those locations need explicit markers,
while optional feature adoption needs to be distinguishable from
required version migrations.

### How?

Add `@next-codemod-error` comments to temporary `UnsafeUnwrapped*`
casts, with instructions for completing the repair. Preserve documented
ignores and avoid duplicating casts or markers when the transform runs
again. Mark generated Cache Components opt-outs with
`@next-codemod-ignore`, a removal condition and a link to the adoption
guide.

Add `upgrade --skip-adoption` to skip the Cache Components `instant =
false` transform and partial-prefetch adoption cleanup. Required
migrations and normal dependency and bundler selection keep their
existing behavior. Both adoption transforms can still be run explicitly.
2026-09-14 12:36:33 +02:00
Tobias Koppers 39f5d797ab Optimize strict module factory chunks (#98421)
### What?

Reduce Turbopack ECMAScript chunk size by emitting strict module
factories in a shared strict-mode scope instead of repeating `"use
strict"` in every factory.

The chunk format now chooses among the existing flat representation, a
mixed representation with flat non-strict factories plus a nested strict
array, and an all-strict representation that wraps the complete chunk in
a strict IIFE. Each factory is generated through the existing single
code path; the production minimizer removes its redundant directive when
the factory is created inside a strict wrapper. Browser and Node.js
emitters share the mode selection and serialization implementation,
while the runtime remains compatible with the existing flat format.

### Why?

ECMAScript modules are always strict, so large chunks currently repeat
the same directive across many factories. Moving strictness to the scope
where those factories are created removes redundant bytes without
copying arrays or changing strict/sloppy execution semantics.

For `bench/basic-app`, this reduces total raw JavaScript by 16,831 bytes
(0.161%). Partitioning factories changes compression behavior, resulting
in a 1,286-byte (0.051%) gzip increase across all JavaScript; format
selection therefore remains based on emitted raw bytes.

### How?

- Determine each chunk item's strictness from the already-collected
chunk metadata.
- Generate every factory once with its normal directive, then rely on
the minimizer to remove redundant directives inside strict wrappers.
- Compare the expected minified directive bytes removed with the exact
wrapper bytes added and keep small chunks in the existing flat format.
- Keep non-strict factories flat in mixed chunks and append strict
factories as an array produced by a strict IIFE.
- Wrap the complete registration/export in a strict IIFE when every
factory is strict, keeping those factories flat and avoiding a nested
array entirely.
- Use the shorter arrow IIFE when the target supports it, with a
function IIFE fallback for older targets.
- Preserve source-map sections, scope-hoisted module IDs, factory
naming, and ordering within each partition.
- Add focused strict, sloppy, mixed, all-strict, old-target, and
below-threshold coverage and update affected Turbopack snapshots.

### Verification

- `cargo nextest run -p turbopack-tests -E 'test(snapshot)'`
- `cargo clippy -p turbopack-ecmascript -p turbopack-browser -p
turbopack-nodejs`
- `pnpm build-all`
- `pnpm --dir turbopack/crates/turbopack-ecmascript-runtime/js
check:nodejs`
- `bench/basic-app` production Turbopack build, repeated to confirm
deterministic size totals

<!-- NEXT_JS_LLM -->


<!-- fleet 528ffe69-eb27-466b-a132-842272a782cf -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 12:34:30 +02:00
Tobias Koppers df1502c713 Sort batched chunk items globally by path (#98433)
### What?

Globally order all emitted Turbopack ECMAScript chunk items by module
path, including items nested inside batches. Browser and Node.js chunk
emitters now consume the same flattened, ordered result.

### Why?

The previous ordering compared only the first module in each item or
batch group. Remaining modules in a batch stayed together even when
their paths belonged elsewhere in the global order, reducing locality
between similar modules and slightly worsening gzip compression.

For `bench/basic-app`, production JavaScript chunk gzip sizes changed as
follows while raw bytes and chunk counts stayed unchanged:

| Output | Before | After | Change |
|---|---:|---:|---:|
| Browser chunks | 1,619,643 B | 1,619,314 B | -329 B (-0.020%) |
| Server chunks | 792,379 B | 792,186 B | -193 B (-0.024%) |
| Combined | 2,412,022 B | 2,411,500 B | -522 B (-0.022%) |

### How?

The shared ECMAScript chunk-content accessor now flattens resolved item
and batch groups and sorts every module by `(path, module id)`. Keeping
this ordering in the shared layer gives browser and Node.js output
identical deterministic behavior and removes duplicated emitter-side
sorting.

### Verification

- `cargo fmt --all -- --check`
- `cargo check -p turbopack-browser -p turbopack-nodejs`
- `cargo test -p turbopack-browser -p turbopack-nodejs`
- `pnpm build-all`
- Clean baseline and candidate production builds of `bench/basic-app`

<!-- NEXT_JS_LLM -->


<!-- fleet d30d9e3f-6885-4665-ae08-b37fa6151b95 -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 12:34:29 +02:00
next-js-bot[bot] 156050f1c4 v16.4.0-canary.29 v16.4.0-canary.29 2026-09-14 10:15:24 +00:00
Niklas Mischkulnig 164d9c1900 Durable use cache: fix runtime env var mutation (#98558)
### Problem
1. Next computes the warmup cache key while `SOME_ENV_VAR` is unset.
2. Rendering ends up setting `SOME_ENV_VAR`.
3. Final prerender expects the cache entry to exist, but the env var
changed, so the key changed and it's a miss.
4. Unexpected miss and bailout.

This lead to
```
Error: Route "foo": Unexpected cache miss after cache warming phase during prerendering. This is
likely caused by non-deterministic arguments that differ between the cache warming phase and the
final prerender phase (e.g. unstable array order). Ensure that arguments passed to cached functions
are deterministic.

Error: Route "foo": Next.js encountered uncached or runtime data during
prerendering.
```

### Solution

~~Instead, snapshot the env vars once at module evaluation time of the
`use cache` function. This ensures that the cache key doesn't change
over time. This is also how it worked thus far (with deployment id as
the cache key): changing the env var over the lifetime of the process
didn't lead to a reexecution of the `use cache` function~~

RDC already stores cache entries generated in the previous phase (be it
during `next build` multi-phase rendering, or from prerender->resuming
at runtime). Root params are already excluded from the cache keys when
storing in RDC. Also exclude the env var hash bit, to conform to this
system of preventing tearing (at the cost of potential staleness).
2026-09-14 11:32:24 +02:00
Niklas Mischkulnig 729ff712b7 refactor(turbopack): drop the generic on new_atom, and stop it copying an owned string (#98520)
### What?

Follow-up to the review comments on #98497.

`new_atom` loses its generic parameter and takes a `Cow<'_, str>`, which
as a side effect stops it
copying a string it was handed ownership of. `FileSystemPath`'s
`get_relative_request_to` stops
hand-rolling a conversion the standard `From` impl already does, and the
two tests added for it in
`path.rs` are replaced by one that covers what that layer actually adds.

### Why?

@bgw noted that `get_relative_request_to` was matching on the `Cow` it
gets back only to call
`.into()` in both arms, and wondered whether `impl From<Cow<'_, str>>
for RcStr` was missing a fast
path for the owned case.

It was not missing one, and it never needed one — but an owned string
*was* being copied, for a
different reason. `new_atom` was generic over `T: AsRef<str> +
Into<String>` and opened with
`let text = text.as_ref();`, which shadows the owned value. From that
point on only a `&str` was in
scope, so building the `Box<str>` allocated a fresh buffer and copied
into it, dropping the `String`
that a `Cow::Owned` had just handed over.

So this is not a fast path being added. It is an accidental
pessimisation being removed: the generic
plus the `as_ref()` were exactly what stopped the standard conversion
from doing the right thing on
its own. Whether that copy ever mattered in practice is a separate
question, and one this does not
attempt to answer.

The other two comments were about the tests: they re-ran the case table
that already lives in
`turbo-unix-path`, which is where the path computation itself is
implemented and tested — "60 LOC to
test a 3 LOC branch".

### How?

`new_atom` takes a `Cow<'_, str>`. All five of its callers already
passed one, so nothing else
changes, and ownership now survives to where the `Box<str>` is built —
`Box<str>`'s own
`From<Cow<'_, str>>` moves an owned buffer in and copies only a borrowed
one. The inline-atom path is
untouched, since it reads through a `&str` either way and does not
allocate.

Contents-based assertions cannot distinguish a moved buffer from a
copied one, so the test compares
the data pointer before and after the conversion. It uses a string
longer than `MAX_INLINE_LEN`,
because an inline atom stores its bytes in the tagged value by design
and could never preserve a
pointer, and it asserts the atom really is dynamic so it cannot pass
vacuously if that boundary ever
moves. It fails on `canary` today.

`get_relative_request_to` collapses to a single `.into()`. Its sibling
`get_relative_path_to` keeps
its match on purpose: one arm uses `std::ptr::eq` to hand back a clone
of the `RcStr` the path
already holds, which the blanket conversion cannot do. That arm predates
#98497 and is an
optimisation rather than the redundancy that was flagged.

For the tests, `turbo-unix-path` remains the place where the relative
path computation is covered,
untouched. What is left here is only what this layer adds on top: that
each method reaches for the
form it names, and that neither returns a path across filesystems.
Keeping the first of those is a
judgement call — a method delegating to the wrong free function is
precisely the bug #98497 existed
to fix, and it is invisible to the lower-level table — so it is asserted
with one descendant pair
rather than a table. Happy to drop it entirely if you would rather.

To check that the smaller test still earns its place,
`get_relative_request_to` was temporarily
pointed at the plain-path function; the test fails as it should.

<!-- NEXT_JS_LLM -->

<!-- fleet 3acc7e63-52a8-4472-89cd-b1bbb8ad59fa -->

---------

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 07:43:14 +00:00
Tobias Koppers c2e4f05fb1 Serialize turbo-tasks trybuild tests (#98415)
### What?

Run all `turbo-tasks-macros-tests` trybuild fixture groups through one
test process and one generated Cargo project lifecycle.

### Why?

Trybuild uses one generated Cargo project per package. Cargo-nextest
previously launched the five test groups as separate processes, allowing
them to overwrite that project's manifest while another group was
compiling. On canary this surfaced as diagnostics from the wrong
fixture, missing `trybuild00N` bin targets, and compile-fail cases
incorrectly succeeding.

The tracked stderr snapshots are correct; the instability is in
concurrent harness execution rather than compiler output or the SWC
version.

### How?

A single `TestCases` instance now queues every existing pass and
compile-fail glob. This preserves per-fixture diagnostics and coverage
while preventing cross-process manifest collisions. A comment documents
the shared-project invariant so the groups are not split into concurrent
tests again.

### Verification

- `cargo nextest run -p turbo-tasks-macros-tests --cargo-profile
release-with-assertions --no-fail-fast`
- `cargo fmt --all -- --check`
- `git diff --check`

<!-- NEXT_JS_LLM -->


<!-- fleet ae1d9ec9-156e-4b53-80c0-f24bf0b22f13 -->

Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com>
2026-09-14 09:11:02 +02:00
next-js-bot[bot] 748cc4a6e8 v16.4.0-canary.28 v16.4.0-canary.28 2026-09-12 23:21:08 +00:00