11import { expect , test } from '@playwright/test' ;
2- import { waitForTransaction } from '@sentry-internal/test-utils' ;
3- import { SEMANTIC_ATTRIBUTE_SENTRY_OP , SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/nuxt' ;
2+ import { collectStreamedSpans } from '@sentry-internal/test-utils' ;
43
54test . describe ( 'Cache Instrumentation' , ( ) => {
65 const SEMANTIC_ATTRIBUTE_CACHE_KEY = 'cache.key' ;
76 const SEMANTIC_ATTRIBUTE_CACHE_HIT = 'cache.hit' ;
87
8+ async function collectCacheSpans ( ) {
9+ const spans = await collectStreamedSpans ( 'nuxt-5' , spans =>
10+ spans . some ( span => span . is_segment && span . attributes [ 'url.path' ] ?. value === '/api/cache-test' ) ,
11+ ) ;
12+ const rootSpan = spans . find ( span => span . is_segment && span . attributes [ 'url.path' ] ?. value === '/api/cache-test' ) ;
13+
14+ return spans . filter (
15+ span => span . trace_id === rootSpan ?. trace_id && span . attributes [ 'sentry.origin' ] ?. value === 'auto.cache.nuxt' ,
16+ ) ;
17+ }
18+
919 test ( 'instruments cachedFunction and cachedEventHandler calls and creates spans with correct attributes' , async ( {
1020 request,
1121 } ) => {
12- const transactionPromise = waitForTransaction ( 'nuxt-5' , transactionEvent => {
13- return transactionEvent . transaction ?. includes ( 'GET /api/cache-test' ) ?? false ;
14- } ) ;
22+ const cacheSpansPromise = collectCacheSpans ( ) ;
1523
1624 const response = await request . get ( '/api/cache-test' ) ;
1725 expect ( response . status ( ) ) . toBe ( 200 ) ;
1826
19- const transaction = await transactionPromise ;
27+ const allCacheSpans = await cacheSpansPromise ;
28+ expect ( allCacheSpans . length ) . toBeGreaterThan ( 0 ) ;
2029
2130 // Helper to find spans by operation
22- const findSpansByMethod = ( method : string ) => {
23- return transaction . spans ?. filter ( span => span . data ?. [ 'db.operation.name' ] === method ) || [ ] ;
24- } ;
31+ const findSpansByMethod = ( method : string ) =>
32+ allCacheSpans . filter ( span => span . attributes [ 'db.operation.name' ] ?. value === method ) ;
2533
26- // Test that we have cache operations from cachedFunction and cachedEventHandler
27- const allCacheSpans = transaction . spans ?. filter (
28- span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] === 'auto.cache.nuxt' ,
29- ) ;
30- expect ( allCacheSpans ?. length ) . toBeGreaterThan ( 0 ) ;
34+ const getCacheKey = ( span : ( typeof allCacheSpans ) [ number ] ) => span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] ?. value ;
3135
3236 // Test getItem spans for cachedFunction - should have both cache miss and cache hit
3337 const getItemSpans = findSpansByMethod ( 'getItem' ) ;
@@ -36,34 +40,36 @@ test.describe('Cache Instrumentation', () => {
3640 // Find cache miss (first call to getCachedUser('123'))
3741 const cacheMissSpan = getItemSpans . find (
3842 span =>
39- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
40- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'user:123' ) &&
41- ! span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ,
43+ typeof getCacheKey ( span ) === 'string' &&
44+ ( getCacheKey ( span ) as string ) . includes ( 'user:123' ) &&
45+ ! span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value ,
4246 ) ;
4347 if ( cacheMissSpan ) {
44- expect ( cacheMissSpan . data ) . toMatchObject ( {
45- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'cache.get' ,
46- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.cache.nuxt' ,
47- [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : false ,
48- 'db.operation.name' : 'getItem' ,
49- 'db.collection.name' : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) ,
48+ expect ( cacheMissSpan . attributes ) . toMatchObject ( {
49+ 'sentry.op' : { type : 'string' , value : 'cache.get' } ,
50+ 'cache.operation' : { type : 'string' , value : 'get' } ,
51+ 'sentry.origin' : { type : 'string' , value : 'auto.cache.nuxt' } ,
52+ [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : { type : 'boolean' , value : false } ,
53+ 'db.operation.name' : { type : 'string' , value : 'getItem' } ,
54+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
5055 } ) ;
5156 }
5257
5358 // Find cache hit (second call to getCachedUser('123'))
5459 const cacheHitSpan = getItemSpans . find (
5560 span =>
56- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
57- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'user:123' ) &&
58- span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ,
61+ typeof getCacheKey ( span ) === 'string' &&
62+ ( getCacheKey ( span ) as string ) . includes ( 'user:123' ) &&
63+ span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value ,
5964 ) ;
6065 if ( cacheHitSpan ) {
61- expect ( cacheHitSpan . data ) . toMatchObject ( {
62- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'cache.get' ,
63- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.cache.nuxt' ,
64- [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : true ,
65- 'db.operation.name' : 'getItem' ,
66- 'db.collection.name' : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) ,
66+ expect ( cacheHitSpan . attributes ) . toMatchObject ( {
67+ 'sentry.op' : { type : 'string' , value : 'cache.get' } ,
68+ 'cache.operation' : { type : 'string' , value : 'get' } ,
69+ 'sentry.origin' : { type : 'string' , value : 'auto.cache.nuxt' } ,
70+ [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : { type : 'boolean' , value : true } ,
71+ 'db.operation.name' : { type : 'string' , value : 'getItem' } ,
72+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
6773 } ) ;
6874 }
6975
@@ -72,42 +78,34 @@ test.describe('Cache Instrumentation', () => {
7278 expect ( setItemSpans . length ) . toBeGreaterThan ( 0 ) ;
7379
7480 const cacheSetSpan = setItemSpans . find (
75- span =>
76- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
77- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'user:123' ) ,
81+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'user:123' ) ,
7882 ) ;
7983 if ( cacheSetSpan ) {
80- expect ( cacheSetSpan . data ) . toMatchObject ( {
81- [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] : 'cache.put' ,
82- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.cache.nuxt' ,
83- 'db.operation.name' : 'setItem' ,
84- 'db.collection.name' : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) ,
84+ expect ( cacheSetSpan . attributes ) . toMatchObject ( {
85+ 'sentry.op' : { type : 'string' , value : 'cache.put' } ,
86+ 'cache.operation' : { type : 'string' , value : 'put' } ,
87+ 'sentry.origin' : { type : 'string' , value : 'auto.cache.nuxt' } ,
88+ 'db.operation.name' : { type : 'string' , value : 'setItem' } ,
89+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
8590 } ) ;
8691 }
8792
8893 // Test that we have spans for different cached functions
8994 const dataKeySpans = getItemSpans . filter (
90- span =>
91- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
92- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'data:test-key' ) ,
95+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'data:test-key' ) ,
9396 ) ;
9497 expect ( dataKeySpans . length ) . toBeGreaterThan ( 0 ) ;
9598
9699 // Test that we have spans for cachedEventHandler
97100 const cachedHandlerSpans = getItemSpans . filter (
98- span =>
99- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
100- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'cachedHandler' ) ,
101+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'cachedHandler' ) ,
101102 ) ;
102103 expect ( cachedHandlerSpans . length ) . toBeGreaterThan ( 0 ) ;
103104
104- // Verify all cache spans have OK status
105- allCacheSpans ? .forEach ( span => {
105+ // Verify all cache spans have OK status and are nested under the request's root span
106+ allCacheSpans . forEach ( span => {
106107 expect ( span . status ) . toBe ( 'ok' ) ;
107- } ) ;
108-
109- // Verify cache spans are properly nested under the transaction
110- allCacheSpans ?. forEach ( span => {
108+ expect ( span . is_segment ) . toBe ( false ) ;
111109 expect ( span . parent_span_id ) . toBeDefined ( ) ;
112110 } ) ;
113111 } ) ;
@@ -117,41 +115,38 @@ test.describe('Cache Instrumentation', () => {
117115 const uniqueUser = `test-${ Date . now ( ) } ` ;
118116 const uniqueData = `data-${ Date . now ( ) } ` ;
119117
120- const transactionPromise = waitForTransaction ( 'nuxt-5' , transactionEvent => {
121- return transactionEvent . transaction ?. includes ( 'GET /api/cache-test' ) ?? false ;
122- } ) ;
118+ const cacheSpansPromise = collectCacheSpans ( ) ;
123119
124120 await request . get ( `/api/cache-test?user=${ uniqueUser } &data=${ uniqueData } ` ) ;
125- const transaction1 = await transactionPromise ;
126121
127122 // Get all cache-related spans
128- const allCacheSpans = transaction1 . spans ?. filter (
129- span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] === 'auto.cache.nuxt' ,
130- ) ;
123+ const allCacheSpans = await cacheSpansPromise ;
131124
132125 // We should have cache operations
133- expect ( allCacheSpans ? .length ) . toBeGreaterThan ( 0 ) ;
126+ expect ( allCacheSpans . length ) . toBeGreaterThan ( 0 ) ;
134127
135128 // Get all getItem operations
136- const allGetItemSpans = allCacheSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] === 'cache.get' ) ;
129+ const allGetItemSpans = allCacheSpans . filter ( span => span . attributes [ 'sentry.op' ] ?. value === 'cache.get' ) ;
137130
138131 // Get all setItem operations
139- const allSetItemSpans = allCacheSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] === 'cache.put' ) ;
132+ const allSetItemSpans = allCacheSpans . filter ( span => span . attributes [ 'sentry.op' ] ?. value === 'cache.put' ) ;
140133
141134 // We should have both get and set operations
142- expect ( allGetItemSpans ? .length ) . toBeGreaterThan ( 0 ) ;
143- expect ( allSetItemSpans ? .length ) . toBeGreaterThan ( 0 ) ;
135+ expect ( allGetItemSpans . length ) . toBeGreaterThan ( 0 ) ;
136+ expect ( allSetItemSpans . length ) . toBeGreaterThan ( 0 ) ;
144137
145138 // Check for cache misses (cache.hit = false)
146- const cacheMissSpans = allGetItemSpans ?. filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] === false ) ;
139+ const cacheMissSpans = allGetItemSpans . filter (
140+ span => span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value === false ,
141+ ) ;
147142
148143 // Check for cache hits (cache.hit = true)
149- const cacheHitSpans = allGetItemSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] === true ) ;
144+ const cacheHitSpans = allGetItemSpans . filter ( span => span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value === true ) ;
150145
151146 // We should have at least one cache miss (first calls to getCachedUser and getCachedData)
152- expect ( cacheMissSpans ? .length ) . toBeGreaterThanOrEqual ( 1 ) ;
147+ expect ( cacheMissSpans . length ) . toBeGreaterThanOrEqual ( 1 ) ;
153148
154149 // We should have at least one cache hit (second calls to getCachedUser and getCachedData)
155- expect ( cacheHitSpans ? .length ) . toBeGreaterThanOrEqual ( 1 ) ;
150+ expect ( cacheHitSpans . length ) . toBeGreaterThanOrEqual ( 1 ) ;
156151 } ) ;
157152} ) ;
0 commit comments