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,34 @@ 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+ 'sentry.origin' : { type : 'string' , value : ' auto.cache.nuxt' } ,
51+ [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : { type : 'boolean' , value : false } ,
52+ 'db.operation.name' : { type : 'string' , value : ' getItem' } ,
53+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
5054 } ) ;
5155 }
5256
5357 // Find cache hit (second call to getCachedUser('123'))
5458 const cacheHitSpan = getItemSpans . find (
5559 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 ] ,
60+ typeof getCacheKey ( span ) === 'string' &&
61+ ( getCacheKey ( span ) as string ) . includes ( 'user:123' ) &&
62+ span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value ,
5963 ) ;
6064 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 ) ? $ / ) ,
65+ expect ( cacheHitSpan . attributes ) . toMatchObject ( {
66+ 'sentry.op' : { type : 'string' , value : ' cache.get' } ,
67+ 'sentry.origin' : { type : 'string' , value : ' auto.cache.nuxt' } ,
68+ [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] : { type : 'boolean' , value : true } ,
69+ 'db.operation.name' : { type : 'string' , value : ' getItem' } ,
70+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
6771 } ) ;
6872 }
6973
@@ -72,42 +76,33 @@ test.describe('Cache Instrumentation', () => {
7276 expect ( setItemSpans . length ) . toBeGreaterThan ( 0 ) ;
7377
7478 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' ) ,
79+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'user:123' ) ,
7880 ) ;
7981 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 ) ? $ / ) ,
82+ expect ( cacheSetSpan . attributes ) . toMatchObject ( {
83+ 'sentry.op' : { type : 'string' , value : ' cache.put' } ,
84+ 'sentry.origin' : { type : 'string' , value : ' auto.cache.nuxt' } ,
85+ 'db.operation.name' : { type : 'string' , value : ' setItem' } ,
86+ 'db.collection.name' : { type : 'string' , value : expect . stringMatching ( / ^ ( c a c h e ) ? $ / ) } ,
8587 } ) ;
8688 }
8789
8890 // Test that we have spans for different cached functions
8991 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' ) ,
92+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'data:test-key' ) ,
9393 ) ;
9494 expect ( dataKeySpans . length ) . toBeGreaterThan ( 0 ) ;
9595
9696 // Test that we have spans for cachedEventHandler
9797 const cachedHandlerSpans = getItemSpans . filter (
98- span =>
99- typeof span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] === 'string' &&
100- span . data [ SEMANTIC_ATTRIBUTE_CACHE_KEY ] . includes ( 'cachedHandler' ) ,
98+ span => typeof getCacheKey ( span ) === 'string' && ( getCacheKey ( span ) as string ) . includes ( 'cachedHandler' ) ,
10199 ) ;
102100 expect ( cachedHandlerSpans . length ) . toBeGreaterThan ( 0 ) ;
103101
104- // Verify all cache spans have OK status
105- allCacheSpans ? .forEach ( span => {
102+ // Verify all cache spans have OK status and are nested under the request's root span
103+ allCacheSpans . forEach ( span => {
106104 expect ( span . status ) . toBe ( 'ok' ) ;
107- } ) ;
108-
109- // Verify cache spans are properly nested under the transaction
110- allCacheSpans ?. forEach ( span => {
105+ expect ( span . is_segment ) . toBe ( false ) ;
111106 expect ( span . parent_span_id ) . toBeDefined ( ) ;
112107 } ) ;
113108 } ) ;
@@ -117,41 +112,38 @@ test.describe('Cache Instrumentation', () => {
117112 const uniqueUser = `test-${ Date . now ( ) } ` ;
118113 const uniqueData = `data-${ Date . now ( ) } ` ;
119114
120- const transactionPromise = waitForTransaction ( 'nuxt-5' , transactionEvent => {
121- return transactionEvent . transaction ?. includes ( 'GET /api/cache-test' ) ?? false ;
122- } ) ;
115+ const cacheSpansPromise = collectCacheSpans ( ) ;
123116
124117 await request . get ( `/api/cache-test?user=${ uniqueUser } &data=${ uniqueData } ` ) ;
125- const transaction1 = await transactionPromise ;
126118
127119 // Get all cache-related spans
128- const allCacheSpans = transaction1 . spans ?. filter (
129- span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] === 'auto.cache.nuxt' ,
130- ) ;
120+ const allCacheSpans = await cacheSpansPromise ;
131121
132122 // We should have cache operations
133- expect ( allCacheSpans ? .length ) . toBeGreaterThan ( 0 ) ;
123+ expect ( allCacheSpans . length ) . toBeGreaterThan ( 0 ) ;
134124
135125 // Get all getItem operations
136- const allGetItemSpans = allCacheSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] === 'cache.get' ) ;
126+ const allGetItemSpans = allCacheSpans . filter ( span => span . attributes [ 'sentry.op' ] ?. value === 'cache.get' ) ;
137127
138128 // Get all setItem operations
139- const allSetItemSpans = allCacheSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_SENTRY_OP ] === 'cache.put' ) ;
129+ const allSetItemSpans = allCacheSpans . filter ( span => span . attributes [ 'sentry.op' ] ?. value === 'cache.put' ) ;
140130
141131 // We should have both get and set operations
142- expect ( allGetItemSpans ? .length ) . toBeGreaterThan ( 0 ) ;
143- expect ( allSetItemSpans ? .length ) . toBeGreaterThan ( 0 ) ;
132+ expect ( allGetItemSpans . length ) . toBeGreaterThan ( 0 ) ;
133+ expect ( allSetItemSpans . length ) . toBeGreaterThan ( 0 ) ;
144134
145135 // Check for cache misses (cache.hit = false)
146- const cacheMissSpans = allGetItemSpans ?. filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] === false ) ;
136+ const cacheMissSpans = allGetItemSpans . filter (
137+ span => span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value === false ,
138+ ) ;
147139
148140 // Check for cache hits (cache.hit = true)
149- const cacheHitSpans = allGetItemSpans ? .filter ( span => span . data ?. [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] === true ) ;
141+ const cacheHitSpans = allGetItemSpans . filter ( span => span . attributes [ SEMANTIC_ATTRIBUTE_CACHE_HIT ] ?. value === true ) ;
150142
151143 // We should have at least one cache miss (first calls to getCachedUser and getCachedData)
152- expect ( cacheMissSpans ? .length ) . toBeGreaterThanOrEqual ( 1 ) ;
144+ expect ( cacheMissSpans . length ) . toBeGreaterThanOrEqual ( 1 ) ;
153145
154146 // We should have at least one cache hit (second calls to getCachedUser and getCachedData)
155- expect ( cacheHitSpans ? .length ) . toBeGreaterThanOrEqual ( 1 ) ;
147+ expect ( cacheHitSpans . length ) . toBeGreaterThanOrEqual ( 1 ) ;
156148 } ) ;
157149} ) ;
0 commit comments