Skip to content

Commit db29666

Browse files
committed
ref(browser): Create standalone INP spans via startInactiveSpan
1 parent 13be067 commit db29666

File tree

2 files changed

+71
-49
lines changed
  • dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp
  • packages/browser-utils/src/metrics

2 files changed

+71
-49
lines changed

dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-inp/test.ts

+64-21
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { expect } from '@playwright/test';
2-
import type { Event as SentryEvent, SpanJSON } from '@sentry/types';
2+
import type { Event as SentryEvent, SpanEnvelope, SpanJSON } from '@sentry/types';
33

44
import { sentryTest } from '../../../../utils/fixtures';
55
import {
66
getFirstSentryEnvelopeRequest,
77
getMultipleSentryEnvelopeRequests,
8+
properFullEnvelopeRequestParser,
89
shouldSkipTracingTest,
910
} from '../../../../utils/helpers';
11+
import { getFullRecordingSnapshots } from '../../../../utils/replayHelpers';
1012

1113
sentryTest('should capture an INP click event span.', async ({ browserName, getLocalTestPath, page }) => {
1214
const supportedBrowsers = ['chromium'];
@@ -28,9 +30,12 @@ sentryTest('should capture an INP click event span.', async ({ browserName, getL
2830
await page.goto(url);
2931
await getFirstSentryEnvelopeRequest<SentryEvent>(page); // wait for page load
3032

31-
const spanEnvelopesPromise = getMultipleSentryEnvelopeRequests<SpanJSON>(page, 1, {
32-
envelopeType: 'span',
33-
});
33+
const spanEnvelopePromise = getMultipleSentryEnvelopeRequests<SpanEnvelope>(
34+
page,
35+
1,
36+
{ envelopeType: 'span' },
37+
properFullEnvelopeRequestParser,
38+
);
3439

3540
await page.locator('[data-test-id=normal-button]').click();
3641
await page.locator('.clicked[data-test-id=normal-button]').isVisible();
@@ -43,14 +48,53 @@ sentryTest('should capture an INP click event span.', async ({ browserName, getL
4348
});
4449

4550
// Get the INP span envelope
46-
const spanEnvelopes = await spanEnvelopesPromise;
47-
48-
expect(spanEnvelopes).toHaveLength(1);
49-
expect(spanEnvelopes[0].op).toBe('ui.interaction.click');
50-
expect(spanEnvelopes[0].description).toBe('body > NormalButton');
51-
expect(spanEnvelopes[0].exclusive_time).toBeGreaterThan(0);
52-
expect(spanEnvelopes[0].measurements?.inp.value).toBeGreaterThan(0);
53-
expect(spanEnvelopes[0].measurements?.inp.unit).toBe('millisecond');
51+
const spanEnvelope = (await spanEnvelopePromise)[0];
52+
53+
const spanEnvelopeHeaders = spanEnvelope[0];
54+
const spanEnvelopeItem = spanEnvelope[1][0][1];
55+
56+
const traceId = spanEnvelopeHeaders.trace!.trace_id;
57+
expect(traceId).toMatch(/[a-f0-9]{32}/);
58+
59+
expect(spanEnvelopeHeaders).toEqual({
60+
sent_at: expect.any(String),
61+
trace: {
62+
environment: 'production',
63+
public_key: 'public',
64+
sample_rate: '1',
65+
sampled: 'true',
66+
trace_id: traceId,
67+
},
68+
});
69+
70+
const inpValue = spanEnvelopeItem.measurements?.inp.value;
71+
expect(inpValue).toBeGreaterThan(0);
72+
73+
expect(spanEnvelopeItem).toEqual({
74+
data: {
75+
'sentry.exclusive_time': inpValue,
76+
'sentry.op': 'ui.interaction.click',
77+
'sentry.origin': 'manual',
78+
'sentry.sample_rate': 1,
79+
'sentry.source': 'custom',
80+
},
81+
measurements: {
82+
inp: {
83+
unit: 'millisecond',
84+
value: inpValue,
85+
},
86+
},
87+
description: 'body > NormalButton',
88+
exclusive_time: inpValue,
89+
op: 'ui.interaction.click',
90+
origin: 'manual',
91+
is_segment: true,
92+
segment_id: spanEnvelopeItem.span_id,
93+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
94+
start_timestamp: expect.any(Number),
95+
timestamp: expect.any(Number),
96+
trace_id: traceId,
97+
});
5498
});
5599

56100
sentryTest(
@@ -85,7 +129,7 @@ sentryTest(
85129

86130
await page.waitForTimeout(500);
87131

88-
const spanEnvelopesPromise = getMultipleSentryEnvelopeRequests<SpanJSON>(page, 1, {
132+
const spanPromise = getMultipleSentryEnvelopeRequests<SpanJSON>(page, 1, {
89133
envelopeType: 'span',
90134
});
91135

@@ -95,13 +139,12 @@ sentryTest(
95139
});
96140

97141
// Get the INP span envelope
98-
const spanEnvelopes = await spanEnvelopesPromise;
99-
100-
expect(spanEnvelopes).toHaveLength(1);
101-
expect(spanEnvelopes[0].op).toBe('ui.interaction.click');
102-
expect(spanEnvelopes[0].description).toBe('body > SlowButton');
103-
expect(spanEnvelopes[0].exclusive_time).toBeGreaterThan(400);
104-
expect(spanEnvelopes[0].measurements?.inp.value).toBeGreaterThan(400);
105-
expect(spanEnvelopes[0].measurements?.inp.unit).toBe('millisecond');
142+
const span = (await spanPromise)[0];
143+
144+
expect(span.op).toBe('ui.interaction.click');
145+
expect(span.description).toBe('body > SlowButton');
146+
expect(span.exclusive_time).toBeGreaterThan(400);
147+
expect(span.measurements?.inp.value).toBeGreaterThan(400);
148+
expect(span.measurements?.inp.unit).toBe('millisecond');
106149
},
107150
);

packages/browser-utils/src/metrics/inp.ts

+7-28
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
sampleSpan,
1212
spanIsSampled,
1313
spanToJSON,
14+
startInactiveSpan,
1415
} from '@sentry/core';
1516
import type { Integration, SpanAttributes } from '@sentry/types';
1617
import { browserPerformanceTimeOrigin, dropUndefinedKeys, htmlTreeAsString, logger } from '@sentry/utils';
@@ -100,7 +101,6 @@ function _trackINP(): () => void {
100101
const profileId = scope.getScopeData().contexts?.profile?.profile_id as string | undefined;
101102

102103
const name = htmlTreeAsString(entry.target);
103-
const parentSampled = activeSpan ? spanIsSampled(activeSpan) : undefined;
104104
const attributes: SpanAttributes = dropUndefinedKeys({
105105
release: options.release,
106106
environment: options.environment,
@@ -111,42 +111,21 @@ function _trackINP(): () => void {
111111
replay_id: replayId || undefined,
112112
});
113113

114-
/** Check to see if the span should be sampled */
115-
const [sampled] = sampleSpan(options, {
114+
const span = startInactiveSpan({
116115
name,
117-
parentSampled,
118-
attributes,
119-
transactionContext: {
120-
name,
121-
parentSampled,
122-
},
123-
});
124-
125-
// Nothing to do
126-
if (!sampled) {
127-
return;
128-
}
129-
130-
const span = new SentrySpan({
131-
startTimestamp: startTime,
132-
endTimestamp: startTime + duration,
133116
op: `ui.interaction.${interactionType}`,
134-
name,
135117
attributes,
118+
startTime: startTime,
119+
experimental: {
120+
standalone: true,
121+
},
136122
});
137123

138124
span.addEvent('inp', {
139125
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_UNIT]: 'millisecond',
140126
[SEMANTIC_ATTRIBUTE_SENTRY_MEASUREMENT_VALUE]: metric.value,
141127
});
142128

143-
const envelope = createSpanEnvelope([span]);
144-
const transport = client && client.getTransport();
145-
if (transport) {
146-
transport.send(envelope).then(null, reason => {
147-
DEBUG_BUILD && logger.error('Error while sending interaction:', reason);
148-
});
149-
}
150-
return;
129+
span.end(startTime + duration);
151130
});
152131
}

0 commit comments

Comments
 (0)