From 87124f03ed49c21ef1e93444c3aaa265a52a9b92 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 7 Jul 2026 18:17:09 +0200 Subject: [PATCH 1/2] fix(browser): Send web vital span `sentry.segment.name` attribute --- .../metrics/web-vitals-cls-streamed-spans/test.ts | 4 ++++ .../metrics/web-vitals-inp-streamed-spans/test.ts | 4 ++++ .../metrics/web-vitals-lcp-streamed-spans/test.ts | 4 ++++ packages/browser-utils/package.json | 3 ++- packages/browser-utils/src/metrics/webVitalSpans.ts | 9 +++++++-- .../browser-utils/test/metrics/webVitalSpans.test.ts | 8 ++++++++ 6 files changed, 29 insertions(+), 3 deletions(-) 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 409d79327a91..4abb2df344b4 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 @@ -38,6 +38,10 @@ sentryTest('captures CLS as a streamed span with source attributes', async ({ ge 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' }); + // Check browser.web_vital.cls.source attributes expect(clsSpan.attributes?.['browser.web_vital.cls.source.1']?.value).toEqual( expect.stringContaining('body > div#content > p'), 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 893be918553d..6b08ec5b904c 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 @@ -31,6 +31,10 @@ sentryTest('captures INP click as a streamed span', async ({ getLocalTestUrl, pa 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' }); + const inpValue = inpSpan.attributes?.['browser.web_vital.inp.value']?.value as number; expect(inpValue).toBeGreaterThan(0); 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 8cff98edfcd0..45b373aa6bf9 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 @@ -40,6 +40,10 @@ sentryTest('captures LCP as a streamed span with element attributes', async ({ g 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' }); + // 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( diff --git a/packages/browser-utils/package.json b/packages/browser-utils/package.json index 4c28b3296d65..2604f0162a9b 100644 --- a/packages/browser-utils/package.json +++ b/packages/browser-utils/package.json @@ -40,7 +40,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "10.64.0" + "@sentry/core": "10.64.0", + "@sentry/conventions": "^0.15.1" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/browser-utils/src/metrics/webVitalSpans.ts b/packages/browser-utils/src/metrics/webVitalSpans.ts index 1cb4854a3c18..e5e6af09355f 100644 --- a/packages/browser-utils/src/metrics/webVitalSpans.ts +++ b/packages/browser-utils/src/metrics/webVitalSpans.ts @@ -22,6 +22,7 @@ import { isValidLcpMetric } from './lcp'; import type { WebVitalReportEvent } from './utils'; import { getBrowserPerformanceAPI, listenForWebVitalReportEvents, msToSec, supportsWebVital } from './utils'; import type { PerformanceEventTiming } from './instrument'; +import { SENTRY_SEGMENT_NAME, SENTRY_TRANSACTION } from '@sentry/conventions/attributes'; // Locally-defined interfaces to avoid leaking bare global type references into the // generated .d.ts. The `declare global` augmentations in web-vitals/types.ts make these @@ -79,7 +80,9 @@ export function _emitWebVitalSpan(options: WebVitalSpanOptions): void { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: op, [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: 0, [`browser.web_vital.${metricName}.value`]: value, - 'sentry.transaction': routeName, + // oxlint-disable-next-line typescript-eslint/no-deprecated + [SENTRY_TRANSACTION]: routeName, + [SENTRY_SEGMENT_NAME]: routeName, // Web vital score calculation relies on the user agent 'user_agent.original': WINDOW.navigator?.userAgent, ...passedAttributes, @@ -304,7 +307,9 @@ export function _sendInpSpan(inpValue: number, entry: PerformanceEventTiming): v value: inpValue, attributes: { [SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME]: entry.duration, - 'sentry.transaction': routeName, + // oxlint-disable-next-line typescript-eslint/no-deprecated + [SENTRY_TRANSACTION]: routeName, + [SENTRY_SEGMENT_NAME]: routeName, }, startTime, endTime: startTime + duration, diff --git a/packages/browser-utils/test/metrics/webVitalSpans.test.ts b/packages/browser-utils/test/metrics/webVitalSpans.test.ts index 640a2ee95f71..ef8d2ae41f00 100644 --- a/packages/browser-utils/test/metrics/webVitalSpans.test.ts +++ b/packages/browser-utils/test/metrics/webVitalSpans.test.ts @@ -80,6 +80,7 @@ describe('_emitWebVitalSpan', () => { 'sentry.exclusive_time': 0, 'browser.web_vital.lcp.value': 100, 'sentry.transaction': 'test-transaction', + 'sentry.segment.name': 'test-transaction', 'user_agent.original': 'test-user-agent', }, startTime: 1.5, @@ -256,6 +257,7 @@ describe('_sendLcpSpan', () => { 'browser.web_vital.lcp.size': 50000, 'browser.web_vital.lcp.report_event': 'pagehide', 'sentry.transaction': 'test-route', + 'sentry.segment.name': 'test-route', }), startTime: 1, // timeOrigin: 1000 / 1000 parentSpan: mockPageloadSpan, @@ -348,6 +350,7 @@ describe('_sendClsSpan', () => { 'browser.web_vital.cls.source.2': '', 'browser.web_vital.cls.report_event': 'navigation', 'sentry.transaction': 'test-route', + 'sentry.segment.name': 'test-route', }), parentSpan: mockPageloadSpan, }), @@ -414,6 +417,7 @@ describe('_sendInpSpan', () => { 'sentry.op': 'ui.interaction.click', 'sentry.exclusive_time': 120, 'sentry.transaction': 'test-route', + 'sentry.segment.name': 'test-route', }), }), ); @@ -472,6 +476,10 @@ describe('_sendInpSpan', () => { name: 'body > CachedButton', attributes: expect.objectContaining({ 'sentry.transaction': 'cached-route', + // `sentry.segment.name` is set in `_emitWebVitalSpan` from the current scope's + // transaction name, not from `_sendInpSpan`'s `routeName` (which comes from the + // cached interaction's span and only feeds `sentry.transaction`). + 'sentry.segment.name': 'test-route', }), parentSpan: mockRootSpan, }), From 3c1a26985053f9e8d51970d8ff31add4aa41b0d9 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Tue, 7 Jul 2026 18:40:51 +0200 Subject: [PATCH 2/2] fix test --- packages/browser-utils/test/metrics/webVitalSpans.test.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/browser-utils/test/metrics/webVitalSpans.test.ts b/packages/browser-utils/test/metrics/webVitalSpans.test.ts index ef8d2ae41f00..7995fb411c3a 100644 --- a/packages/browser-utils/test/metrics/webVitalSpans.test.ts +++ b/packages/browser-utils/test/metrics/webVitalSpans.test.ts @@ -476,10 +476,7 @@ describe('_sendInpSpan', () => { name: 'body > CachedButton', attributes: expect.objectContaining({ 'sentry.transaction': 'cached-route', - // `sentry.segment.name` is set in `_emitWebVitalSpan` from the current scope's - // transaction name, not from `_sendInpSpan`'s `routeName` (which comes from the - // cached interaction's span and only feeds `sentry.transaction`). - 'sentry.segment.name': 'test-route', + 'sentry.segment.name': 'cached-route', }), parentSpan: mockRootSpan, }),