Files
next.js/test/e2e/app-dir/server-source-maps/server-source-maps.test.ts
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

921 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
// TODO(veil): Should not appear in sourcemapped stackframes.
.replaceAll('webpack:///', 'bundler:///')
.replaceAll(/at [a-zA-Z] \(/g, 'at <mangled> (')
)
}
describe('app-dir - server source maps', () => {
const dependencies = {
// `link:` simulates a package in a monorepo
'internal-pkg': `link:./internal-pkg`,
'external-pkg': `file:./external-pkg`,
}
const { skipped, next, isNextDev, isTurbopack, isRspack } = nextTestSetup({
dependencies,
files: path.join(__dirname, 'fixtures/default'),
// 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 () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/rsc-error-log')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: rsc-error-log'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'Error: rsc-error-log' +
'\n at logError (app/rsc-error-log/page.js:4:17)' +
'\n at Page (app/rsc-error-log/page.js:9:3)' +
'\n 2 |' +
'\n 3 | function logError() {' +
"\n> 4 | const error = new Error('rsc-error-log')" +
'\n | ^' +
'\n 5 | console.error(error)' +
'\n 6 | }' +
'\n 7 |' +
'\n'
)
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths in webpack
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/rsc-error-log/page.js:4:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
"\n> 4 | const error = new Error('rsc-error-log')" +
'\n | ^'
)
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
it('logged errors have a sourcemapped `cause`', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/rsc-error-log-cause')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: rsc-error-log-cause'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'Error: rsc-error-log-cause' +
'\n at logError (app/rsc-error-log-cause/page.js:2:17)' +
'\n at Page (app/rsc-error-log-cause/page.js:8:3)' +
'\n 1 | function logError(cause) {' +
"\n> 2 | const error = new Error('rsc-error-log-cause', { 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:17)' +
'\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 {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/rsc-error-log-cause/page.js:2:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/rsc-error-log-cause/page.js:7:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
"\n> 2 | const error = new Error('rsc-error-log-cause', { cause })" +
'\n | ^'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
"\n > 7 | const error = new Error('Boom')" +
'\n | ^'
)
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
it('logged errors collapse deeply nested causes at depth 2', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/rsc-error-log-nested')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: rsc-error-log-nested'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'Error: rsc-error-log-nested' +
'\n at logError (app/rsc-error-log-nested/page.js:6:18)' +
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
"\n 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
"\n 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
"\n> 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
'\n | ^' +
'\n 7 | console.error(depth0)' +
'\n 8 | }' +
'\n 9 | {' +
'\n [cause]: Error: Depth 1 error' +
'\n at logError (app/rsc-error-log-nested/page.js:5:18)' +
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
"\n 3 | const depth3 = new Error('Depth 3 error', { cause: depth4 })" +
"\n 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
"\n > 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
'\n | ^' +
"\n 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
'\n 7 | console.error(depth0)' +
'\n 8 | } {' +
'\n [cause]: Error: Depth 2 error' +
'\n at logError (app/rsc-error-log-nested/page.js:4:18)' +
'\n at Page (app/rsc-error-log-nested/page.js:11:3)' +
"\n 2 | const depth4 = new Error('Depth 4 error')" +
"\n 3 | const depth3 = new Error('Depth 3 error', { cause: depth4 })" +
"\n > 4 | const depth2 = new Error('Depth 2 error', { cause: depth3 })" +
'\n | ^' +
"\n 5 | const depth1 = new Error('Depth 1 error', { cause: depth2 })" +
"\n 6 | const depth0 = new Error('rsc-error-log-nested', { cause: depth1 })" +
'\n 7 | console.error(depth0) {' +
'\n [cause]: [Error]' +
'\n }' +
'\n }' +
'\n}'
)
// Verify depth 3+ are NOT shown (truncated to [Error])
expect(
normalizeCliOutput(next.cliOutput.slice(outputIndex))
).not.toContain('Error: Depth 3 error')
expect(
normalizeCliOutput(next.cliOutput.slice(outputIndex))
).not.toContain('Error: Depth 4 error')
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths in production
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/rsc-error-log-nested/page.js:6:18)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain('[cause]: [Error]')
expect(normalizeCliOutput(next.cliOutput)).not.toContain(
'Error: Depth 3 error'
)
expect(normalizeCliOutput(next.cliOutput)).not.toContain(
'Error: Depth 4 error'
)
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
it('logged errors include `[errors]` for AggregateError', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/rsc-error-log-aggregate')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'AggregateError: rsc-error-log-aggregate'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'AggregateError: rsc-error-log-aggregate' +
'\n at logError (app/rsc-error-log-aggregate/page.js:6:26)' +
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
"\n 4 | const error2 = new TypeError('Error 2')" +
"\n 5 | const rootError = new Error('Root error')" +
'\n> 6 | const aggregateError = new AggregateError(' +
'\n | ^' +
'\n 7 | [error1, error2],' +
"\n 8 | 'rsc-error-log-aggregate'," +
'\n 9 | { cause: rootError } {' +
'\n [cause]: Error: Root error' +
'\n at logError (app/rsc-error-log-aggregate/page.js:5:21)' +
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
"\n 3 | const error1 = new Error('Error 1')" +
"\n 4 | const error2 = new TypeError('Error 2')" +
"\n > 5 | const rootError = new Error('Root error')" +
'\n | ^' +
'\n 6 | const aggregateError = new AggregateError(' +
'\n 7 | [error1, error2],' +
"\n 8 | 'rsc-error-log-aggregate',," +
'\n [errors]: [' +
'\n Error: Error 1' +
'\n at logError (app/rsc-error-log-aggregate/page.js:3:18)' +
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
'\n 1 | /* global AggregateError */' +
'\n 2 | function logError() {' +
"\n > 3 | const error1 = new Error('Error 1')" +
'\n | ^' +
"\n 4 | const error2 = new TypeError('Error 2')" +
"\n 5 | const rootError = new Error('Root error')" +
'\n 6 | const aggregateError = new AggregateError(,' +
'\n TypeError: Error 2' +
'\n at logError (app/rsc-error-log-aggregate/page.js:4:18)' +
'\n at Page (app/rsc-error-log-aggregate/page.js:15:3)' +
'\n 2 | function logError() {' +
"\n 3 | const error1 = new Error('Error 1')" +
"\n > 4 | const error2 = new TypeError('Error 2')" +
'\n | ^' +
"\n 5 | const rootError = new Error('Root error')" +
'\n 6 | const aggregateError = new AggregateError(' +
'\n 7 | [error1, error2],' +
'\n ]' +
'\n}'
)
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths in production
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/rsc-error-log-aggregate/page.js:6:26)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain('[errors]:')
expect(normalizeCliOutput(next.cliOutput)).toContain('[cause]:')
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
it('stack frames are ignore-listed in ssr', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/ssr-error-log-ignore-listed')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: ssr-error-log-ignore-listed'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
isTurbopack
? 'Error: ssr-error-log-ignore-listed' +
'\n at logError (app/ssr-error-log-ignore-listed/page.js:9:17)' +
'\n at runWithInternalIgnored (app/ssr-error-log-ignore-listed/page.js:19:13)' +
'\n at runWithExternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:18:29)' +
'\n at runWithExternal (app/ssr-error-log-ignore-listed/page.js:17:32)' +
'\n at runWithInternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:16:18)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternalSourceMapped (internal-pkg/sourcemapped.ts:5:10)' +
'\n at runWithInternal (app/ssr-error-log-ignore-listed/page.js:15:28)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternal (internal-pkg/index.js:2:10)' +
'\n at Page (app/ssr-error-log-ignore-listed/page.js:14:14)' +
'\n 7 |' +
'\n'
: 'Error: ssr-error-log-ignore-listed' +
'\n at logError (app/ssr-error-log-ignore-listed/page.js:9:17)' +
'\n at runWithInternalIgnored (app/ssr-error-log-ignore-listed/page.js:19:13)' +
// TODO(veil-NDX-910): Webpacks's sourcemap loader drops `ignoreList`
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
'\n at runInternalIgnored (webpack-internal:/(ssr)/internal-pkg/ignored.ts:6:10)' +
'\n at runWithExternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:18:29)' +
'\n at runWithExternal (app/ssr-error-log-ignore-listed/page.js:17:32)' +
'\n at runWithInternalSourceMapped (app/ssr-error-log-ignore-listed/page.js:16:18)' +
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternalSourceMapped (webpack-internal:/(ssr)/internal-pkg/sourcemapped.ts:5:10)' +
'\n at runWithInternal (app/ssr-error-log-ignore-listed/page.js:15:28)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternal (internal-pkg/index.js:2:10)' +
'\n at Page (app/ssr-error-log-ignore-listed/page.js:14:14)' +
'\n 7 |' +
'\n'
)
if (isTurbopack) {
// TODO(veil): Turbopack errors because it thinks the sources are not part of the project.
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "ssr-error-log-ignore-listed",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-error-log-ignore-listed/page.js (9:17) @ logError
> 9 | const error = new Error('ssr-error-log-ignore-listed')
| ^",
"stack": [
"logError app/ssr-error-log-ignore-listed/page.js (9:17)",
"runWithInternalIgnored app/ssr-error-log-ignore-listed/page.js (19:13)",
"runWithExternalSourceMapped app/ssr-error-log-ignore-listed/page.js (18:29)",
"runWithExternal app/ssr-error-log-ignore-listed/page.js (17:32)",
"runWithInternalSourceMapped app/ssr-error-log-ignore-listed/page.js (16:18)",
"runInternalSourceMapped internal-pkg/sourcemapped.ts (5:10)",
"runWithInternal app/ssr-error-log-ignore-listed/page.js (15:28)",
"runInternal internal-pkg/index.js (2:10)",
"Page app/ssr-error-log-ignore-listed/page.js (14:14)",
],
}
`)
} else {
// TODO(veil-NDX-910): Webpacks's sourcemap loader drops `ignoreList`
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "ssr-error-log-ignore-listed",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-error-log-ignore-listed/page.js (9:17) @ logError
> 9 | const error = new Error('ssr-error-log-ignore-listed')
| ^",
"stack": [
"logError app/ssr-error-log-ignore-listed/page.js (9:17)",
"runWithInternalIgnored app/ssr-error-log-ignore-listed/page.js (19:13)",
"runInternalIgnored ignored.ts (6:10)",
"runWithExternalSourceMapped app/ssr-error-log-ignore-listed/page.js (18:29)",
"runWithExternal app/ssr-error-log-ignore-listed/page.js (17:32)",
"runWithInternalSourceMapped app/ssr-error-log-ignore-listed/page.js (16:18)",
"runInternalSourceMapped sourcemapped.ts (5:10)",
"runWithInternal app/ssr-error-log-ignore-listed/page.js (15:28)",
"runInternal internal-pkg/index.js (2:10)",
"Page app/ssr-error-log-ignore-listed/page.js (14:14)",
],
}
`)
}
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'(app/ssr-error-log-ignore-listed/page.js:9:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'\n' +
"> 9 | const error = new Error('ssr-error-log-ignore-listed')\n" +
' | ^\n'
)
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
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.slice(outputIndex))).toContain(
'Error: rsc-error-log-ignore-listed'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
isTurbopack
? 'Error: rsc-error-log-ignore-listed' +
'\n at logError (app/rsc-error-log-ignore-listed/page.js:8:17)' +
'\n at runWithInternalIgnored (app/rsc-error-log-ignore-listed/page.js:18:13)' +
'\n at runWithExternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:17:29)' +
'\n at runWithExternal (app/rsc-error-log-ignore-listed/page.js:16:32)' +
'\n at runWithInternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:15:18)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternalSourceMapped (internal-pkg/sourcemapped.ts:5:10)' +
'\n at runWithInternal (app/rsc-error-log-ignore-listed/page.js:14:28)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternal (internal-pkg/index.js:2:10)' +
'\n at Page (app/rsc-error-log-ignore-listed/page.js:13:14)' +
'\n 6 |' +
'\n'
: 'Error: rsc-error-log-ignore-listed' +
'\n at logError (app/rsc-error-log-ignore-listed/page.js:8:17)' +
'\n at runWithInternalIgnored (app/rsc-error-log-ignore-listed/page.js:18:13)' +
// TODO(veil): Webpacks's sourcemap loader drops `ignoreList`
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
'\n at runInternalIgnored (webpack-internal:/(rsc)/internal-pkg/ignored.ts:6:10)' +
'\n at runWithExternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:17:29)' +
'\n at runWithExternal (app/rsc-error-log-ignore-listed/page.js:16:32)' +
'\n at runWithInternalSourceMapped (app/rsc-error-log-ignore-listed/page.js:15:18)' +
// TODO(veil): Webpack's sourcemap loader creates an incorrect `sources` entry.
// Can be worked around by using `./sourcemapped.ts` instead of `sourcemapped.ts`.
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternalSourceMapped (webpack-internal:/(rsc)/internal-pkg/sourcemapped.ts:5:10)' +
'\n at runWithInternal (app/rsc-error-log-ignore-listed/page.js:14:28)' +
// Realpath does not point into node_modules so we don't ignore it.
'\n at runInternal (internal-pkg/index.js:2:10)' +
'\n at Page (app/rsc-error-log-ignore-listed/page.js:13:14)' +
'\n 6 |' +
'\n'
)
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
// TODO(veil): relative paths
expect(normalizeCliOutput(next.cliOutput)).toContain(
'at <unknown> (app/rsc-error-log-ignore-listed/page.js:8:17)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
"\n> 8 | const error = new Error('rsc-error-log-ignore-listed')" +
'\n | ^'
)
} else {
// TODO(veil): line/column numbers are flaky in Webpack
}
}
})
it('thrown SSR errors', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/ssr-throw')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain('Error: ssr-throw')
})
const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
expect(cliOutput).toContain(
' Error: ssr-throw' +
'\n at throwError (app/ssr-throw/Thrower.js:4:9)' +
'\n at Thrower (app/ssr-throw/Thrower.js:8:3)' +
'\n 2 |' +
'\n 3 | function throwError() {' +
"\n> 4 | throw new Error('ssr-throw')" +
'\n | ^' +
'\n 5 | }' +
'\n 6 |' +
'\n 7 | export function Thrower() { {' +
"\n digest: '"
)
expect(cliOutput).toMatch(/digest: '\d+'/)
await expect(browser).toDisplayRedbox(`
{
"description": "ssr-throw",
"environmentLabel": null,
"label": "Runtime Error",
"source": "app/ssr-throw/Thrower.js (4:9) @ throwError
> 4 | throw new Error('ssr-throw')
| ^",
"stack": [
"throwError app/ssr-throw/Thrower.js (4:9)",
"Thrower app/ssr-throw/Thrower.js (8:3)",
"Page app/ssr-throw/page.js (6:10)",
],
}
`)
} else {
// SSR errors are not logged because React retries them during hydration.
}
})
it('logged errors preserve their name', async () => {
let cliOutput = next.cliOutput
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/rsc-error-log-custom-name')
cliOutput = next.cliOutput.slice(outputIndex)
}
await retry(() => {
expect(cliOutput).toContain(
// TODO: isNextDev ? 'UnnamedError: rsc-error-log-custom-name-Foo' : '[Error]: rsc-error-log-custom-name-Foo'
isNextDev
? 'Error: rsc-error-log-custom-name-Foo'
: 'Error: rsc-error-log-custom-name-Foo'
)
})
expect(cliOutput).toContain(
// TODO: isNextDev ? 'NamedError [MyError]: rsc-error-log-custom-name-Bar' : '[MyError]: rsc-error-log-custom-name-Bar'
isNextDev
? 'Error [MyError]: rsc-error-log-custom-name-Bar'
: 'Error [MyError]: rsc-error-log-custom-name-Bar'
)
})
it('handles invalid sourcemaps gracefully', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/bad-sourcemap')
await retry(() => {
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'Error: bad-sourcemap'
)
})
if (isTurbopack) {
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
// Node.js is fine with invalid URLs in index maps apparently.
'' +
'Error: bad-sourcemap' +
'\n at logError (app/bad-sourcemap/custom:/[badhost]/app/bad-sourcemap/page.js:6:17)' +
'\n at Page (app/bad-sourcemap/custom:/[badhost]/app/bad-sourcemap/page.js:10:3)' +
'\n'
)
} else {
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
// Node.js is not fine with invalid URLs in vanilla source maps.
// Feel free to adjust these locations. They're just here to showcase
// sourcemapping is broken on invalid sources.
'' +
`\nwebpack-internal:///(rsc)/./app/bad-sourcemap/page.js: Invalid source map. Only conformant source maps can be used to find the original code. Cause: TypeError [ERR_INVALID_ARG_TYPE]: The "payload" argument must be of type object. Received null` +
'\nError: bad-sourcemap' +
'\n at logError (webpack-internal:///(rsc)/./app/bad-sourcemap/page.js:12:19)' +
'\n at Page (webpack-internal:///(rsc)/./app/bad-sourcemap/page.js:15:5)'
)
// Expect the invalid sourcemap warning only once per render.
// Dynamic I/O renders three times.
// One from filterStackFrameDEV.
// One from findSourceMapURLDEV.
expect(
normalizeCliOutput(next.cliOutput.slice(outputIndex)).split(
'Invalid source map.'
).length - 1
).toEqual(3)
}
} else {
// Bundlers silently drop invalid sourcemaps.
expect(
normalizeCliOutput(next.cliOutput).split('Invalid source map.').length -
1
).toEqual(0)
}
})
it('sourcemaps errors during module evaluation', async () => {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/module-evaluation')
if (isNextDev) {
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: module-evaluation'
)
})
const cliOutput = stripAnsi(next.cliOutput.slice(outputIndex))
if (isTurbopack) {
expect(cliOutput).toContain(
'Error: module-evaluation' +
// TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
'\n at module evaluation (app/module-evaluation/module.js:1:22)' +
// TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
'\n at module evaluation (app/module-evaluation/page.js:1:1)' +
'\n at module evaluation (.next'
)
} else {
expect(cliOutput).toContain(
'Error: module-evaluation' +
// TODO(veil): Should map to no name like you'd get with native stacks without a bundler.
// TODO(veil): Location should be sourcemapped
'\n at eval (app/module-evaluation/module.js:1:22)' +
// TODO(veil): Added frames from bundler should be sourcemapped (https://linear.app/vercel/issue/NDX-509/)
'\n at <unknown> (rsc)/.'
)
}
expect(cliOutput).toContain(
'' +
"\n> 1 | export const error = new Error('module-evaluation')" +
'\n | ^'
)
if (isTurbopack) {
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "module-evaluation",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/module-evaluation/module.js (1:22) @ module evaluation
> 1 | export const error = new Error('module-evaluation')
| ^",
"stack": [
"module evaluation app/module-evaluation/module.js (1:22)",
"module evaluation app/module-evaluation/page.js (1:1)",
"module evaluation app/module-evaluation/page.js (6:1)",
"Page <anonymous>",
],
}
`)
} else if (isRspack) {
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "module-evaluation",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/module-evaluation/module.js (1:22) @ eval
> 1 | export const error = new Error('module-evaluation')
| ^",
"stack": [
"eval app/module-evaluation/module.js (1:22)",
"<FIXME-file-protocol>",
"<FIXME-file-protocol>",
"eval about:/Prerender/webpack-internal:///(rsc)/app/module-evaluation/page.js (5:60)",
"<FIXME-file-protocol>",
"<FIXME-file-protocol>",
"Function.all <anonymous>",
"Function.all <anonymous>",
"Page <anonymous>",
],
}
`)
} else {
await expect(browser).toDisplayCollapsedRedbox(`
{
"description": "module-evaluation",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/module-evaluation/module.js (1:22) @ eval
> 1 | export const error = new Error('module-evaluation')
| ^",
"stack": [
"eval app/module-evaluation/module.js (1:22)",
"<FIXME-file-protocol>",
"eval about:/Prerender/webpack-internal:///(rsc)/app/module-evaluation/page.js (5:65)",
"<FIXME-file-protocol>",
"Page <anonymous>",
],
}
`)
}
} else {
if (isTurbopack) {
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
'\nError: module-evaluation' +
// TODO(veil): Turbopack internals. Feel free to update. Tracked in https://linear.app/vercel/issue/NEXT-4362
'\n at module evaluation (app/module-evaluation/module.js:1:22)'
)
expect(normalizeCliOutput(next.cliOutput)).toContain(
'' +
"\n> 1 | export const error = new Error('module-evaluation')" +
'\n | ^'
)
} else {
expect(
normalizeCliOutput(next.cliOutput).replaceAll(
/at \d+ /g,
'at <WebpackModuleID> '
)
).toContain(
'' +
'\nError: module-evaluation' +
// TODO(veil): column numbers are flaky in Webpack
'\n at <WebpackModuleID> (bundler:///app/module-evaluation/module.js:1:'
)
}
}
})
it('ignore-lists anonymous rsc stack frame sandwiches', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/rsc-anonymous-stack-frame-sandwich')
// TODO(veil): Implement sandwich heuristic in `filterStackFrameDEV`
if (isTurbopack) {
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "rsc-anonymous-stack-frame-sandwich: external",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
| ^",
"stack": [
"Set.forEach <anonymous>",
"Set.forEach <anonymous>",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
"Page <anonymous>",
],
},
{
"description": "rsc-anonymous-stack-frame-sandwich: internal",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
| ^",
"stack": [
"Set.forEach <anonymous>",
"Set.forEach <anonymous>",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
"Page <anonymous>",
],
},
]
`)
} else if (isRspack) {
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "rsc-anonymous-stack-frame-sandwich: external",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
| ^",
"stack": [
"Set.forEach <anonymous>",
"Set.forEach <anonymous>",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
"Page <anonymous>",
],
},
{
"description": "rsc-anonymous-stack-frame-sandwich: internal",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
| ^",
"stack": [
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (18:54)",
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (12:7)",
"Set.forEach <anonymous>",
"eval webpack-internal:/(ssr)/internal-pkg/ignored.ts (11:9)",
"Set.forEach <anonymous>",
"runSetOfSets webpack-internal:/(ssr)/internal-pkg/ignored.ts (10:13)",
"runHiddenSetOfSets webpack-internal:/(ssr)/internal-pkg/ignored.ts (18:3)",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
"Page <anonymous>",
],
},
]
`)
} else {
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "rsc-anonymous-stack-frame-sandwich: external",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (5:29) @ Page
> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')
| ^",
"stack": [
"Set.forEach <anonymous>",
"Set.forEach <anonymous>",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (5:29)",
"Page <anonymous>",
],
},
{
"description": "rsc-anonymous-stack-frame-sandwich: internal",
"environmentLabel": "Prerender",
"label": "Console Error",
"source": "app/rsc-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
> 6 | runHiddenSetOfSetsInternal('rsc-anonymous-stack-frame-sandwich: internal')
| ^",
"stack": [
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (18:54)",
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (12:7)",
"Set.forEach <anonymous>",
"eval webpack-internal:/(rsc)/internal-pkg/ignored.ts (11:9)",
"Set.forEach <anonymous>",
"runSetOfSets webpack-internal:/(rsc)/internal-pkg/ignored.ts (10:13)",
"runHiddenSetOfSets webpack-internal:/(rsc)/internal-pkg/ignored.ts (18:3)",
"Page app/rsc-anonymous-stack-frame-sandwich/page.js (6:29)",
"Page <anonymous>",
],
},
]
`)
}
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'' +
'Error: rsc-anonymous-stack-frame-sandwich: external' +
'\n at Page (app/rsc-anonymous-stack-frame-sandwich/page.js:5:29)' +
'\n 3 |' +
'\n 4 | export default function Page() {' +
"\n> 5 | runHiddenSetOfSetsExternal('rsc-anonymous-stack-frame-sandwich: external')" +
'\n | ^'
)
// TODO: assert on 2nd error once that's bug free
} else {
// TODO(veil): assert on 1st error once cursor position is consistent
// TODO(veil): assert on 2nd error once that's bug free
}
})
it('ignore-lists anonymous ssr stack frame sandwiches', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
const browser = await next.browser('/ssr-anonymous-stack-frame-sandwich')
if (isTurbopack) {
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "ssr-anonymous-stack-frame-sandwich: external",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')
| ^",
"stack": [
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (6:29)",
],
},
{
"description": "ignore-listed frames",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (7:29) @ Page
> 7 | runHiddenSetOfSetsInternal('ssr-anonymous-stack-frame-sandwich: internal')
| ^",
"stack": [
"<unknown> internal-pkg/sourcemapped.ts (18:43)",
"<unknown> internal-pkg/sourcemapped.ts (11:7)",
"Set.forEach <anonymous>",
"<unknown> internal-pkg/sourcemapped.ts (10:9)",
"Set.forEach <anonymous>",
"runSetOfSets internal-pkg/sourcemapped.ts (9:13)",
"runHiddenSetOfSets internal-pkg/sourcemapped.ts (17:3)",
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (7:29)",
],
},
]
`)
} else {
// 2nd error from runHiddenSetOfSetsInternal hits https://linear.app/vercel/issue/NEXT-4412
await expect(browser).toDisplayCollapsedRedbox(`
[
{
"description": "ssr-anonymous-stack-frame-sandwich: external",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (6:29) @ Page
> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')
| ^",
"stack": [
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (6:29)",
],
},
{
"description": "ignore-listed frames",
"environmentLabel": null,
"label": "Console Error",
"source": "app/ssr-anonymous-stack-frame-sandwich/page.js (7:29) @ Page
> 7 | runHiddenSetOfSetsInternal('ssr-anonymous-stack-frame-sandwich: internal')
| ^",
"stack": [
"eval sourcemapped.ts (18:43)",
"eval sourcemapped.ts (11:7)",
"Set.forEach <anonymous>",
"eval sourcemapped.ts (10:9)",
"Set.forEach <anonymous>",
"runSetOfSets sourcemapped.ts (9:13)",
"runHiddenSetOfSets sourcemapped.ts (17:3)",
"Page app/ssr-anonymous-stack-frame-sandwich/page.js (7:29)",
],
},
]
`)
}
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'' +
'Error: ssr-anonymous-stack-frame-sandwich: external' +
'\n at Page (app/ssr-anonymous-stack-frame-sandwich/page.js:6:29)' +
'\n 4 |' +
'\n 5 | export default function Page() {' +
"\n> 6 | runHiddenSetOfSetsExternal('ssr-anonymous-stack-frame-sandwich: external')" +
'\n | ^'
)
// TODO(veil): assert on 2nd error once that's bug free
} else {
// TODO(veil): assert on 1st error once cursor position is consistent
// TODO(veil): assert on 2nd error once that's bug free
}
})
})