Skip to content

Commit c910be0

Browse files
authored
Use snapshots to verify error stack traces for dynamic validation errors (#80946)
For now, this is just documenting the current state of affairs with regards to how we print dynamic validation errors during prerendering. Previously, the tests were run with and without `experimental.serverMinification`. Now, we're running them with and without the `--debug-prerender` CLI flag instead. In follow-ups, we will add assertions for `next dev`, and also improve the errors for `next build`. > [!NOTE] > This PR is best reviewed with hidden whitespace changes.
1 parent 345d4d4 commit c910be0

File tree

29 files changed

+1389
-811
lines changed

29 files changed

+1389
-811
lines changed
Lines changed: 99 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,119 @@
11
import { nextTestSetup } from 'e2e-utils'
2+
import { getPrerenderOutput } from './utils'
23

3-
import { createExpectError } from './utils'
4+
describe.each([
5+
{ inDebugMode: true, name: 'With --prerender-debug' },
6+
{ inDebugMode: false, name: 'Without --prerender-debug' },
7+
])('Dynamic IO Errors - $name', ({ inDebugMode }) => {
8+
describe('Sync Dynamic - With Fallback - Math.random()', () => {
9+
const { next, isNextDev, skipped } = nextTestSetup({
10+
files: __dirname + '/fixtures/sync-random-with-fallback',
11+
skipStart: true,
12+
skipDeployment: true,
13+
buildOptions: inDebugMode ? ['--debug-prerender'] : undefined,
14+
})
415

5-
function runTests(options: { withMinification: boolean }) {
6-
const { withMinification } = options
7-
describe(`Dynamic IO Errors - ${withMinification ? 'With Minification' : 'Without Minification'}`, () => {
8-
describe('Sync Dynamic - With Fallback - Math.random()', () => {
9-
const { next, isNextDev, skipped } = nextTestSetup({
10-
files: __dirname + '/fixtures/sync-random-with-fallback',
11-
skipStart: true,
12-
skipDeployment: true,
13-
})
16+
if (skipped) {
17+
return
18+
}
1419

15-
if (skipped) {
16-
return
17-
}
20+
if (isNextDev) {
21+
it('does not run in dev', () => {})
22+
return
23+
}
1824

19-
if (isNextDev) {
20-
it('does not run in dev', () => {})
21-
return
25+
it('should not error the build when calling Math.random() if all dynamic access is inside a Suspense boundary', async () => {
26+
try {
27+
await next.start()
28+
} catch {
29+
throw new Error('expected build not to fail for fully static project')
2230
}
2331

24-
beforeEach(async () => {
25-
if (!withMinification) {
26-
await next.patchFile('next.config.js', (content) =>
27-
content.replace(
28-
'serverMinification: true,',
29-
'serverMinification: false,'
30-
)
31-
)
32-
}
33-
})
34-
35-
it('should not error the build when calling Math.random() if all dynamic access is inside a Suspense boundary', async () => {
36-
try {
37-
await next.start()
38-
} catch {
39-
throw new Error('expected build not to fail for fully static project')
40-
}
32+
expect(next.cliOutput).toContain('◐ / ')
33+
const $ = await next.render$('/')
34+
expect($('[data-fallback]').length).toBe(2)
35+
})
36+
})
4137

42-
expect(next.cliOutput).toContain('◐ / ')
43-
const $ = await next.render$('/')
44-
expect($('[data-fallback]').length).toBe(2)
45-
})
38+
describe('Sync Dynamic - Without Fallback - Math.random()', () => {
39+
const { next, isNextDev, isTurbopack, skipped } = nextTestSetup({
40+
files: __dirname + '/fixtures/sync-random-without-fallback',
41+
skipStart: true,
42+
skipDeployment: true,
43+
buildOptions: inDebugMode ? ['--debug-prerender'] : undefined,
4644
})
4745

48-
describe('Sync Dynamic - Without Fallback - Math.random()', () => {
49-
const { next, isNextDev, skipped } = nextTestSetup({
50-
files: __dirname + '/fixtures/sync-random-without-fallback',
51-
skipStart: true,
52-
skipDeployment: true,
53-
})
46+
if (skipped) {
47+
return
48+
}
5449

55-
if (skipped) {
56-
return
57-
}
50+
if (isNextDev) {
51+
it('does not run in dev', () => {})
52+
return
53+
}
5854

59-
if (isNextDev) {
60-
it('does not run in dev', () => {})
61-
return
55+
it('should error the build if Math.random() happens before some component outside a Suspense boundary is complete', async () => {
56+
try {
57+
await next.start()
58+
} catch {
59+
// we expect the build to fail
6260
}
6361

64-
beforeEach(async () => {
65-
if (!withMinification) {
66-
await next.patchFile('next.config.js', (content) =>
67-
content.replace(
68-
'serverMinification: true,',
69-
'serverMinification: false,'
70-
)
71-
)
72-
}
62+
const output = getPrerenderOutput(next.cliOutput, {
63+
isMinified: !inDebugMode,
7364
})
7465

75-
it('should error the build if Math.random() happens before some component outside a Suspense boundary is complete', async () => {
76-
try {
77-
await next.start()
78-
} catch {
79-
// we expect the build to fail
66+
if (isTurbopack) {
67+
if (inDebugMode) {
68+
expect(output).toMatchInlineSnapshot(`
69+
"Error: Route "/" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random
70+
at RandomReadingComponent (turbopack:///[project]/app/page.tsx:35:22)
71+
33 | use(new Promise((r) => process.nextTick(r)))
72+
34 | }
73+
> 35 | const random = Math.random()
74+
| ^
75+
36 | return (
76+
37 | <div>
77+
38 | <span id="rand">{random}</span>
78+
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
79+
80+
> Export encountered errors on following paths:
81+
/page: /"
82+
`)
83+
} else {
84+
expect(output).toMatchInlineSnapshot(`
85+
"Error: Route "/" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random
86+
at a (<next-dist-dir>)
87+
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
88+
Export encountered an error on /page: /, exiting the build."
89+
`)
8090
}
81-
const expectError = createExpectError(next.cliOutput)
91+
} else {
92+
if (inDebugMode) {
93+
expect(output).toMatchInlineSnapshot(`
94+
"Error: Route "/" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random
95+
at RandomReadingComponent (webpack:///app/page.tsx:35:22)
96+
33 | use(new Promise((r) => process.nextTick(r)))
97+
34 | }
98+
> 35 | const random = Math.random()
99+
| ^
100+
36 | return (
101+
37 | <div>
102+
38 | <span id="rand">{random}</span>
103+
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
82104
83-
expectError(
84-
'Error: Route "/" used `Math.random()` outside of `"use cache"` and without explicitly calling `await connection()` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random'
85-
)
86-
expectError('Error occurred prerendering page "/"')
87-
expectError('exiting the build.')
88-
})
105+
> Export encountered errors on following paths:
106+
/page: /"
107+
`)
108+
} else {
109+
expect(output).toMatchInlineSnapshot(`
110+
"Error: Route "/" used \`Math.random()\` outside of \`"use cache"\` and without explicitly calling \`await connection()\` beforehand. See more info here: https://nextjs.org/docs/messages/next-prerender-random
111+
at a (<next-dist-dir>)
112+
Error occurred prerendering page "/". Read more: https://nextjs.org/docs/messages/prerender-error
113+
Export encountered an error on /page: /, exiting the build."
114+
`)
115+
}
116+
}
89117
})
90118
})
91-
}
92-
93-
runTests({ withMinification: true })
94-
runTests({ withMinification: false })
119+
})

0 commit comments

Comments
 (0)