@@ -5,14 +5,15 @@ import { findPort, nextBuild } from 'next-test-utils'
5
5
import { isNextDeploy , isNextDev } from 'e2e-utils'
6
6
import { start } from './server.mjs'
7
7
8
+ const isCacheComponentsEnabled =
9
+ process . env . __NEXT_EXPERIMENTAL_CACHE_COMPONENTS === 'true'
10
+
8
11
describe ( 'segment cache (CDN cache busting)' , ( ) => {
9
12
if ( isNextDev || isNextDeploy ) {
10
13
test ( 'should not run during dev or deploy test runs' , ( ) => { } )
11
14
return
12
15
}
13
16
14
- // TODO(runtime-ppr): add tests for runtime prefetches
15
-
16
17
// To debug these tests locally, run:
17
18
// node start.mjs
18
19
//
@@ -34,110 +35,216 @@ describe('segment cache (CDN cache busting)', () => {
34
35
await cleanup ( )
35
36
} )
36
37
37
- it (
38
+ describe (
38
39
"perform fully prefetched navigation with a CDN that doesn't respect " +
39
40
'the Vary header' ,
40
- async ( ) => {
41
- let act
42
- const browser = await webdriver ( port , '/' , {
43
- beforePageLoad ( p : Playwright . Page ) {
44
- act = createRouterAct ( p )
45
- } ,
41
+ ( ) => {
42
+ it ( 'static prefetch' , async ( ) => {
43
+ let act
44
+ const browser = await webdriver ( port , '/' , {
45
+ beforePageLoad ( p : Playwright . Page ) {
46
+ act = createRouterAct ( p )
47
+ } ,
48
+ } )
49
+
50
+ // Initiate a prefetch. Each segment will be prefetched individually,
51
+ // using the pathname of the target page and a custom header specifying
52
+ // the segment. If we didn't also set a cache-busting search param, then
53
+ // the fake CDN used by this test suite would incorrectly use the same
54
+ // entry for every segment, poisoning the cache.
55
+ await act (
56
+ async ( ) => {
57
+ const linkToggle = await browser . elementByCss (
58
+ '#prefetch-auto [data-link-accordion="/target-page"]'
59
+ )
60
+ await linkToggle . click ( )
61
+ } ,
62
+ {
63
+ includes : 'Target page' ,
64
+ }
65
+ )
66
+
67
+ // Navigate to the prefetched target page.
68
+ await act ( async ( ) => {
69
+ const link = await browser . elementByCss ( 'a[href="/target-page"]' )
70
+ await link . click ( )
71
+
72
+ // The page was prefetched, so we're able to render the target
73
+ // page immediately.
74
+ const div = await browser . elementById ( 'target-page' )
75
+ expect ( await div . text ( ) ) . toBe ( 'Target page' )
76
+ } , 'no-requests' )
46
77
} )
47
78
48
- // Initiate a prefetch. Each segment will be prefetched individually,
49
- // using the pathname of the target page and a custom header specifying
50
- // the segment. If we didn't also set a cache-busting search param, then
51
- // the fake CDN used by this test suite would incorrectly use the same
52
- // entry for every segment, poisoning the cache.
53
- await act (
54
- async ( ) => {
55
- const linkToggle = await browser . elementByCss (
56
- '[data-link-accordion="/target-page"]'
79
+ if ( isCacheComponentsEnabled ) {
80
+ it ( 'runtime prefetch' , async ( ) => {
81
+ let act
82
+ const browser = await webdriver ( port , '/' , {
83
+ beforePageLoad ( p : Playwright . Page ) {
84
+ act = createRouterAct ( p )
85
+ } ,
86
+ } )
87
+
88
+ // Initiate a prefetch. We'll send two requests - one to prefetch the tree,
89
+ // and another to prefetch the page content (which is static, so it will be complete).
90
+ // If we didn't also set a cache-busting search param, then
91
+ // the fake CDN used by this test suite would incorrectly use the same
92
+ // entry for both responses, poisoning the cache.
93
+ await act (
94
+ async ( ) => {
95
+ const linkToggle = await browser . elementByCss (
96
+ '#prefetch-runtime [data-link-accordion="/target-page"]'
97
+ )
98
+ await linkToggle . click ( )
99
+ } ,
100
+ {
101
+ // This should be returned as part of the second request, if it wasn't cache poisoned.
102
+ includes : 'Target page' ,
103
+ }
57
104
)
58
- await linkToggle . click ( )
59
- } ,
60
- {
61
- includes : 'Target page' ,
62
- }
63
- )
64
-
65
- // Navigate to the prefetched target page.
66
- await act ( async ( ) => {
67
- const link = await browser . elementByCss ( 'a[href="/target-page"]' )
68
- await link . click ( )
69
-
70
- // The page was prefetched, so we're able to render the target
71
- // page immediately.
72
- const div = await browser . elementById ( 'target-page' )
73
- expect ( await div . text ( ) ) . toBe ( 'Target page' )
74
- } , 'no-requests' )
105
+
106
+ // Navigate to the prefetched target page.
107
+ await act ( async ( ) => {
108
+ const link = await browser . elementByCss ( 'a[href="/target-page"]' )
109
+ await link . click ( )
110
+
111
+ // The page was prefetched, so we're able to render the target
112
+ // page immediately.
113
+ const div = await browser . elementById ( 'target-page' )
114
+ expect ( await div . text ( ) ) . toBe ( 'Target page' )
115
+ } , 'no-requests' )
116
+ } )
117
+ }
75
118
}
76
119
)
77
120
78
- it (
121
+ describe (
79
122
'prevent cache poisoning attacks by responding with a redirect to correct ' +
80
123
'cache busting query param if a custom header is sent during a prefetch ' +
81
124
'without a corresponding cache-busting search param' ,
82
- async ( ) => {
83
- const browser = await webdriver ( port , '/' )
84
- const { status, responseUrl, redirected } = await browser . eval (
85
- async ( ) => {
86
- const res = await fetch ( '/target-page' , {
87
- headers : {
88
- rsc : '1' ,
89
- 'next-router-prefetch' : '1' ,
90
- 'next-router-segment-prefetch' : '/_tree' ,
91
- } ,
92
- } )
93
- return {
94
- status : res . status ,
95
- responseUrl : res . url ,
96
- redirected : res . redirected ,
125
+ ( ) => {
126
+ it ( 'static prefetch' , async ( ) => {
127
+ const browser = await webdriver ( port , '/' )
128
+ const { status, responseUrl, redirected } = await browser . eval (
129
+ async ( ) => {
130
+ const res = await fetch ( '/target-page' , {
131
+ headers : {
132
+ rsc : '1' ,
133
+ 'next-router-prefetch' : '1' ,
134
+ 'next-router-segment-prefetch' : '/_tree' ,
135
+ } ,
136
+ } )
137
+ return {
138
+ status : res . status ,
139
+ responseUrl : res . url ,
140
+ redirected : res . redirected ,
141
+ }
97
142
}
98
- }
99
- )
100
- expect ( status ) . toBe ( 200 )
101
- expect ( responseUrl ) . toContain ( '_rsc=' )
102
- expect ( redirected ) . toBe ( true )
143
+ )
144
+ expect ( status ) . toBe ( 200 )
145
+ expect ( responseUrl ) . toContain ( '_rsc=' )
146
+ expect ( redirected ) . toBe ( true )
147
+ } )
148
+
149
+ if ( isCacheComponentsEnabled ) {
150
+ it ( 'runtime prefetch' , async ( ) => {
151
+ const browser = await webdriver ( port , '/' )
152
+ const { status, responseUrl, redirected } = await browser . eval (
153
+ async ( ) => {
154
+ const res = await fetch ( '/target-page' , {
155
+ headers : {
156
+ rsc : '1' ,
157
+ 'next-router-prefetch' : '2' ,
158
+ } ,
159
+ } )
160
+ return {
161
+ status : res . status ,
162
+ responseUrl : res . url ,
163
+ redirected : res . redirected ,
164
+ }
165
+ }
166
+ )
167
+ expect ( status ) . toBe ( 200 )
168
+ expect ( responseUrl ) . toContain ( '_rsc=' )
169
+ expect ( redirected ) . toBe ( true )
170
+ } )
171
+ }
103
172
}
104
173
)
105
174
106
- it (
175
+ describe (
107
176
'perform fully prefetched navigation when a third-party proxy ' +
108
177
'performs a redirect' ,
109
- async ( ) => {
110
- let act
111
- const browser = await webdriver ( port , '/' , {
112
- beforePageLoad ( p : Playwright . Page ) {
113
- act = createRouterAct ( p )
114
- } ,
178
+ ( ) => {
179
+ it ( 'static prefetch' , async ( ) => {
180
+ let act
181
+ const browser = await webdriver ( port , '/' , {
182
+ beforePageLoad ( p : Playwright . Page ) {
183
+ act = createRouterAct ( p )
184
+ } ,
185
+ } )
186
+
187
+ await act (
188
+ async ( ) => {
189
+ const linkToggle = await browser . elementByCss (
190
+ '#prefetch-auto [data-link-accordion="/redirect-to-target-page"]'
191
+ )
192
+ await linkToggle . click ( )
193
+ } ,
194
+ {
195
+ includes : 'Target page' ,
196
+ }
197
+ )
198
+
199
+ // Navigate to the prefetched target page.
200
+ await act ( async ( ) => {
201
+ const link = await browser . elementByCss (
202
+ 'a[href="/redirect-to-target-page"]'
203
+ )
204
+ await link . click ( )
205
+
206
+ // The page was prefetched, so we're able to render the target
207
+ // page immediately.
208
+ const div = await browser . elementById ( 'target-page' )
209
+ expect ( await div . text ( ) ) . toBe ( 'Target page' )
210
+ } , 'no-requests' )
115
211
} )
116
212
117
- await act (
118
- async ( ) => {
119
- const linkToggle = await browser . elementByCss (
120
- '[data-link-accordion="/redirect-to-target-page"]'
213
+ if ( isCacheComponentsEnabled ) {
214
+ it ( 'runtime prefetch' , async ( ) => {
215
+ let act
216
+ const browser = await webdriver ( port , '/' , {
217
+ beforePageLoad ( p : Playwright . Page ) {
218
+ act = createRouterAct ( p )
219
+ } ,
220
+ } )
221
+
222
+ await act (
223
+ async ( ) => {
224
+ const linkToggle = await browser . elementByCss (
225
+ '#prefetch-runtime [data-link-accordion="/redirect-to-target-page"]'
226
+ )
227
+ await linkToggle . click ( )
228
+ } ,
229
+ {
230
+ includes : 'Target page' ,
231
+ }
121
232
)
122
- await linkToggle . click ( )
123
- } ,
124
- {
125
- includes : 'Target page' ,
126
- }
127
- )
128
-
129
- // Navigate to the prefetched target page.
130
- await act ( async ( ) => {
131
- const link = await browser . elementByCss (
132
- 'a[href="/redirect-to-target-page"]'
133
- )
134
- await link . click ( )
135
233
136
- // The page was prefetched, so we're able to render the target
137
- // page immediately.
138
- const div = await browser . elementById ( 'target-page' )
139
- expect ( await div . text ( ) ) . toBe ( 'Target page' )
140
- } , 'no-requests' )
234
+ // Navigate to the prefetched target page.
235
+ await act ( async ( ) => {
236
+ const link = await browser . elementByCss (
237
+ 'a[href="/redirect-to-target-page"]'
238
+ )
239
+ await link . click ( )
240
+
241
+ // The page was prefetched, so we're able to render the target
242
+ // page immediately.
243
+ const div = await browser . elementById ( 'target-page' )
244
+ expect ( await div . text ( ) ) . toBe ( 'Target page' )
245
+ } , 'no-requests' )
246
+ } )
247
+ }
141
248
}
142
249
)
143
250
} )
0 commit comments