Skip to content

Re-implement router-act with browser-based fetch instrumentation#90959

Draft
acdlite wants to merge 1 commit into
vercel:canaryfrom
acdlite:browser-router-act
Draft

Re-implement router-act with browser-based fetch instrumentation#90959
acdlite wants to merge 1 commit into
vercel:canaryfrom
acdlite:browser-router-act

Conversation

@acdlite

@acdlite acdlite commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Re-implements the router-act test utility to use a browser-side fetch monkey-patch instead of Playwright's page.route() API. The old approach was flaky due to issues with streaming responses, redirect handling, and execution context destruction.

The new implementation (@next/router-act) is a private workspace package with three files:

  • setup.ts — browser-side runtime that intercepts fetch calls and runs the drain loop
  • index.ts — thin Playwright orchestrator for config parsing, error formatting, and assertions
  • component.tsx<RouterAct /> client component rendered in test fixture layouts

The <RouterAct /> component installs the fetch patch via useEffect so it doesn't interfere with hydration. This works because router-act is only used in test fixtures we control.

All 30 test files have been migrated. The old implementation is preserved at test/lib/deprecated-router-act.ts for a handful of tests that trigger hard navigations destroying the browser context mid-act — those will be reworked in a follow-up PR.

Test plan

  • All migrated test files pass locally with NEXT_SKIP_ISOLATE=1 NEXT_TEST_MODE=start
  • Tests covering: basic prefetch/navigation, nested act scopes, blocking, response ordering, forbidden responses, server actions, interception routes, memory pressure (200+ requests), cache staleness, revalidation, deployment skew, and more

@nextjs-bot nextjs-bot added created-by: Next.js team PRs by the Next.js team. tests labels Mar 6, 2026
@nextjs-bot

nextjs-bot commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

Failing test suites

Commit: cf731d8 | About building and testing Next.js

pnpm test-dev test/development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts (job)

  • instant-nav-panel > should show loading skeleton during SPA navigation after clicking Start (DD)
Expand output

● instant-nav-panel › should show loading skeleton during SPA navigation after clicking Start

expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

  161 |           '[data-testid="dynamic-skeleton"]'
  162 |         )
> 163 |         expect(skeleton).toBe(true)
      |                          ^
  164 |       },
  165 |       30000,
  166 |       500

  at toBe (development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts:163:26)
  at retry (lib/next-test-utils.ts:861:14)
  at Object.<anonymous> (development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts:158:5)

pnpm test-start test/production/app-dir/server-action-period-hash/server-action-period-hash.test.ts (job)

  • app-dir - server-action-period-hash > should have same manifest between continuous two builds (DD)
  • app-dir - server-action-period-hash > should have different manifest between two builds with period hash (DD)
Expand output

● app-dir - server-action-period-hash › should have same manifest between continuous two builds

