From 03d1c7ab316064c00913a69c809fece3570abdc2 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 8 Jul 2026 11:20:10 +0200 Subject: [PATCH 1/3] test: Drop optional chaining on SerializedStreamedSpan attributes Now that attributes is required on SerializedStreamedSpan, remove unnecessary attributes?. checks in streamed span tests and helpers. Co-Authored-By: Cursor --- .../cultureContext-streamed/test.ts | 6 +- .../integrations/httpContext-streamed/test.ts | 6 +- .../traceLifecycleMode_streamed-spans/test.ts | 8 +- .../beforeSendSpan-streamed/test.ts | 2 +- .../suites/replay/span-streaming/test.ts | 2 +- .../backgroundtab-pageload-streamed/test.ts | 2 +- .../consistent-sampling/default/test.ts | 8 +- .../consistent-sampling/meta/test.ts | 20 +- .../tracesSampler-precedence/test.ts | 6 +- .../linked-traces-streamed/default/test.ts | 4 +- .../negatively-sampled/test.ts | 2 +- .../test.ts | 4 +- .../test.ts | 4 +- .../tracing/http-client-span-streamed/test.ts | 4 +- .../tracing/metrics/element-timing/test.ts | 4 +- .../web-vitals-cls-streamed-spans/test.ts | 20 +- .../web-vitals-inp-streamed-spans/test.ts | 18 +- .../web-vitals-lcp-streamed-spans/test.ts | 24 +- .../metrics/web-vitals-ttfb-streamed/test.ts | 6 +- .../utils/spanUtils.ts | 2 +- .../tests/index.test.ts | 8 +- .../nextjs-15/tests/ai-test.test.ts | 32 +-- .../tests/parameterized-routes.test.ts | 8 +- .../nextjs-16/tests/ai-test.test.ts | 32 +-- .../node-express-streaming/tests/mcp.test.ts | 42 +-- .../tests/spans.test.ts | 22 +- .../node-express-streaming/tests/trpc.test.ts | 4 +- .../tests/spans.test.ts | 14 +- .../beforeSendSpan-streamed/test.ts | 2 +- .../parallel-root-spans-streamed/test.ts | 2 +- .../parallel-spans-in-scope-streamed/test.ts | 2 +- .../test.ts | 2 +- .../aws-integration-streamed/test.ts | 4 +- .../parallel-root-spans-streamed/test.ts | 2 +- .../parallel-spans-in-scope-streamed/test.ts | 2 +- .../test.ts | 2 +- .../suites/tracing/anthropic/test.ts | 4 +- .../suites/tracing/google-genai/test.ts | 4 +- .../tracing/http-client-span-streamed/test.ts | 2 +- .../fetch-basic-streamed/test.ts | 2 +- .../tracing/httpIntegration-streamed/test.ts | 6 +- .../ignoreSpans-streamed/children/test.ts | 2 +- .../suites/tracing/langchain/test.ts | 4 +- .../suites/tracing/langgraph/test.ts | 4 +- .../suites/tracing/mysql/test.ts | 2 +- .../suites/tracing/openai/test.ts | 8 +- .../suites/tracing/postgres-streamed/test.ts | 2 +- .../tracing/requestData-streamed/test.ts | 28 +- .../vercelai/span-streaming-v4/test.ts | 4 +- .../suites/tracing/vercelai/v6_v7/test.ts | 252 +++++++++--------- .../test-utils/src/event-proxy-server.ts | 2 +- .../lib/tracing/spans/captureSpan.test.ts | 2 +- .../spans/spanJsonToStreamedSpan.test.ts | 8 +- 53 files changed, 334 insertions(+), 334 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/integrations/cultureContext-streamed/test.ts b/dev-packages/browser-integration-tests/suites/integrations/cultureContext-streamed/test.ts index 731ce2db6146..0d3484ef41bd 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/cultureContext-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/cultureContext-streamed/test.ts @@ -15,7 +15,7 @@ sentryTest('cultureContextIntegration captures locale, timezone, and calendar', const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload'); - expect(pageloadSpan!.attributes?.['culture.locale']).toEqual({ type: 'string', value: expect.any(String) }); - expect(pageloadSpan!.attributes?.['culture.timezone']).toEqual({ type: 'string', value: expect.any(String) }); - expect(pageloadSpan!.attributes?.['culture.calendar']).toEqual({ type: 'string', value: expect.any(String) }); + expect(pageloadSpan!.attributes['culture.locale']).toEqual({ type: 'string', value: expect.any(String) }); + expect(pageloadSpan!.attributes['culture.timezone']).toEqual({ type: 'string', value: expect.any(String) }); + expect(pageloadSpan!.attributes['culture.calendar']).toEqual({ type: 'string', value: expect.any(String) }); }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts index cb1e88072a9f..9b5563dec995 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpContext-streamed/test.ts @@ -15,12 +15,12 @@ sentryTest('httpContextIntegration captures url, user-agent, and referer', async const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload'); - expect(pageloadSpan!.attributes?.['url.full']).toEqual({ type: 'string', value: expect.any(String) }); - expect(pageloadSpan!.attributes?.['http.request.header.user_agent']).toEqual({ + expect(pageloadSpan!.attributes['url.full']).toEqual({ type: 'string', value: expect.any(String) }); + expect(pageloadSpan!.attributes['http.request.header.user_agent']).toEqual({ type: 'string', value: expect.any(String), }); - expect(pageloadSpan!.attributes?.['http.request.header.referer']).toEqual({ + expect(pageloadSpan!.attributes['http.request.header.referer']).toEqual({ type: 'string', value: 'https://sentry.io/', }); diff --git a/dev-packages/browser-integration-tests/suites/profiling/traceLifecycleMode_streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/profiling/traceLifecycleMode_streamed-spans/test.ts index f033302c9579..775a05136107 100644 --- a/dev-packages/browser-integration-tests/suites/profiling/traceLifecycleMode_streamed-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/profiling/traceLifecycleMode_streamed-spans/test.ts @@ -22,15 +22,15 @@ sentryTest( const rootSpan = spans.find(s => s.name === 'root-fibonacci'); expect(rootSpan).toBeDefined(); - expect(rootSpan!.attributes?.['thread.id']).toEqual({ type: 'string', value: '0' }); - expect(rootSpan!.attributes?.['thread.name']).toEqual({ type: 'string', value: 'main' }); + expect(rootSpan!.attributes['thread.id']).toEqual({ type: 'string', value: '0' }); + expect(rootSpan!.attributes['thread.name']).toEqual({ type: 'string', value: 'main' }); const childSpans = spans.filter(s => s.name === 'child-span-1' || s.name === 'child-span-2'); expect(childSpans.length).toBeGreaterThanOrEqual(1); for (const child of childSpans) { - expect(child.attributes?.['thread.id']).toEqual({ type: 'string', value: '0' }); - expect(child.attributes?.['thread.name']).toEqual({ type: 'string', value: 'main' }); + expect(child.attributes['thread.id']).toEqual({ type: 'string', value: '0' }); + expect(child.attributes['thread.name']).toEqual({ type: 'string', value: 'main' }); } }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts index be5d0feee840..c3218f27e98a 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts @@ -26,7 +26,7 @@ sentryTest('beforeSendSpan applies changes to streamed span', async ({ getLocalT }, }, ]); - expect(pageloadSpan.attributes?.['sentry.custom_attribute']).toEqual({ + expect(pageloadSpan.attributes['sentry.custom_attribute']).toEqual({ type: 'string', value: 'customAttributeValue', }); diff --git a/dev-packages/browser-integration-tests/suites/replay/span-streaming/test.ts b/dev-packages/browser-integration-tests/suites/replay/span-streaming/test.ts index 65a459997cae..01fe6e1d1da2 100644 --- a/dev-packages/browser-integration-tests/suites/replay/span-streaming/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/span-streaming/test.ts @@ -33,7 +33,7 @@ sentryTest( expect(pageloadSpan).toBeDefined(); // Span attribute: sentry.replay_id - expect(pageloadSpan!.attributes?.['sentry.replay_id']).toEqual({ + expect(pageloadSpan!.attributes['sentry.replay_id']).toEqual({ type: 'string', value: replay.session?.id, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts index a851918b5438..f33f988be946 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/backgroundtab-pageload-streamed/test.ts @@ -14,5 +14,5 @@ sentryTest('finishes streamed pageload span when the page goes background', asyn // TODO: Is this what we want? expect(pageloadSpan.status).toBe('ok'); - expect(pageloadSpan.attributes?.['sentry.cancellation_reason']?.value).toBe('document.hidden'); + expect(pageloadSpan.attributes['sentry.cancellation_reason']?.value).toBe('document.hidden'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts index 61c8fc3303dd..70b6f2c66fd4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/default/test.ts @@ -21,7 +21,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { const pageloadSampleRand = Number(envelope[0].trace?.sample_rand); const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(pageloadSpan.attributes?.['sentry.sample_rate']?.value).toBe(1); + expect(pageloadSpan.attributes['sentry.sample_rate']?.value).toBe(1); expect(Number.isNaN(pageloadSampleRand)).toBe(false); expect(pageloadSampleRand).toBeGreaterThanOrEqual(0); expect(pageloadSampleRand).toBeLessThanOrEqual(1); @@ -101,7 +101,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(pageloadSpan.attributes?.['sentry.sample_rate']?.value).toBe(1); + expect(pageloadSpan.attributes['sentry.sample_rate']?.value).toBe(1); return { pageloadSpan, pageloadSampleRand }; }); @@ -125,8 +125,8 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { expect(fetchTraceSampleRand).toBe(pageloadSampleRand); - expect(fetchTraceSpan.attributes?.['sentry.sample_rate']?.value).toEqual( - pageloadSpan.attributes?.['sentry.sample_rate']?.value, + expect(fetchTraceSpan.attributes['sentry.sample_rate']?.value).toEqual( + pageloadSpan.attributes['sentry.sample_rate']?.value, ); expect(fetchTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts index 154fe167a4a1..2657bed50d61 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/meta/test.ts @@ -32,8 +32,8 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes?.['sentry.sample_rate']).toBeUndefined(); - expect(span.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes['sentry.sample_rate']).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); return span; }); @@ -57,10 +57,10 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(envelope[0].trace?.sampled).toBe('true'); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes?.['sentry.sample_rate']).toBeUndefined(); + expect(span.attributes['sentry.sample_rate']).toBeUndefined(); // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(span.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); return span; }); @@ -86,10 +86,10 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(envelope[0].trace?.sampled).toEqual('true'); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(navSpan.attributes?.['sentry.sample_rate']).toBeUndefined(); + expect(navSpan.attributes['sentry.sample_rate']).toBeUndefined(); // but we need to set this attribute to still be able to correctly add the sample rate to the DSC (checked above in trace header) - expect(navSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); + expect(navSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe(metaTagSampleRate); }); }); @@ -115,8 +115,8 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(Number(envelope[0].trace?.sample_rate)).toBe(metaTagSampleRate); // since the local sample rate was not applied, the sample rate attribute shouldn't be set - expect(span.attributes?.['sentry.sample_rate']).toBeUndefined(); - expect(span.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); + expect(span.attributes['sentry.sample_rate']).toBeUndefined(); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]).toBeUndefined(); return span; }); @@ -140,8 +140,8 @@ sentryTest.describe('When `consistentTraceSampling` is `true` and page contains expect(fetchTraceSampleRand).toEqual(metaTagSampleRand); - expect(fetchTraceSpan.attributes?.['sentry.sample_rate']).toBeUndefined(); - expect(fetchTraceSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe( + expect(fetchTraceSpan.attributes['sentry.sample_rate']).toBeUndefined(); + expect(fetchTraceSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_PREVIOUS_TRACE_SAMPLE_RATE]?.value).toBe( metaTagSampleRate, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts index cd319e614c71..32d62350d6b4 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/consistent-sampling/tracesSampler-precedence/test.ts @@ -32,7 +32,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { const envelope = await pageloadEnvelopePromise; const pageloadSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'pageload')!; - expect(pageloadSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(pageloadSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); expect(Number(envelope[0].trace?.sample_rand)).toBeGreaterThanOrEqual(0); return { pageloadSpan }; @@ -96,7 +96,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { const envelope = await customEnvelopePromise; const customTrace2Span = envelope[1][0][1].items.find(s => getSpanOp(s) === 'custom')!; - expect(customTrace2Span.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(customTrace2Span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); expect(customTrace2Span.trace_id).not.toEqual(pageloadSpan.trace_id); expect(customTrace2Span.parent_span_id).toBeUndefined(); @@ -129,7 +129,7 @@ sentryTest.describe('When `consistentTraceSampling` is `true`', () => { const envelope = await navigationEnvelopePromise; const navigationSpan = envelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - expect(navigationSpan.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); + expect(navigationSpan.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]?.value).toBe(1); expect(navigationSpan.trace_id).not.toEqual(customTrace2Span.trace_id); expect(navigationSpan.parent_span_id).toBeUndefined(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts index fd63b6358bdf..c8d7af8246be 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/default/test.ts @@ -41,7 +41,7 @@ sentryTest("navigation spans link back to previous trace's root span", async ({ }, ]); - expect(navigation1Span.attributes?.['sentry.previous_trace']).toEqual({ + expect(navigation1Span.attributes['sentry.previous_trace']).toEqual({ type: 'string', value: `${pageloadTraceId}-${pageloadSpan.span_id}-1`, }); @@ -60,7 +60,7 @@ sentryTest("navigation spans link back to previous trace's root span", async ({ }, ]); - expect(navigation2Span.attributes?.['sentry.previous_trace']).toEqual({ + expect(navigation2Span.attributes['sentry.previous_trace']).toEqual({ type: 'string', value: `${navigation1TraceId}-${navigation1Span.span_id}-1`, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts index 0ff22e58a405..8b9b3d044dce 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/linked-traces-streamed/negatively-sampled/test.ts @@ -34,7 +34,7 @@ sentryTest('includes a span link to a previously negatively sampled span', async }, ]); - expect(navigationSpan.attributes?.['sentry.previous_trace']).toEqual({ + expect(navigationSpan.attributes['sentry.previous_trace']).toEqual({ type: 'string', value: expect.stringMatching(/[a-f\d]{32}-[a-f\d]{16}-0/), }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts index 040b78a89e9d..379495f4e5ac 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-animation-frame-enabled-streamed/test.ts @@ -30,7 +30,7 @@ sentryTest( expect(uiSpans.length).toBeGreaterThanOrEqual(1); const topLevelUISpan = uiSpans.find( - s => s.attributes?.['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', + s => s.attributes['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', )!; expect(topLevelUISpan).toEqual( @@ -83,7 +83,7 @@ sentryTest('captures long animation frame span for event listener.', async ({ br expect(uiSpans.length).toBeGreaterThanOrEqual(2); const eventListenerUISpan = uiSpans.find( - s => s.attributes?.['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', + s => s.attributes['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', )!; expect(eventListenerUISpan).toEqual( diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts index 2529c6e2f66d..4e3ef4f35f2c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-and-animation-frame-enabled-streamed/test.ts @@ -32,7 +32,7 @@ sentryTest( expect(uiSpans.length).toBeGreaterThanOrEqual(1); const topLevelUISpan = uiSpans.find( - s => s.attributes?.['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', + s => s.attributes['browser.script.invoker']?.value === 'https://sentry-test-site.example/path/to/script.js', )!; expect(topLevelUISpan).toEqual( @@ -85,7 +85,7 @@ sentryTest('captures long animation frame span for event listener.', async ({ br expect(uiSpans.length).toBeGreaterThanOrEqual(2); const eventListenerUISpan = uiSpans.find( - s => s.attributes?.['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', + s => s.attributes['browser.script.invoker']?.value === 'BUTTON#clickme.onclick', )!; expect(eventListenerUISpan).toEqual( diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts index 79290f65f3cf..3072734d2b79 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts @@ -25,7 +25,7 @@ sentryTest( const span = await spanPromise; expect(span.name).toMatch(/^GET /); - expect(span.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser' }); - expect(span.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'http.client' }); + expect(span.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser' }); + expect(span.attributes['sentry.op']).toEqual({ type: 'string', value: 'http.client' }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts index 6f418c79a024..fa15130c9ef7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts @@ -5,11 +5,11 @@ import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipMetricsTest, shouldSkipTracingTest, waitForMetrics } from '../../../../utils/helpers'; function getIdentifier(m: SerializedMetric): unknown { - return m.attributes?.['ui.element.identifier']?.value; + return m.attributes['ui.element.identifier']?.value; } function getPaintType(m: SerializedMetric): unknown { - return m.attributes?.['ui.element.paint_type']?.value; + return m.attributes['ui.element.paint_type']?.value; } sentryTest( diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts index 4abb2df344b4..0d29544e927b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-cls-streamed-spans/test.ts @@ -33,22 +33,22 @@ sentryTest('captures CLS as a streamed span with source attributes', async ({ ge const clsSpan = await clsSpanPromise; const pageloadSpan = await pageloadSpanPromise; - expect(clsSpan.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.cls' }); - expect(clsSpan.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.cls' }); - expect(clsSpan.attributes?.['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); - expect(clsSpan.attributes?.['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); + expect(clsSpan.attributes['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.cls' }); + expect(clsSpan.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.cls' }); + expect(clsSpan.attributes['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); + expect(clsSpan.attributes['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); // Check the CLS span carries the transaction/segment name it belongs to - expect(clsSpan.attributes?.['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); - expect(clsSpan.attributes?.['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); + expect(clsSpan.attributes['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); + expect(clsSpan.attributes['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); // Check browser.web_vital.cls.source attributes - expect(clsSpan.attributes?.['browser.web_vital.cls.source.1']?.value).toEqual( + expect(clsSpan.attributes['browser.web_vital.cls.source.1']?.value).toEqual( expect.stringContaining('body > div#content > p'), ); // Check pageload span id is present - expect(clsSpan.attributes?.['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); + expect(clsSpan.attributes['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); // CLS is a point-in-time metric expect(clsSpan.start_timestamp).toEqual(clsSpan.end_timestamp); @@ -72,9 +72,9 @@ sentryTest('CLS streamed span has web vital value attribute', async ({ getLocalT const clsSpan = await clsSpanPromise; // The CLS value should be set as a browser.web_vital.cls.value attribute - expect(clsSpan.attributes?.['browser.web_vital.cls.value']?.type).toBe('double'); + expect(clsSpan.attributes['browser.web_vital.cls.value']?.type).toBe('double'); // Flakey value dependent on timings -> we check for a range - const clsValue = clsSpan.attributes?.['browser.web_vital.cls.value']?.value as number; + const clsValue = clsSpan.attributes['browser.web_vital.cls.value']?.value as number; expect(clsValue).toBeGreaterThan(0.05); expect(clsValue).toBeLessThan(0.15); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/test.ts index 6b08ec5b904c..947522d14d1c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp-streamed-spans/test.ts @@ -27,18 +27,18 @@ sentryTest('captures INP click as a streamed span', async ({ getLocalTestUrl, pa const inpSpan = await inpSpanPromise; const pageloadSpan = await pageloadSpanPromise; - expect(inpSpan.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'ui.interaction.click' }); - expect(inpSpan.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.inp' }); - expect(inpSpan.attributes?.['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); + expect(inpSpan.attributes['sentry.op']).toEqual({ type: 'string', value: 'ui.interaction.click' }); + expect(inpSpan.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.inp' }); + expect(inpSpan.attributes['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); // Check the INP span carries the transaction/segment name it belongs to - expect(inpSpan.attributes?.['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); - expect(inpSpan.attributes?.['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); + expect(inpSpan.attributes['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); + expect(inpSpan.attributes['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); - const inpValue = inpSpan.attributes?.['browser.web_vital.inp.value']?.value as number; + const inpValue = inpSpan.attributes['browser.web_vital.inp.value']?.value as number; expect(inpValue).toBeGreaterThan(0); - expect(inpSpan.attributes?.['sentry.exclusive_time']?.value).toBeGreaterThan(0); + expect(inpSpan.attributes['sentry.exclusive_time']?.value).toBeGreaterThan(0); expect(inpSpan.name).toBe('body > NormalButton'); @@ -76,8 +76,8 @@ sentryTest('captures the slowest interaction as streamed INP span', async ({ get const inpSpan = await inpSpanPromise; expect(inpSpan.name).toBe('body > SlowButton'); - expect(inpSpan.attributes?.['sentry.exclusive_time']?.value).toBeGreaterThan(400); + expect(inpSpan.attributes['sentry.exclusive_time']?.value).toBeGreaterThan(400); - const inpValue = inpSpan.attributes?.['browser.web_vital.inp.value']?.value as number; + const inpValue = inpSpan.attributes['browser.web_vital.inp.value']?.value as number; expect(inpValue).toBeGreaterThan(400); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts index 45b373aa6bf9..433c82905412 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts @@ -35,28 +35,28 @@ sentryTest('captures LCP as a streamed span with element attributes', async ({ g const lcpSpan = await lcpSpanPromise; const pageloadSpan = await pageloadSpanPromise; - expect(lcpSpan.attributes?.['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.lcp' }); - expect(lcpSpan.attributes?.['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.lcp' }); - expect(lcpSpan.attributes?.['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); - expect(lcpSpan.attributes?.['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); + expect(lcpSpan.attributes['sentry.op']).toEqual({ type: 'string', value: 'ui.webvital.lcp' }); + expect(lcpSpan.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser.lcp' }); + expect(lcpSpan.attributes['sentry.exclusive_time']).toEqual({ type: 'integer', value: 0 }); + expect(lcpSpan.attributes['user_agent.original']?.value).toEqual(expect.stringContaining('Chrome')); // Check the LCP span carries the transaction/segment name it belongs to - expect(lcpSpan.attributes?.['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); - expect(lcpSpan.attributes?.['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); + expect(lcpSpan.attributes['sentry.transaction']).toEqual({ type: 'string', value: '/index.html' }); + expect(lcpSpan.attributes['sentry.segment.name']).toEqual({ type: 'string', value: '/index.html' }); // Check browser.web_vital.lcp.* attributes - expect(lcpSpan.attributes?.['browser.web_vital.lcp.element']?.value).toEqual(expect.stringContaining('body > img')); - expect(lcpSpan.attributes?.['browser.web_vital.lcp.url']?.value).toBe( + expect(lcpSpan.attributes['browser.web_vital.lcp.element']?.value).toEqual(expect.stringContaining('body > img')); + expect(lcpSpan.attributes['browser.web_vital.lcp.url']?.value).toBe( 'https://sentry-test-site.example/my/image.png', ); - expect(lcpSpan.attributes?.['browser.web_vital.lcp.size']?.value).toEqual(expect.any(Number)); + expect(lcpSpan.attributes['browser.web_vital.lcp.size']?.value).toEqual(expect.any(Number)); // Check web vital value attribute - expect(lcpSpan.attributes?.['browser.web_vital.lcp.value']?.type).toMatch(/^(double)|(integer)$/); - expect(lcpSpan.attributes?.['browser.web_vital.lcp.value']?.value).toBeGreaterThan(0); + expect(lcpSpan.attributes['browser.web_vital.lcp.value']?.type).toMatch(/^(double)|(integer)$/); + expect(lcpSpan.attributes['browser.web_vital.lcp.value']?.value).toBeGreaterThan(0); // Check pageload span id is present - expect(lcpSpan.attributes?.['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); + expect(lcpSpan.attributes['sentry.pageload.span_id']?.value).toBe(pageloadSpan.span_id); // Span should have meaningful duration (navigation start -> LCP event) expect(lcpSpan.end_timestamp).toBeGreaterThan(lcpSpan.start_timestamp); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-ttfb-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-ttfb-streamed/test.ts index d410b1eaa356..4a51ad0f6948 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-ttfb-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-ttfb-streamed/test.ts @@ -27,10 +27,10 @@ sentryTest( // This seems to happen somewhat randomly, so we handle it. const responseStart = await page.evaluate("performance.getEntriesByType('navigation')[0].responseStart;"); if (responseStart !== 0) { - expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.value']?.type).toMatch(/^(double)|(integer)$/); - expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.value']?.value).toBeGreaterThan(0); + expect(pageloadSpan.attributes['browser.web_vital.ttfb.value']?.type).toMatch(/^(double)|(integer)$/); + expect(pageloadSpan.attributes['browser.web_vital.ttfb.value']?.value).toBeGreaterThan(0); } - expect(pageloadSpan.attributes?.['browser.web_vital.ttfb.request_time']?.type).toMatch(/^(double)|(integer)$/); + expect(pageloadSpan.attributes['browser.web_vital.ttfb.request_time']?.type).toMatch(/^(double)|(integer)$/); }, ); diff --git a/dev-packages/browser-integration-tests/utils/spanUtils.ts b/dev-packages/browser-integration-tests/utils/spanUtils.ts index 67b5798b66f1..532f2ce5c526 100644 --- a/dev-packages/browser-integration-tests/utils/spanUtils.ts +++ b/dev-packages/browser-integration-tests/utils/spanUtils.ts @@ -125,7 +125,7 @@ export async function observeStreamedSpan( } export function getSpanOp(span: SerializedStreamedSpan): string | undefined { - return span.attributes?.['sentry.op']?.type === 'string' ? span.attributes?.['sentry.op']?.value : undefined; + return span.attributes['sentry.op']?.type === 'string' ? span.attributes['sentry.op']?.value : undefined; } export function getSpansFromEnvelope(envelope: StreamedSpanEnvelope): SerializedStreamedSpan[] { diff --git a/dev-packages/e2e-tests/test-applications/cloudflare-workers-streaming/tests/index.test.ts b/dev-packages/e2e-tests/test-applications/cloudflare-workers-streaming/tests/index.test.ts index e984247a01d8..e20d8e3823fb 100644 --- a/dev-packages/e2e-tests/test-applications/cloudflare-workers-streaming/tests/index.test.ts +++ b/dev-packages/e2e-tests/test-applications/cloudflare-workers-streaming/tests/index.test.ts @@ -114,8 +114,8 @@ test('Storage operations create spans in Durable Object', async ({ baseURL }) => const putSpan = spans.find(span => span.name === 'durable_object_storage_put' && getSpanOp(span) === 'db'); expect(putSpan).toBeDefined(); - expect(putSpan?.attributes?.['db.system.name']?.value).toBe('cloudflare.durable_object.storage'); - expect(putSpan?.attributes?.['db.operation.name']?.value).toBe('put'); + expect(putSpan?.attributes['db.system.name']?.value).toBe('cloudflare.durable_object.storage'); + expect(putSpan?.attributes['db.operation.name']?.value).toBe('put'); }); test.describe('Alarm instrumentation', () => { @@ -150,13 +150,13 @@ test.describe('Alarm instrumentation', () => { // Alarm creates a streamed span with correct attributes expect(getSpanOp(alarmSpan)).toBe('function'); - expect(alarmSpan.attributes?.['sentry.origin']?.value).toBe('auto.faas.cloudflare.durable_object'); + expect(alarmSpan.attributes['sentry.origin']?.value).toBe('auto.faas.cloudflare.durable_object'); // Alarm starts a new trace (different trace ID from the request that called setAlarm) expect(alarmSpan.trace_id).not.toBe(setAlarmSpan.trace_id); // Alarm links to the trace that called setAlarm via sentry.previous_trace attribute - const previousTrace = alarmSpan.attributes?.['sentry.previous_trace']?.value; + const previousTrace = alarmSpan.attributes['sentry.previous_trace']?.value; expect(previousTrace).toBeDefined(); expect(previousTrace).toContain(setAlarmSpan.trace_id); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts index 566771016a72..c38662827693 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-15/tests/ai-test.test.ts @@ -35,34 +35,34 @@ test('should create AI spans with correct attributes', async ({ page }) => { // First AI call - should have telemetry enabled and record inputs/outputs (dataCollection: { userInfo: true }) /* const firstPipelineSpan = aiPipelineSpans[0]; - expect(firstPipelineSpan?.attributes?.['vercel.ai.model.id']?.value).toBe('mock-model-id'); - expect(firstPipelineSpan?.attributes?.['vercel.ai.model.provider']?.value).toBe('mock-provider'); - expect(firstPipelineSpan?.attributes?.['vercel.ai.prompt']?.value).toContain('Where is the first span?'); - expect(firstPipelineSpan?.attributes?.['gen_ai.output.messages']?.value).toContain('First span here!'); - expect(firstPipelineSpan?.attributes?.['gen_ai.usage.input_tokens']?.value).toBe(10); - expect(firstPipelineSpan?.attributes?.['gen_ai.usage.output_tokens']?.value).toBe(20); */ + expect(firstPipelineSpan?.attributes['vercel.ai.model.id']?.value).toBe('mock-model-id'); + expect(firstPipelineSpan?.attributes['vercel.ai.model.provider']?.value).toBe('mock-provider'); + expect(firstPipelineSpan?.attributes['vercel.ai.prompt']?.value).toContain('Where is the first span?'); + expect(firstPipelineSpan?.attributes['gen_ai.output.messages']?.value).toContain('First span here!'); + expect(firstPipelineSpan?.attributes['gen_ai.usage.input_tokens']?.value).toBe(10); + expect(firstPipelineSpan?.attributes['gen_ai.usage.output_tokens']?.value).toBe(20); */ // Second AI call - explicitly enabled telemetry const secondPipelineSpan = aiPipelineSpans[0]; - expect(secondPipelineSpan?.attributes?.['vercel.ai.prompt']?.value).toContain('Where is the second span?'); - expect(secondPipelineSpan?.attributes?.['gen_ai.output.messages']?.value).toContain('Second span here!'); + expect(secondPipelineSpan?.attributes['vercel.ai.prompt']?.value).toContain('Where is the second span?'); + expect(secondPipelineSpan?.attributes['gen_ai.output.messages']?.value).toContain('Second span here!'); // Third AI call - with tool calls /* const thirdPipelineSpan = aiPipelineSpans[2]; - expect(thirdPipelineSpan?.attributes?.['vercel.ai.response.finishReason']?.value).toBe('tool-calls'); - expect(thirdPipelineSpan?.attributes?.['gen_ai.usage.input_tokens']?.value).toBe(15); - expect(thirdPipelineSpan?.attributes?.['gen_ai.usage.output_tokens']?.value).toBe(25); */ + expect(thirdPipelineSpan?.attributes['vercel.ai.response.finishReason']?.value).toBe('tool-calls'); + expect(thirdPipelineSpan?.attributes['gen_ai.usage.input_tokens']?.value).toBe(15); + expect(thirdPipelineSpan?.attributes['gen_ai.usage.output_tokens']?.value).toBe(25); */ // Tool call span /* const toolSpan = toolCallSpans[0]; - expect(toolSpan?.attributes?.['vercel.ai.toolCall.name']?.value).toBe('getWeather'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.id']?.value).toBe('call-1'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.args']?.value).toContain('San Francisco'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.result']?.value).toContain('Sunny, 72°F'); */ + expect(toolSpan?.attributes['vercel.ai.toolCall.name']?.value).toBe('getWeather'); + expect(toolSpan?.attributes['vercel.ai.toolCall.id']?.value).toBe('call-1'); + expect(toolSpan?.attributes['vercel.ai.toolCall.args']?.value).toContain('San Francisco'); + expect(toolSpan?.attributes['vercel.ai.toolCall.result']?.value).toContain('Sunny, 72°F'); */ // Verify the fourth call was not captured (telemetry disabled) const promptsInSpans = genAiSpans - .map(span => span.attributes?.['vercel.ai.prompt']?.value) + .map(span => span.attributes['vercel.ai.prompt']?.value) .filter((prompt): prompt is string => typeof prompt === 'string'); const hasDisabledPrompt = promptsInSpans.some(prompt => prompt.includes('Where is the third span?')); expect(hasDisabledPrompt).toBe(false); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/parameterized-routes.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/parameterized-routes.test.ts index 7990953bf7a5..88231caa6e79 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/parameterized-routes.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16-streaming/tests/parameterized-routes.test.ts @@ -12,7 +12,7 @@ test('should create a parameterized streamed span when the `app` directory is us expect(span.name).toBe('/parameterized/:one'); expect(span.trace_id).toMatch(/[a-f0-9]{32}/); - expect(span.attributes?.['sentry.source']?.value).toBe('route'); + expect(span.attributes['sentry.source']?.value).toBe('route'); }); test('should create a static streamed span when the `app` directory is used and the route is not parameterized', async ({ @@ -28,7 +28,7 @@ test('should create a static streamed span when the `app` directory is used and expect(span.name).toBe('/parameterized/static'); expect(span.trace_id).toMatch(/[a-f0-9]{32}/); - expect(span.attributes?.['sentry.source']?.value).toBe('url'); + expect(span.attributes['sentry.source']?.value).toBe('url'); }); test('should create a partially parameterized streamed span when the `app` directory is used', async ({ page }) => { @@ -42,7 +42,7 @@ test('should create a partially parameterized streamed span when the `app` direc expect(span.name).toBe('/parameterized/:one/beep'); expect(span.trace_id).toMatch(/[a-f0-9]{32}/); - expect(span.attributes?.['sentry.source']?.value).toBe('route'); + expect(span.attributes['sentry.source']?.value).toBe('route'); }); test('should create a nested parameterized streamed span when the `app` directory is used.', async ({ page }) => { @@ -56,5 +56,5 @@ test('should create a nested parameterized streamed span when the `app` director expect(span.name).toBe('/parameterized/:one/beep/:two'); expect(span.trace_id).toMatch(/[a-f0-9]{32}/); - expect(span.attributes?.['sentry.source']?.value).toBe('route'); + expect(span.attributes['sentry.source']?.value).toBe('route'); }); diff --git a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/ai-test.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/ai-test.test.ts index 3117255c8d15..7899fd579717 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-16/tests/ai-test.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-16/tests/ai-test.test.ts @@ -35,34 +35,34 @@ test('should create AI spans with correct attributes', async ({ page }) => { // First AI call - should have telemetry enabled and record inputs/outputs (dataCollection: { userInfo: true }) /* const firstPipelineSpan = aiPipelineSpans[0]; - expect(firstPipelineSpan?.attributes?.['vercel.ai.model.id']?.value).toBe('mock-model-id'); - expect(firstPipelineSpan?.attributes?.['vercel.ai.model.provider']?.value).toBe('mock-provider'); - expect(firstPipelineSpan?.attributes?.['vercel.ai.prompt']?.value).toContain('Where is the first span?'); - expect(firstPipelineSpan?.attributes?.['gen_ai.output.messages']?.value).toContain('First span here!'); - expect(firstPipelineSpan?.attributes?.['gen_ai.usage.input_tokens']?.value).toBe(10); - expect(firstPipelineSpan?.attributes?.['gen_ai.usage.output_tokens']?.value).toBe(20); */ + expect(firstPipelineSpan?.attributes['vercel.ai.model.id']?.value).toBe('mock-model-id'); + expect(firstPipelineSpan?.attributes['vercel.ai.model.provider']?.value).toBe('mock-provider'); + expect(firstPipelineSpan?.attributes['vercel.ai.prompt']?.value).toContain('Where is the first span?'); + expect(firstPipelineSpan?.attributes['gen_ai.output.messages']?.value).toContain('First span here!'); + expect(firstPipelineSpan?.attributes['gen_ai.usage.input_tokens']?.value).toBe(10); + expect(firstPipelineSpan?.attributes['gen_ai.usage.output_tokens']?.value).toBe(20); */ // Second AI call - explicitly enabled telemetry const secondPipelineSpan = aiPipelineSpans[0]; - expect(secondPipelineSpan?.attributes?.['vercel.ai.prompt']?.value).toContain('Where is the second span?'); - expect(secondPipelineSpan?.attributes?.['gen_ai.output.messages']?.value).toContain('Second span here!'); + expect(secondPipelineSpan?.attributes['vercel.ai.prompt']?.value).toContain('Where is the second span?'); + expect(secondPipelineSpan?.attributes['gen_ai.output.messages']?.value).toContain('Second span here!'); // Third AI call - with tool calls /* const thirdPipelineSpan = aiPipelineSpans[2]; - expect(thirdPipelineSpan?.attributes?.['vercel.ai.response.finishReason']?.value).toBe('tool-calls'); - expect(thirdPipelineSpan?.attributes?.['gen_ai.usage.input_tokens']?.value).toBe(15); - expect(thirdPipelineSpan?.attributes?.['gen_ai.usage.output_tokens']?.value).toBe(25); */ + expect(thirdPipelineSpan?.attributes['vercel.ai.response.finishReason']?.value).toBe('tool-calls'); + expect(thirdPipelineSpan?.attributes['gen_ai.usage.input_tokens']?.value).toBe(15); + expect(thirdPipelineSpan?.attributes['gen_ai.usage.output_tokens']?.value).toBe(25); */ // Tool call span /* const toolSpan = toolCallSpans[0]; - expect(toolSpan?.attributes?.['vercel.ai.toolCall.name']?.value).toBe('getWeather'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.id']?.value).toBe('call-1'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.args']?.value).toContain('San Francisco'); - expect(toolSpan?.attributes?.['vercel.ai.toolCall.result']?.value).toContain('Sunny, 72°F'); */ + expect(toolSpan?.attributes['vercel.ai.toolCall.name']?.value).toBe('getWeather'); + expect(toolSpan?.attributes['vercel.ai.toolCall.id']?.value).toBe('call-1'); + expect(toolSpan?.attributes['vercel.ai.toolCall.args']?.value).toContain('San Francisco'); + expect(toolSpan?.attributes['vercel.ai.toolCall.result']?.value).toContain('Sunny, 72°F'); */ // Verify the fourth call was not captured (telemetry disabled) const promptsInSpans = genAiSpans - .map(span => span.attributes?.['vercel.ai.prompt']?.value) + .map(span => span.attributes['vercel.ai.prompt']?.value) .filter((prompt): prompt is string => typeof prompt === 'string'); const hasDisabledPrompt = promptsInSpans.some(prompt => prompt.includes('Where is the third span?')); expect(hasDisabledPrompt).toBe(false); diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/mcp.test.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/mcp.test.ts index ec82de5af455..08e1e51b1ca2 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/mcp.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/mcp.test.ts @@ -25,9 +25,9 @@ test.skip('Should record streamed spans for mcp handlers', async ({ baseURL }) = const initializeSpan = await initializeSpanPromise; expect(initializeSpan).toBeDefined(); expect(getSpanOp(initializeSpan)).toBe('mcp.server'); - expect(initializeSpan.attributes?.['mcp.method.name']?.value).toBe('initialize'); - expect(initializeSpan.attributes?.['mcp.client.name']?.value).toBe('test-client'); - expect(initializeSpan.attributes?.['mcp.server.name']?.value).toBe('Echo'); + expect(initializeSpan.attributes['mcp.method.name']?.value).toBe('initialize'); + expect(initializeSpan.attributes['mcp.client.name']?.value).toBe('test-client'); + expect(initializeSpan.attributes['mcp.server.name']?.value).toBe('Echo'); }); await test.step('tool handler', async () => { @@ -61,7 +61,7 @@ test.skip('Should record streamed spans for mcp handlers', async ({ baseURL }) = const toolSpan = await toolSpanPromise; expect(toolSpan).toBeDefined(); expect(getSpanOp(toolSpan)).toBe('mcp.server'); - expect(toolSpan.attributes?.['mcp.method.name']?.value).toBe('tools/call'); + expect(toolSpan.attributes['mcp.method.name']?.value).toBe('tools/call'); }); await test.step('registerTool handler', async () => { @@ -95,8 +95,8 @@ test.skip('Should record streamed spans for mcp handlers', async ({ baseURL }) = const toolSpan = await toolSpanPromise; expect(toolSpan).toBeDefined(); expect(getSpanOp(toolSpan)).toBe('mcp.server'); - expect(toolSpan.attributes?.['mcp.method.name']?.value).toBe('tools/call'); - expect(toolSpan.attributes?.['mcp.tool.name']?.value).toBe('echo-register'); + expect(toolSpan.attributes['mcp.method.name']?.value).toBe('tools/call'); + expect(toolSpan.attributes['mcp.tool.name']?.value).toBe('echo-register'); }); await test.step('resource handler', async () => { @@ -122,7 +122,7 @@ test.skip('Should record streamed spans for mcp handlers', async ({ baseURL }) = const resourceSpan = await resourceSpanPromise; expect(resourceSpan).toBeDefined(); expect(getSpanOp(resourceSpan)).toBe('mcp.server'); - expect(resourceSpan.attributes?.['mcp.method.name']?.value).toBe('resources/read'); + expect(resourceSpan.attributes['mcp.method.name']?.value).toBe('resources/read'); }); await test.step('prompt handler', async () => { @@ -159,7 +159,7 @@ test.skip('Should record streamed spans for mcp handlers', async ({ baseURL }) = const promptSpan = await promptSpanPromise; expect(promptSpan).toBeDefined(); expect(getSpanOp(promptSpan)).toBe('mcp.server'); - expect(promptSpan.attributes?.['mcp.method.name']?.value).toBe('prompts/get'); + expect(promptSpan.attributes['mcp.method.name']?.value).toBe('prompts/get'); }); await test.step('error tool sets span status to error', async () => { @@ -192,7 +192,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp return ( span.name === 'initialize' && getSpanOp(span) === 'mcp.server' && - span.attributes?.['mcp.server.name']?.value === 'Echo-Streamable' + span.attributes['mcp.server.name']?.value === 'Echo-Streamable' ); }); @@ -202,10 +202,10 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp const initializeSpan = await initializeSpanPromise; expect(initializeSpan).toBeDefined(); expect(getSpanOp(initializeSpan)).toBe('mcp.server'); - expect(initializeSpan.attributes?.['mcp.method.name']?.value).toBe('initialize'); - expect(initializeSpan.attributes?.['mcp.client.name']?.value).toBe('test-client-streamable'); - expect(initializeSpan.attributes?.['mcp.server.name']?.value).toBe('Echo-Streamable'); - expect(String(initializeSpan.attributes?.['mcp.transport']?.value)).toMatch(/StreamableHTTPServerTransport/); + expect(initializeSpan.attributes['mcp.method.name']?.value).toBe('initialize'); + expect(initializeSpan.attributes['mcp.client.name']?.value).toBe('test-client-streamable'); + expect(initializeSpan.attributes['mcp.server.name']?.value).toBe('Echo-Streamable'); + expect(String(initializeSpan.attributes['mcp.transport']?.value)).toMatch(/StreamableHTTPServerTransport/); }); await test.step('tool handler (tests wrapper transport correlation)', async () => { @@ -213,7 +213,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp return ( span.name === 'tools/call echo' && getSpanOp(span) === 'mcp.server' && - String(span.attributes?.['mcp.transport']?.value).includes('StreamableHTTPServerTransport') + String(span.attributes['mcp.transport']?.value).includes('StreamableHTTPServerTransport') ); }); @@ -236,9 +236,9 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp const toolSpan = await toolSpanPromise; expect(toolSpan).toBeDefined(); expect(getSpanOp(toolSpan)).toBe('mcp.server'); - expect(toolSpan.attributes?.['mcp.method.name']?.value).toBe('tools/call'); - expect(toolSpan.attributes?.['mcp.tool.name']?.value).toBe('echo'); - expect(toolSpan.attributes?.['mcp.tool.result.content_count']?.value).toBe(1); + expect(toolSpan.attributes['mcp.method.name']?.value).toBe('tools/call'); + expect(toolSpan.attributes['mcp.tool.name']?.value).toBe('echo'); + expect(toolSpan.attributes['mcp.tool.result.content_count']?.value).toBe(1); }); await test.step('resource handler', async () => { @@ -246,7 +246,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp return ( span.name === 'resources/read echo://streamable-test' && getSpanOp(span) === 'mcp.server' && - String(span.attributes?.['mcp.transport']?.value).includes('StreamableHTTPServerTransport') + String(span.attributes['mcp.transport']?.value).includes('StreamableHTTPServerTransport') ); }); @@ -261,7 +261,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp const resourceSpan = await resourceSpanPromise; expect(resourceSpan).toBeDefined(); expect(getSpanOp(resourceSpan)).toBe('mcp.server'); - expect(resourceSpan.attributes?.['mcp.method.name']?.value).toBe('resources/read'); + expect(resourceSpan.attributes['mcp.method.name']?.value).toBe('resources/read'); }); await test.step('prompt handler', async () => { @@ -269,7 +269,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp return ( span.name === 'prompts/get echo' && getSpanOp(span) === 'mcp.server' && - String(span.attributes?.['mcp.transport']?.value).includes('StreamableHTTPServerTransport') + String(span.attributes['mcp.transport']?.value).includes('StreamableHTTPServerTransport') ); }); @@ -295,7 +295,7 @@ test('Should record streamed spans for streamable HTTP transport (wrapper transp const promptSpan = await promptSpanPromise; expect(promptSpan).toBeDefined(); expect(getSpanOp(promptSpan)).toBe('mcp.server'); - expect(promptSpan.attributes?.['mcp.method.name']?.value).toBe('prompts/get'); + expect(promptSpan.attributes['mcp.method.name']?.value).toBe('prompts/get'); }); await client.close(); diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts index 38cb1402c623..6632f1bff0f8 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/spans.test.ts @@ -18,9 +18,9 @@ test('Sends streamed spans for an API route', async ({ baseURL }) => { expect(getSpanOp(rootSpan!)).toBe('http.server'); expect(rootSpan!.status).toBe('ok'); expect(rootSpan!.trace_id).toMatch(/[a-f0-9]{32}/); - expect(rootSpan!.attributes?.['sentry.source']?.value).toBe('route'); - expect(rootSpan!.attributes?.['sentry.origin']?.value).toBe('auto.http.otel.http'); - expect(rootSpan!.attributes?.['http.response.status_code']?.value).toBe(200); + expect(rootSpan!.attributes['sentry.source']?.value).toBe('route'); + expect(rootSpan!.attributes['sentry.origin']?.value).toBe('auto.http.otel.http'); + expect(rootSpan!.attributes['http.response.status_code']?.value).toBe(200); const childSpans = spans.filter(span => !span.is_segment); @@ -74,7 +74,7 @@ test('Sends streamed spans for an errored route', async ({ baseURL }) => { expect(rootSpan.name).toBe('GET /test-exception/:id'); expect(getSpanOp(rootSpan)).toBe('http.server'); expect(rootSpan.status).toBe('error'); - expect(rootSpan.attributes?.['http.status_code']?.value).toBe(500); + expect(rootSpan.attributes['http.status_code']?.value).toBe(500); }); test('Outgoing fetch spans are streamed', async ({ baseURL }) => { @@ -104,7 +104,7 @@ test.skip('Outgoing fetch spans include response headers when headersToSpanAttri const fetchSpan = await fetchSpanPromise; expect(fetchSpan).toBeDefined(); - expect(fetchSpan.attributes?.['http.response.header.content-length']).toBeDefined(); + expect(fetchSpan.attributes['http.response.header.content-length']).toBeDefined(); }); test('Extracts HTTP request headers as streamed span attributes', async ({ baseURL }) => { @@ -113,7 +113,7 @@ test('Extracts HTTP request headers as streamed span attributes', async ({ baseU span.name === 'GET /test-transaction' && getSpanOp(span) === 'http.server' && span.is_segment && - span.attributes?.['http.request.header.user_agent']?.value === 'Custom-Agent/1.0 (Test)' + span.attributes['http.request.header.user_agent']?.value === 'Custom-Agent/1.0 (Test)' ); }); @@ -129,9 +129,9 @@ test('Extracts HTTP request headers as streamed span attributes', async ({ baseU const rootSpan = await rootSpanPromise; - expect(rootSpan.attributes?.['http.request.header.user_agent']?.value).toBe('Custom-Agent/1.0 (Test)'); - expect(rootSpan.attributes?.['http.request.header.content_type']?.value).toBe('application/json'); - expect(rootSpan.attributes?.['http.request.header.x_custom_header']?.value).toBe('test-value'); - expect(rootSpan.attributes?.['http.request.header.accept']?.value).toBe('application/json, text/plain'); - expect(rootSpan.attributes?.['http.request.header.x_request_id']?.value).toBe('req-123'); + expect(rootSpan.attributes['http.request.header.user_agent']?.value).toBe('Custom-Agent/1.0 (Test)'); + expect(rootSpan.attributes['http.request.header.content_type']?.value).toBe('application/json'); + expect(rootSpan.attributes['http.request.header.x_custom_header']?.value).toBe('test-value'); + expect(rootSpan.attributes['http.request.header.accept']?.value).toBe('application/json, text/plain'); + expect(rootSpan.attributes['http.request.header.x_request_id']?.value).toBe('req-123'); }); diff --git a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/trpc.test.ts b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/trpc.test.ts index bc0b982adbe9..1253a66627bc 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/trpc.test.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-streaming/tests/trpc.test.ts @@ -22,7 +22,7 @@ test('Should record streamed span for trpc query', async ({ baseURL }) => { expect(trpcSpan).toBeDefined(); expect(trpcSpan.name).toBe('trpc/getSomething'); expect(getSpanOp(trpcSpan)).toBe('rpc.server'); - expect(trpcSpan.attributes?.['sentry.origin']?.value).toBe('auto.rpc.trpc'); + expect(trpcSpan.attributes['sentry.origin']?.value).toBe('auto.rpc.trpc'); }); test('Should record streamed span for trpc mutation', async ({ baseURL }) => { @@ -44,7 +44,7 @@ test('Should record streamed span for trpc mutation', async ({ baseURL }) => { expect(trpcSpan).toBeDefined(); expect(trpcSpan.name).toBe('trpc/createSomething'); expect(getSpanOp(trpcSpan)).toBe('rpc.server'); - expect(trpcSpan.attributes?.['sentry.origin']?.value).toBe('auto.rpc.trpc'); + expect(trpcSpan.attributes['sentry.origin']?.value).toBe('auto.rpc.trpc'); }); test('Should record streamed span and error for a crashing trpc handler', async ({ baseURL }) => { diff --git a/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/tests/spans.test.ts b/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/tests/spans.test.ts index 0080a584463a..6056d85f09e0 100644 --- a/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/tests/spans.test.ts +++ b/dev-packages/e2e-tests/test-applications/react-router-7-spa-streaming/tests/spans.test.ts @@ -13,8 +13,8 @@ test('sends a pageload span with a parameterized URL', async ({ page }) => { expect(span.name).toBe('/'); expect(span.trace_id).toMatch(/[a-f0-9]{32}/); expect(span.status).toBe('ok'); - expect(span.attributes?.['sentry.origin']?.value).toBe('auto.pageload.react.reactrouter_v7'); - expect(span.attributes?.['sentry.source']?.value).toBe('route'); + expect(span.attributes['sentry.origin']?.value).toBe('auto.pageload.react.reactrouter_v7'); + expect(span.attributes['sentry.source']?.value).toBe('route'); }); test('sends a navigation span with a parameterized URL', async ({ page }) => { @@ -37,8 +37,8 @@ test('sends a navigation span with a parameterized URL', async ({ page }) => { expect(navigationSpan.name).toBe('/user/:id'); expect(navigationSpan.trace_id).toMatch(/[a-f0-9]{32}/); expect(navigationSpan.status).toBe('ok'); - expect(navigationSpan.attributes?.['sentry.origin']?.value).toBe('auto.navigation.react.reactrouter_v7'); - expect(navigationSpan.attributes?.['sentry.source']?.value).toBe('route'); + expect(navigationSpan.attributes['sentry.origin']?.value).toBe('auto.navigation.react.reactrouter_v7'); + expect(navigationSpan.attributes['sentry.source']?.value).toBe('route'); }); test('sends an INP span', async ({ page }) => { @@ -63,7 +63,7 @@ test('sends an INP span', async ({ page }) => { expect(inpSpan.trace_id).toMatch(/[a-f0-9]{32}/); expect(inpSpan.span_id).toMatch(/[a-f0-9]{16}/); expect(inpSpan.end_timestamp).toBeGreaterThan(inpSpan.start_timestamp); - expect(inpSpan.attributes?.['sentry.op']?.value).toBe('ui.interaction.click'); - expect(inpSpan.attributes?.['sentry.origin']?.value).toBe('auto.http.browser.inp'); - expect(inpSpan.attributes?.['sentry.exclusive_time']?.value).toEqual(expect.any(Number)); + expect(inpSpan.attributes['sentry.op']?.value).toBe('ui.interaction.click'); + expect(inpSpan.attributes['sentry.origin']?.value).toBe('auto.http.browser.inp'); + expect(inpSpan.attributes['sentry.exclusive_time']?.value).toEqual(expect.any(Number)); }); diff --git a/dev-packages/node-core-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts index 0de0f732f552..ac91ce74d1bb 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts @@ -11,7 +11,7 @@ test('beforeSendSpan applies changes to streamed span', async () => { const customChildSpan = spans.find(s => s.name === 'customChildSpanName'); expect(customChildSpan).toBeDefined(); - expect(customChildSpan!.attributes?.['sentry.custom_attribute']).toEqual({ + expect(customChildSpan!.attributes['sentry.custom_attribute']).toEqual({ type: 'string', value: 'customAttributeValue', }); diff --git a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts index f332715bbc42..5324d891819d 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts @@ -21,7 +21,7 @@ test('sends manually started streamed parallel root spans in root context', asyn expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); // Different trace ID than the first span - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); expect(trace1Id).not.toBe(traceId); diff --git a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts index 02456c9e93b2..a0c8ac343edb 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts @@ -17,7 +17,7 @@ test('sends manually started streamed parallel root spans outside of root contex expect(traceId).toMatch(/^[0-9a-f]{32}$/); expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); // Different trace ID as the first span diff --git a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts index 325f0f0d5d42..33f4ed3b3f11 100644 --- a/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts +++ b/dev-packages/node-core-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts @@ -17,7 +17,7 @@ test('sends manually started streamed parallel root spans outside of root contex expect(traceId).toMatch(/^[0-9a-f]{32}$/); expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); // Different trace ID as the first span diff --git a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts index a14e4067b948..7491894494eb 100644 --- a/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/aws-serverless/aws-integration-streamed/test.ts @@ -73,7 +73,7 @@ function assertAwsServiceSpans(spanCcontainer: SerializedStreamedSpanContainer): }), }, // Two spans share the name `S3.GetObject`; disambiguate by HTTP status code. - item => item.attributes?.['http.status_code']?.value === 200, + item => item.attributes['http.status_code']?.value === 200, ); // S3 - GetObject (errored, missing key) @@ -87,7 +87,7 @@ function assertAwsServiceSpans(spanCcontainer: SerializedStreamedSpanContainer): 'rpc.service': { value: 'S3', type: 'string' }, }), }, - item => item.attributes?.['http.status_code']?.value === 404, + item => item.attributes['http.status_code']?.value === 404, ); // DynamoDB - PutItem diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts index f332715bbc42..5324d891819d 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-root-spans-streamed/test.ts @@ -21,7 +21,7 @@ test('sends manually started streamed parallel root spans in root context', asyn expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); // Different trace ID than the first span - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); expect(trace1Id).not.toBe(traceId); diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts index 02456c9e93b2..a0c8ac343edb 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-streamed/test.ts @@ -17,7 +17,7 @@ test('sends manually started streamed parallel root spans outside of root contex expect(traceId).toMatch(/^[0-9a-f]{32}$/); expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); // Different trace ID as the first span diff --git a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts index 325f0f0d5d42..33f4ed3b3f11 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startSpan/parallel-spans-in-scope-with-parentSpanId-streamed/test.ts @@ -17,7 +17,7 @@ test('sends manually started streamed parallel root spans outside of root contex expect(traceId).toMatch(/^[0-9a-f]{32}$/); expect(spanContainer.items[0]!.parent_span_id).toBeUndefined(); - const trace1Id = spanContainer.items[0]!.attributes?.spanIdTraceId?.value; + const trace1Id = spanContainer.items[0]!.attributes.spanIdTraceId?.value; expect(trace1Id).toMatch(/^[0-9a-f]{32}$/); // Different trace ID as the first span diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts index a1aeb48f80e3..aa40f0f57650 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts @@ -750,7 +750,7 @@ describe('Anthropic integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -776,7 +776,7 @@ describe('Anthropic integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts index 023c3eb2c855..c425648d7758 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts @@ -551,7 +551,7 @@ describe('Google GenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -577,7 +577,7 @@ describe('Google GenAI integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","parts":[{"text":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts index 58eb063ed345..eee1e85933f6 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-span-streamed/test.ts @@ -12,7 +12,7 @@ describe('http.client span with streaming enabled', () => { .expect({ span: span => { const httpClientSpan = span.items.find(item => - item.attributes?.['sentry.op'] + item.attributes['sentry.op'] ? item.attributes['sentry.op'].type === 'string' && item.attributes['sentry.op'].value === 'http.client' : false, ); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts index b9b47e0601ab..bc8807cc0548 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts @@ -23,7 +23,7 @@ describe('streamed outgoing fetch spans', () => { span: container => { const httpClientSpan = container.items.find( item => - item.attributes?.['sentry.op']?.type === 'string' && + item.attributes['sentry.op']?.type === 'string' && item.attributes['sentry.op'].value === 'http.client', ); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts index 7ebd70673b96..eb5d8af100f7 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts @@ -13,15 +13,15 @@ describe('httpIntegration-streamed', () => { span: container => { const serverSpan = container.items.find( item => - item.attributes?.['sentry.op']?.type === 'string' && + item.attributes['sentry.op']?.type === 'string' && item.attributes['sentry.op'].value === 'http.server', ); expect(serverSpan).toBeDefined(); expect(serverSpan?.is_segment).toBe(true); expect(serverSpan?.name).toBe('GET /test'); - expect(serverSpan?.attributes?.['sentry.source']).toEqual({ type: 'string', value: 'route' }); - expect(serverSpan?.attributes?.['sentry.span.source']).toEqual({ type: 'string', value: 'route' }); + expect(serverSpan?.attributes['sentry.source']).toEqual({ type: 'string', value: 'route' }); + expect(serverSpan?.attributes['sentry.span.source']).toEqual({ type: 'string', value: 'route' }); }, }) .start(); diff --git a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts index 263f8f74c1c3..60ef7a9f1d95 100644 --- a/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/ignoreSpans-streamed/children/test.ts @@ -29,7 +29,7 @@ describe('filtering child spans with ignoreSpans (streaming)', () => { expect(container.items).toHaveLength(8); const getSpan = (name: string, op: string) => container.items.find( - item => item.name === name && item.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === op, + item => item.name === name && item.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === op, ); const queryMiddlewareSpan = getSpan('query', 'middleware.express'); const corsMiddlewareSpan = getSpan('corsMiddleware', 'middleware.express'); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts index 71267fabbe08..3ec9951b53ee 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts @@ -495,7 +495,7 @@ describe('LangChain integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -520,7 +520,7 @@ describe('LangChain integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index cb6920133d52..71378d02209e 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -324,7 +324,7 @@ describe('LangGraph integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -349,7 +349,7 @@ describe('LangGraph integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts index fdb936cf3d70..6b4807caec50 100644 --- a/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/mysql/test.ts @@ -197,7 +197,7 @@ describe('mysql auto instrumentation', () => { expect(segmentSpan?.name).toBe('Test Transaction'); const dbSpans = container.items.filter( - spanItem => spanItem.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'db', + spanItem => spanItem.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'db', ); expect(dbSpans.length).toBe(2); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts index d5cd027f78dc..fbdbf71a2961 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -1677,14 +1677,14 @@ describe('OpenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); expect(chatSpan).toBeDefined(); const responsesSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongString, ), ); @@ -1710,7 +1710,7 @@ describe('OpenAI integration', () => { // With explicit enableTruncation: true, content should be truncated despite streaming. // Truncation keeps only the last message (50k 'A's) and crops it to the byte limit. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); @@ -1721,7 +1721,7 @@ describe('OpenAI integration', () => { // The responses API string input (50k 'B's) should also be truncated. const responsesSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith('BBB'), + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith('BBB'), ); expect(responsesSpan).toBeDefined(); expect( diff --git a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts index e6d4e1300866..82496d3565c0 100644 --- a/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/postgres-streamed/test.ts @@ -156,7 +156,7 @@ const CREATE_NATIVE_USER_TABLE_STATEMENT = 'CREATE TABLE "NativeUser" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"));'; function getDbSpans(container: SerializedStreamedSpanContainer): SerializedStreamedSpanContainer['items'] { - return container.items.filter(item => item.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'db'); + return container.items.filter(item => item.attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP]?.value === 'db'); } describe('postgres auto instrumentation (streamed)', () => { diff --git a/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts index 36e15927c52d..314bb0cd4cfb 100644 --- a/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/requestData-streamed/test.ts @@ -15,27 +15,27 @@ describe('requestData-streamed', () => { expect(serverSpan).toBeDefined(); - expect(serverSpan?.attributes?.['url.full']).toEqual({ + expect(serverSpan?.attributes['url.full']).toEqual({ type: 'string', value: expect.stringContaining('/test?foo=bar'), }); - expect(serverSpan?.attributes?.['http.request.method']).toEqual({ + expect(serverSpan?.attributes['http.request.method']).toEqual({ type: 'string', value: 'GET', }); - expect(serverSpan?.attributes?.['url.query']).toEqual({ + expect(serverSpan?.attributes['url.query']).toEqual({ type: 'string', value: 'foo=bar', }); - expect(serverSpan?.attributes?.['http.request.header.host']).toEqual({ + expect(serverSpan?.attributes['http.request.header.host']).toEqual({ type: 'string', value: expect.any(String), }); - expect(serverSpan?.attributes?.['user.ip_address']).toEqual({ + expect(serverSpan?.attributes['user.ip_address']).toEqual({ type: 'string', value: expect.any(String), }); @@ -58,27 +58,27 @@ describe('requestData-streamed', () => { expect(serverSpan).toBeDefined(); - expect(serverSpan?.attributes?.['url.full']).toEqual({ + expect(serverSpan?.attributes['url.full']).toEqual({ type: 'string', value: expect.stringContaining('/test?foo=bar'), }); - expect(serverSpan?.attributes?.['http.request.method']).toEqual({ + expect(serverSpan?.attributes['http.request.method']).toEqual({ type: 'string', value: 'GET', }); - expect(serverSpan?.attributes?.['url.query']).toEqual({ + expect(serverSpan?.attributes['url.query']).toEqual({ type: 'string', value: 'foo=bar', }); - expect(serverSpan?.attributes?.['http.request.header.host']).toEqual({ + expect(serverSpan?.attributes['http.request.header.host']).toEqual({ type: 'string', value: expect.any(String), }); - expect(serverSpan?.attributes?.['user.ip_address']).toEqual({ + expect(serverSpan?.attributes['user.ip_address']).toEqual({ type: 'string', value: expect.any(String), }); @@ -105,12 +105,12 @@ describe('requestData-streamed', () => { expect(serverSpan).toBeDefined(); - expect(serverSpan?.attributes?.['http.request.header.host']).toEqual({ + expect(serverSpan?.attributes['http.request.header.host']).toEqual({ type: 'string', value: expect.any(String), }); - expect(serverSpan?.attributes?.['user.ip_address']).toBeUndefined(); + expect(serverSpan?.attributes['user.ip_address']).toBeUndefined(); }, }) .start(); @@ -133,8 +133,8 @@ describe('requestData-streamed', () => { // url.query and user.ip_address are only set by applyScopeToSegmentSpan // (not by OTel instrumentation), so they should be absent when the integration is removed - expect(serverSpan?.attributes?.['url.query']).toBeUndefined(); - expect(serverSpan?.attributes?.['user.ip_address']).toBeUndefined(); + expect(serverSpan?.attributes['url.query']).toBeUndefined(); + expect(serverSpan?.attributes['user.ip_address']).toBeUndefined(); }, }) .start(); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts index dc621a182bc6..8901f3b8fcaf 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts @@ -314,7 +314,7 @@ describe.skipIf(isOrchestrionEnabled())('Vercel AI integration (streaming v4)', const spans = container.items; const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( +getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -335,7 +335,7 @@ describe.skipIf(isOrchestrionEnabled())('Vercel AI integration (streaming v4)', // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => - getStringAttributeValue(s.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts index 83de92f3d454..0e973b176f03 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts @@ -59,105 +59,105 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); expect(container.items).toHaveLength(7); const firstInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === '[{"role":"user","content":"Where is the first span?"}]', )!; expect(firstInvokeAgentSpan).toBeDefined(); expect(firstInvokeAgentSpan.name).toBe('invoke_agent'); expect(firstInvokeAgentSpan.status).toBe('ok'); - expect(firstInvokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(firstInvokeAgentSpan.attributes?.['vercel.ai.operationId']?.value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(firstInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(firstInvokeAgentSpan.attributes['vercel.ai.operationId']?.value).toBe('ai.generateText'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( '[{"role":"user","content":"Where is the first span?"}]', ); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(firstInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"First span here!"}],"finish_reason":"stop"}]', ); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - (span.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( + (span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( 'First span here!', ), )!; expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(firstGenerateContentSpan.status).toBe('ok'); - expect(firstGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(firstGenerateContentSpan.attributes?.['vercel.ai.operationId']?.value).toBe( + expect(firstGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(firstGenerateContentSpan.attributes['vercel.ai.operationId']?.value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect(firstGenerateContentSpan.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toContain( + expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(firstGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toContain( 'First span here!', ); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === '[{"role":"user","content":"Where is the second span?"}]', )!; expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan.status).toBe('ok'); - expect(secondInvokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(secondInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - (span.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( + (span.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string | undefined)?.includes( 'Second span here!', ), )!; expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(secondGenerateContentSpan.status).toBe('ok'); - expect(secondGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(secondGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); const toolInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === + span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value === '[{"role":"user","content":"What is the weather in San Francisco?"}]', )!; expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan.name).toBe('invoke_agent'); expect(toolInvokeAgentSpan.status).toBe('ok'); - expect(toolInvokeAgentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(toolInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( '[{"role":"user","content":"What is the weather in San Francisco?"}]', ); const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes?.[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] !== undefined, + span.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE] !== undefined, )!; expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(toolGenerateContentSpan.status).toBe('ok'); - expect(toolGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(toolGenerateContentSpan.attributes?.[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toBeDefined(); - expect(toolGenerateContentSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); + expect(toolGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(toolGenerateContentSpan.attributes[GEN_AI_REQUEST_AVAILABLE_TOOLS_ATTRIBUTE]).toBeDefined(); + expect(toolGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); const toolExecutionSpan = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan.status).toBe('ok'); - expect(toolExecutionSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE]?.value).toBe( + expect(toolExecutionSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_DESCRIPTION_ATTRIBUTE]?.value).toBe( 'Get the current weather for a location', ); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_INPUT_ATTRIBUTE]).toBeDefined(); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_OUTPUT_ATTRIBUTE]).toBeDefined(); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_INPUT_ATTRIBUTE]).toBeDefined(); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_OUTPUT_ATTRIBUTE]).toBeDefined(); }, }) .start() @@ -183,85 +183,85 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); expect(container.items).toHaveLength(7); const firstInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes?.['vercel.ai.operationId']?.value === 'ai.generateText' && - span.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, + span.attributes['vercel.ai.operationId']?.value === 'ai.generateText' && + span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, )!; expect(firstInvokeAgentSpan).toBeDefined(); expect(firstInvokeAgentSpan.name).toBe('invoke_agent'); expect(firstInvokeAgentSpan.status).toBe('ok'); - expect(firstInvokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(firstInvokeAgentSpan.attributes?.['vercel.ai.operationId']?.value).toBe('ai.generateText'); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); - expect(firstInvokeAgentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(firstInvokeAgentSpan.attributes['vercel.ai.operationId']?.value).toBe('ai.generateText'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_RESPONSE_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); + expect(firstInvokeAgentSpan.attributes[GEN_AI_USAGE_TOTAL_TOKENS_ATTRIBUTE]?.value).toBe(30); + expect(firstInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); const firstGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes?.['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && - span.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && - span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, + span.attributes['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && + span.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE] === undefined && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 10, )!; expect(firstGenerateContentSpan).toBeDefined(); expect(firstGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(firstGenerateContentSpan.status).toBe('ok'); - expect(firstGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(firstGenerateContentSpan.attributes?.['vercel.ai.operationId']?.value).toBe( + expect(firstGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(firstGenerateContentSpan.attributes['vercel.ai.operationId']?.value).toBe( 'ai.generateText.doGenerate', ); - expect(firstGenerateContentSpan.attributes?.[GEN_AI_SYSTEM_ATTRIBUTE]?.value).toBe('mock-provider'); - expect(firstGenerateContentSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(firstGenerateContentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); + expect(firstGenerateContentSpan.attributes[GEN_AI_SYSTEM_ATTRIBUTE]?.value).toBe('mock-provider'); + expect(firstGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); + expect(firstGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeUndefined(); const secondInvokeAgentSpan = container.items.find( span => span.name === 'invoke_agent' && - span.attributes?.['vercel.ai.operationId']?.value === 'ai.generateText' && - span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, + span.attributes['vercel.ai.operationId']?.value === 'ai.generateText' && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, )!; expect(secondInvokeAgentSpan).toBeDefined(); expect(secondInvokeAgentSpan.name).toBe('invoke_agent'); expect(secondInvokeAgentSpan.status).toBe('ok'); - expect(secondInvokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(secondInvokeAgentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(secondInvokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(secondInvokeAgentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value).toBe( '[{"role":"user","content":"Where is the second span?"}]', ); - expect(secondInvokeAgentSpan.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( + expect(secondInvokeAgentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value).toBe( '[{"role":"assistant","parts":[{"type":"text","content":"Second span here!"}],"finish_reason":"stop"}]', ); const secondGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes?.['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && - span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, + span.attributes['vercel.ai.operationId']?.value === 'ai.generateText.doGenerate' && + span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 91, )!; expect(secondGenerateContentSpan).toBeDefined(); expect(secondGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(secondGenerateContentSpan.status).toBe('ok'); - expect(secondGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(secondGenerateContentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); + expect(secondGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); expect( - secondGenerateContentSpan.attributes?.[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value as string, + secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value as string, ).toContain('Where is the second span?'); expect( - secondGenerateContentSpan.attributes?.[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string, + secondGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string, ).toContain('Second span here!'); const toolInvokeAgentSpan = container.items.find( span => - span.name === 'invoke_agent' && span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span.name === 'invoke_agent' && span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, )!; expect(toolInvokeAgentSpan).toBeDefined(); expect(toolInvokeAgentSpan.name).toBe('invoke_agent'); @@ -270,7 +270,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe const toolGenerateContentSpan = container.items.find( span => span.name === 'generate_content mock-model-id' && - span.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, + span.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value === 15, )!; expect(toolGenerateContentSpan).toBeDefined(); expect(toolGenerateContentSpan.name).toBe('generate_content mock-model-id'); @@ -280,10 +280,10 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(toolExecutionSpan).toBeDefined(); expect(toolExecutionSpan.name).toBe('execute_tool getWeather'); expect(toolExecutionSpan.status).toBe('ok'); - expect(toolExecutionSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); - expect(toolExecutionSpan.attributes?.[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); + expect(toolExecutionSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); + expect(toolExecutionSpan.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); }, }) .start() @@ -316,26 +316,26 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); expect(container.items).toHaveLength(3); const invokeAgentSpan = container.items.find(span => span.name === 'invoke_agent')!; expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan.name).toBe('invoke_agent'); - expect(invokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(invokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); const generateContentSpan = container.items.find(span => span.name === 'generate_content mock-model-id')!; expect(generateContentSpan).toBeDefined(); expect(generateContentSpan.name).toBe('generate_content mock-model-id'); expect(generateContentSpan.status).toBe('ok'); - expect(generateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(generateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); const toolSpan = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(toolSpan).toBeDefined(); expect(toolSpan.name).toBe('execute_tool getWeather'); expect(toolSpan.status).toBe('error'); - expect(toolSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolSpan.attributes?.[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); + expect(toolSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); }, }) .expect({ @@ -381,21 +381,21 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); expect(container.items).toHaveLength(7); const invokeAgentSpans = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', ); expect(invokeAgentSpans).toHaveLength(3); const generateContentSpans = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', ); expect(generateContentSpans).toHaveLength(3); const toolSpan = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.execute_tool', + span => span.attributes['sentry.op']?.value === 'gen_ai.execute_tool', ); expect(toolSpan).toBeDefined(); }, @@ -423,44 +423,44 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); expect(container.items).toHaveLength(4); const invokeAgentSpan = container.items.find(span => span.name === 'invoke_agent weather_agent')!; expect(invokeAgentSpan).toBeDefined(); expect(invokeAgentSpan.name).toBe('invoke_agent weather_agent'); expect(invokeAgentSpan.status).toBe('ok'); - expect(invokeAgentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.invoke_agent'); - expect(invokeAgentSpan.attributes?.[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); + expect(invokeAgentSpan.attributes['sentry.op']?.value).toBe('gen_ai.invoke_agent'); + expect(invokeAgentSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); const toolCallsGenerateContentSpan = container.items.find( - span => span.attributes?.[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["tool-calls"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["tool-calls"]', )!; expect(toolCallsGenerateContentSpan).toBeDefined(); expect(toolCallsGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(toolCallsGenerateContentSpan.status).toBe('ok'); - expect(toolCallsGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(toolCallsGenerateContentSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); - expect(toolCallsGenerateContentSpan.attributes?.[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); + expect(toolCallsGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); + expect(toolCallsGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); const toolSpan = container.items.find(span => span.name === 'execute_tool getWeather')!; expect(toolSpan).toBeDefined(); expect(toolSpan.name).toBe('execute_tool getWeather'); expect(toolSpan.status).toBe('ok'); - expect(toolSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.execute_tool'); - expect(toolSpan.attributes?.[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); - expect(toolSpan.attributes?.[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); - expect(toolSpan.attributes?.[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); + expect(toolSpan.attributes['sentry.op']?.value).toBe('gen_ai.execute_tool'); + expect(toolSpan.attributes[GEN_AI_TOOL_NAME_ATTRIBUTE]?.value).toBe('getWeather'); + expect(toolSpan.attributes[GEN_AI_TOOL_CALL_ID_ATTRIBUTE]?.value).toBe('call-1'); + expect(toolSpan.attributes[GEN_AI_TOOL_TYPE_ATTRIBUTE]?.value).toBe('function'); const finalGenerateContentSpan = container.items.find( - span => span.attributes?.[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["stop"]', + span => span.attributes[GEN_AI_RESPONSE_FINISH_REASONS_ATTRIBUTE]?.value === '["stop"]', )!; expect(finalGenerateContentSpan).toBeDefined(); expect(finalGenerateContentSpan.name).toBe('generate_content mock-model-id'); expect(finalGenerateContentSpan.status).toBe('ok'); - expect(finalGenerateContentSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.generate_content'); - expect(finalGenerateContentSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); - expect(finalGenerateContentSpan.attributes?.[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(25); + expect(finalGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); + expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(15); + expect(finalGenerateContentSpan.attributes[GEN_AI_USAGE_OUTPUT_TOKENS_ATTRIBUTE]?.value).toBe(25); }, }) .start() @@ -486,13 +486,13 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); const invokeAgents = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', ); const generateContents = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', ); // Two concurrent operations -> two invoke_agent + two generate_content spans. @@ -536,10 +536,10 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe .expect({ span: container => { const invokeAgents = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', ); const generateContents = container.items.filter( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', ); // Two concurrent operations -> two invoke_agent + two generate_content spans. @@ -581,20 +581,20 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); const invokeAgent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', )!; expect(invokeAgent).toBeDefined(); - expect(invokeAgent.attributes?.['vercel.ai.operationId']?.value).toBe('ai.streamText'); + expect(invokeAgent.attributes['vercel.ai.operationId']?.value).toBe('ai.streamText'); const generateContent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', )!; expect(generateContent).toBeDefined(); expect(generateContent.parent_span_id).toBe(invokeAgent.span_id); - expect(generateContent.attributes?.['vercel.ai.operationId']?.value).toBe('ai.streamText.doStream'); + expect(generateContent.attributes['vercel.ai.operationId']?.value).toBe('ai.streamText.doStream'); }, }) .start() @@ -620,18 +620,18 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); // The model throws, so the operation rejects. The spans must still be *finished* (and // therefore present in the transaction) with an error status — not left open. const invokeAgent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', )!; expect(invokeAgent).toBeDefined(); expect(invokeAgent.status).toBe('error'); const generateContent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', )!; expect(generateContent).toBeDefined(); expect(generateContent.status).toBe('error'); @@ -660,22 +660,22 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); const generateContent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.generate_content', + span => span.attributes['sentry.op']?.value === 'gen_ai.generate_content', )!; expect(generateContent).toBeDefined(); // Cache/reasoning token breakdown and conversation id are derived from the model's // `providerMetadata` — by the OTel processor on v6 and by the channel subscriber on v7, // both via the shared `getProviderMetadataAttributes` helper, so the shape is identical. - expect(generateContent.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE]?.value).toBe(5); - expect(generateContent.attributes?.['gen_ai.usage.output_tokens.reasoning']?.value).toBe(7); - expect(generateContent.attributes?.[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value).toBe('resp_abc123'); + expect(generateContent.attributes[GEN_AI_USAGE_INPUT_TOKENS_CACHED_ATTRIBUTE]?.value).toBe(5); + expect(generateContent.attributes['gen_ai.usage.output_tokens.reasoning']?.value).toBe(7); + expect(generateContent.attributes[GEN_AI_CONVERSATION_ID_ATTRIBUTE]?.value).toBe('resp_abc123'); const invokeAgent = container.items.find( - span => span.attributes?.['sentry.op']?.value === 'gen_ai.invoke_agent', + span => span.attributes['sentry.op']?.value === 'gen_ai.invoke_agent', )!; expect(invokeAgent).toBeDefined(); @@ -683,8 +683,8 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe // instrumentation surfaces it (as `gen_ai.system_instructions`); v6 has no such option. if (version === '7') { const expected = '[{"type":"text","content":"You are a helpful assistant."}]'; - expect(invokeAgent.attributes?.[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); - expect(generateContent.attributes?.[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); + expect(invokeAgent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); + expect(generateContent.attributes[GEN_AI_SYSTEM_INSTRUCTIONS_ATTRIBUTE]?.value).toBe(expected); } }, }) @@ -717,29 +717,29 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe span: container => { // Every emitted gen_ai span carries the version-appropriate origin. container.items - .filter(s => String(s.attributes?.['sentry.op']?.value ?? '').startsWith('gen_ai.')) - .forEach(s => expect(s.attributes?.['sentry.origin']?.value).toBe(expectedOrigin)); + .filter(s => String(s.attributes['sentry.op']?.value ?? '').startsWith('gen_ai.')) + .forEach(s => expect(s.attributes['sentry.origin']?.value).toBe(expectedOrigin)); const embedSpan = container.items.find( - span => span.attributes?.[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === 'Embedding test!', )!; expect(embedSpan).toBeDefined(); expect(embedSpan.name).toBe('embeddings mock-model-id'); expect(embedSpan.status).toBe('ok'); - expect(embedSpan.attributes?.['sentry.op']?.value).toBe('gen_ai.embeddings'); - expect(embedSpan.attributes?.[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); - expect(embedSpan.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); + expect(embedSpan.attributes['sentry.op']?.value).toBe('gen_ai.embeddings'); + expect(embedSpan.attributes[GEN_AI_REQUEST_MODEL_ATTRIBUTE]?.value).toBe('mock-model-id'); + expect(embedSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); const embedManySpan = container.items.find( span => - span.attributes?.[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', + span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', ); if (embedManyInstrumented) { expect(embedManySpan).toBeDefined(); expect(embedManySpan!.name).toBe('embeddings mock-model-id'); expect(embedManySpan!.status).toBe('ok'); - expect(embedManySpan!.attributes?.['sentry.op']?.value).toBe('gen_ai.embeddings'); - expect(embedManySpan!.attributes?.[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); + expect(embedManySpan!.attributes['sentry.op']?.value).toBe('gen_ai.embeddings'); + expect(embedManySpan!.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(20); } else { expect(embedManySpan).toBeUndefined(); } diff --git a/dev-packages/test-utils/src/event-proxy-server.ts b/dev-packages/test-utils/src/event-proxy-server.ts index 77effa924ff4..16f080d10b77 100644 --- a/dev-packages/test-utils/src/event-proxy-server.ts +++ b/dev-packages/test-utils/src/event-proxy-server.ts @@ -617,7 +617,7 @@ export function waitForStreamedSpans( * ``` */ export function getSpanOp(span: SerializedStreamedSpan): string | undefined { - return span.attributes?.['sentry.op']?.type === 'string' ? span.attributes['sentry.op'].value : undefined; + return span.attributes['sentry.op']?.type === 'string' ? span.attributes['sentry.op'].value : undefined; } const TEMP_FILE_PREFIX = 'event-proxy-server-'; diff --git a/packages/core/test/lib/tracing/spans/captureSpan.test.ts b/packages/core/test/lib/tracing/spans/captureSpan.test.ts index e2b81fbd85df..57a5eb4bbfd2 100644 --- a/packages/core/test/lib/tracing/spans/captureSpan.test.ts +++ b/packages/core/test/lib/tracing/spans/captureSpan.test.ts @@ -397,7 +397,7 @@ describe('captureSpan', () => { }); expect(serializedChild.is_segment).toBe(false); - expect(serializedChild.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]).toBeUndefined(); + expect(serializedChild.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SDK_INTEGRATIONS]).toBeUndefined(); }); describe('client hooks', () => { diff --git a/packages/core/test/lib/tracing/spans/spanJsonToStreamedSpan.test.ts b/packages/core/test/lib/tracing/spans/spanJsonToStreamedSpan.test.ts index e274746a594c..94b25c590d19 100644 --- a/packages/core/test/lib/tracing/spans/spanJsonToStreamedSpan.test.ts +++ b/packages/core/test/lib/tracing/spans/spanJsonToStreamedSpan.test.ts @@ -73,10 +73,10 @@ describe('spanJsonToSerializedStreamedSpan', () => { const result = spanJsonToSerializedStreamedSpan(span); - expect(result.attributes?.['gen_ai.system']).toEqual({ type: 'string', value: 'openai' }); - expect(result.attributes?.['gen_ai.request.model']).toEqual({ type: 'string', value: 'gpt-4' }); - expect(result.attributes?.['gen_ai.usage.input_tokens']).toEqual({ type: 'integer', value: 100 }); - expect(result.attributes?.['gen_ai.usage.output_tokens']).toEqual({ type: 'integer', value: 50 }); + expect(result.attributes['gen_ai.system']).toEqual({ type: 'string', value: 'openai' }); + expect(result.attributes['gen_ai.request.model']).toEqual({ type: 'string', value: 'gpt-4' }); + expect(result.attributes['gen_ai.usage.input_tokens']).toEqual({ type: 'integer', value: 100 }); + expect(result.attributes['gen_ai.usage.output_tokens']).toEqual({ type: 'integer', value: 50 }); }); it('carries over links', () => { From 7131b28351a24a94ca9065af196b659da6ea5df8 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Wed, 8 Jul 2026 11:24:05 +0200 Subject: [PATCH 2/3] style: Format streamed span attribute test updates Co-Authored-By: Cursor --- .../metrics/web-vitals-lcp-streamed-spans/test.ts | 4 +--- .../suites/tracing/anthropic/test.ts | 4 ++-- .../suites/tracing/google-genai/test.ts | 2 +- .../fetch-basic-streamed/test.ts | 3 +-- .../tracing/httpIntegration-streamed/test.ts | 3 +-- .../suites/tracing/langchain/test.ts | 4 ++-- .../suites/tracing/langgraph/test.ts | 4 ++-- .../suites/tracing/openai/test.ts | 6 +++--- .../tracing/vercelai/span-streaming-v4/test.ts | 2 +- .../suites/tracing/vercelai/v6_v7/test.ts | 15 +++++++-------- 10 files changed, 21 insertions(+), 26 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts index 433c82905412..a57756fc70e0 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-lcp-streamed-spans/test.ts @@ -46,9 +46,7 @@ sentryTest('captures LCP as a streamed span with element attributes', async ({ g // Check browser.web_vital.lcp.* attributes expect(lcpSpan.attributes['browser.web_vital.lcp.element']?.value).toEqual(expect.stringContaining('body > img')); - expect(lcpSpan.attributes['browser.web_vital.lcp.url']?.value).toBe( - 'https://sentry-test-site.example/my/image.png', - ); + expect(lcpSpan.attributes['browser.web_vital.lcp.url']?.value).toBe('https://sentry-test-site.example/my/image.png'); expect(lcpSpan.attributes['browser.web_vital.lcp.size']?.value).toEqual(expect.any(Number)); // Check web vital value attribute diff --git a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts index aa40f0f57650..e8129890c978 100644 --- a/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/anthropic/test.ts @@ -750,7 +750,7 @@ describe('Anthropic integration', () => { const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -776,7 +776,7 @@ getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.i // With explicit enableTruncation: true, content should be truncated despite streaming. // Find the chat span by matching the start of the truncated content (the 'A' repeated messages). const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts index c425648d7758..3332ff1d862c 100644 --- a/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/google-genai/test.ts @@ -551,7 +551,7 @@ describe('Google GenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts index bc8807cc0548..1e92515d8e30 100644 --- a/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/http-client-spans/fetch-basic-streamed/test.ts @@ -23,8 +23,7 @@ describe('streamed outgoing fetch spans', () => { span: container => { const httpClientSpan = container.items.find( item => - item.attributes['sentry.op']?.type === 'string' && - item.attributes['sentry.op'].value === 'http.client', + item.attributes['sentry.op']?.type === 'string' && item.attributes['sentry.op'].value === 'http.client', ); expect(httpClientSpan).toBeDefined(); diff --git a/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts b/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts index eb5d8af100f7..2a3d21716a89 100644 --- a/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/httpIntegration-streamed/test.ts @@ -13,8 +13,7 @@ describe('httpIntegration-streamed', () => { span: container => { const serverSpan = container.items.find( item => - item.attributes['sentry.op']?.type === 'string' && - item.attributes['sentry.op'].value === 'http.server', + item.attributes['sentry.op']?.type === 'string' && item.attributes['sentry.op'].value === 'http.server', ); expect(serverSpan).toBeDefined(); diff --git a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts index 3ec9951b53ee..ed342ce9d1a2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langchain/test.ts @@ -495,7 +495,7 @@ describe('LangChain integration', () => { const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -520,7 +520,7 @@ getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.i // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts index 71378d02209e..817aef2923ca 100644 --- a/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/langgraph/test.ts @@ -324,7 +324,7 @@ describe('LangGraph integration', () => { const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); @@ -349,7 +349,7 @@ getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.i // With explicit enableTruncation: true, content should be truncated despite streaming. const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts index fbdbf71a2961..3f102d360e40 100644 --- a/dev-packages/node-integration-tests/suites/tracing/openai/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/openai/test.ts @@ -1677,14 +1677,14 @@ describe('OpenAI integration', () => { const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); expect(chatSpan).toBeDefined(); const responsesSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongString, ), ); @@ -1710,7 +1710,7 @@ getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.i // With explicit enableTruncation: true, content should be truncated despite streaming. // Truncation keeps only the last message (50k 'A's) and crops it to the byte limit. const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.startsWith( '[{"role":"user","content":"AAAA', ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts index 8901f3b8fcaf..b6649fb6b029 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/span-streaming-v4/test.ts @@ -314,7 +314,7 @@ describe.skipIf(isOrchestrionEnabled())('Vercel AI integration (streaming v4)', const spans = container.items; const chatSpan = spans.find(s => -getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( + getStringAttributeValue(s.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value)?.includes( streamingLongContent, ), ); diff --git a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts index 0e973b176f03..9a13d02a3b65 100644 --- a/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/vercelai/v6_v7/test.ts @@ -252,12 +252,12 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(secondGenerateContentSpan.status).toBe('ok'); expect(secondGenerateContentSpan.attributes['sentry.op']?.value).toBe('gen_ai.generate_content'); expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]).toBeDefined(); - expect( - secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value as string, - ).toContain('Where is the second span?'); - expect( - secondGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string, - ).toContain('Second span here!'); + expect(secondGenerateContentSpan.attributes[GEN_AI_INPUT_MESSAGES_ATTRIBUTE]?.value as string).toContain( + 'Where is the second span?', + ); + expect(secondGenerateContentSpan.attributes[GEN_AI_OUTPUT_MESSAGES_ATTRIBUTE]?.value as string).toContain( + 'Second span here!', + ); const toolInvokeAgentSpan = container.items.find( span => @@ -731,8 +731,7 @@ describe.each(matrix)('Vercel AI integration (version %s)', (version, vercelAiVe expect(embedSpan.attributes[GEN_AI_USAGE_INPUT_TOKENS_ATTRIBUTE]?.value).toBe(10); const embedManySpan = container.items.find( - span => - span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', + span => span.attributes[GEN_AI_EMBEDDINGS_INPUT_ATTRIBUTE]?.value === '["First input","Second input"]', ); if (embedManyInstrumented) { expect(embedManySpan).toBeDefined(); From 5541426088cc0eb7de130ef2c3f75f26f2938690 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Wed, 8 Jul 2026 14:03:18 +0200 Subject: [PATCH 3/3] fix types --- .../suites/tracing/metrics/element-timing/test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts index fa15130c9ef7..6f418c79a024 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/element-timing/test.ts @@ -5,11 +5,11 @@ import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipMetricsTest, shouldSkipTracingTest, waitForMetrics } from '../../../../utils/helpers'; function getIdentifier(m: SerializedMetric): unknown { - return m.attributes['ui.element.identifier']?.value; + return m.attributes?.['ui.element.identifier']?.value; } function getPaintType(m: SerializedMetric): unknown { - return m.attributes['ui.element.paint_type']?.value; + return m.attributes?.['ui.element.paint_type']?.value; } sentryTest(