Skip to content

Commit 5d4d2fa

Browse files
committed
test(browser): Add span streaming integration tests
1 parent 67ba790 commit 5d4d2fa

8 files changed

Lines changed: 517 additions & 1 deletion

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
5+
Sentry.init({
6+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
7+
integrations: [Sentry.spanStreamingIntegration()],
8+
tracesSampleRate: 1.0,
9+
debug: true,
10+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Sentry.startSpan({ name: 'test-span', op: 'test' }, () => {
2+
Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => {
3+
// noop
4+
});
5+
6+
const inactiveSpan = Sentry.startInactiveSpan({ name: 'test-inactive-span' });
7+
inactiveSpan.end();
8+
9+
Sentry.startSpanManual({ name: 'test-manual-span' }, (span) => {
10+
// noop
11+
span.end();
12+
});
13+
});
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { expect } from '@playwright/test';
2+
import {
3+
SDK_VERSION,
4+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
5+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
6+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
7+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
8+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
9+
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
10+
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
11+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
12+
} from '@sentry/core';
13+
import { sentryTest } from '../../../../utils/fixtures';
14+
import { shouldSkipTracingTest } from '../../../../utils/helpers';
15+
import { waitForStreamdSpanEnvelope } from '../../../../utils/spanUtils';
16+
17+
sentryTest(
18+
'sends a streamed span envelope if spanStreamingIntegration is enabled',
19+
async ({ getLocalTestUrl, page }) => {
20+
sentryTest.skip(shouldSkipTracingTest());
21+
22+
const spanEnvelopePromise = waitForStreamdSpanEnvelope(page);
23+
24+
const url = await getLocalTestUrl({ testDir: __dirname });
25+
await page.goto(url);
26+
27+
const spanEnvelope = await spanEnvelopePromise;
28+
29+
const envelopeHeader = spanEnvelope[0];
30+
const envelopeItem = spanEnvelope[1];
31+
const spans = envelopeItem[0][1].items;
32+
33+
expect(envelopeHeader).toEqual({
34+
sdk: {
35+
name: 'sentry.javascript.browser',
36+
version: SDK_VERSION,
37+
},
38+
sent_at: expect.any(String),
39+
trace: {
40+
environment: 'production',
41+
public_key: 'public',
42+
sample_rand: expect.any(String),
43+
sample_rate: '1',
44+
sampled: 'true',
45+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
46+
transaction: 'test-span',
47+
},
48+
});
49+
50+
const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!);
51+
const traceId = envelopeHeader.trace!.trace_id;
52+
53+
expect(Number.isNaN(numericSampleRand)).toBe(false);
54+
55+
expect(envelopeItem).toEqual([
56+
[
57+
{ content_type: 'application/vnd.sentry.items.span.v2+json', item_count: 4, type: 'span' },
58+
{
59+
items: expect.any(Array),
60+
},
61+
],
62+
]);
63+
64+
const segmentSpanId = spans.find(s => !!s.is_segment)?.span_id;
65+
expect(segmentSpanId).toBeDefined();
66+
67+
expect(spans).toEqual([
68+
{
69+
attributes: {
70+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: {
71+
type: 'string',
72+
value: 'test-child',
73+
},
74+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: {
75+
type: 'string',
76+
value: 'manual',
77+
},
78+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: {
79+
type: 'string',
80+
value: 'sentry.javascript.browser',
81+
},
82+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: {
83+
type: 'string',
84+
value: SDK_VERSION,
85+
},
86+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
87+
type: 'string',
88+
value: segmentSpanId,
89+
},
90+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: {
91+
type: 'string',
92+
value: 'test-span',
93+
},
94+
},
95+
end_timestamp: expect.any(Number),
96+
is_segment: false,
97+
name: 'test-child-span',
98+
parent_span_id: segmentSpanId,
99+
span_id: expect.stringMatching(/^[\da-f]{16}$/),
100+
start_timestamp: expect.any(Number),
101+
status: 'ok',
102+
trace_id: traceId,
103+
},
104+
{
105+
attributes: {
106+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: {
107+
type: 'string',
108+
value: 'manual',
109+
},
110+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: {
111+
type: 'string',
112+
value: 'sentry.javascript.browser',
113+
},
114+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: {
115+
type: 'string',
116+
value: SDK_VERSION,
117+
},
118+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
119+
type: 'string',
120+
value: segmentSpanId,
121+
},
122+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: {
123+
type: 'string',
124+
value: 'test-span',
125+
},
126+
},
127+
end_timestamp: expect.any(Number),
128+
is_segment: false,
129+
name: 'test-inactive-span',
130+
parent_span_id: segmentSpanId,
131+
span_id: expect.stringMatching(/^[\da-f]{16}$/),
132+
start_timestamp: expect.any(Number),
133+
status: 'ok',
134+
trace_id: traceId,
135+
},
136+
{
137+
attributes: {
138+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: {
139+
type: 'string',
140+
value: 'manual',
141+
},
142+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: {
143+
type: 'string',
144+
value: 'sentry.javascript.browser',
145+
},
146+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: {
147+
type: 'string',
148+
value: SDK_VERSION,
149+
},
150+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
151+
type: 'string',
152+
value: segmentSpanId,
153+
},
154+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: {
155+
type: 'string',
156+
value: 'test-span',
157+
},
158+
},
159+
end_timestamp: expect.any(Number),
160+
is_segment: false,
161+
name: 'test-manual-span',
162+
parent_span_id: segmentSpanId,
163+
span_id: expect.stringMatching(/^[\da-f]{16}$/),
164+
start_timestamp: expect.any(Number),
165+
status: 'ok',
166+
trace_id: traceId,
167+
},
168+
{
169+
attributes: {
170+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: {
171+
type: 'string',
172+
value: 'test',
173+
},
174+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: {
175+
type: 'string',
176+
value: 'manual',
177+
},
178+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: {
179+
type: 'integer',
180+
value: 1,
181+
},
182+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: {
183+
type: 'string',
184+
value: 'sentry.javascript.browser',
185+
},
186+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: {
187+
type: 'string',
188+
value: SDK_VERSION,
189+
},
190+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
191+
type: 'string',
192+
value: segmentSpanId,
193+
},
194+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: {
195+
type: 'string',
196+
value: 'test-span',
197+
},
198+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: {
199+
type: 'string',
200+
value: 'custom',
201+
},
202+
'sentry.span.source': {
203+
type: 'string',
204+
value: 'custom',
205+
},
206+
},
207+
end_timestamp: expect.any(Number),
208+
is_segment: true,
209+
name: 'test-span',
210+
span_id: segmentSpanId,
211+
start_timestamp: expect.any(Number),
212+
status: 'ok',
213+
trace_id: traceId,
214+
},
215+
]);
216+
},
217+
);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window._testBaseTimestamp = performance.timeOrigin / 1000;
5+
6+
Sentry.init({
7+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
8+
integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()],
9+
traceLifecycle: 'stream',
10+
tracesSampleRate: 1,
11+
});
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { expect } from '@playwright/test';
2+
import {
3+
SDK_VERSION,
4+
SEMANTIC_ATTRIBUTE_SENTRY_OP,
5+
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
6+
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
7+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
8+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
9+
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,
10+
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
11+
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
12+
} from '@sentry/core';
13+
import { sentryTest } from '../../../../utils/fixtures';
14+
import { shouldSkipTracingTest } from '../../../../utils/helpers';
15+
import { getSpanOp, getSpansFromEnvelope, waitForStreamdSpanEnvelope } from '../../../../utils/spanUtils';
16+
17+
sentryTest(
18+
'creates a pageload streamed span envelope with url as pageload span name source',
19+
async ({ getLocalTestUrl, page }) => {
20+
sentryTest.skip(shouldSkipTracingTest());
21+
22+
const spanEnvelopePromise = waitForStreamdSpanEnvelope(
23+
page,
24+
env => !!getSpansFromEnvelope(env).find(s => getSpanOp(s) === 'pageload'),
25+
);
26+
27+
const url = await getLocalTestUrl({ testDir: __dirname });
28+
await page.goto(url);
29+
30+
const spanEnvelope = await spanEnvelopePromise;
31+
const envelopeHeader = spanEnvelope[0];
32+
const envelopeItem = spanEnvelope[1];
33+
const spans = envelopeItem[0][1].items;
34+
const pageloadSpan = spans.find(s => getSpanOp(s) === 'pageload');
35+
36+
const timeOrigin = await page.evaluate<number>('window._testBaseTimestamp');
37+
38+
expect(envelopeHeader).toEqual({
39+
sdk: {
40+
name: 'sentry.javascript.browser',
41+
version: SDK_VERSION,
42+
},
43+
sent_at: expect.any(String),
44+
trace: {
45+
environment: 'production',
46+
public_key: 'public',
47+
sample_rand: expect.any(String),
48+
sample_rate: '1',
49+
sampled: 'true',
50+
trace_id: expect.stringMatching(/^[\da-f]{32}$/),
51+
},
52+
});
53+
54+
const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!);
55+
const traceId = envelopeHeader.trace!.trace_id;
56+
57+
expect(Number.isNaN(numericSampleRand)).toBe(false);
58+
59+
expect(envelopeItem[0][0].item_count).toBeGreaterThan(1);
60+
61+
expect(pageloadSpan?.start_timestamp).toBeCloseTo(timeOrigin, 1);
62+
63+
expect(pageloadSpan).toEqual({
64+
attributes: {
65+
effectiveConnectionType: {
66+
type: 'string',
67+
value: expect.any(String),
68+
},
69+
hardwareConcurrency: {
70+
type: 'string',
71+
value: expect.any(String),
72+
},
73+
'performance.activationStart': {
74+
type: 'integer',
75+
value: expect.any(Number),
76+
},
77+
'performance.timeOrigin': {
78+
type: 'double',
79+
value: expect.any(Number),
80+
},
81+
'sentry.idle_span_finish_reason': {
82+
type: 'string',
83+
value: 'idleTimeout',
84+
},
85+
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: {
86+
type: 'string',
87+
value: 'pageload',
88+
},
89+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: {
90+
type: 'string',
91+
value: 'auto.pageload.browser',
92+
},
93+
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: {
94+
type: 'integer',
95+
value: 1,
96+
},
97+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: {
98+
type: 'string',
99+
value: 'sentry.javascript.browser',
100+
},
101+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: {
102+
type: 'string',
103+
value: SDK_VERSION,
104+
},
105+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: {
106+
type: 'string',
107+
value: pageloadSpan?.span_id,
108+
},
109+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: {
110+
type: 'string',
111+
value: '/index.html',
112+
},
113+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: {
114+
type: 'string',
115+
value: 'url',
116+
},
117+
'sentry.span.source': {
118+
type: 'string',
119+
value: 'url',
120+
},
121+
},
122+
end_timestamp: expect.any(Number),
123+
is_segment: true,
124+
name: '/index.html',
125+
span_id: expect.stringMatching(/^[\da-f]{16}$/),
126+
start_timestamp: expect.any(Number),
127+
status: 'ok',
128+
trace_id: traceId,
129+
});
130+
},
131+
);

dev-packages/browser-integration-tests/utils/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const eventAndTraceHeaderRequestParser = (request: Request | null): Event
6262
return getEventAndTraceHeader(envelope);
6363
};
6464

65-
const properFullEnvelopeParser = <T extends Envelope>(request: Request | null): T => {
65+
export const properFullEnvelopeParser = <T extends Envelope>(request: Request | null): T => {
6666
// https://develop.sentry.dev/sdk/envelopes/
6767
const envelope = request?.postData() || '';
6868

0 commit comments

Comments
 (0)