@@ -120,3 +120,196 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
120
120
} ) ;
121
121
} ) ;
122
122
} ) ;
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