diff --git a/test/development/app-render-error-log/app-render-error-log.test.ts b/test/development/app-render-error-log/app-render-error-log.test.ts deleted file mode 100644 index 517193371d32b..0000000000000 --- a/test/development/app-render-error-log/app-render-error-log.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { nextTestSetup } from 'e2e-utils' -import { check } from 'next-test-utils' - -describe('app-render-error-log', () => { - const { next } = nextTestSetup({ - files: __dirname, - }) - it('should log the correct values on app-render error', async () => { - const outputIndex = next.cliOutput.length - await next.fetch('/') - - await check(() => next.cliOutput.slice(outputIndex), /at Page/) - const cliOutput = next.cliOutput.slice(outputIndex) - - await check(() => cliOutput, /digest:/) - expect(cliOutput).toInclude('Error: boom') - expect(cliOutput).toInclude('at fn2 (./app/fn.ts') - expect(cliOutput).toMatch(/at (Module\.)?fn1 \(\.\/app\/fn\.ts/) - expect(cliOutput).toInclude('at Page (./app/page.tsx') - - expect(cliOutput).not.toInclude('webpack-internal') - }) - - it('should log the correct values on app-render error with edge runtime', async () => { - const outputIndex = next.cliOutput.length - await next.fetch('/edge') - - await check(() => next.cliOutput.slice(outputIndex), /at EdgePage/) - const cliOutput = next.cliOutput.slice(outputIndex) - - await check(() => cliOutput, /digest:/) - expect(cliOutput).toInclude('Error: boom') - expect(cliOutput).toInclude('at fn2 (./app/fn.ts') - expect(cliOutput).toMatch(/at (Module\.)?fn1 \(\.\/app\/fn\.ts/) - expect(cliOutput).toInclude('at EdgePage (./app/edge/page.tsx') - - expect(cliOutput).not.toInclude('webpack-internal') - }) -}) diff --git a/test/development/app-render-error-log/app/edge/page.tsx b/test/development/app-render-error-log/app/edge/page.tsx deleted file mode 100644 index 5bc2b8e490188..0000000000000 --- a/test/development/app-render-error-log/app/edge/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -export const runtime = 'edge' - -import { fn1 } from '../fn' - -export default function EdgePage() { - fn1() - return

hello world

-} diff --git a/test/development/app-render-error-log/app/fn.ts b/test/development/app-render-error-log/app/fn.ts deleted file mode 100644 index 29a1f5b1f0abf..0000000000000 --- a/test/development/app-render-error-log/app/fn.ts +++ /dev/null @@ -1,7 +0,0 @@ -function fn2() { - throw new Error('boom') -} - -export function fn1() { - fn2() -} diff --git a/test/development/app-render-error-log/app/layout.tsx b/test/development/app-render-error-log/app/layout.tsx deleted file mode 100644 index e7077399c03ce..0000000000000 --- a/test/development/app-render-error-log/app/layout.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Root({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} diff --git a/test/development/app-render-error-log/app/page.tsx b/test/development/app-render-error-log/app/page.tsx deleted file mode 100644 index 99b304cc5d294..0000000000000 --- a/test/development/app-render-error-log/app/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { fn1 } from './fn' - -export default function Page() { - fn1() - return

hello world

-} diff --git a/test/development/app-render-error-log/next.config.js b/test/development/app-render-error-log/next.config.js deleted file mode 100644 index 4ba52ba2c8df6..0000000000000 --- a/test/development/app-render-error-log/next.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/default/app/layout.js b/test/e2e/app-dir/server-source-maps/fixtures/default/app/layout.js index a3a86a5ca1e12..10dc132240695 100644 --- a/test/e2e/app-dir/server-source-maps/fixtures/default/app/layout.js +++ b/test/e2e/app-dir/server-source-maps/fixtures/default/app/layout.js @@ -1,7 +1,11 @@ +import { Suspense } from 'react' + export default function Root({ children }) { return ( - {children} + + {children} + ) } diff --git a/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/Thrower.js b/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/Thrower.js new file mode 100644 index 0000000000000..f1a4d31cf3937 --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/Thrower.js @@ -0,0 +1,9 @@ +'use client' + +function throwError() { + throw new Error('Boom') +} + +export function Thrower() { + throwError() +} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/page.js b/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/page.js new file mode 100644 index 0000000000000..a24b6d668d41b --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-throw/page.js @@ -0,0 +1,7 @@ +import { connection } from 'next/server' +import { Thrower } from './Thrower' + +export default async function Page() { + await connection() + return +} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/edge/app/layout.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/layout.js new file mode 100644 index 0000000000000..40b9a2c4d5e74 --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/layout.js @@ -0,0 +1,9 @@ +export const runtime = 'edge' + +export default function Root({ children }) { + return ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-error-log/page.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-error-log/page.js similarity index 55% rename from test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-error-log/page.js rename to test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-error-log/page.js index 041613f088e3e..8fbe469d0a32a 100644 --- a/test/e2e/app-dir/server-source-maps/fixtures/default/app/ssr-error-log/page.js +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-error-log/page.js @@ -1,8 +1,5 @@ -'use client' - function logError() { - const error = new Error('Boom') - console.error(error) + console.error(new Error('Boom')) } export default function Page() { diff --git a/test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-throw/page.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-throw/page.js new file mode 100644 index 0000000000000..8ff01ee0ebc09 --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/rsc-throw/page.js @@ -0,0 +1,8 @@ +function throwError() { + throw new Error('Boom') +} + +export default function Page() { + throwError() + return null +} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/edge/app/ssr-throw/page.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/ssr-throw/page.js new file mode 100644 index 0000000000000..db2eced881c45 --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/app/ssr-throw/page.js @@ -0,0 +1,10 @@ +'use client' + +function throwError() { + throw new Error('Boom') +} + +export default function Page() { + throwError() + return null +} diff --git a/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js b/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js new file mode 100644 index 0000000000000..70e8c4e01e19b --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/fixtures/edge/next.config.js @@ -0,0 +1,10 @@ +/** + * @type {import('next').NextConfig} + */ +const nextConfig = { + experimental: { + serverSourceMaps: true, + }, +} + +module.exports = nextConfig diff --git a/test/e2e/app-dir/server-source-maps/server-source-maps-edge.test.ts b/test/e2e/app-dir/server-source-maps/server-source-maps-edge.test.ts new file mode 100644 index 0000000000000..fe33317af030d --- /dev/null +++ b/test/e2e/app-dir/server-source-maps/server-source-maps-edge.test.ts @@ -0,0 +1,99 @@ +/* eslint-disable jest/no-standalone-expect */ +import * as path from 'path' +import { nextTestSetup } from 'e2e-utils' +import stripAnsi from 'strip-ansi' +import { retry } from 'next-test-utils' + +function normalizeCliOutput(output: string) { + return stripAnsi(output) +} + +describe('app-dir - server source maps edge runtime', () => { + const { skipped, next, isNextDev, isTurbopack } = nextTestSetup({ + files: path.join(__dirname, 'fixtures/edge'), + // Deploy tests don't have access to runtime logs. + // Manually verify that the runtime logs match. + skipDeployment: true, + }) + + if (skipped) return + + it('logged errors have a sourcemapped stack with a codeframe', async () => { + const outputIndex = next.cliOutput.length + await next.render('/rsc-error-log') + + if (isNextDev) { + await retry(() => { + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') + }) + expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain( + isTurbopack + ? // TODO(veil): Error stack should be printed + '\n[Error: Boom]\n' + : // TODO(veil): Error stack should be printed + '\n[Error: Boom]\n' + ) + } else { + // TODO: Test `next build` with `--enable-source-maps`. + } + }) + + it('thrown SSR errors', async () => { + const outputIndex = next.cliOutput.length + await next.render('/ssr-throw') + + if (isNextDev) { + await retry(() => { + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') + }) + + const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) + expect(cliOutput).toContain( + isTurbopack + ? '\n ⨯ Error: Boom' + + '\n at throwError (./app/ssr-throw/page.js:4:9)' + + '\n at Page (./app/ssr-throw/page.js:8:3)' + + '\ndigest: "' + : '\n ⨯ Error: Boom' + + '\n at throwError (./app/ssr-throw/page.js:6:11)' + + '\n at Page (./app/ssr-throw/page.js:9:5)' + + '\ndigest: "' + ) + expect(cliOutput).toMatch(/digest: "\d+"/) + } else { + // TODO: Test `next build` with `--enable-source-maps`. + } + }) + + it('should log the correct values on app-render error', async () => { + const outputIndex = next.cliOutput.length + await next.fetch('/rsc-throw') + + if (isNextDev) { + await retry(() => { + expect(next.cliOutput.slice(outputIndex)).toMatch(/Error: Boom/) + }) + + const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) + // TODO(veil): Hide Node.js internal stackframes + expect(cliOutput).toContain( + isTurbopack + ? '\n ⨯ Error: Boom' + + '\n at throwError (./app/rsc-throw/page.js:2:9)' + + '\n at Page (./app/rsc-throw/page.js:6:3)' + + // TODO(veil): Hide Node.js internal stackframes + '\n at AsyncLocalStorage.run (node:async_hooks:346:14)' + + '\ndigest: "' + : '\n ⨯ Error: Boom' + + '\n at throwError (./app/rsc-throw/page.js:6:11)' + + '\n at Page (./app/rsc-throw/page.js:9:5)' + + // TODO(veil): Hide Node.js internal stackframes + '\n at AsyncLocalStorage.run (node:async_hooks:346:14)' + + '\ndigest: "' + ) + expect(cliOutput).toMatch(/digest: "\d+"/) + } else { + // TODO: Test `next build` with `--enable-source-maps`. + } + }) +}) diff --git a/test/e2e/app-dir/server-source-maps/server-source-maps.test.ts b/test/e2e/app-dir/server-source-maps/server-source-maps.test.ts index 9f794beb20331..19faa1c4c5bbe 100644 --- a/test/e2e/app-dir/server-source-maps/server-source-maps.test.ts +++ b/test/e2e/app-dir/server-source-maps/server-source-maps.test.ts @@ -26,88 +26,92 @@ describe('app-dir - server source maps', () => { if (skipped) return it('logged errors have a sourcemapped stack with a codeframe', async () => { + const outputIndex = next.cliOutput.length await next.render('/rsc-error-log') if (isNextDev) { await retry(() => { - expect(normalizeCliOutput(next.cliOutput)).toContain( - isTurbopack - ? '\nError: Boom' + - '\n at logError (turbopack://[project]/app/rsc-error-log/page.js:2:16)' + - '\n at Page (turbopack://[project]/app/rsc-error-log/page.js:7:2)' + - '\n 1 | function logError() {' + - "\n> 2 | const error = new Error('Boom')" + - '\n | ^' + - '\n 3 | console.error(error)' + - '\n 4 | }' + - '\n 5 |' + - '\n' - : '\nError: Boom' + - '\n at logError (app/rsc-error-log/page.js:2:16)' + - // FIXME: Method name should be "Page" - '\n at logError (app/rsc-error-log/page.js:7:2)' + - '\n 1 | function logError() {' + - "\n> 2 | const error = new Error('Boom')" + - '\n | ^' + - '\n 3 | console.error(error)' + - '\n 4 | }' + - '\n 5 |' + - '\n' - ) + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') }) + expect(normalizeCliOutput(next.cliOutput)).toContain( + isTurbopack + ? '\nError: Boom' + + '\n at logError (turbopack://[project]/app/rsc-error-log/page.js:2:16)' + + '\n at Page (turbopack://[project]/app/rsc-error-log/page.js:7:2)' + + '\n 1 | function logError() {' + + "\n> 2 | const error = new Error('Boom')" + + '\n | ^' + + '\n 3 | console.error(error)' + + '\n 4 | }' + + '\n 5 |' + + '\n' + : '\nError: Boom' + + '\n at logError (app/rsc-error-log/page.js:2:16)' + + // FIXME: Method name should be "Page" + '\n at logError (app/rsc-error-log/page.js:7:2)' + + '\n 1 | function logError() {' + + "\n> 2 | const error = new Error('Boom')" + + '\n | ^' + + '\n 3 | console.error(error)' + + '\n 4 | }' + + '\n 5 |' + + '\n' + ) } else { // TODO: Test `next build` with `--enable-source-maps`. } }) it('logged errors have a sourcemapped `cause`', async () => { + const outputIndex = next.cliOutput.length await next.render('/rsc-error-log-cause') if (isNextDev) { await retry(() => { - expect(normalizeCliOutput(next.cliOutput)).toContain( - isTurbopack - ? '\nError: Boom' + - '\n at logError (turbopack://[project]/app/rsc-error-log-cause/page.js:2:16)' + - '\n at Page (turbopack://[project]/app/rsc-error-log-cause/page.js:8:2)' + - '\n 1 | function logError(cause) {' + - "\n> 2 | const error = new Error('Boom', { cause })" + - '\n | ^' + - '\n 3 | console.error(error)' + - '\n 4 | }' + - '\n 5 | {' + - '\n [cause]: Error: Boom' + - '\n at Page (turbopack://[project]/app/rsc-error-log-cause/page.js:7:16)' + - '\n 5 |' + - '\n 6 | export default function Page() {' + - "\n > 7 | const error = new Error('Boom')" + - '\n | ^' + - '\n 8 | logError(error)' + - '\n 9 | return null' + - '\n 10 | }' + - '\n' - : '\nError: Boom' + - '\n at logError (app/rsc-error-log-cause/page.js:2:16)' + - // FIXME: Method name should be "Page" - '\n at logError (app/rsc-error-log-cause/page.js:8:2)' + - '\n 1 | function logError(cause) {' + - "\n> 2 | const error = new Error('Boom', { cause })" + - '\n | ^' + - '\n 3 | console.error(error)' + - '\n 4 | }' + - '\n 5 | {' + - '\n [cause]: Error: Boom' + - '\n at Page (app/rsc-error-log-cause/page.js:7:16)' + - '\n 5 |' + - '\n 6 | export default function Page() {' + - "\n > 7 | const error = new Error('Boom')" + - '\n | ^' + - '\n 8 | logError(error)' + - '\n 9 | return null' + - '\n 10 | }' + - '\n' - ) + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') }) + expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain( + isTurbopack + ? '\nError: Boom' + + '\n at logError (turbopack://[project]/app/rsc-error-log-cause/page.js:2:16)' + + '\n at Page (turbopack://[project]/app/rsc-error-log-cause/page.js:8:2)' + + '\n 1 | function logError(cause) {' + + "\n> 2 | const error = new Error('Boom', { cause })" + + '\n | ^' + + '\n 3 | console.error(error)' + + '\n 4 | }' + + '\n 5 | {' + + '\n [cause]: Error: Boom' + + '\n at Page (turbopack://[project]/app/rsc-error-log-cause/page.js:7:16)' + + '\n 5 |' + + '\n 6 | export default function Page() {' + + "\n > 7 | const error = new Error('Boom')" + + '\n | ^' + + '\n 8 | logError(error)' + + '\n 9 | return null' + + '\n 10 | }' + + '\n' + : '\nError: Boom' + + '\n at logError (app/rsc-error-log-cause/page.js:2:16)' + + // FIXME: Method name should be "Page" + '\n at logError (app/rsc-error-log-cause/page.js:8:2)' + + '\n 1 | function logError(cause) {' + + "\n> 2 | const error = new Error('Boom', { cause })" + + '\n | ^' + + '\n 3 | console.error(error)' + + '\n 4 | }' + + '\n 5 | {' + + '\n [cause]: Error: Boom' + + '\n at Page (app/rsc-error-log-cause/page.js:7:16)' + + '\n 5 |' + + '\n 6 | export default function Page() {' + + "\n > 7 | const error = new Error('Boom')" + + '\n | ^' + + '\n 8 | logError(error)' + + '\n 9 | return null' + + '\n 10 | }' + + '\n' + ) } else { // TODO: Test `next build` with `--enable-source-maps`. } @@ -118,22 +122,24 @@ describe('app-dir - server source maps', () => { ;(isTurbopack ? it.skip : it)( 'stack frames are ignore-listed in ssr', async () => { + const outputIndex = next.cliOutput.length await next.render('/ssr-error-log-ignore-listed') if (isNextDev) { await retry(() => { - expect(normalizeCliOutput(next.cliOutput)).toContain( - isTurbopack - ? // FIXME: Turbopack resolver bug - "Module not found: Can't resolve 'internal-pkg'" - : '\nError: Boom' + - '\n at logError (app/ssr-error-log-ignore-listed/page.js:5:16)' + - // FIXME: Method name should be "Page" - '\n at logError (app/ssr-error-log-ignore-listed/page.js:10:12)' + - '\n at Page (app/ssr-error-log-ignore-listed/page.js:10:6)' + - '\n 3 |' - ) + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') }) + expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain( + isTurbopack + ? // FIXME: Turbopack resolver bug + "Module not found: Can't resolve 'internal-pkg'" + : '\nError: Boom' + + '\n at logError (app/ssr-error-log-ignore-listed/page.js:5:16)' + + // FIXME: Method name should be "Page" + '\n at logError (app/ssr-error-log-ignore-listed/page.js:10:12)' + + '\n at Page (app/ssr-error-log-ignore-listed/page.js:10:6)' + + '\n 3 |' + ) } else { // TODO: Test `next build` with `--enable-source-maps`. } @@ -145,36 +151,68 @@ describe('app-dir - server source maps', () => { ;(isTurbopack ? it.skip : it)( 'stack frames are ignore-listed in rsc', async () => { + const outputIndex = next.cliOutput.length await next.render('/rsc-error-log-ignore-listed') if (isNextDev) { await retry(() => { - expect(normalizeCliOutput(next.cliOutput)).toContain( - isTurbopack - ? // FIXME: Turbopack resolver bug - "Module not found: Can't resolve 'internal-pkg'" - : '\nError: Boom' + - '\n at logError (app/rsc-error-log-ignore-listed/page.js:4:16)' + - // FIXME: Method name should be "Page" - '\n at logError (app/rsc-error-log-ignore-listed/page.js:9:12)' + - '\n at Page (app/rsc-error-log-ignore-listed/page.js:9:6)' + - '\n 2 |' - ) + expect( + normalizeCliOutput(next.cliOutput.slice(outputIndex)) + ).toContain('Error: Boom') }) + expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain( + isTurbopack + ? // FIXME: Turbopack resolver bug + "Module not found: Can't resolve 'internal-pkg'" + : '\nError: Boom' + + '\n at logError (app/rsc-error-log-ignore-listed/page.js:4:16)' + + // FIXME: Method name should be "Page" + '\n at logError (app/rsc-error-log-ignore-listed/page.js:9:12)' + + '\n at Page (app/rsc-error-log-ignore-listed/page.js:9:6)' + + '\n 2 |' + ) } else { // TODO: Test `next build` with `--enable-source-maps`. } } ) + it('thrown SSR errors', async () => { + const outputIndex = next.cliOutput.length + await next.render('/ssr-throw') + + if (isNextDev) { + await retry(() => { + expect(next.cliOutput.slice(outputIndex)).toContain('Error: Boom') + }) + + const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex)) + expect(cliOutput).toContain( + isTurbopack + ? '\n ⨯ Error: Boom' + + '\n at throwError (./app/ssr-throw/Thrower.js:4:9)' + + '\n at Thrower (./app/ssr-throw/Thrower.js:8:3)' + + '\ndigest: "' + : '\n ⨯ Error: Boom' + + '\n at throwError (./app/ssr-throw/Thrower.js:6:11)' + + '\n at Thrower (./app/ssr-throw/Thrower.js:9:5)' + + '\ndigest: "' + ) + expect(cliOutput).toMatch(/digest: "\d+"/) + } else { + // TODO: Test `next build` with `--enable-source-maps`. + } + }) + it('logged errors preserve their name', async () => { + const outputIndex = next.cliOutput.length await next.render('/rsc-error-log-custom-name') - expect(next.cliOutput).toContain( + expect(next.cliOutput.slice(outputIndex)).toContain( // TODO: isNextDev ? 'UnnamedError: Foo' : '[Error]: Foo' isNextDev ? 'Error: Foo' : 'Error: Foo' ) - expect(next.cliOutput).toContain( + expect(next.cliOutput.slice(outputIndex)).toContain( // TODO: isNextDev ? 'NamedError [MyError]: Bar' : '[MyError]: Bar' isNextDev ? 'Error [MyError]: Bar' : 'Error [MyError]: Bar' )