can not run export while server is running, use next.stop() first

  251 |   ) {
  252 |     if (this.childProcess) {
> 253 |       throw new Error(
      |             ^
  254 |         `can not run export while server is running, use next.stop() first`
  255 |       )
  256 |     }

  at NextStartInstance.build (lib/next-modes/next-start.ts:253:13)
  at Object.build (production/app-dir/server-action-period-hash/server-action-period-hash.test.ts:17:16)

● app-dir - server-action-period-hash › should have different manifest between two builds with period hash

can not run export while server is running, use next.stop() first

  251 |   ) {
  252 |     if (this.childProcess) {
> 253 |       throw new Error(
      |             ^
  254 |         `can not run export while server is running, use next.stop() first`
  255 |       )
  256 |     }

  at NextStartInstance.build (lib/next-modes/next-start.ts:253:13)
  at Object.build (production/app-dir/server-action-period-hash/server-action-period-hash.test.ts:27:16)

pnpm test test/integration/empty-project/test/index.test.ts (job)

  • Image Component Default Tests > production mode > should load the images (DD)
  • Image Component Default Tests > production mode > should preload priority images (DD)
  • Image Component Default Tests > production mode > should work with preload prop (DD)
  • Image Component Default Tests > production mode > should not pass through user-provided srcset (causing a flash) (DD)
  • Image Component Default Tests > production mode > should update the image on src change (DD)
  • Image Component Default Tests > production mode > should callback onLoadingComplete when image is fully loaded (DD)
  • Image Component Default Tests > production mode > should callback native onLoad with sythetic event (DD)
  • Image Component Default Tests > production mode > should callback native onError when error occurred while loading image (DD)
  • Image Component Default Tests > production mode > should callback native onError even when error before hydration (DD)
  • Image Component Default Tests > production mode > should work with image with blob src (DD)
  • Image Component Default Tests > production mode > should work when using flexbox (DD)
  • Image Component Default Tests > production mode > should work when using overrideSrc prop (DD)
  • Image Component Default Tests > production mode > should work with sizes and automatically use responsive srcset (DD)
  • Image Component Default Tests > production mode > should render no wrappers or sizers (DD)
  • Image Component Default Tests > production mode > should lazy load with placeholder=blur (DD)
  • Image Component Default Tests > production mode > should handle the styles prop appropriately (DD)
  • Image Component Default Tests > production mode > should warn when legacy prop layout=fill (DD)
  • Image Component Default Tests > production mode > should warn when legacy prop layout=responsive (DD)
  • Image Component Default Tests > production mode > should render picture via getImageProps (DD)
  • Image Component Default Tests > production mode > should not create an image folder in server/chunks (DD)
  • Image Component Default Tests > production mode > should render as unoptimized with missing src prop (DD)
  • Image Component Default Tests > production mode > should render as unoptimized with empty string src prop (DD)
  • Image Component Default Tests > production mode > should correctly ignore prose styles (DD)
  • Image Component Default Tests > production mode > should apply style inheritance for img elements but not wrapper elements (DD)
  • Image Component Default Tests > production mode > should apply filter style after image loads (DD)
  • Image Component Default Tests > production mode > should emit image for next/dynamic with non ssr case (DD)
  • Image Component Default Tests > production mode > should correctly rotate image (DD)
  • Image Component Default Tests > production mode > should have data url placeholder when enabled (DD)
  • Image Component Default Tests > production mode > should remove data url placeholder after image loads (DD)
  • Image Component Default Tests > production mode > should render correct objectFit when data url placeholder and fill (DD)
  • Image Component Default Tests > production mode > should have blurry placeholder when enabled (DD)
  • Image Component Default Tests > production mode > should remove blurry placeholder after image loads (DD)
  • Image Component Default Tests > production mode > should render correct objectFit when blurDataURL and fill (DD)
  • Image Component Default Tests > production mode > should be valid HTML (DD)
  • Image Component Default Tests > production mode > should call callback ref cleanups when unmounting (DD)
  • Image Component Default Tests > production mode > should build correct images-manifest.json (DD)
  • Image Component Default Tests > production mode > Fill-mode tests > should include a data-attribute on fill images (DD)
  • Image Component Default Tests > production mode > Fill-mode tests > should add position:absolute to fill images (DD)
  • Image Component Default Tests > production mode > Fill-mode tests > should add 100% width and height to fill images (DD)
  • Image Component Default Tests > production mode > Fill-mode tests > should add position styles to fill images (DD)
Expand output

● Image Component Default Tests › production mode › should load the images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should preload priority images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should work with preload prop

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should not pass through user-provided srcset (causing a flash)

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should update the image on src change

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should callback onLoadingComplete when image is fully loaded

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should callback native onLoad with sythetic event

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should callback native onError when error occurred while loading image

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should callback native onError even when error before hydration

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should work with image with blob src

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should work when using flexbox

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should work when using overrideSrc prop

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should work with sizes and automatically use responsive srcset

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render no wrappers or sizers

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should lazy load with placeholder=blur

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should handle the styles prop appropriately

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should warn when legacy prop layout=fill

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should warn when legacy prop layout=responsive

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render picture via getImageProps

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should not create an image folder in server/chunks

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render as unoptimized with missing src prop

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render as unoptimized with empty string src prop

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should correctly ignore prose styles

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should apply style inheritance for img elements but not wrapper elements

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should apply filter style after image loads

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should emit image for next/dynamic with non ssr case

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › Fill-mode tests › should include a data-attribute on fill images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › Fill-mode tests › should include a data-attribute on fill images

page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:45465/fill
Call log:
  - navigating to "http://localhost:45465/fill", waiting until "load"

  369 |     await opts?.beforePageLoad?.(page)
  370 |
> 371 |     await page.goto(url, { waitUntil: opts?.waitUntil ?? 'load' })
      |                ^
  372 |   }
  373 |
  374 |   back(options?: Parameters<Page['goBack']>[0]) {

  at Playwright.goto (lib/browsers/playwright.ts:371:16)
  at webdriver (lib/next-webdriver.ts:160:3)
  at Object.<anonymous> (integration/next-image-new/app-dir/test/index.test.ts:1519:17)

● Image Component Default Tests › production mode › Fill-mode tests › should add position:absolute to fill images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › Fill-mode tests › should add position:absolute to fill images

page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:45465/fill
Call log:
  - navigating to "http://localhost:45465/fill", waiting until "load"

  369 |     await opts?.beforePageLoad?.(page)
  370 |
> 371 |     await page.goto(url, { waitUntil: opts?.waitUntil ?? 'load' })
      |                ^
  372 |   }
  373 |
  374 |   back(options?: Parameters<Page['goBack']>[0]) {

  at Playwright.goto (lib/browsers/playwright.ts:371:16)
  at webdriver (lib/next-webdriver.ts:160:3)
  at Object.<anonymous> (integration/next-image-new/app-dir/test/index.test.ts:1519:17)

● Image Component Default Tests › production mode › Fill-mode tests › should add 100% width and height to fill images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › Fill-mode tests › should add 100% width and height to fill images

page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:45465/fill
Call log:
  - navigating to "http://localhost:45465/fill", waiting until "load"

  369 |     await opts?.beforePageLoad?.(page)
  370 |
> 371 |     await page.goto(url, { waitUntil: opts?.waitUntil ?? 'load' })
      |                ^
  372 |   }
  373 |
  374 |   back(options?: Parameters<Page['goBack']>[0]) {

  at Playwright.goto (lib/browsers/playwright.ts:371:16)
  at webdriver (lib/next-webdriver.ts:160:3)
  at Object.<anonymous> (integration/next-image-new/app-dir/test/index.test.ts:1519:17)

● Image Component Default Tests › production mode › Fill-mode tests › should add position styles to fill images

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › Fill-mode tests › should add position styles to fill images

page.goto: net::ERR_CONNECTION_REFUSED at http://localhost:45465/fill
Call log:
  - navigating to "http://localhost:45465/fill", waiting until "load"

  369 |     await opts?.beforePageLoad?.(page)
  370 |
> 371 |     await page.goto(url, { waitUntil: opts?.waitUntil ?? 'load' })
      |                ^
  372 |   }
  373 |
  374 |   back(options?: Parameters<Page['goBack']>[0]) {

  at Playwright.goto (lib/browsers/playwright.ts:371:16)
  at webdriver (lib/next-webdriver.ts:160:3)
  at Object.<anonymous> (integration/next-image-new/app-dir/test/index.test.ts:1519:17)

● Image Component Default Tests › production mode › should correctly rotate image

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should have data url placeholder when enabled

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should remove data url placeholder after image loads

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render correct objectFit when data url placeholder and fill

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should have blurry placeholder when enabled

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should remove blurry placeholder after image loads

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should render correct objectFit when blurDataURL and fill

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should be valid HTML

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should call callback ref cleanups when unmounting

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

● Image Component Default Tests › production mode › should build correct images-manifest.json

thrown: "Exceeded timeout of 60000 ms for a hook.
Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

  1849 |     'production mode',
  1850 |     () => {
> 1851 |       beforeAll(async () => {
       |       ^
  1852 |         await nextBuild(appDir)
  1853 |         appPort = await findPort()
  1854 |         app = await nextStart(appDir, appPort)

  at beforeAll (integration/next-image-new/app-dir/test/index.test.ts:1851:7)
  at integration/next-image-new/app-dir/test/index.test.ts:1848:56
  at Object.describe (integration/next-image-new/app-dir/test/index.test.ts:1833:1)

@acdlite acdlite force-pushed the browser-router-act branch 5 times, most recently from 485f506 to 789c0b2 Compare March 6, 2026 07:46
Comment thread packages/next-router-act/package.json
@acdlite acdlite force-pushed the browser-router-act branch 13 times, most recently from 7668fe9 to 514b57c Compare March 7, 2026 01:56
@mondyzzz-glitch

This comment was marked as spam.

@acdlite acdlite force-pushed the browser-router-act branch 5 times, most recently from 52d8fc6 to 90a4017 Compare March 13, 2026 17:23
@acdlite acdlite force-pushed the browser-router-act branch 2 times, most recently from 7889ced to 6cf2064 Compare March 16, 2026 17:26
@acdlite acdlite force-pushed the browser-router-act branch 10 times, most recently from 77af5e1 to fb33082 Compare March 24, 2026 03:57
The existing `router-act` test utility intercepts Next.js Router
network requests using Playwright's `page.route()` API. This approach
is flaky — Playwright's network interception has issues with streaming
responses (`transfer-encoding: chunked`), redirect handling (307/308),
and execution context destruction. The implementation requires many
workarounds and retry loops.

This commit introduces `@next/router-act`, a private workspace package
that re-implements the same API using a browser-side fetch monkey-patch.
The patch is installed via a `<RouterAct />` client component rendered
in test fixture layouts. Using a React component (rather than
`page.addInitScript` or similar) ensures the monkey-patch is installed
after hydration, avoiding interference with the hydration process
itself. This is viable because router-act is only used in test fixtures
that we control — it's not designed for testing arbitrary Next.js apps.

The browser runtime handles all scheduling internally — idle callbacks,
waiting for in-flight requests, response matching, and resolve/block
decisions run in a single `drainQueue()` call. The Playwright
orchestrator communicates with the browser via `page.evaluate()` and
is only responsible for config parsing, error formatting with stack
traces, and final sequence assertions using jest utilities.

All existing test files that use `router-act` have been migrated to the
new implementation. The old Playwright-based implementation is preserved
at `test/lib/deprecated-router-act.ts` because a handful of tests
trigger hard navigations that destroy the browser JS context mid-act.
Those tests will be reworked in a follow-up PR, after which the old
implementation can be deleted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

created-by: Next.js team PRs by the Next.js team. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants