Skip to content

Commit bc8a5ff

Browse files
s1gr1dclaude
andcommitted
test(e2e): Migrate nuxt-5 to span streaming
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1cbf721 commit bc8a5ff

14 files changed

Lines changed: 760 additions & 822 deletions

dev-packages/e2e-tests/test-applications/nuxt-5/sentry.client.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as Sentry from '@sentry/nuxt';
22
import { /* usePinia,*/ useRuntimeConfig } from '#imports';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
65
dsn: useRuntimeConfig().public.sentry.dsn,
76
tunnel: `http://localhost:3031/`, // proxy server
87
tracesSampleRate: 1.0,

dev-packages/e2e-tests/test-applications/nuxt-5/sentry.server.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as Sentry from '@sentry/nuxt';
22

33
Sentry.init({
4-
traceLifecycle: 'static',
54
dsn: 'https://public@dsn.ingest.sentry.io/1337',
65
tracesSampleRate: 1.0, // Capture 100% of the transactions
76
tunnel: 'http://localhost:3031/', // proxy server

dev-packages/e2e-tests/test-applications/nuxt-5/server/api/db-test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export default defineHandler(async event => {
6161
}
6262

6363
case 'error': {
64+
// A successful query runs first so the captured error carries a query breadcrumb: with span
65+
// streaming there is no transaction event left to read breadcrumbs off.
66+
await db.exec('CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, message TEXT, level TEXT)');
67+
await db.exec(`INSERT INTO logs (message, level) VALUES ('Test log', 'INFO')`);
68+
6469
const stmt = db.prepare('SELECT * FROM nonexistent_table WHERE invalid_column = ?');
6570
await stmt.get(1);
6671
return { success: false, message: 'Should have thrown an error' };
Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
import { 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

54
test.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(/^(cache)?$/),
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(/^(cache)?$/) },
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(/^(cache)?$/),
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(/^(cache)?$/) },
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(/^(cache)?$/),
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(/^(cache)?$/) },
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

Comments
 (0)