@@ -5,14 +5,15 @@ import { findPort, nextBuild } from 'next-test-utils'
55import { isNextDeploy , isNextDev } from 'e2e-utils'
66import { start } from './server.mjs'
77
8+ const isCacheComponentsEnabled =
9+ process . env . __NEXT_EXPERIMENTAL_CACHE_COMPONENTS === 'true'
10+
811describe ( 'segment cache (CDN cache busting)' , ( ) => {
912 if ( isNextDev || isNextDeploy ) {
1013 test ( 'should not run during dev or deploy test runs' , ( ) => { } )
1114 return
1215 }
1316
14- // TODO(runtime-ppr): add tests for runtime prefetches
15-
1617 // To debug these tests locally, run:
1718 // node start.mjs
1819 //
@@ -34,110 +35,216 @@ describe('segment cache (CDN cache busting)', () => {
3435 await cleanup ( )
3536 } )
3637
37- it (
38+ describe (
3839 "perform fully prefetched navigation with a CDN that doesn't respect " +
3940 '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' )
4677 } )
4778
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+ }
57104 )
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+ }
75118 }
76119 )
77120
78- it (
121+ describe (
79122 'prevent cache poisoning attacks by responding with a redirect to correct ' +
80123 'cache busting query param if a custom header is sent during a prefetch ' +
81124 '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+ }
97142 }
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+ }
103172 }
104173 )
105174
106- it (
175+ describe (
107176 'perform fully prefetched navigation when a third-party proxy ' +
108177 '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' )
115211 } )
116212
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+ }
121232 )
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 ( )
135233
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+ }
141248 }
142249 )
143250} )
0 commit comments