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',