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('logged errors in client components during ssr have a sourcemapped stack with a codeframe', async () => {
if (isNextDev) {
const outputIndex = next.cliOutput.length
await next.render('/ssr-error-log')
await retry(() => {
expect(next.cliOutput.slice(outputIndex)).toContain(
'Error: ssr-error-log'
)
})
expect(normalizeCliOutput(next.cliOutput.slice(outputIndex))).toContain(
'Error: ssr-error-log' +
'\n at logError (app/ssr-error-log/page.js:4:17)' +
'\n at Page (app/ssr-error-log/page.js:9:3)' +
'\n 2 |' +
'\n 3 | function logError() {' +
"\n> 4 | const error = new Error('ssr-error-log')" +
'\n | ^' +
'\n 5 | console.error(error)' +
'\n 6 | }' +
'\n 7 |' +
'\n'
)
} else {
if (isTurbopack) {
// TODO(veil): Sourcemap names
expect(normalizeCliOutput(next.cliOutput)).toContain(
'Error: ssr-error-log' +
'\n at <unknown> (app/ssr-error-log/page.js:4:17)' +
'\n 2 |' +
'\n 3 | function logError() {' +
"\n> 4 | const error = new Error('ssr-error-log')" +
'\n | ^' +
'\n 5 | console.error(error)' +
'\n 6 | }' +
'\n 7 |' +
'\n'
)
} 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
}
})
})