diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts index 799a6df407a3..582508f7a584 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts @@ -1,10 +1,11 @@ import { expect } from '@playwright/test'; -import type { Event as SentryEvent, SpanJSON } from '@sentry/types'; +import type { Event as SentryEvent, SpanEnvelope, SpanJSON } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, + properFullEnvelopeRequestParser, shouldSkipTracingTest, } from '../../../../utils/helpers'; @@ -28,9 +29,12 @@ sentryTest('should capture an INP click event span.', async ({ browserName, getL await page.goto(url); await getFirstSentryEnvelopeRequest(page); // wait for page load - const spanEnvelopesPromise = getMultipleSentryEnvelopeRequests(page, 1, { - envelopeType: 'span', - }); + const spanEnvelopePromise = getMultipleSentryEnvelopeRequests( + page, + 1, + { envelopeType: 'span' }, + properFullEnvelopeRequestParser, + ); await page.locator('[data-test-id=normal-button]').click(); await page.locator('.clicked[data-test-id=normal-button]').isVisible(); @@ -43,14 +47,53 @@ sentryTest('should capture an INP click event span.', async ({ browserName, getL }); // Get the INP span envelope - const spanEnvelopes = await spanEnvelopesPromise; - - expect(spanEnvelopes).toHaveLength(1); - expect(spanEnvelopes[0].op).toBe('ui.interaction.click'); - expect(spanEnvelopes[0].description).toBe('body > NormalButton'); - expect(spanEnvelopes[0].exclusive_time).toBeGreaterThan(0); - expect(spanEnvelopes[0].measurements?.inp.value).toBeGreaterThan(0); - expect(spanEnvelopes[0].measurements?.inp.unit).toBe('millisecond'); + const spanEnvelope = (await spanEnvelopePromise)[0]; + + const spanEnvelopeHeaders = spanEnvelope[0]; + const spanEnvelopeItem = spanEnvelope[1][0][1]; + + const traceId = spanEnvelopeHeaders.trace!.trace_id; + expect(traceId).toMatch(/[a-f0-9]{32}/); + + expect(spanEnvelopeHeaders).toEqual({ + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: traceId, + }, + }); + + const inpValue = spanEnvelopeItem.measurements?.inp.value; + expect(inpValue).toBeGreaterThan(0); + + expect(spanEnvelopeItem).toEqual({ + data: { + 'sentry.exclusive_time': inpValue, + 'sentry.op': 'ui.interaction.click', + 'sentry.origin': 'manual', + 'sentry.sample_rate': 1, + 'sentry.source': 'custom', + }, + measurements: { + inp: { + unit: 'millisecond', + value: inpValue, + }, + }, + description: 'body > NormalButton', + exclusive_time: inpValue, + op: 'ui.interaction.click', + origin: 'manual', + is_segment: true, + segment_id: spanEnvelopeItem.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: traceId, + }); }); sentryTest( @@ -85,7 +128,7 @@ sentryTest( await page.waitForTimeout(500); - const spanEnvelopesPromise = getMultipleSentryEnvelopeRequests(page, 1, { + const spanPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'span', }); @@ -95,13 +138,12 @@ sentryTest( }); // Get the INP span envelope - const spanEnvelopes = await spanEnvelopesPromise; - - expect(spanEnvelopes).toHaveLength(1); - expect(spanEnvelopes[0].op).toBe('ui.interaction.click'); - expect(spanEnvelopes[0].description).toBe('body > SlowButton'); - expect(spanEnvelopes[0].exclusive_time).toBeGreaterThan(400); - expect(spanEnvelopes[0].measurements?.inp.value).toBeGreaterThan(400); - expect(spanEnvelopes[0].measurements?.inp.unit).toBe('millisecond'); + const span = (await spanPromise)[0]; + + expect(span.op).toBe('ui.interaction.click'); + expect(span.description).toBe('body > SlowButton'); + expect(span.exclusive_time).toBeGreaterThan(400); + expect(span.measurements?.inp.value).toBeGreaterThan(400); + expect(span.measurements?.inp.unit).toBe('millisecond'); }, ); diff --git a/packages/browser-utils/src/metrics/inp.ts b/packages/browser-utils/src/metrics/inp.ts index 5c7808d264cf..eeead7b12018 100644 --- a/packages/browser-utils/src/metrics/inp.ts +++ b/packages/browser-utils/src/metrics/inp.ts @@ -2,19 +2,15 @@ import { SEMANTIC_ATTRIBUTE_EXCLUSIVE_TIME, SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT, SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE, - SentrySpan, - createSpanEnvelope, getActiveSpan, getClient, getCurrentScope, getRootSpan, - sampleSpan, - spanIsSampled, spanToJSON, + startInactiveSpan, } from '@sentry/core'; import type { Integration, SpanAttributes } from '@sentry/types'; -import { browserPerformanceTimeOrigin, dropUndefinedKeys, htmlTreeAsString, logger } from '@sentry/utils'; -import { DEBUG_BUILD } from '../debug-build'; +import { browserPerformanceTimeOrigin, dropUndefinedKeys, htmlTreeAsString } from '@sentry/utils'; import { addInpInstrumentationHandler } from './instrument'; import { getBrowserPerformanceAPI, msToSec } from './utils'; @@ -100,7 +96,6 @@ function _trackINP(): () => void { const profileId = scope.getScopeData().contexts?.profile?.profile_id as string | undefined; const name = htmlTreeAsString(entry.target); - const parentSampled = activeSpan ? spanIsSampled(activeSpan) : undefined; const attributes: SpanAttributes = dropUndefinedKeys({ release: options.release, environment: options.environment, @@ -111,28 +106,14 @@ function _trackINP(): () => void { replay_id: replayId || undefined, }); - /** Check to see if the span should be sampled */ - const [sampled] = sampleSpan(options, { + const span = startInactiveSpan({ name, - parentSampled, - attributes, - transactionContext: { - name, - parentSampled, - }, - }); - - // Nothing to do - if (!sampled) { - return; - } - - const span = new SentrySpan({ - startTimestamp: startTime, - endTimestamp: startTime + duration, op: `ui.interaction.${interactionType}`, - name, attributes, + startTime: startTime, + experimental: { + standalone: true, + }, }); span.addEvent('inp', { @@ -140,13 +121,6 @@ function _trackINP(): () => void { [SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: metric.value, }); - const envelope = createSpanEnvelope([span]); - const transport = client && client.getTransport(); - if (transport) { - transport.send(envelope).then(null, reason => { - DEBUG_BUILD && logger.error('Error while sending interaction:', reason); - }); - } - return; + span.end(startTime + duration); }); } diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index 874edd8d2eda..358f60ccee18 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -106,7 +106,7 @@ type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext }; type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders; type ReplayEnvelopeHeaders = BaseEnvelopeHeaders; type StatsdEnvelopeHeaders = BaseEnvelopeHeaders; -type SpanEnvelopeHeaders = BaseEnvelopeHeaders; +type SpanEnvelopeHeaders = BaseEnvelopeHeaders & { trace?: DynamicSamplingContext }; export type EventEnvelope = BaseEnvelope< EventEnvelopeHeaders,