Skip to content

Commit f1c4611

Browse files
authored
test(browser-integration-test): Add trace lifetime tests for XHR requests (#11624)
Add tests that check that XHR request headers propagate the correct traceId, as spec'd out in #11599.
1 parent 379a9e5 commit f1c4611

File tree

6 files changed

+332
-62
lines changed

6 files changed

+332
-62
lines changed

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts

Lines changed: 131 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,24 @@ sentryTest('creates a new trace on each navigation', async ({ getLocalTestPath,
1818
const navigationEvent1 = await getFirstSentryEnvelopeRequest<Event>(page, `${url}#foo`);
1919
const navigationEvent2 = await getFirstSentryEnvelopeRequest<Event>(page, `${url}#bar`);
2020

21-
expect(navigationEvent1.contexts?.trace?.op).toBe('navigation');
22-
expect(navigationEvent2.contexts?.trace?.op).toBe('navigation');
23-
24-
const navigation1TraceId = navigationEvent1.contexts?.trace?.trace_id;
25-
const navigation2TraceId = navigationEvent2.contexts?.trace?.trace_id;
26-
27-
expect(navigation1TraceId).toMatch(/^[0-9a-f]{32}$/);
28-
expect(navigation2TraceId).toMatch(/^[0-9a-f]{32}$/);
29-
expect(navigation1TraceId).not.toEqual(navigation2TraceId);
21+
const navigation1TraceContext = navigationEvent1.contexts?.trace;
22+
const navigation2TraceContext = navigationEvent2.contexts?.trace;
23+
24+
expect(navigation1TraceContext).toMatchObject({
25+
op: 'navigation',
26+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
27+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
28+
});
29+
expect(navigation1TraceContext).not.toHaveProperty('parent_span_id');
30+
31+
expect(navigation2TraceContext).toMatchObject({
32+
op: 'navigation',
33+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
34+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
35+
});
36+
expect(navigation2TraceContext).not.toHaveProperty('parent_span_id');
37+
38+
expect(navigation1TraceContext?.trace_id).not.toEqual(navigation2TraceContext?.trace_id);
3039
});
3140

3241
sentryTest('error after navigation has navigation traceId', async ({ getLocalTestPath, page }) => {
@@ -40,17 +49,24 @@ sentryTest('error after navigation has navigation traceId', async ({ getLocalTes
4049
await getFirstSentryEnvelopeRequest<Event>(page, url);
4150

4251
const navigationEvent = await getFirstSentryEnvelopeRequest<Event>(page, `${url}#foo`);
43-
expect(navigationEvent.contexts?.trace?.op).toBe('navigation');
52+
const navigationTraceContext = navigationEvent.contexts?.trace;
4453

45-
const navigationTraceId = navigationEvent.contexts?.trace?.trace_id;
46-
expect(navigationTraceId).toMatch(/^[0-9a-f]{32}$/);
54+
expect(navigationTraceContext).toMatchObject({
55+
op: 'navigation',
56+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
57+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
58+
});
59+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
4760

4861
const errorEventPromise = getFirstSentryEnvelopeRequest<Event>(page);
4962
await page.locator('#errorBtn').click();
5063
const errorEvent = await errorEventPromise;
5164

52-
const errorTraceId = errorEvent.contexts?.trace?.trace_id;
53-
expect(errorTraceId).toBe(navigationTraceId);
65+
const errorTraceContext = errorEvent.contexts?.trace;
66+
expect(errorTraceContext).toEqual({
67+
trace_id: navigationTraceContext?.trace_id,
68+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
69+
});
5470
});
5571

5672
sentryTest('error during navigation has new navigation traceId', async ({ getLocalTestPath, page }) => {
@@ -71,13 +87,20 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc
7187
const navigationEvent = events.find(event => event.type === 'transaction');
7288
const errorEvent = events.find(event => !event.type);
7389

74-
expect(navigationEvent?.contexts?.trace?.op).toBe('navigation');
75-
76-
const navigationTraceId = navigationEvent?.contexts?.trace?.trace_id;
77-
expect(navigationTraceId).toMatch(/^[0-9a-f]{32}$/);
78-
79-
const errorTraceId = errorEvent?.contexts?.trace?.trace_id;
80-
expect(errorTraceId).toBe(navigationTraceId);
90+
const navigationTraceContext = navigationEvent?.contexts?.trace;
91+
expect(navigationTraceContext).toMatchObject({
92+
op: 'navigation',
93+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
94+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
95+
});
96+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
97+
98+
const errorTraceContext = errorEvent?.contexts?.trace;
99+
expect(errorTraceContext).toMatchObject({
100+
op: 'navigation',
101+
trace_id: errorTraceContext?.trace_id,
102+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
103+
});
81104
});
82105

83106
sentryTest(
@@ -93,17 +116,22 @@ sentryTest(
93116
await getFirstSentryEnvelopeRequest<Event>(page, url);
94117

95118
const navigationEvent = await getFirstSentryEnvelopeRequest<Event>(page, `${url}#foo`);
96-
expect(navigationEvent.contexts?.trace?.op).toBe('navigation');
97119

98-
const navigationTraceId = navigationEvent.contexts?.trace?.trace_id;
99-
expect(navigationTraceId).toMatch(/^[0-9a-f]{32}$/);
120+
const navigationTraceContext = navigationEvent.contexts?.trace;
121+
expect(navigationTraceContext).toMatchObject({
122+
op: 'navigation',
123+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
124+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
125+
});
126+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
100127

101128
const requestPromise = page.waitForRequest('http://example.com/*');
102129
await page.locator('#fetchBtn').click();
103130
const request = await requestPromise;
104131
const headers = request.headers();
105132

106133
// sampling decision is deferred b/c of no active span at the time of request
134+
const navigationTraceId = navigationTraceContext?.trace_id;
107135
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}$`));
108136
expect(headers['baggage']).toEqual(
109137
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId}`,
@@ -129,14 +157,90 @@ sentryTest(
129157
await page.locator('#fetchBtn').click();
130158
const [navigationEvent, request] = await Promise.all([navigationEventPromise, requestPromise]);
131159

132-
expect(navigationEvent.contexts?.trace?.op).toBe('navigation');
160+
const navigationTraceContext = navigationEvent.contexts?.trace;
161+
expect(navigationTraceContext).toMatchObject({
162+
op: 'navigation',
163+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
164+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
165+
});
166+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
167+
168+
const headers = request.headers();
169+
170+
// sampling decision is propagated from active span sampling decision
171+
const navigationTraceId = navigationTraceContext?.trace_id;
172+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
173+
expect(headers['baggage']).toEqual(
174+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`,
175+
);
176+
},
177+
);
178+
179+
sentryTest(
180+
'outgoing XHR request after navigation has navigation traceId in headers',
181+
async ({ getLocalTestPath, page }) => {
182+
if (shouldSkipTracingTest()) {
183+
sentryTest.skip();
184+
}
185+
186+
const url = await getLocalTestPath({ testDir: __dirname });
187+
188+
// ensure navigation transaction is finished
189+
await getFirstSentryEnvelopeRequest<Event>(page, url);
190+
191+
const navigationEvent = await getFirstSentryEnvelopeRequest<Event>(page, `${url}#foo`);
192+
193+
const navigationTraceContext = navigationEvent.contexts?.trace;
194+
expect(navigationTraceContext).toMatchObject({
195+
op: 'navigation',
196+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
197+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
198+
});
199+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
200+
201+
const xhrPromise = page.waitForRequest('http://example.com/*');
202+
await page.locator('#xhrBtn').click();
203+
const request = await xhrPromise;
204+
const headers = request.headers();
205+
206+
// sampling decision is deferred b/c of no active span at the time of request
207+
const navigationTraceId = navigationTraceContext?.trace_id;
208+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}$`));
209+
expect(headers['baggage']).toEqual(
210+
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId}`,
211+
);
212+
},
213+
);
214+
215+
sentryTest(
216+
'outgoing XHR request during navigation has navigation traceId in headers',
217+
async ({ getLocalTestPath, page }) => {
218+
if (shouldSkipTracingTest()) {
219+
sentryTest.skip();
220+
}
133221

134-
const navigationTraceId = navigationEvent.contexts?.trace?.trace_id;
135-
expect(navigationTraceId).toMatch(/^[0-9a-f]{32}$/);
222+
const url = await getLocalTestPath({ testDir: __dirname });
223+
224+
// ensure navigation transaction is finished
225+
await getFirstSentryEnvelopeRequest<Event>(page, url);
226+
227+
const navigationEventPromise = getFirstSentryEnvelopeRequest<Event>(page);
228+
const requestPromise = page.waitForRequest('http://example.com/*');
229+
await page.goto(`${url}#foo`);
230+
await page.locator('#xhrBtn').click();
231+
const [navigationEvent, request] = await Promise.all([navigationEventPromise, requestPromise]);
136232

233+
const navigationTraceContext = navigationEvent.contexts?.trace;
234+
expect(navigationTraceContext).toMatchObject({
235+
op: 'navigation',
236+
trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
237+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
238+
});
239+
expect(navigationTraceContext).not.toHaveProperty('parent_span_id');
137240
const headers = request.headers();
138241

139242
// sampling decision is propagated from active span sampling decision
243+
const navigationTraceId = navigationTraceContext?.trace_id;
140244
expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`));
141245
expect(headers['baggage']).toEqual(
142246
`sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sample_rate=1,sentry-sampled=true`,

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/template.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
<body>
1010
<button id="errorBtn">Throw Error</button>
1111
<button id="fetchBtn">Fetch Request</button>
12+
<button id="xhrBtn">XHR Request</button>
1213
</body>
1314
</html>

dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,61 @@ sentryTest(
157157
expect(headers['baggage']).toBe(META_TAG_BAGGAGE);
158158
},
159159
);
160+
161+
sentryTest(
162+
'outgoing XHR request after <meta> tag pageload has pageload traceId in headers',
163+
async ({ getLocalTestPath, page }) => {
164+
if (shouldSkipTracingTest()) {
165+
sentryTest.skip();
166+
}
167+
168+
const url = await getLocalTestPath({ testDir: __dirname });
169+
170+
const pageloadEvent = await getFirstSentryEnvelopeRequest<Event>(page, url);
171+
expect(pageloadEvent?.contexts?.trace).toMatchObject({
172+
op: 'pageload',
173+
trace_id: META_TAG_TRACE_ID,
174+
parent_span_id: META_TAG_PARENT_SPAN_ID,
175+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
176+
});
177+
178+
const requestPromise = page.waitForRequest('http://example.com/*');
179+
await page.locator('#xhrBtn').click();
180+
const request = await requestPromise;
181+
const headers = request.headers();
182+
183+
// sampling decision is propagated from meta tag's sentry-trace sampled flag
184+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${META_TAG_TRACE_ID}-[0-9a-f]{16}-1$`));
185+
expect(headers['baggage']).toBe(META_TAG_BAGGAGE);
186+
},
187+
);
188+
189+
sentryTest(
190+
'outgoing XHR request during <meta> tag pageload has pageload traceId in headers',
191+
async ({ getLocalTestPath, page }) => {
192+
if (shouldSkipTracingTest()) {
193+
sentryTest.skip();
194+
}
195+
196+
const url = await getLocalTestPath({ testDir: __dirname });
197+
198+
const pageloadEventPromise = getFirstSentryEnvelopeRequest<Event>(page);
199+
const requestPromise = page.waitForRequest('http://example.com/*');
200+
await page.goto(url);
201+
await page.locator('#xhrBtn').click();
202+
const [pageloadEvent, request] = await Promise.all([pageloadEventPromise, requestPromise]);
203+
204+
expect(pageloadEvent?.contexts?.trace).toMatchObject({
205+
op: 'pageload',
206+
trace_id: META_TAG_TRACE_ID,
207+
parent_span_id: META_TAG_PARENT_SPAN_ID,
208+
span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
209+
});
210+
211+
const headers = request.headers();
212+
213+
// sampling decision is propagated from meta tag's sentry-trace sampled flag
214+
expect(headers['sentry-trace']).toMatch(new RegExp(`^${META_TAG_TRACE_ID}-[0-9a-f]{16}-1$`));
215+
expect(headers['baggage']).toBe(META_TAG_BAGGAGE);
216+
},
217+
);

0 commit comments

Comments
 (0)