From 68eed5cb514b4b0add5272f136f795ade58bd53a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 13:01:48 +0200 Subject: [PATCH 01/18] test: skip webpack configuration on turbopack build error --- tests/fixtures/middleware/next.config.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/fixtures/middleware/next.config.js b/tests/fixtures/middleware/next.config.js index 03c2828b32..d59e5d898c 100644 --- a/tests/fixtures/middleware/next.config.js +++ b/tests/fixtures/middleware/next.config.js @@ -19,6 +19,15 @@ const nextConfig = { return config }, + // turbopack becomes default for builds in Next 16. There is failure when webpack configuration is present + // without turbopack configuration, so we add a turbopack configuration here to ensure this fixture + // works with default build bundler for all tested versions + // see https://github.com/vercel/next.js/blob/ba5a0ca79944b4c8a59d80d677bfedaf0fef33d6/packages/next/src/lib/turbopack-warning.ts#L159-L177 + turbopack: { + // there need to be some keys here, as empty object despite currently being suggesting is not actually allowing build to go through + // so we'll set root (which serves same purpose as outputFileTracingRoot more generally) + root: __dirname, + }, outputFileTracingRoot: __dirname, } From 817975d17f9a0f49eb5750fc296c7e98d8843923 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 13:19:36 +0200 Subject: [PATCH 02/18] test: migrate page.textContent to locator.textContent or locator.toHaveText --- .../cli-before-regional-blobs-support.test.ts | 2 +- tests/e2e/on-demand-app.test.ts | 10 +-- tests/e2e/page-router.test.ts | 62 +++++++++---------- tests/e2e/simple-app.test.ts | 4 +- tests/e2e/turborepo.test.ts | 16 ++--- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/tests/e2e/cli-before-regional-blobs-support.test.ts b/tests/e2e/cli-before-regional-blobs-support.test.ts index e4779339f8..c6f7193e01 100644 --- a/tests/e2e/cli-before-regional-blobs-support.test.ts +++ b/tests/e2e/cli-before-regional-blobs-support.test.ts @@ -17,7 +17,7 @@ test('should serve 404 page when requesting non existing page (no matching route const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('h1')).toBe('404') + await expect(page.locator('h1')).toHaveText('404') // https://github.com/vercel/next.js/pull/69802 made changes to returned cache-control header, // after that (14.2.10 and canary.147) 404 pages would have `private` directive, before that it diff --git a/tests/e2e/on-demand-app.test.ts b/tests/e2e/on-demand-app.test.ts index 13c0ee3dd2..239e000395 100644 --- a/tests/e2e/on-demand-app.test.ts +++ b/tests/e2e/on-demand-app.test.ts @@ -96,9 +96,9 @@ test.describe('app router on-demand revalidation', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.textContent('[data-testid="date-now"]') + const date1 = await page.locator('[data-testid="date-now"]').textContent() - const h1 = await page.textContent('h1') + const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) const response2 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, { @@ -127,7 +127,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') + const date2 = await page.locator('[data-testid="date-now"]').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href) @@ -159,7 +159,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') + const date3 = await page.locator('[data-testid="date-now"]').textContent() expect(date3).not.toBe(date2) const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, { @@ -188,7 +188,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date4 = await page.textContent('[data-testid="date-now"]') + const date4 = await page.locator('[data-testid="date-now"]').textContent() expect(date4).toBe(date3) }) } diff --git a/tests/e2e/page-router.test.ts b/tests/e2e/page-router.test.ts index d0c8f2ee11..ccb71849a1 100644 --- a/tests/e2e/page-router.test.ts +++ b/tests/e2e/page-router.test.ts @@ -174,12 +174,12 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) if (fallbackWasServed) { - const loading = await page.textContent('[data-testid="loading"]') + const loading = await page.locator('[data-testid="loading"]').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') + const date1 = await page.locator('[data-testid="date-now"]').textContent() + const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) // check json route @@ -238,7 +238,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') + const date2 = await page.locator('[data-testid="date-now"]').textContent() expect(date2).toBe(date1) // check json route @@ -299,7 +299,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') + const date3 = await page.locator('[data-testid="date-now"]').textContent() expect(date3).not.toBe(date2) // check json route @@ -366,7 +366,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { }, ) expect(response1?.status()).toBe(200) - const date1 = (await page.textContent('[data-testid="date-now"]')) ?? '' + const date1 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' // ensure response was produced before invocation (served from cache) expect(date1.localeCompare(beforeFetch)).toBeLessThan(0) @@ -391,7 +391,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { }, ) expect(response2?.status()).toBe(200) - const date2 = (await page.textContent('[data-testid="date-now"]')) ?? '' + const date2 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' // ensure response was produced after initial invocation expect(beforeFetch.localeCompare(date2)).toBeLessThan(0) @@ -416,7 +416,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) // ensure response was NOT produced before invocation - const date1 = (await page.textContent('[data-testid="date-now"]')) ?? '' + const date1 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' expect(date1.localeCompare(beforeFirstFetch)).toBeGreaterThan(0) // allow page to get stale @@ -431,7 +431,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { /s-maxage=60, stale-while-revalidate=[0-9]+, durable/, ) - const date2 = (await page.textContent('[data-testid="date-now"]')) ?? '' + const date2 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' expect(date2).toBe(date1) // wait a bit to ensure background work has a chance to finish @@ -450,7 +450,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { /s-maxage=60, stale-while-revalidate=[0-9]+, durable/, ) - const date3 = (await page.textContent('[data-testid="date-now"]')) ?? '' + const date3 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' expect(date3.localeCompare(date2)).toBeGreaterThan(0) }) @@ -469,7 +469,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('p')).toBe('Custom 404 page') + await expect(page.locator('p')).toHaveText('Custom 404 page') // https://github.com/vercel/next.js/pull/69802 made changes to returned cache-control header, // after that (14.2.10 and canary.147) 404 pages would have `private` directive, before that @@ -493,7 +493,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('p')).toBe('Custom 404 page') + await expect(page.locator('p')).toHaveText('Custom 404 page') expect(headers['debug-netlify-cdn-cache-control']).toBe( nextVersionSatisfies('>=15.0.0-canary.187') @@ -748,12 +748,12 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServedImplicitLocale) { - const loading = await page.textContent('[data-testid="loading"]') + const loading = await page.locator('[data-testid="loading"]').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1ImplicitLocale = await page.textContent('[data-testid="date-now"]') - const h1ImplicitLocale = await page.textContent('h1') + const date1ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const h1ImplicitLocale = await page.locator('h1').textContent() expect(h1ImplicitLocale).toBe(expectedH1Content) const response1ExplicitLocale = await pollUntilHeadersMatch( @@ -790,12 +790,12 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServedExplicitLocale) { - const loading = await page.textContent('[data-testid="loading"]') + const loading = await page.locator('[data-testid="loading"]').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1ExplicitLocale = await page.textContent('[data-testid="date-now"]') - const h1ExplicitLocale = await page.textContent('h1') + const date1ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const h1ExplicitLocale = await page.locator('h1').textContent() expect(h1ExplicitLocale).toBe(expectedH1Content) // implicit and explicit locale paths should be the same (same cached response) @@ -861,7 +861,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2ImplicitLocale = await page.textContent('[data-testid="date-now"]') + const date2ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date2ImplicitLocale).toBe(date1ImplicitLocale) const response2ExplicitLocale = await pollUntilHeadersMatch( @@ -893,7 +893,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2ExplicitLocale = await page.textContent('[data-testid="date-now"]') + const date2ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date2ExplicitLocale).toBe(date1ExplicitLocale) // check json route @@ -961,7 +961,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3ImplicitLocale = await page.textContent('[data-testid="date-now"]') + const date3ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date3ImplicitLocale).not.toBe(date2ImplicitLocale) const response3ExplicitLocale = await pollUntilHeadersMatch( @@ -984,7 +984,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3ExplicitLocale = await page.textContent('[data-testid="date-now"]') + const date3ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date3ExplicitLocale).not.toBe(date2ExplicitLocale) // implicit and explicit locale paths should be the same (same cached response) @@ -1057,7 +1057,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers4ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date4ImplicitLocale = await page.textContent('[data-testid="date-now"]') + const date4ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date4ImplicitLocale).not.toBe(date3ImplicitLocale) const response4ExplicitLocale = await pollUntilHeadersMatch( @@ -1080,7 +1080,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers4ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date4ExplicitLocale = await page.textContent('[data-testid="date-now"]') + const date4ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() expect(date4ExplicitLocale).not.toBe(date3ExplicitLocale) // implicit and explicit locale paths should be the same (same cached response) @@ -1173,12 +1173,12 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServed) { - const loading = await page.textContent('[data-testid="loading"]') + const loading = await page.locator('[data-testid="loading"]').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') + const date1 = await page.locator('[data-testid="date-now"]').textContent() + const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) // check json route @@ -1241,7 +1241,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') + const date2 = await page.locator('[data-testid="date-now"]').textContent() expect(date2).toBe(date1) // check json route @@ -1309,7 +1309,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') + const date3 = await page.locator('[data-testid="date-now"]').textContent() expect(date3).not.toBe(date2) // check json route @@ -1360,7 +1360,7 @@ test.describe('Page Router with basePath and i18n', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('p')).toBe('Custom 404 page for locale: en') + await expect(page.locator('p')).toHaveText('Custom 404 page for locale: en') expect(headers['debug-netlify-cdn-cache-control']).toMatch( /no-cache, no-store, max-age=0, must-revalidate, durable/m, @@ -1378,7 +1378,7 @@ test.describe('Page Router with basePath and i18n', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('p')).toBe('Custom 404 page for locale: en') + await expect(page.locator('p')).toHaveText('Custom 404 page for locale: en') // Prior to v14.2.4 notFound pages are not cacheable // https://github.com/vercel/next.js/pull/66674 diff --git a/tests/e2e/simple-app.test.ts b/tests/e2e/simple-app.test.ts index fb790afcff..5c497dc905 100644 --- a/tests/e2e/simple-app.test.ts +++ b/tests/e2e/simple-app.test.ts @@ -227,7 +227,7 @@ test('requesting a non existing page route that needs to be fetched from the blo const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('h1')).toBe('404 Not Found') + await expect(page.locator('h1')).toHaveText('404 Not Found') // https://github.com/vercel/next.js/pull/66674 made changes to returned cache-control header, // before that 404 page would have `private` directive, after that (14.2.4 and canary.24) it @@ -254,7 +254,7 @@ test('requesting a non existing page route that needs to be fetched from the blo const headers = response?.headers() || {} expect(response?.status()).toBe(404) - expect(await page.textContent('h1')).toBe('404 Not Found') + await expect(page.locator('h1')).toHaveText('404 Not Found') expect(headers['debug-netlify-cdn-cache-control']).toBe( nextVersionSatisfies('>=15.0.0-canary.187') diff --git a/tests/e2e/turborepo.test.ts b/tests/e2e/turborepo.test.ts index 0475994571..bfc4725657 100644 --- a/tests/e2e/turborepo.test.ts +++ b/tests/e2e/turborepo.test.ts @@ -41,8 +41,8 @@ test.describe('[PNPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') + const date1 = await page.locator('[data-testid="date-now"]').textContent() + const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') const response2 = await pollUntilHeadersMatch( @@ -74,7 +74,7 @@ test.describe('[PNPM] Package manager', () => { ) // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') + const date2 = await page.locator('[data-testid="date-now"]').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepo.url).href) @@ -104,7 +104,7 @@ test.describe('[PNPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') + const date3 = await page.locator('[data-testid="date-now"]').textContent() expect(date3).not.toBe(date2) }) }) @@ -149,8 +149,8 @@ test.describe('[NPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.textContent('[data-testid="date-now"]') - const h1 = await page.textContent('h1') + const date1 = await page.locator('[data-testid="date-now"]').textContent() + const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') const response2 = await pollUntilHeadersMatch( @@ -182,7 +182,7 @@ test.describe('[NPM] Package manager', () => { ) // the page is cached - const date2 = await page.textContent('[data-testid="date-now"]') + const date2 = await page.locator('[data-testid="date-now"]').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepoNPM.url).href) @@ -212,7 +212,7 @@ test.describe('[NPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.textContent('[data-testid="date-now"]') + const date3 = await page.locator('[data-testid="date-now"]').textContent() expect(date3).not.toBe(date2) }) From 6aac5e84fc7a07fccbc076764bfa579075ef2059 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 15:03:21 +0200 Subject: [PATCH 03/18] test: migrate page.textContent to locator.textContent --- tests/e2e/on-demand-app.test.ts | 8 +-- tests/e2e/page-router.test.ts | 54 +++++++++---------- tests/e2e/turborepo.test.ts | 12 ++--- .../pages/404.js | 2 +- .../page-router-base-path-i18n/pages/404.js | 2 +- tests/fixtures/page-router/pages/404.js | 2 +- tests/integration/simple-app.test.ts | 23 ++++---- tests/utils/fixture.ts | 7 ++- tests/utils/next-version-helpers.mjs | 4 ++ 9 files changed, 63 insertions(+), 51 deletions(-) diff --git a/tests/e2e/on-demand-app.test.ts b/tests/e2e/on-demand-app.test.ts index 239e000395..e21d30d256 100644 --- a/tests/e2e/on-demand-app.test.ts +++ b/tests/e2e/on-demand-app.test.ts @@ -96,7 +96,7 @@ test.describe('app router on-demand revalidation', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.locator('[data-testid="date-now"]').textContent() + const date1 = await page.getByTestId("date-now").textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) @@ -127,7 +127,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date2 = await page.locator('[data-testid="date-now"]').textContent() + const date2 = await page.getByTestId("date-now").textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href) @@ -159,7 +159,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page has now an updated date - const date3 = await page.locator('[data-testid="date-now"]').textContent() + const date3 = await page.getByTestId("date-now").textContent() expect(date3).not.toBe(date2) const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, { @@ -188,7 +188,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date4 = await page.locator('[data-testid="date-now"]').textContent() + const date4 = await page.getByTestId("date-now").textContent() expect(date4).toBe(date3) }) } diff --git a/tests/e2e/page-router.test.ts b/tests/e2e/page-router.test.ts index ccb71849a1..759928c7b2 100644 --- a/tests/e2e/page-router.test.ts +++ b/tests/e2e/page-router.test.ts @@ -174,11 +174,11 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) if (fallbackWasServed) { - const loading = await page.locator('[data-testid="loading"]').textContent() + const loading = await page.getByTestId('loading').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1 = await page.locator('[data-testid="date-now"]').textContent() + const date1 = await page.getByTestId('date-now').textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) @@ -238,7 +238,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) // the page is cached - const date2 = await page.locator('[data-testid="date-now"]').textContent() + const date2 = await page.getByTestId('date-now').textContent() expect(date2).toBe(date1) // check json route @@ -299,7 +299,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.locator('[data-testid="date-now"]').textContent() + const date3 = await page.getByTestId('date-now').textContent() expect(date3).not.toBe(date2) // check json route @@ -366,7 +366,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { }, ) expect(response1?.status()).toBe(200) - const date1 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' + const date1 = (await page.getByTestId('date-now').textContent()) ?? '' // ensure response was produced before invocation (served from cache) expect(date1.localeCompare(beforeFetch)).toBeLessThan(0) @@ -391,7 +391,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { }, ) expect(response2?.status()).toBe(200) - const date2 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' + const date2 = (await page.getByTestId('date-now').textContent()) ?? '' // ensure response was produced after initial invocation expect(beforeFetch.localeCompare(date2)).toBeLessThan(0) @@ -416,7 +416,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { ) // ensure response was NOT produced before invocation - const date1 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' + const date1 = (await page.getByTestId('date-now').textContent()) ?? '' expect(date1.localeCompare(beforeFirstFetch)).toBeGreaterThan(0) // allow page to get stale @@ -431,7 +431,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { /s-maxage=60, stale-while-revalidate=[0-9]+, durable/, ) - const date2 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' + const date2 = (await page.getByTestId('date-now').textContent()) ?? '' expect(date2).toBe(date1) // wait a bit to ensure background work has a chance to finish @@ -450,7 +450,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { /s-maxage=60, stale-while-revalidate=[0-9]+, durable/, ) - const date3 = (await page.locator('[data-testid="date-now"]').textContent()) ?? '' + const date3 = (await page.getByTestId('date-now').textContent()) ?? '' expect(date3.localeCompare(date2)).toBeGreaterThan(0) }) @@ -469,7 +469,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - await expect(page.locator('p')).toHaveText('Custom 404 page') + await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page') // https://github.com/vercel/next.js/pull/69802 made changes to returned cache-control header, // after that (14.2.10 and canary.147) 404 pages would have `private` directive, before that @@ -493,7 +493,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - await expect(page.locator('p')).toHaveText('Custom 404 page') + await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page') expect(headers['debug-netlify-cdn-cache-control']).toBe( nextVersionSatisfies('>=15.0.0-canary.187') @@ -748,11 +748,11 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServedImplicitLocale) { - const loading = await page.locator('[data-testid="loading"]').textContent() + const loading = await page.getByTestId('loading').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date1ImplicitLocale = await page.getByTestId('date-now').textContent() const h1ImplicitLocale = await page.locator('h1').textContent() expect(h1ImplicitLocale).toBe(expectedH1Content) @@ -790,11 +790,11 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServedExplicitLocale) { - const loading = await page.locator('[data-testid="loading"]').textContent() + const loading = await page.getByTestId('loading').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date1ExplicitLocale = await page.getByTestId('date-now').textContent() const h1ExplicitLocale = await page.locator('h1').textContent() expect(h1ExplicitLocale).toBe(expectedH1Content) @@ -861,7 +861,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date2ImplicitLocale = await page.getByTestId('date-now').textContent() expect(date2ImplicitLocale).toBe(date1ImplicitLocale) const response2ExplicitLocale = await pollUntilHeadersMatch( @@ -893,7 +893,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date2ExplicitLocale = await page.getByTestId('date-now').textContent() expect(date2ExplicitLocale).toBe(date1ExplicitLocale) // check json route @@ -961,7 +961,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date3ImplicitLocale = await page.getByTestId('date-now').textContent() expect(date3ImplicitLocale).not.toBe(date2ImplicitLocale) const response3ExplicitLocale = await pollUntilHeadersMatch( @@ -984,7 +984,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date3ExplicitLocale = await page.getByTestId('date-now').textContent() expect(date3ExplicitLocale).not.toBe(date2ExplicitLocale) // implicit and explicit locale paths should be the same (same cached response) @@ -1057,7 +1057,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers4ImplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date4ImplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date4ImplicitLocale = await page.getByTestId('date-now').textContent() expect(date4ImplicitLocale).not.toBe(date3ImplicitLocale) const response4ExplicitLocale = await pollUntilHeadersMatch( @@ -1080,7 +1080,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers4ExplicitLocale?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date4ExplicitLocale = await page.locator('[data-testid="date-now"]').textContent() + const date4ExplicitLocale = await page.getByTestId('date-now').textContent() expect(date4ExplicitLocale).not.toBe(date3ExplicitLocale) // implicit and explicit locale paths should be the same (same cached response) @@ -1173,11 +1173,11 @@ test.describe('Page Router with basePath and i18n', () => { ) if (fallbackWasServed) { - const loading = await page.locator('[data-testid="loading"]').textContent() + const loading = await page.getByTestId('loading').textContent() expect(loading, 'Fallback should be shown').toBe('Loading...') } - const date1 = await page.locator('[data-testid="date-now"]').textContent() + const date1 = await page.getByTestId('date-now').textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) @@ -1241,7 +1241,7 @@ test.describe('Page Router with basePath and i18n', () => { ) // the page is cached - const date2 = await page.locator('[data-testid="date-now"]').textContent() + const date2 = await page.getByTestId('date-now').textContent() expect(date2).toBe(date1) // check json route @@ -1309,7 +1309,7 @@ test.describe('Page Router with basePath and i18n', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.locator('[data-testid="date-now"]').textContent() + const date3 = await page.getByTestId('date-now').textContent() expect(date3).not.toBe(date2) // check json route @@ -1360,7 +1360,7 @@ test.describe('Page Router with basePath and i18n', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - await expect(page.locator('p')).toHaveText('Custom 404 page for locale: en') + await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page for locale: en') expect(headers['debug-netlify-cdn-cache-control']).toMatch( /no-cache, no-store, max-age=0, must-revalidate, durable/m, @@ -1378,7 +1378,7 @@ test.describe('Page Router with basePath and i18n', () => { const headers = response?.headers() || {} expect(response?.status()).toBe(404) - await expect(page.locator('p')).toHaveText('Custom 404 page for locale: en') + await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page for locale: en') // Prior to v14.2.4 notFound pages are not cacheable // https://github.com/vercel/next.js/pull/66674 diff --git a/tests/e2e/turborepo.test.ts b/tests/e2e/turborepo.test.ts index bfc4725657..de0b79f0c0 100644 --- a/tests/e2e/turborepo.test.ts +++ b/tests/e2e/turborepo.test.ts @@ -41,7 +41,7 @@ test.describe('[PNPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.locator('[data-testid="date-now"]').textContent() + const date1 = await page.getByTestId("date-now").textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') @@ -74,7 +74,7 @@ test.describe('[PNPM] Package manager', () => { ) // the page is cached - const date2 = await page.locator('[data-testid="date-now"]').textContent() + const date2 = await page.getByTestId("date-now").textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepo.url).href) @@ -104,7 +104,7 @@ test.describe('[PNPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.locator('[data-testid="date-now"]').textContent() + const date3 = await page.getByTestId("date-now").textContent() expect(date3).not.toBe(date2) }) }) @@ -149,7 +149,7 @@ test.describe('[NPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.locator('[data-testid="date-now"]').textContent() + const date1 = await page.getByTestId("date-now").textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') @@ -182,7 +182,7 @@ test.describe('[NPM] Package manager', () => { ) // the page is cached - const date2 = await page.locator('[data-testid="date-now"]').textContent() + const date2 = await page.getByTestId("date-now").textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepoNPM.url).href) @@ -212,7 +212,7 @@ test.describe('[NPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.locator('[data-testid="date-now"]').textContent() + const date3 = await page.getByTestId("date-now").textContent() expect(date3).not.toBe(date2) }) diff --git a/tests/fixtures/page-router-404-get-static-props-with-revalidate/pages/404.js b/tests/fixtures/page-router-404-get-static-props-with-revalidate/pages/404.js index cb047699af..d44d5f5d5b 100644 --- a/tests/fixtures/page-router-404-get-static-props-with-revalidate/pages/404.js +++ b/tests/fixtures/page-router-404-get-static-props-with-revalidate/pages/404.js @@ -1,6 +1,6 @@ export default function NotFound({ timestamp }) { return ( -

