@@ -120,3 +120,196 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
120120    } ) ; 
121121  } ) ; 
122122} ) ; 
123+ 
124+ test . describe ( 'nested SSR routes (client, server, server request)' ,  ( )  =>  { 
125+   /** The user-page route fetches from an endpoint and creates a deeply nested span structure: 
126+    * pageload — /user-page/myUsername123 
127+    * ├── browser.** — multiple browser spans 
128+    * └── browser.request — /user-page/myUsername123 
129+    *     └── http.server — GET /user-page/[userId]                    (SSR page request) 
130+    *         └── http.client — GET /api/user/myUsername123.json       (executing fetch call from SSR page - span) 
131+    *             └── http.server — GET /api/user/myUsername123.json   (server request) 
132+    */ 
133+   test ( 'sends connected server and client pageload and request spans with the same trace id' ,  async  ( {  page } )  =>  { 
134+     const  clientPageloadTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
135+       return  txnEvent ?. transaction ?. startsWith ( '/user-page/' )  ??  false ; 
136+     } ) ; 
137+ 
138+     const  serverPageRequestTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
139+       return  txnEvent ?. transaction ?. startsWith ( 'GET /user-page/' )  ??  false ; 
140+     } ) ; 
141+ 
142+     const  serverHTTPServerRequestTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
143+       return  txnEvent ?. transaction ?. startsWith ( 'GET /api/user/' )  ??  false ; 
144+     } ) ; 
145+ 
146+     await  page . goto ( '/user-page/myUsername123' ) ; 
147+ 
148+     const  clientPageloadTxn  =  await  clientPageloadTxnPromise ; 
149+     const  serverPageRequestTxn  =  await  serverPageRequestTxnPromise ; 
150+     const  serverHTTPServerRequestTxn  =  await  serverHTTPServerRequestTxnPromise ; 
151+     const  serverRequestHTTPClientSpan  =  serverPageRequestTxn . spans ?. find ( 
152+       span  =>  span . op  ===  'http.client'  &&  span . description ?. includes ( '/api/user/' ) , 
153+     ) ; 
154+ 
155+     const  clientPageloadTraceId  =  clientPageloadTxn . contexts ?. trace ?. trace_id ; 
156+ 
157+     // Verify all spans have the same trace ID 
158+     expect ( clientPageloadTraceId ) . toEqual ( serverPageRequestTxn . contexts ?. trace ?. trace_id ) ; 
159+     expect ( clientPageloadTraceId ) . toEqual ( serverHTTPServerRequestTxn . contexts ?. trace ?. trace_id ) ; 
160+     expect ( clientPageloadTraceId ) . toEqual ( serverRequestHTTPClientSpan ?. trace_id ) ; 
161+ 
162+     // serverPageRequest has no parent (root span) 
163+     expect ( serverPageRequestTxn . contexts ?. trace ?. parent_span_id ) . toBeUndefined ( ) ; 
164+ 
165+     // clientPageload's parent and serverRequestHTTPClient's parent is serverPageRequest 
166+     const  serverPageRequestSpanId  =  serverPageRequestTxn . contexts ?. trace ?. span_id ; 
167+     expect ( clientPageloadTxn . contexts ?. trace ?. parent_span_id ) . toEqual ( serverPageRequestSpanId ) ; 
168+     expect ( serverRequestHTTPClientSpan ?. parent_span_id ) . toEqual ( serverPageRequestSpanId ) ; 
169+ 
170+     // serverHTTPServerRequest's parent is serverRequestHTTPClient 
171+     expect ( serverHTTPServerRequestTxn . contexts ?. trace ?. parent_span_id ) . toEqual ( serverRequestHTTPClientSpan ?. span_id ) ; 
172+   } ) ; 
173+ 
174+   test ( 'sends parametrized pageload, server and API request transaction names' ,  async  ( {  page } )  =>  { 
175+     const  clientPageloadTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
176+       return  txnEvent ?. transaction ?. startsWith ( '/user-page/' )  ??  false ; 
177+     } ) ; 
178+ 
179+     const  serverPageRequestTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
180+       return  txnEvent ?. transaction ?. startsWith ( 'GET /user-page/' )  ??  false ; 
181+     } ) ; 
182+ 
183+     const  serverHTTPServerRequestTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
184+       return  txnEvent ?. transaction ?. startsWith ( 'GET /api/user/' )  ??  false ; 
185+     } ) ; 
186+ 
187+     await  page . goto ( '/user-page/myUsername123' ) ; 
188+ 
189+     const  clientPageloadTxn  =  await  clientPageloadTxnPromise ; 
190+     const  serverPageRequestTxn  =  await  serverPageRequestTxnPromise ; 
191+     const  serverHTTPServerRequestTxn  =  await  serverHTTPServerRequestTxnPromise ; 
192+ 
193+     const  serverRequestHTTPClientSpan  =  serverPageRequestTxn . spans ?. find ( 
194+       span  =>  span . op  ===  'http.client'  &&  span . description ?. includes ( '/api/user/' ) , 
195+     ) ; 
196+ 
197+     // Client pageload transaction - actual URL with pageload operation 
198+     expect ( clientPageloadTxn ) . toMatchObject ( { 
199+       transaction : '/user-page/myUsername123' ,  // todo: parametrize 
200+       transaction_info : {  source : 'url'  } , 
201+       contexts : { 
202+         trace : { 
203+           op : 'pageload' , 
204+           origin : 'auto.pageload.browser' , 
205+           data : { 
206+             'sentry.op' : 'pageload' , 
207+             'sentry.origin' : 'auto.pageload.browser' , 
208+             'sentry.source' : 'url' , 
209+           } , 
210+         } , 
211+       } , 
212+     } ) ; 
213+ 
214+     // Server page request transaction - parametrized transaction name with actual URL in data 
215+     expect ( serverPageRequestTxn ) . toMatchObject ( { 
216+       transaction : 'GET /user-page/[userId]' , 
217+       transaction_info : {  source : 'route'  } , 
218+       contexts : { 
219+         trace : { 
220+           op : 'http.server' , 
221+           origin : 'auto.http.astro' , 
222+           data : { 
223+             'sentry.op' : 'http.server' , 
224+             'sentry.origin' : 'auto.http.astro' , 
225+             'sentry.source' : 'route' , 
226+             url : expect . stringContaining ( '/user-page/myUsername123' ) , 
227+           } , 
228+         } , 
229+       } , 
230+       request : {  url : expect . stringContaining ( '/user-page/myUsername123' )  } , 
231+     } ) ; 
232+ 
233+     // HTTP client span - actual API URL with client operation 
234+     expect ( serverRequestHTTPClientSpan ) . toMatchObject ( { 
235+       op : 'http.client' , 
236+       origin : 'auto.http.otel.node_fetch' , 
237+       description : 'GET http://localhost:3030/api/user/myUsername123.json' ,  // http.client does not need to be parametrized 
238+       data : { 
239+         'sentry.op' : 'http.client' , 
240+         'sentry.origin' : 'auto.http.otel.node_fetch' , 
241+         'url.full' : expect . stringContaining ( '/api/user/myUsername123.json' ) , 
242+         'url.path' : '/api/user/myUsername123.json' , 
243+         url : expect . stringContaining ( '/api/user/myUsername123.json' ) , 
244+       } , 
245+     } ) ; 
246+ 
247+     // Server HTTP request transaction - should be parametrized 
248+     expect ( serverHTTPServerRequestTxn ) . toMatchObject ( { 
249+       transaction : 'GET /api/user/myUsername123.json' ,  // todo: parametrize 
250+       transaction_info : {  source : 'route'  } , 
251+       contexts : { 
252+         trace : { 
253+           op : 'http.server' , 
254+           origin : 'auto.http.astro' , 
255+           data : { 
256+             'sentry.op' : 'http.server' , 
257+             'sentry.origin' : 'auto.http.astro' , 
258+             'sentry.source' : 'route' , 
259+             url : expect . stringContaining ( '/api/user/myUsername123.json' ) , 
260+           } , 
261+         } , 
262+       } , 
263+       request : {  url : expect . stringContaining ( '/api/user/myUsername123.json' )  } , 
264+     } ) ; 
265+   } ) ; 
266+ 
267+   test ( 'sends parametrized pageload and server transaction names for catch-all routes' ,  async  ( {  page } )  =>  { 
268+     const  clientPageloadTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
269+       return  txnEvent ?. transaction ?. startsWith ( '/catchAll/' )  ??  false ; 
270+     } ) ; 
271+ 
272+     const  serverPageRequestTxnPromise  =  waitForTransaction ( 'astro-4' ,  txnEvent  =>  { 
273+       return  txnEvent ?. transaction ?. startsWith ( 'GET /catchAll/' )  ??  false ; 
274+     } ) ; 
275+ 
276+     await  page . goto ( '/catchAll/hell0/whatever-do' ) ; 
277+ 
278+     const  clientPageloadTxn  =  await  clientPageloadTxnPromise ; 
279+     const  serverPageRequestTxn  =  await  serverPageRequestTxnPromise ; 
280+ 
281+     expect ( clientPageloadTxn ) . toMatchObject ( { 
282+       transaction : '/catchAll/hell0/whatever-do' ,  // todo: parametrize 
283+       transaction_info : {  source : 'url'  } , 
284+       contexts : { 
285+         trace : { 
286+           op : 'pageload' , 
287+           origin : 'auto.pageload.browser' , 
288+           data : { 
289+             'sentry.op' : 'pageload' , 
290+             'sentry.origin' : 'auto.pageload.browser' , 
291+             'sentry.source' : 'url' , 
292+           } , 
293+         } , 
294+       } , 
295+     } ) ; 
296+ 
297+     expect ( serverPageRequestTxn ) . toMatchObject ( { 
298+       transaction : 'GET /catchAll/[path]' , 
299+       transaction_info : {  source : 'route'  } , 
300+       contexts : { 
301+         trace : { 
302+           op : 'http.server' , 
303+           origin : 'auto.http.astro' , 
304+           data : { 
305+             'sentry.op' : 'http.server' , 
306+             'sentry.origin' : 'auto.http.astro' , 
307+             'sentry.source' : 'route' , 
308+             url : expect . stringContaining ( '/catchAll/hell0/whatever-do' ) , 
309+           } , 
310+         } , 
311+       } , 
312+       request : {  url : expect . stringContaining ( '/catchAll/hell0/whatever-do' )  } , 
313+     } ) ; 
314+   } ) ; 
315+ } ) ; 
0 commit comments