Files
Hendrik LiebauandGitHub 29464ac56f [test] Reorder hmr-intercept-routes test to avoid a reload/refresh race (#95134)
The `hmr-intercept-routes` test created the `@intercept` parallel-route
slot files before editing the root layout to render the slot. Adding a
new parallel-route slot is a structural change that forces a full page
reload, and that reload raced with the Fast Refresh for the layout edit.
Under load the client's `reloading` guard could drop the in-flight
refresh, so the new slot never reached the browser and the test timed
out waiting for `#default-intercept`, [failing 30-40% of the time in
CI](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22hmr-intercept-routes%20should%20update%20intercept%20routes%20via%20HMR%22%20%40test.status%3A%28fail%20OR%20pass%29&agg_m=count&agg_m_source=base&agg_t=count&currentTab=overview&eventStack=&fromUser=false&index=citest&start=1781719381980&end=1782324181980&paused=false).

<img width="428" height="176" alt="Screenshot 2026-06-24 at 20 04 01"
src="https://github.com/user-attachments/assets/0272886e-dd75-4dbd-bd8c-b26ee8f40082"
/>

This change edits the layout first, so the `intercept` slot is already
rendered as an undefined, no-op prop before the slot files exist.
Creating the slot files afterward triggers the structural reload, and
because the layout already renders the slot, that reload serves the
complete updated route tree in a single fetch with no competing Fast
Refresh. The server always resolved the route correctly; only the
browser-side delivery was racy.
2026-06-24 20:42:13 +02:00

66 lines
2.6 KiB
TypeScript

import { nextTestSetup } from 'e2e-utils'
// This only works for Turbopack HMR builds
;(process.env.IS_TURBOPACK_TEST ? describe : describe.skip)(
'hmr-intercept-routes',
() => {
const { next } = nextTestSetup({
files: __dirname,
})
it('should update intercept routes via HMR', async () => {
const browser = await next.browser('/')
expect(await browser.elementByCss('h1').text()).toBe('Main Page')
const parallelDefaultContent = await next.readFile(
'fixtures/@intercept/default.js'
)
const parallelInterceptContent = await next.readFile(
'fixtures/@intercept/(.)intercept/page.js'
)
// Read the original code of the root layout page
const rootLayoutContent = await next.readFile('app/layout.js')
const fixtureLayoutContent = await next.readFile('fixtures/layout.js')
// Render the `intercept` parallel-route slot from the root layout first.
// No `@intercept` directory exists yet, so the slot prop is undefined and
// nothing renders for it. This is a plain Fast Refresh that does not
// force a page reload.
await next.patchFile('app/layout.js', fixtureLayoutContent)
// Now create the slot. Adding a new parallel-route slot is a structural
// change that forces a full page reload, and because the layout already
// renders the slot, that reload serves the complete updated route tree.
//
// Writing the slot files before the layout edit (the reverse order) is
// flaky: the structural reload races with the layout's Fast Refresh, and
// under load the client's `reloading` guard can drop the in-flight
// refresh, leaving the new slot absent until the next change.
await next.patchFile('app/@intercept/default.js', parallelDefaultContent)
await next.patchFile(
'app/@intercept/(.)intercept/page.js',
parallelInterceptContent
)
// Check to make sure that the main page now has the correct layout changes
await browser.waitForElementByCss('#default-intercept')
expect(await browser.elementById('default-intercept').text()).toBe(
"I'm the default intercept"
)
// Go to the intercept route and check that the intercept worked correctly
await browser.elementById('to-intercept').click()
await browser.waitForElementByCss('#intercept')
expect(await browser.elementById('intercept').text()).toBe(
"I'm the intercept"
)
// Reset the file statuses
await next.patchFile('app/layout.js', rootLayoutContent)
await next.deleteFile('app/@intercept')
})
}
)