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