+

Custom 404 page with revalidate:

{timestamp}

) diff --git a/tests/fixtures/page-router-base-path-i18n/pages/404.js b/tests/fixtures/page-router-base-path-i18n/pages/404.js index 0f42ed27ed..75e2ba1607 100644 --- a/tests/fixtures/page-router-base-path-i18n/pages/404.js +++ b/tests/fixtures/page-router-base-path-i18n/pages/404.js @@ -1,6 +1,6 @@ export default function NotFound({ locale }) { return ( -

+

Custom 404 page for locale:

{locale}

) diff --git a/tests/fixtures/page-router/pages/404.js b/tests/fixtures/page-router/pages/404.js index 3c251e6665..a1c17694d1 100644 --- a/tests/fixtures/page-router/pages/404.js +++ b/tests/fixtures/page-router/pages/404.js @@ -1,3 +1,3 @@ export default function NotFound() { - return

Custom 404 page

+ return

Custom 404 page

} diff --git a/tests/integration/simple-app.test.ts b/tests/integration/simple-app.test.ts index d49dcc9e3d..7d865559fc 100644 --- a/tests/integration/simple-app.test.ts +++ b/tests/integration/simple-app.test.ts @@ -35,6 +35,7 @@ import { startMockBlobStore, } from '../utils/helpers.js' import { + hasDefaultTurbopackBuilds, nextVersionSatisfies, shouldHaveAppRouterGlobalErrorInPrerenderManifest, shouldHaveAppRouterNotFoundInPrerenderManifest, @@ -421,19 +422,23 @@ test.skipIf(process.env.NEXT_VERSION !== 'canary')( }, ) -test('can require CJS module that is not bundled', async (ctx) => { - await createFixture('simple', ctx) - await runPlugin(ctx) +// setup for this test only works with webpack builds due to usage of ` __non_webpack_require__` to avoid bundling a file +test.skipIf(hasDefaultTurbopackBuilds())( + 'can require CJS module that is not bundled', + async (ctx) => { + await createFixture('simple', ctx) + await runPlugin(ctx) - const response = await invokeFunction(ctx, { url: '/api/cjs-file-with-js-extension' }) + const response = await invokeFunction(ctx, { url: '/api/cjs-file-with-js-extension' }) - expect(response.statusCode).toBe(200) + expect(response.statusCode).toBe(200) - const parsedBody = JSON.parse(response.body) + const parsedBody = JSON.parse(response.body) - expect(parsedBody.notBundledCJSModule.isBundled).toEqual(false) - expect(parsedBody.bundledCJSModule.isBundled).toEqual(true) -}) + expect(parsedBody.notBundledCJSModule.isBundled).toEqual(false) + expect(parsedBody.bundledCJSModule.isBundled).toEqual(true) + }, +) describe('next patching', async () => { const { cp: originalCp, appendFile } = (await vi.importActual( diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index d0f92bf530..5a5a08665a 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -31,7 +31,7 @@ import { } from '../../src/build/plugin-context.js' import { BLOB_TOKEN } from './constants.mjs' import { type FixtureTestContext } from './contexts.js' -import { setNextVersionInFixture } from './next-version-helpers.mjs' +import { hasDefaultTurbopackBuilds, setNextVersionInFixture } from './next-version-helpers.mjs' const bootstrapURL = await getBootstrapURL() const actualCwd = await vi.importActual('process').then((p) => p.cwd()) @@ -569,5 +569,8 @@ export async function invokeSandboxedFunction( } export const EDGE_MIDDLEWARE_FUNCTION_NAME = '___netlify-edge-handler-middleware' -export const EDGE_MIDDLEWARE_SRC_FUNCTION_NAME = '___netlify-edge-handler-src-middleware' +// Turbopack has different output than webpack +export const EDGE_MIDDLEWARE_SRC_FUNCTION_NAME = hasDefaultTurbopackBuilds() + ? '___netlify-edge-handler-src-middleware' + : EDGE_MIDDLEWARE_FUNCTION_NAME export const NODE_MIDDLEWARE_FUNCTION_NAME = '___netlify-edge-handler-node-middleware' diff --git a/tests/utils/next-version-helpers.mjs b/tests/utils/next-version-helpers.mjs index 018639ab49..5761f20ca6 100644 --- a/tests/utils/next-version-helpers.mjs +++ b/tests/utils/next-version-helpers.mjs @@ -49,6 +49,10 @@ export function hasNodeMiddlewareSupport() { return nextVersionSatisfies(isNextCanary() ? '>=15.2.0' : '>=15.5.0') } +export function hasDefaultTurbopackBuilds() { + return nextVersionSatisfies('>=15.6.0-canary.40') +} + /** * Check if current next version requires React 19 * @param {string} version Next version From f83a27ada945181a7788a4eadbdb9be74c254dcd Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 15:48:11 +0200 Subject: [PATCH 04/18] test: update wasm fixtures to use next/og instead of @vercel/og --- tests/fixtures/wasm-src/package.json | 1 - tests/fixtures/wasm-src/src/app/og-node/route.js | 2 +- tests/fixtures/wasm-src/src/app/og/route.js | 2 +- tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js | 2 +- tests/fixtures/wasm-src/src/pages/api/og.js | 2 +- tests/fixtures/wasm/app/og-node/route.js | 2 +- tests/fixtures/wasm/app/og/route.js | 2 +- tests/fixtures/wasm/package.json | 1 - tests/fixtures/wasm/pages/api/og-wrong-runtime.js | 2 +- tests/fixtures/wasm/pages/api/og.js | 2 +- 10 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/fixtures/wasm-src/package.json b/tests/fixtures/wasm-src/package.json index d5a533479f..2db1390c4c 100644 --- a/tests/fixtures/wasm-src/package.json +++ b/tests/fixtures/wasm-src/package.json @@ -8,7 +8,6 @@ "build": "next build" }, "dependencies": { - "@vercel/og": "latest", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/tests/fixtures/wasm-src/src/app/og-node/route.js b/tests/fixtures/wasm-src/src/app/og-node/route.js index 6338e7e61b..757fa83aad 100644 --- a/tests/fixtures/wasm-src/src/app/og-node/route.js +++ b/tests/fixtures/wasm-src/src/app/og-node/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/app/og/route.js b/tests/fixtures/wasm-src/src/app/og/route.js index 575c5a01ae..0e6d9e3f70 100644 --- a/tests/fixtures/wasm-src/src/app/og/route.js +++ b/tests/fixtures/wasm-src/src/app/og/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js index a693c6f5df..63a54c10e1 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm-src/src/pages/api/og.js b/tests/fixtures/wasm-src/src/pages/api/og.js index 55ab54d2c1..b3da62740c 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og.js +++ b/tests/fixtures/wasm-src/src/pages/api/og.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export const config = { runtime: 'edge', diff --git a/tests/fixtures/wasm/app/og-node/route.js b/tests/fixtures/wasm/app/og-node/route.js index 6338e7e61b..757fa83aad 100644 --- a/tests/fixtures/wasm/app/og-node/route.js +++ b/tests/fixtures/wasm/app/og-node/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/app/og/route.js b/tests/fixtures/wasm/app/og/route.js index 575c5a01ae..0e6d9e3f70 100644 --- a/tests/fixtures/wasm/app/og/route.js +++ b/tests/fixtures/wasm/app/og/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/package.json b/tests/fixtures/wasm/package.json index d5a533479f..2db1390c4c 100644 --- a/tests/fixtures/wasm/package.json +++ b/tests/fixtures/wasm/package.json @@ -8,7 +8,6 @@ "build": "next build" }, "dependencies": { - "@vercel/og": "latest", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js index a693c6f5df..63a54c10e1 100644 --- a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm/pages/api/og.js b/tests/fixtures/wasm/pages/api/og.js index 55ab54d2c1..b3da62740c 100644 --- a/tests/fixtures/wasm/pages/api/og.js +++ b/tests/fixtures/wasm/pages/api/og.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +import { ImageResponse } from 'next/og' export const config = { runtime: 'edge', From 91b56873fc4eb29e5736bc0784edd9d0ba3a7289 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 15:49:37 +0200 Subject: [PATCH 05/18] test: correct edge function name for turbopack/webpack builds --- tests/utils/fixture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index 5a5a08665a..8dfc6b83d7 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -571,6 +571,6 @@ export async function invokeSandboxedFunction( export const EDGE_MIDDLEWARE_FUNCTION_NAME = '___netlify-edge-handler-middleware' // Turbopack has different output than webpack export const EDGE_MIDDLEWARE_SRC_FUNCTION_NAME = hasDefaultTurbopackBuilds() - ? '___netlify-edge-handler-src-middleware' - : EDGE_MIDDLEWARE_FUNCTION_NAME + ? EDGE_MIDDLEWARE_FUNCTION_NAME + : '___netlify-edge-handler-src-middleware' export const NODE_MIDDLEWARE_FUNCTION_NAME = '___netlify-edge-handler-node-middleware' From c9db2ceb005f87cabbb934e6b6af447da7f3c2e0 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 16:10:55 +0200 Subject: [PATCH 06/18] test: upgrade nx fixture --- tests/fixtures/nx-integrated/.gitignore | 3 +++ tests/fixtures/nx-integrated/nx.json | 26 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tests/fixtures/nx-integrated/.gitignore b/tests/fixtures/nx-integrated/.gitignore index f44f4e80ba..ffa6719d96 100644 --- a/tests/fixtures/nx-integrated/.gitignore +++ b/tests/fixtures/nx-integrated/.gitignore @@ -39,9 +39,12 @@ testem.log Thumbs.db .nx/cache +.nx/workspace-data # Next.js .next # Local Netlify folder .netlify +.cursor/rules/nx-rules.mdc +.github/instructions/nx.instructions.md diff --git a/tests/fixtures/nx-integrated/nx.json b/tests/fixtures/nx-integrated/nx.json index 706a7a57a8..efe7c78792 100644 --- a/tests/fixtures/nx-integrated/nx.json +++ b/tests/fixtures/nx-integrated/nx.json @@ -3,20 +3,33 @@ "targetDefaults": { "build": { "cache": true, - "dependsOn": ["^build"], - "inputs": ["production", "^production"] + "dependsOn": [ + "^build" + ], + "inputs": [ + "production", + "^production" + ] }, "lint": { "cache": true }, "@nx/next:build": { "cache": true, - "dependsOn": ["^build"], - "inputs": ["production", "^production"] + "dependsOn": [ + "^build" + ], + "inputs": [ + "production", + "^production" + ] } }, "namedInputs": { - "default": ["{projectRoot}/**/*", "sharedGlobals"], + "default": [ + "{projectRoot}/**/*", + "sharedGlobals" + ], "production": [ "default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", @@ -31,5 +44,6 @@ "linter": "eslint" } } - } + }, + "useInferencePlugins": false } From 09a8d76b69950a6944f2beefb2f51e90bc958092 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 16:17:32 +0200 Subject: [PATCH 07/18] test: use verbose flag for nx fixture builds --- tests/utils/create-e2e-fixture.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index fe2546da2e..ad604f12a2 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -394,14 +394,14 @@ export const fixtureFactories = { createE2EFixture('nx-integrated', { packageManger: 'pnpm', packagePath: 'apps/next-app', - buildCommand: 'nx run next-app:build', + buildCommand: 'nx run next-app:build --verbose', publishDirectory: 'dist/apps/next-app/.next', }), nxIntegratedDistDir: () => createE2EFixture('nx-integrated', { packageManger: 'pnpm', packagePath: 'apps/custom-dist-dir', - buildCommand: 'nx run custom-dist-dir:build', + buildCommand: 'nx run custom-dist-dir:build --verbose', publishDirectory: 'dist/apps/custom-dist-dir/dist', }), cliBeforeRegionalBlobsSupport: () => From cb2128fb0e42c659e07f4d3b5a40950872935177 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 16:31:47 +0200 Subject: [PATCH 08/18] test: actually upgrade nx --- tests/fixtures/nx-integrated/package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/fixtures/nx-integrated/package.json b/tests/fixtures/nx-integrated/package.json index 814f4d924b..4495ac3944 100644 --- a/tests/fixtures/nx-integrated/package.json +++ b/tests/fixtures/nx-integrated/package.json @@ -13,16 +13,16 @@ "tslib": "^2.3.0" }, "devDependencies": { - "@nx/js": "17.3.0", - "@nx/next": "17.3.0", - "@nx/workspace": "17.3.0", - "@swc-node/register": "~1.6.7", - "@swc/core": "~1.3.85", - "@swc/helpers": "~0.5.2", + "@nx/js": "21.6.3", + "@nx/next": "21.6.3", + "@nx/workspace": "21.6.3", + "@swc-node/register": "1.9.2", + "@swc/core": "1.5.7", + "@swc/helpers": "0.5.17", "@types/node": "18.16.9", "@types/react": "18.2.33", "@types/react-dom": "18.2.14", - "nx": "17.3.0", + "nx": "21.6.3", "ts-node": "10.9.1", "typescript": "~5.3.2" }, From e78f8519047429bf19134be2bf4e2826141ba9d2 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 18:27:08 +0200 Subject: [PATCH 09/18] test: deflake middeware navigation tests by waiting for hydration before clicking links --- tests/e2e/middleware.test.ts | 10 ++++++++++ tests/fixtures/middleware-i18n/pages/link/index.js | 7 +++++++ tests/fixtures/middleware-pages/pages/link/index.js | 7 +++++++ 3 files changed, 24 insertions(+) diff --git a/tests/e2e/middleware.test.ts b/tests/e2e/middleware.test.ts index a25de675a4..369d65f2a1 100644 --- a/tests/e2e/middleware.test.ts +++ b/tests/e2e/middleware.test.ts @@ -246,6 +246,11 @@ for (const { expectedRuntime, isNodeMiddleware, label, testWithSwitchableMiddlew const pageResponse = await page.goto(`${edgeOrNodeMiddlewarePages.url}/link`) expect(await pageResponse?.headerValue('x-runtime')).toEqual(expectedRuntime) + // wait for hydration to finish before doing client navigation + await expect(page.getByTestId('hydration')).toHaveText('hydrated', { + timeout: 10_000, + }) + await page.evaluate(() => { // set some value to window to check later if browser did reload and lost this state ;(window as ExtendedWindow).didReload = false @@ -305,6 +310,11 @@ for (const { expectedRuntime, isNodeMiddleware, label, testWithSwitchableMiddlew ) expect(await pageResponse?.headerValue('x-runtime')).toEqual(expectedRuntime) + // wait for hydration to finish before doing client navigation + await expect(page.getByTestId('hydration')).toHaveText('hydrated', { + timeout: 10_000, + }) + await page.evaluate(() => { // set some value to window to check later if browser did reload and lost this state ;(window as ExtendedWindow).didReload = false diff --git a/tests/fixtures/middleware-i18n/pages/link/index.js b/tests/fixtures/middleware-i18n/pages/link/index.js index 73699d73a1..e0d58ba263 100644 --- a/tests/fixtures/middleware-i18n/pages/link/index.js +++ b/tests/fixtures/middleware-i18n/pages/link/index.js @@ -1,6 +1,12 @@ +import { useState, useEffect } from 'react' import Link from 'next/link' export default function Page() { + const [isHydrated, setIsHydrated] = useState(false) + useEffect(() => { + setIsHydrated(true) + }, []) + return (

Page with Links

@@ -62,6 +68,7 @@ export default function Page() { +
{isHydrated ? 'hydrated' : 'hydrating'}
) } diff --git a/tests/fixtures/middleware-pages/pages/link/index.js b/tests/fixtures/middleware-pages/pages/link/index.js index 73699d73a1..e0d58ba263 100644 --- a/tests/fixtures/middleware-pages/pages/link/index.js +++ b/tests/fixtures/middleware-pages/pages/link/index.js @@ -1,6 +1,12 @@ +import { useState, useEffect } from 'react' import Link from 'next/link' export default function Page() { + const [isHydrated, setIsHydrated] = useState(false) + useEffect(() => { + setIsHydrated(true) + }, []) + return (

Page with Links

@@ -62,6 +68,7 @@ export default function Page() { +
{isHydrated ? 'hydrated' : 'hydrating'}
) } From a67d101c703c0a35ddeb3afaeaef8c3324cd1ce6 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Fri, 3 Oct 2025 18:46:14 +0200 Subject: [PATCH 10/18] test: skip in e2e as well --- tests/e2e/simple-app.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e/simple-app.test.ts b/tests/e2e/simple-app.test.ts index 5c497dc905..c9b4aea017 100644 --- a/tests/e2e/simple-app.test.ts +++ b/tests/e2e/simple-app.test.ts @@ -1,5 +1,5 @@ import { expect, type Locator, type Response } from '@playwright/test' -import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs' +import { hasDefaultTurbopackBuilds, nextVersionSatisfies } from '../utils/next-version-helpers.mjs' import { test } from '../utils/playwright-helpers.js' const expectImageWasLoaded = async (locator: Locator) => { @@ -273,6 +273,8 @@ test('Compressed rewrites are readable', async ({ simple }) => { }) test('can require CJS module that is not bundled', async ({ simple }) => { + // setup for this test only works with webpack builds due to usage of ` __non_webpack_require__` to avoid bundling a file + test.skip(hasDefaultTurbopackBuilds(), 'Setup for this test only works with webpack builds') const resp = await fetch(`${simple.url}/api/cjs-file-with-js-extension`) expect(resp.status).toBe(200) From 3041ba5fd50b08c0ba16c1bc075f94988cd33ac3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 11:37:02 +0200 Subject: [PATCH 11/18] ensure to bundle edge assets for turbopack builds --- src/build/content/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build/content/server.ts b/src/build/content/server.ts index 8aa2287be4..76d49bdd1b 100644 --- a/src/build/content/server.ts +++ b/src/build/content/server.ts @@ -106,7 +106,7 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise => { `server/*`, `server/chunks/**/*`, `server/edge-chunks/**/*`, - `server/edge/chunks/**/*`, + `server/edge/**/*`, `server/+(app|pages)/**/*.js`, ], { From 44c9e7b26328853fb42d255e2c3bb3425737905e Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 12:52:54 +0200 Subject: [PATCH 12/18] upload artifcats aa --- .github/workflows/run-tests.yml | 109 ++++++++++++++++-------------- playwright.config.ts | 2 +- tests/utils/create-e2e-fixture.ts | 27 ++++++-- 3 files changed, 82 insertions(+), 56 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 719e82df1d..5accb38af9 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,15 +1,15 @@ -name: 'Run tests' +name: "Run tests" on: pull_request: branches: [main] schedule: - - cron: '0 6 * * *' # Run every day at 6am UTC + - cron: "0 6 * * *" # Run every day at 6am UTC workflow_dispatch: inputs: versions: - description: 'The versions of Next.js to test against (quoted and comma separated)' + description: "The versions of Next.js to test against (quoted and comma separated)" required: false - default: 'latest' + default: "latest" jobs: setup: @@ -65,12 +65,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: 'Install Node' + - name: "Install Node" uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: 'npm' - cache-dependency-path: '**/package-lock.json' + cache: "npm" + cache-dependency-path: "**/package-lock.json" - uses: oven-sh/setup-bun@v2 - name: setup pnpm/yarn run: | @@ -82,9 +82,9 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 deno-version: v2.2.4 - - name: 'Install dependencies' + - name: "Install dependencies" run: npm ci - - name: 'Prepare Netlify CLI' + - name: "Prepare Netlify CLI" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} run: | @@ -97,8 +97,8 @@ jobs: - uses: actions/cache@v4 id: playwright-cache with: - path: '~/.cache/ms-playwright' - key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' + path: "~/.cache/ms-playwright" + key: "${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}" restore-keys: | ${{ runner.os }}-playwright- - name: Install Playwright Browsers @@ -124,6 +124,13 @@ jobs: name: blob-report-${{matrix.version}}-${{ matrix.shard }} path: blob-report retention-days: 1 + - name: Upload debug artifacts + uses: actions/upload-artifact@v4 + if: always() + with: + name: debug-artifacts-${{matrix.version}}-${{ matrix.shard }} + path: debug-artifacts + retention-days: 1 test: needs: setup @@ -135,9 +142,9 @@ jobs: version: ${{ fromJson(needs.setup.outputs.matrix) }} exclude: - os: windows-2025 - version: '13.5.1' + version: "13.5.1" - os: windows-2025 - version: '14.2.15' + version: "14.2.15" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 @@ -154,12 +161,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: 'Install Node' + - name: "Install Node" uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: 'npm' - cache-dependency-path: '**/package-lock.json' + cache: "npm" + cache-dependency-path: "**/package-lock.json" - name: Prefer npm global on windows if: runner.os == 'Windows' # On Windows by default PATH prefers corepack bundled with Node.js @@ -178,11 +185,11 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17 deno-version: v2.2.4 - - name: 'Install dependencies' + - name: "Install dependencies" run: npm ci - - name: 'Build' + - name: "Build" run: npm run build - - name: 'Vendor deno helpers for integration tests' + - name: "Vendor deno helpers for integration tests" run: node tools/vendor-deno-tools.js - name: Resolve Next.js version id: resolve-next-version @@ -207,13 +214,13 @@ jobs: key: integration-fixtures-${{ runner.os }}-${{steps.resolve-next-version.outputs.version}}-${{ steps.fixture-cache-key.outputs.key }} - - name: 'Prepare Fixtures' + - name: "Prepare Fixtures" if: steps.cache-fixtures.outputs.cache-hit != 'true' run: npm run pretest env: NEXT_VERSION: ${{ matrix.version }} NEXT_RESOLVED_VERSION: ${{ steps.resolve-next-version.outputs.version }} - - name: 'Unit and integration tests' + - name: "Unit and integration tests" run: npm run test:ci:unit-and-integration -- --shard=${{ matrix.shard }}/8 env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} @@ -244,12 +251,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: 'Install Node' + - name: "Install Node" uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: 'npm' - cache-dependency-path: '**/package-lock.json' + cache: "npm" + cache-dependency-path: "**/package-lock.json" - name: setup pnpm/yarn run: corepack enable shell: bash @@ -258,11 +265,11 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 deno-version: v2.2.4 - - name: 'Install dependencies' + - name: "Install dependencies" run: npm ci - - name: 'Build' + - name: "Build" run: npm run build - - name: 'Prepare Netlify CLI' + - name: "Prepare Netlify CLI" env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} run: | @@ -276,7 +283,7 @@ jobs: RESOLVED_VERSION=$(npm view next@${{ matrix.version }} version) echo "version=$RESOLVED_VERSION" >> $GITHUB_OUTPUT echo "Resolved Next.js version for 'next@${{ matrix.version }}' is '$RESOLVED_VERSION'" - - name: 'Smoke tests' + - name: "Smoke tests" run: npm run test:ci:smoke env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} @@ -285,7 +292,7 @@ jobs: merge-reports: if: always() - needs: [setup,e2e] + needs: [setup, e2e] strategy: fail-fast: false matrix: @@ -293,28 +300,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 - with: - node-version: 18 - - name: Install dependencies - run: npm ci + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 18 + - name: Install dependencies + run: npm ci - - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v5 - with: - path: all-blob-reports - pattern: blob-report-${{ matrix.version }}-* - merge-multiple: true + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v5 + with: + path: all-blob-reports + pattern: blob-report-${{ matrix.version }}-* + merge-multiple: true - - name: Merge reports - run: | - npx playwright merge-reports --reporter html ./all-blob-reports - npx playwright merge-reports --reporter json ./all-blob-reports > merged_reports.json + - name: Merge reports + run: | + npx playwright merge-reports --reporter html ./all-blob-reports + npx playwright merge-reports --reporter json ./all-blob-reports > merged_reports.json - - name: Upload HTML report - uses: actions/upload-artifact@v4 - with: - name: html-report-${{ matrix.version }}-attempt-${{ github.run_attempt }} - path: playwright-report - retention-days: 14 + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report-${{ matrix.version }}-attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 diff --git a/playwright.config.ts b/playwright.config.ts index 26627015e1..deee9d4316 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: Boolean(process.env.CI), /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, + retries: process.env.CI ? 0 : 0, /* Limit the number of workers on CI, use default locally */ workers: process.env.CI ? 3 : undefined, globalSetup: './tests/test-setup-e2e.ts', diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index ad604f12a2..a19d244702 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -2,9 +2,9 @@ import { execaCommand } from 'execa' import fg from 'fast-glob' import { exec } from 'node:child_process' import { existsSync } from 'node:fs' -import { appendFile, copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises' +import { appendFile, copyFile, cp, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { dirname, join } from 'node:path' +import { basename, dirname, join } from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' import { cpus } from 'os' @@ -69,7 +69,10 @@ export const createE2EFixture = async (fixture: string, config: E2EConfig = {}) } console.log('\n\n\n🪵 Deploy logs:') console.log(logs) - // on failures we don't delete the deploy + // on failures locally we don't delete the deploy + if (process.env.CI) { + return cleanup(isolatedFixtureRoot, deployID) + } } try { const [packageName] = await Promise.all([ @@ -274,7 +277,23 @@ async function deploySite( } const siteDir = join(isolatedFixtureRoot, cwd) - await execaCommand(cmd, { cwd: siteDir, all: true }).pipeAll?.(join(siteDir, outputFile)) + try { + await execaCommand(cmd, { cwd: siteDir, all: true }).pipeAll?.(join(siteDir, outputFile)) + } catch (error: unknown) { + // try to collect zips if they exist + const functionsPath = join(isolatedFixtureRoot, packagePath ?? '', '.netlify/functions') + const zipPaths = await fg.glob('**/*.zip', { + cwd: functionsPath, + dot: true, + }) + if (zipPaths.length) { + const debugDir = join('debug-artifacts', isolatedFixtureRoot) + await mkdir(debugDir, { recursive: true }) + for (const path of zipPaths) { + await cp(join(functionsPath, path), join(debugDir, basename(path))) + } + } + } const output = await readFile(join(siteDir, outputFile), 'utf-8') const { siteName, deployID } = From a109f74bb47c6aa6fa2528184c030d1902e575b1 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 13:53:22 +0200 Subject: [PATCH 13/18] apply filter to monorepo as well --- src/build/content/server.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/build/content/server.ts b/src/build/content/server.ts index 76d49bdd1b..ffa5dbbd4c 100644 --- a/src/build/content/server.ts +++ b/src/build/content/server.ts @@ -291,6 +291,8 @@ async function patchNextModules( export const copyNextDependencies = async (ctx: PluginContext): Promise => { await tracer.withActiveSpan('copyNextDependencies', async () => { const entries = await readdir(ctx.standaloneDir) + const filter = ctx.constants.IS_LOCAL ? undefined : nodeModulesFilter + const promises: Promise[] = entries.map(async (entry) => { // copy all except the distDir (.next) folder as this is handled in a separate function // this will include the node_modules folder as well @@ -299,7 +301,6 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise => } const src = join(ctx.standaloneDir, entry) const dest = join(ctx.serverHandlerDir, entry) - const filter = ctx.constants.IS_LOCAL ? undefined : nodeModulesFilter await cp(src, dest, { recursive: true, verbatimSymlinks: true, @@ -321,7 +322,7 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise => // see: https://github.com/vercel/next.js/issues/50072 if (existsSync(rootSrcDir) && ctx.standaloneRootDir !== ctx.standaloneDir) { promises.push( - cp(rootSrcDir, rootDestDir, { recursive: true, verbatimSymlinks: true }).then(() => + cp(rootSrcDir, rootDestDir, { recursive: true, verbatimSymlinks: true, filter }).then(() => recreateNodeModuleSymlinks(resolve('node_modules'), rootDestDir), ), ) From 1bec12c499c7aaed5ad3f37102ad471dbaa11e85 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 15:35:15 +0200 Subject: [PATCH 14/18] test: use npm for nx --- tests/fixtures/nx-integrated/package.json | 3 +-- tests/utils/create-e2e-fixture.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/fixtures/nx-integrated/package.json b/tests/fixtures/nx-integrated/package.json index 4495ac3944..8635fc4801 100644 --- a/tests/fixtures/nx-integrated/package.json +++ b/tests/fixtures/nx-integrated/package.json @@ -25,6 +25,5 @@ "nx": "21.6.3", "ts-node": "10.9.1", "typescript": "~5.3.2" - }, - "packageManager": "pnpm@8.9.0" + } } diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index a19d244702..87a416583a 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -293,6 +293,7 @@ async function deploySite( await cp(join(functionsPath, path), join(debugDir, basename(path))) } } + throw error } const output = await readFile(join(siteDir, outputFile), 'utf-8') @@ -411,14 +412,12 @@ export const fixtureFactories = { serverComponents: () => createE2EFixture('server-components'), nxIntegrated: () => createE2EFixture('nx-integrated', { - packageManger: 'pnpm', packagePath: 'apps/next-app', buildCommand: 'nx run next-app:build --verbose', publishDirectory: 'dist/apps/next-app/.next', }), nxIntegratedDistDir: () => createE2EFixture('nx-integrated', { - packageManger: 'pnpm', packagePath: 'apps/custom-dist-dir', buildCommand: 'nx run custom-dist-dir:build --verbose', publishDirectory: 'dist/apps/custom-dist-dir/dist', From cacbce2e60fa7023cc7589905be92707fb8f8c54 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 15:51:03 +0200 Subject: [PATCH 15/18] cleanup --- .github/workflows/run-tests.yml | 109 +++++++++++------------ playwright.config.ts | 2 +- tests/fixtures/middleware/next.config.js | 6 +- tests/utils/create-e2e-fixture.ts | 31 ++----- 4 files changed, 60 insertions(+), 88 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5accb38af9..719e82df1d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,15 +1,15 @@ -name: "Run tests" +name: 'Run tests' on: pull_request: branches: [main] schedule: - - cron: "0 6 * * *" # Run every day at 6am UTC + - cron: '0 6 * * *' # Run every day at 6am UTC workflow_dispatch: inputs: versions: - description: "The versions of Next.js to test against (quoted and comma separated)" + description: 'The versions of Next.js to test against (quoted and comma separated)' required: false - default: "latest" + default: 'latest' jobs: setup: @@ -65,12 +65,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: "Install Node" + - name: 'Install Node' uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: "npm" - cache-dependency-path: "**/package-lock.json" + cache: 'npm' + cache-dependency-path: '**/package-lock.json' - uses: oven-sh/setup-bun@v2 - name: setup pnpm/yarn run: | @@ -82,9 +82,9 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 deno-version: v2.2.4 - - name: "Install dependencies" + - name: 'Install dependencies' run: npm ci - - name: "Prepare Netlify CLI" + - name: 'Prepare Netlify CLI' env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} run: | @@ -97,8 +97,8 @@ jobs: - uses: actions/cache@v4 id: playwright-cache with: - path: "~/.cache/ms-playwright" - key: "${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}" + path: '~/.cache/ms-playwright' + key: '${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}' restore-keys: | ${{ runner.os }}-playwright- - name: Install Playwright Browsers @@ -124,13 +124,6 @@ jobs: name: blob-report-${{matrix.version}}-${{ matrix.shard }} path: blob-report retention-days: 1 - - name: Upload debug artifacts - uses: actions/upload-artifact@v4 - if: always() - with: - name: debug-artifacts-${{matrix.version}}-${{ matrix.shard }} - path: debug-artifacts - retention-days: 1 test: needs: setup @@ -142,9 +135,9 @@ jobs: version: ${{ fromJson(needs.setup.outputs.matrix) }} exclude: - os: windows-2025 - version: "13.5.1" + version: '13.5.1' - os: windows-2025 - version: "14.2.15" + version: '14.2.15' runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v5 @@ -161,12 +154,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: "Install Node" + - name: 'Install Node' uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: "npm" - cache-dependency-path: "**/package-lock.json" + cache: 'npm' + cache-dependency-path: '**/package-lock.json' - name: Prefer npm global on windows if: runner.os == 'Windows' # On Windows by default PATH prefers corepack bundled with Node.js @@ -185,11 +178,11 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/edge-bundler/blob/e55f825bd985d3c92e21d1b765d71e70d5628fba/node/bridge.ts#L17 deno-version: v2.2.4 - - name: "Install dependencies" + - name: 'Install dependencies' run: npm ci - - name: "Build" + - name: 'Build' run: npm run build - - name: "Vendor deno helpers for integration tests" + - name: 'Vendor deno helpers for integration tests' run: node tools/vendor-deno-tools.js - name: Resolve Next.js version id: resolve-next-version @@ -214,13 +207,13 @@ jobs: key: integration-fixtures-${{ runner.os }}-${{steps.resolve-next-version.outputs.version}}-${{ steps.fixture-cache-key.outputs.key }} - - name: "Prepare Fixtures" + - name: 'Prepare Fixtures' if: steps.cache-fixtures.outputs.cache-hit != 'true' run: npm run pretest env: NEXT_VERSION: ${{ matrix.version }} NEXT_RESOLVED_VERSION: ${{ steps.resolve-next-version.outputs.version }} - - name: "Unit and integration tests" + - name: 'Unit and integration tests' run: npm run test:ci:unit-and-integration -- --shard=${{ matrix.shard }}/8 env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} @@ -251,12 +244,12 @@ jobs: fi echo "version=$NODE_VERSION" >> $GITHUB_OUTPUT echo "Node version for 'next@${{ matrix.version }}' is '$NODE_VERSION'" - - name: "Install Node" + - name: 'Install Node' uses: actions/setup-node@v5 with: node-version: ${{ steps.decide-node-version.outputs.version }} - cache: "npm" - cache-dependency-path: "**/package-lock.json" + cache: 'npm' + cache-dependency-path: '**/package-lock.json' - name: setup pnpm/yarn run: corepack enable shell: bash @@ -265,11 +258,11 @@ jobs: with: # Should match the `DENO_VERSION_RANGE` from https://github.com/netlify/build/blob/main/packages/edge-bundler/node/bridge.ts#L20 deno-version: v2.2.4 - - name: "Install dependencies" + - name: 'Install dependencies' run: npm ci - - name: "Build" + - name: 'Build' run: npm run build - - name: "Prepare Netlify CLI" + - name: 'Prepare Netlify CLI' env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} run: | @@ -283,7 +276,7 @@ jobs: RESOLVED_VERSION=$(npm view next@${{ matrix.version }} version) echo "version=$RESOLVED_VERSION" >> $GITHUB_OUTPUT echo "Resolved Next.js version for 'next@${{ matrix.version }}' is '$RESOLVED_VERSION'" - - name: "Smoke tests" + - name: 'Smoke tests' run: npm run test:ci:smoke env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} @@ -292,7 +285,7 @@ jobs: merge-reports: if: always() - needs: [setup, e2e] + needs: [setup,e2e] strategy: fail-fast: false matrix: @@ -300,28 +293,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 - - uses: actions/setup-node@v5 - with: - node-version: 18 - - name: Install dependencies - run: npm ci + - uses: actions/checkout@v5 + - uses: actions/setup-node@v5 + with: + node-version: 18 + - name: Install dependencies + run: npm ci - - name: Download blob reports from GitHub Actions Artifacts - uses: actions/download-artifact@v5 - with: - path: all-blob-reports - pattern: blob-report-${{ matrix.version }}-* - merge-multiple: true + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v5 + with: + path: all-blob-reports + pattern: blob-report-${{ matrix.version }}-* + merge-multiple: true - - name: Merge reports - run: | - npx playwright merge-reports --reporter html ./all-blob-reports - npx playwright merge-reports --reporter json ./all-blob-reports > merged_reports.json + - name: Merge reports + run: | + npx playwright merge-reports --reporter html ./all-blob-reports + npx playwright merge-reports --reporter json ./all-blob-reports > merged_reports.json - - name: Upload HTML report - uses: actions/upload-artifact@v4 - with: - name: html-report-${{ matrix.version }}-attempt-${{ github.run_attempt }} - path: playwright-report - retention-days: 14 + - name: Upload HTML report + uses: actions/upload-artifact@v4 + with: + name: html-report-${{ matrix.version }}-attempt-${{ github.run_attempt }} + path: playwright-report + retention-days: 14 diff --git a/playwright.config.ts b/playwright.config.ts index deee9d4316..26627015e1 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -10,7 +10,7 @@ export default defineConfig({ /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: Boolean(process.env.CI), /* Retry on CI only */ - retries: process.env.CI ? 0 : 0, + retries: process.env.CI ? 2 : 0, /* Limit the number of workers on CI, use default locally */ workers: process.env.CI ? 3 : undefined, globalSetup: './tests/test-setup-e2e.ts', diff --git a/tests/fixtures/middleware/next.config.js b/tests/fixtures/middleware/next.config.js index d59e5d898c..54308b902a 100644 --- a/tests/fixtures/middleware/next.config.js +++ b/tests/fixtures/middleware/next.config.js @@ -23,11 +23,7 @@ const nextConfig = { // without turbopack configuration, so we add a turbopack configuration here to ensure this fixture // works with default build bundler for all tested versions // see https://github.com/vercel/next.js/blob/ba5a0ca79944b4c8a59d80d677bfedaf0fef33d6/packages/next/src/lib/turbopack-warning.ts#L159-L177 - turbopack: { - // there need to be some keys here, as empty object despite currently being suggesting is not actually allowing build to go through - // so we'll set root (which serves same purpose as outputFileTracingRoot more generally) - root: __dirname, - }, + turbopack: {}, outputFileTracingRoot: __dirname, } diff --git a/tests/utils/create-e2e-fixture.ts b/tests/utils/create-e2e-fixture.ts index 87a416583a..639a996aeb 100644 --- a/tests/utils/create-e2e-fixture.ts +++ b/tests/utils/create-e2e-fixture.ts @@ -2,9 +2,9 @@ import { execaCommand } from 'execa' import fg from 'fast-glob' import { exec } from 'node:child_process' import { existsSync } from 'node:fs' -import { appendFile, copyFile, cp, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises' +import { appendFile, copyFile, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { basename, dirname, join } from 'node:path' +import { dirname, join } from 'node:path' import process from 'node:process' import { fileURLToPath } from 'node:url' import { cpus } from 'os' @@ -69,9 +69,9 @@ export const createE2EFixture = async (fixture: string, config: E2EConfig = {}) } console.log('\n\n\n🪵 Deploy logs:') console.log(logs) - // on failures locally we don't delete the deploy + // on failures we don't delete the deploy, but we do cleanup the fixture from filesystem in CI if (process.env.CI) { - return cleanup(isolatedFixtureRoot, deployID) + return cleanup(isolatedFixtureRoot, undefined) } } try { @@ -277,24 +277,7 @@ async function deploySite( } const siteDir = join(isolatedFixtureRoot, cwd) - try { - await execaCommand(cmd, { cwd: siteDir, all: true }).pipeAll?.(join(siteDir, outputFile)) - } catch (error: unknown) { - // try to collect zips if they exist - const functionsPath = join(isolatedFixtureRoot, packagePath ?? '', '.netlify/functions') - const zipPaths = await fg.glob('**/*.zip', { - cwd: functionsPath, - dot: true, - }) - if (zipPaths.length) { - const debugDir = join('debug-artifacts', isolatedFixtureRoot) - await mkdir(debugDir, { recursive: true }) - for (const path of zipPaths) { - await cp(join(functionsPath, path), join(debugDir, basename(path))) - } - } - throw error - } + await execaCommand(cmd, { cwd: siteDir, all: true }).pipeAll?.(join(siteDir, outputFile)) const output = await readFile(join(siteDir, outputFile), 'utf-8') const { siteName, deployID } = @@ -413,13 +396,13 @@ export const fixtureFactories = { nxIntegrated: () => createE2EFixture('nx-integrated', { packagePath: 'apps/next-app', - buildCommand: 'nx run next-app:build --verbose', + buildCommand: 'nx run next-app:build', publishDirectory: 'dist/apps/next-app/.next', }), nxIntegratedDistDir: () => createE2EFixture('nx-integrated', { packagePath: 'apps/custom-dist-dir', - buildCommand: 'nx run custom-dist-dir:build --verbose', + buildCommand: 'nx run custom-dist-dir:build', publishDirectory: 'dist/apps/custom-dist-dir/dist', }), cliBeforeRegionalBlobsSupport: () => From d1ec9d210466301923182cf477d0603e46cd4571 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 17:40:09 +0200 Subject: [PATCH 16/18] fix lint --- tests/e2e/on-demand-app.test.ts | 8 ++++---- tests/e2e/turborepo.test.ts | 12 ++++++------ tests/fixtures/nx-integrated/nx.json | 23 +++++------------------ 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/tests/e2e/on-demand-app.test.ts b/tests/e2e/on-demand-app.test.ts index e21d30d256..b4e6ea63aa 100644 --- a/tests/e2e/on-demand-app.test.ts +++ b/tests/e2e/on-demand-app.test.ts @@ -96,7 +96,7 @@ test.describe('app router on-demand revalidation', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.getByTestId("date-now").textContent() + const date1 = await page.getByTestId('date-now').textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe(expectedH1Content) @@ -127,7 +127,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date2 = await page.getByTestId("date-now").textContent() + const date2 = await page.getByTestId('date-now').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href) @@ -159,7 +159,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page has now an updated date - const date3 = await page.getByTestId("date-now").textContent() + const date3 = await page.getByTestId('date-now').textContent() expect(date3).not.toBe(date2) const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, { @@ -188,7 +188,7 @@ test.describe('app router on-demand revalidation', () => { ) // the page is cached - const date4 = await page.getByTestId("date-now").textContent() + const date4 = await page.getByTestId('date-now').textContent() expect(date4).toBe(date3) }) } diff --git a/tests/e2e/turborepo.test.ts b/tests/e2e/turborepo.test.ts index de0b79f0c0..e362536995 100644 --- a/tests/e2e/turborepo.test.ts +++ b/tests/e2e/turborepo.test.ts @@ -41,7 +41,7 @@ test.describe('[PNPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.getByTestId("date-now").textContent() + const date1 = await page.getByTestId('date-now').textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') @@ -74,7 +74,7 @@ test.describe('[PNPM] Package manager', () => { ) // the page is cached - const date2 = await page.getByTestId("date-now").textContent() + const date2 = await page.getByTestId('date-now').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepo.url).href) @@ -104,7 +104,7 @@ test.describe('[PNPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.getByTestId("date-now").textContent() + const date3 = await page.getByTestId('date-now').textContent() expect(date3).not.toBe(date2) }) }) @@ -149,7 +149,7 @@ test.describe('[NPM] Package manager', () => { : 's-maxage=31536000, stale-while-revalidate=31536000, durable', ) - const date1 = await page.getByTestId("date-now").textContent() + const date1 = await page.getByTestId('date-now').textContent() const h1 = await page.locator('h1').textContent() expect(h1).toBe('Show #71') @@ -182,7 +182,7 @@ test.describe('[NPM] Package manager', () => { ) // the page is cached - const date2 = await page.getByTestId("date-now").textContent() + const date2 = await page.getByTestId('date-now').textContent() expect(date2).toBe(date1) const revalidate = await page.goto(new URL('/api/revalidate', turborepoNPM.url).href) @@ -212,7 +212,7 @@ test.describe('[NPM] Package manager', () => { expect(headers3?.['x-nextjs-cache']).toBeUndefined() // the page has now an updated date - const date3 = await page.getByTestId("date-now").textContent() + const date3 = await page.getByTestId('date-now').textContent() expect(date3).not.toBe(date2) }) diff --git a/tests/fixtures/nx-integrated/nx.json b/tests/fixtures/nx-integrated/nx.json index efe7c78792..6e5eda2808 100644 --- a/tests/fixtures/nx-integrated/nx.json +++ b/tests/fixtures/nx-integrated/nx.json @@ -3,33 +3,20 @@ "targetDefaults": { "build": { "cache": true, - "dependsOn": [ - "^build" - ], - "inputs": [ - "production", - "^production" - ] + "dependsOn": ["^build"], + "inputs": ["production", "^production"] }, "lint": { "cache": true }, "@nx/next:build": { "cache": true, - "dependsOn": [ - "^build" - ], - "inputs": [ - "production", - "^production" - ] + "dependsOn": ["^build"], + "inputs": ["production", "^production"] } }, "namedInputs": { - "default": [ - "{projectRoot}/**/*", - "sharedGlobals" - ], + "default": ["{projectRoot}/**/*", "sharedGlobals"], "production": [ "default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", From 090185c82a8dbbe1112f9802a4febd3fbf6d11d3 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 17:41:28 +0200 Subject: [PATCH 17/18] revert back to vercel/og, as that's not available in next@13 --- tests/fixtures/wasm-src/package.json | 1 + tests/fixtures/wasm-src/src/app/og-node/route.js | 2 +- tests/fixtures/wasm-src/src/app/og/route.js | 2 +- tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js | 2 +- tests/fixtures/wasm-src/src/pages/api/og.js | 2 +- tests/fixtures/wasm/app/og-node/route.js | 2 +- tests/fixtures/wasm/app/og/route.js | 2 +- tests/fixtures/wasm/package.json | 1 + tests/fixtures/wasm/pages/api/og-wrong-runtime.js | 2 +- tests/fixtures/wasm/pages/api/og.js | 2 +- 10 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/fixtures/wasm-src/package.json b/tests/fixtures/wasm-src/package.json index 2db1390c4c..d5a533479f 100644 --- a/tests/fixtures/wasm-src/package.json +++ b/tests/fixtures/wasm-src/package.json @@ -8,6 +8,7 @@ "build": "next build" }, "dependencies": { + "@vercel/og": "latest", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/tests/fixtures/wasm-src/src/app/og-node/route.js b/tests/fixtures/wasm-src/src/app/og-node/route.js index 757fa83aad..6338e7e61b 100644 --- a/tests/fixtures/wasm-src/src/app/og-node/route.js +++ b/tests/fixtures/wasm-src/src/app/og-node/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/app/og/route.js b/tests/fixtures/wasm-src/src/app/og/route.js index 0e6d9e3f70..575c5a01ae 100644 --- a/tests/fixtures/wasm-src/src/app/og/route.js +++ b/tests/fixtures/wasm-src/src/app/og/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js index 63a54c10e1..a693c6f5df 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm-src/src/pages/api/og.js b/tests/fixtures/wasm-src/src/pages/api/og.js index b3da62740c..55ab54d2c1 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og.js +++ b/tests/fixtures/wasm-src/src/pages/api/og.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export const config = { runtime: 'edge', diff --git a/tests/fixtures/wasm/app/og-node/route.js b/tests/fixtures/wasm/app/og-node/route.js index 757fa83aad..6338e7e61b 100644 --- a/tests/fixtures/wasm/app/og-node/route.js +++ b/tests/fixtures/wasm/app/og-node/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/app/og/route.js b/tests/fixtures/wasm/app/og/route.js index 0e6d9e3f70..575c5a01ae 100644 --- a/tests/fixtures/wasm/app/og/route.js +++ b/tests/fixtures/wasm/app/og/route.js @@ -1,4 +1,4 @@ -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/package.json b/tests/fixtures/wasm/package.json index 2db1390c4c..d5a533479f 100644 --- a/tests/fixtures/wasm/package.json +++ b/tests/fixtures/wasm/package.json @@ -8,6 +8,7 @@ "build": "next build" }, "dependencies": { + "@vercel/og": "latest", "next": "latest", "react": "18.2.0", "react-dom": "18.2.0" diff --git a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js index 63a54c10e1..a693c6f5df 100644 --- a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm/pages/api/og.js b/tests/fixtures/wasm/pages/api/og.js index b3da62740c..55ab54d2c1 100644 --- a/tests/fixtures/wasm/pages/api/og.js +++ b/tests/fixtures/wasm/pages/api/og.js @@ -1,5 +1,5 @@ // /pages/api/og.jsx -import { ImageResponse } from 'next/og' +import { ImageResponse } from '@vercel/og' export const config = { runtime: 'edge', From db8b6063c60ca6a2a34aa5ef9dde3dc7fdd7fd90 Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Mon, 6 Oct 2025 18:37:20 +0200 Subject: [PATCH 18/18] sigh, use @vercel/og or next/og conditionally based on version --- tests/fixtures/wasm-src/next.config.js | 24 ++++++++++++++++++ tests/fixtures/wasm-src/package.json | 3 ++- .../wasm-src/src/app/og-node/route.js | 3 ++- tests/fixtures/wasm-src/src/app/og/route.js | 3 ++- .../src/pages/api/og-wrong-runtime.js | 4 +-- tests/fixtures/wasm-src/src/pages/api/og.js | 4 +-- tests/fixtures/wasm/app/og-node/route.js | 3 ++- tests/fixtures/wasm/app/og/route.js | 3 ++- tests/fixtures/wasm/next.config.js | 25 +++++++++++++++++++ tests/fixtures/wasm/package.json | 3 ++- .../wasm/pages/api/og-wrong-runtime.js | 4 +-- tests/fixtures/wasm/pages/api/og.js | 4 +-- 12 files changed, 69 insertions(+), 14 deletions(-) diff --git a/tests/fixtures/wasm-src/next.config.js b/tests/fixtures/wasm-src/next.config.js index 4263b7f9c2..07ebbcc865 100644 --- a/tests/fixtures/wasm-src/next.config.js +++ b/tests/fixtures/wasm-src/next.config.js @@ -1,5 +1,6 @@ const { platform } = require('process') const fsPromises = require('fs/promises') +const { satisfies } = require('semver') // Next.js uses `fs.promises.copyFile` to copy files from `.next`to the `.next/standalone` directory // It tries copying the same file twice in parallel. Unix is fine with that, but Windows fails @@ -28,4 +29,27 @@ module.exports = { ignoreDuringBuilds: true, }, outputFileTracingRoot: __dirname, + // there is no single way to use `next/og` or `@vercel/og` depending on Next.js version + // - next@<14 doesn't have 'next/og' export + // - next turbopack builds doesn't work with `@vercel/og` + // so this adds `next-og-alias` alias depending on next version for both webpack and turbopack + // so we can test this in all the versions + webpack: (config) => { + const hasNextOg = !satisfies(require('next/package.json').version, '<14.0.0', { + includePrerelease: true, + }) + + if (!hasNextOg) { + config.resolve.alias['next-og-alias$'] = '@vercel/og' + } else { + config.resolve.alias['next-og-alias$'] = 'next/og' + } + + return config + }, + turbopack: { + resolveAlias: { + 'next-og-alias': 'next/og', + }, + }, } diff --git a/tests/fixtures/wasm-src/package.json b/tests/fixtures/wasm-src/package.json index d5a533479f..7cbbd4d7ba 100644 --- a/tests/fixtures/wasm-src/package.json +++ b/tests/fixtures/wasm-src/package.json @@ -11,6 +11,7 @@ "@vercel/og": "latest", "next": "latest", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "semver": "^7.7.2" } } diff --git a/tests/fixtures/wasm-src/src/app/og-node/route.js b/tests/fixtures/wasm-src/src/app/og-node/route.js index 6338e7e61b..2fa26189c2 100644 --- a/tests/fixtures/wasm-src/src/app/og-node/route.js +++ b/tests/fixtures/wasm-src/src/app/og-node/route.js @@ -1,4 +1,5 @@ -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/app/og/route.js b/tests/fixtures/wasm-src/src/app/og/route.js index 575c5a01ae..ba3552647f 100644 --- a/tests/fixtures/wasm-src/src/app/og/route.js +++ b/tests/fixtures/wasm-src/src/app/og/route.js @@ -1,4 +1,5 @@ -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js index a693c6f5df..58ed5d86c5 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm-src/src/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ -// /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm-src/src/pages/api/og.js b/tests/fixtures/wasm-src/src/pages/api/og.js index 55ab54d2c1..f3885a194e 100644 --- a/tests/fixtures/wasm-src/src/pages/api/og.js +++ b/tests/fixtures/wasm-src/src/pages/api/og.js @@ -1,5 +1,5 @@ -// /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export const config = { runtime: 'edge', diff --git a/tests/fixtures/wasm/app/og-node/route.js b/tests/fixtures/wasm/app/og-node/route.js index 6338e7e61b..2fa26189c2 100644 --- a/tests/fixtures/wasm/app/og-node/route.js +++ b/tests/fixtures/wasm/app/og-node/route.js @@ -1,4 +1,5 @@ -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/app/og/route.js b/tests/fixtures/wasm/app/og/route.js index 575c5a01ae..ba3552647f 100644 --- a/tests/fixtures/wasm/app/og/route.js +++ b/tests/fixtures/wasm/app/og/route.js @@ -1,4 +1,5 @@ -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export async function GET() { return new ImageResponse(
hi
, { diff --git a/tests/fixtures/wasm/next.config.js b/tests/fixtures/wasm/next.config.js index 4263b7f9c2..8f60441cf4 100644 --- a/tests/fixtures/wasm/next.config.js +++ b/tests/fixtures/wasm/next.config.js @@ -1,5 +1,6 @@ const { platform } = require('process') const fsPromises = require('fs/promises') +const { satisfies } = require('semver') // Next.js uses `fs.promises.copyFile` to copy files from `.next`to the `.next/standalone` directory // It tries copying the same file twice in parallel. Unix is fine with that, but Windows fails @@ -28,4 +29,28 @@ module.exports = { ignoreDuringBuilds: true, }, outputFileTracingRoot: __dirname, + outputFileTracingRoot: __dirname, + // there is no single way to use `next/og` or `@vercel/og` depending on Next.js version + // - next@<14 doesn't have 'next/og' export + // - next turbopack builds doesn't work with `@vercel/og` + // so this adds `next-og-alias` alias depending on next version for both webpack and turbopack + // so we can test this in all the versions + webpack: (config) => { + const hasNextOg = !satisfies(require('next/package.json').version, '<14.0.0', { + includePrerelease: true, + }) + + if (!hasNextOg) { + config.resolve.alias['next-og-alias$'] = '@vercel/og' + } else { + config.resolve.alias['next-og-alias$'] = 'next/og' + } + + return config + }, + turbopack: { + resolveAlias: { + 'next-og-alias': 'next/og', + }, + }, } diff --git a/tests/fixtures/wasm/package.json b/tests/fixtures/wasm/package.json index d5a533479f..7cbbd4d7ba 100644 --- a/tests/fixtures/wasm/package.json +++ b/tests/fixtures/wasm/package.json @@ -11,6 +11,7 @@ "@vercel/og": "latest", "next": "latest", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "semver": "^7.7.2" } } diff --git a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js index a693c6f5df..58ed5d86c5 100644 --- a/tests/fixtures/wasm/pages/api/og-wrong-runtime.js +++ b/tests/fixtures/wasm/pages/api/og-wrong-runtime.js @@ -1,5 +1,5 @@ -// /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export default function () { return new ImageResponse( diff --git a/tests/fixtures/wasm/pages/api/og.js b/tests/fixtures/wasm/pages/api/og.js index 55ab54d2c1..f3885a194e 100644 --- a/tests/fixtures/wasm/pages/api/og.js +++ b/tests/fixtures/wasm/pages/api/og.js @@ -1,5 +1,5 @@ -// /pages/api/og.jsx -import { ImageResponse } from '@vercel/og' +// see next.config for details about 'next-og-alias' +import { ImageResponse } from 'next-og-alias' export const config = { runtime: 'edge',