Skip to content

Commit bdbaf6b

Browse files
Lms24billyvg
authored andcommitted
test(browser): Add test for current DSC transaction name updating behavior (#13953)
Add a browser integration test that describes the current behaviour of how we update the `transaction` field in the DSC which is propagated via the `baggage` header and added to envelopes as the `trace` header.
1 parent 100574b commit bdbaf6b

File tree

11 files changed

+599
-0
lines changed

11 files changed

+599
-0
lines changed
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+
5+
Sentry.init({
6+
dsn: 'https://[email protected]/1337',
7+
integrations: [Sentry.browserTracingIntegration({ instrumentNavigation: false, instrumentPageLoad: false })],
8+
tracesSampleRate: 1,
9+
tracePropagationTargets: ['example.com'],
10+
release: '1.1.1',
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const btnStartSpan = document.getElementById('btnStartSpan');
2+
const btnUpdateName = document.getElementById('btnUpdateName');
3+
const btnMakeRequest = document.getElementById('btnMakeRequest');
4+
const btnCaptureError = document.getElementById('btnCaptureError');
5+
const btnEndSpan = document.getElementById('btnEndSpan');
6+
7+
btnStartSpan.addEventListener('click', () => {
8+
Sentry.startSpanManual(
9+
{ name: 'test-root-span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } },
10+
async span => {
11+
window.__traceId = span.spanContext().traceId;
12+
await new Promise(resolve => {
13+
btnEndSpan.addEventListener('click', resolve);
14+
});
15+
span.end();
16+
},
17+
);
18+
});
19+
20+
let updateCnt = 0;
21+
btnUpdateName.addEventListener('click', () => {
22+
const span = Sentry.getActiveSpan();
23+
const rootSpan = Sentry.getRootSpan(span);
24+
rootSpan.updateName(`updated-root-span-${++updateCnt}`);
25+
rootSpan.setAttribute(Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
26+
});
27+
28+
btnMakeRequest.addEventListener('click', () => {
29+
fetch('https://example.com/api');
30+
});
31+
32+
btnCaptureError.addEventListener('click', () => {
33+
Sentry.captureException(new Error('test-error'));
34+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<button id="btnStartSpan">Start Span</button>
2+
<button id="btnUpdateName">Update Name</button>
3+
<button id="btnMakeRequest">Make Request</button>
4+
<button id="btnCaptureError">Capture Error</button>
5+
<button id="btnEndSpan">End Span</button>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { expect } from '@playwright/test';
2+
import type { Page } from '@playwright/test';
3+
import type { DynamicSamplingContext } from '@sentry/types';
4+
import { sentryTest } from '../../../utils/fixtures';
5+
import type { EventAndTraceHeader } from '../../../utils/helpers';
6+
import {
7+
eventAndTraceHeaderRequestParser,
8+
getMultipleSentryEnvelopeRequests,
9+
shouldSkipTracingTest,
10+
} from '../../../utils/helpers';
11+
12+
sentryTest('updates the DSC when the txn name is updated and high-quality', async ({ getLocalTestUrl, page }) => {
13+
if (shouldSkipTracingTest()) {
14+
sentryTest.skip();
15+
}
16+
17+
const url = await getLocalTestUrl({ testDir: __dirname });
18+
19+
await page.goto(url);
20+
21+
/*
22+
Test Steps:
23+
1. Start new span with LQ txn name (source: url)
24+
2. Make request and check that baggage has no transaction name
25+
3. Capture error and check that envelope trace header has no transaction name
26+
4. Update span name and source to HQ (source: route)
27+
5. Make request and check that baggage has HQ txn name
28+
6. Capture error and check that envelope trace header has HQ txn name
29+
7. Update span name again with HQ name (source: route)
30+
8. Make request and check that baggage has updated HQ txn name
31+
9. Capture error and check that envelope trace header has updated HQ txn name
32+
10. End span and check that envelope trace header has updated HQ txn name
33+
11. Make another request and check that there's no span information in baggage
34+
12. Capture an error and check that envelope trace header has no span information
35+
*/
36+
37+
// 1
38+
await page.locator('#btnStartSpan').click();
39+
const traceId = await page.evaluate(() => {
40+
return (window as any).__traceId;
41+
});
42+
43+
expect(traceId).toMatch(/^[0-9a-f]{32}$/);
44+
45+
// 2
46+
const baggageItems = await makeRequestAndGetBaggageItems(page);
47+
expect(baggageItems).toEqual([
48+
'sentry-environment=production',
49+
'sentry-public_key=public',
50+
'sentry-release=1.1.1',
51+
'sentry-sample_rate=1',
52+
'sentry-sampled=true',
53+
`sentry-trace_id=${traceId}`,
54+
]);
55+
56+
// 3
57+
const errorEnvelopeTraceHeader = await captureErrorAndGetEnvelopeTraceHeader(page);
58+
expect(errorEnvelopeTraceHeader).toEqual({
59+
environment: 'production',
60+
public_key: 'public',
61+
release: '1.1.1',
62+
sample_rate: '1',
63+
sampled: 'true',
64+
trace_id: traceId,
65+
});
66+
67+
// 4
68+
await page.locator('#btnUpdateName').click();
69+
70+
// 5
71+
const baggageItemsAfterUpdate = await makeRequestAndGetBaggageItems(page);
72+
expect(baggageItemsAfterUpdate).toEqual([
73+
'sentry-environment=production',
74+
'sentry-public_key=public',
75+
'sentry-release=1.1.1',
76+
'sentry-sample_rate=1',
77+
'sentry-sampled=true',
78+
`sentry-trace_id=${traceId}`,
79+
'sentry-transaction=updated-root-span-1',
80+
]);
81+
82+
// 6
83+
const errorEnvelopeTraceHeaderAfterUpdate = await captureErrorAndGetEnvelopeTraceHeader(page);
84+
expect(errorEnvelopeTraceHeaderAfterUpdate).toEqual({
85+
environment: 'production',
86+
public_key: 'public',
87+
release: '1.1.1',
88+
sample_rate: '1',
89+
sampled: 'true',
90+
trace_id: traceId,
91+
transaction: 'updated-root-span-1',
92+
});
93+
94+
// 7
95+
await page.locator('#btnUpdateName').click();
96+
97+
// 8
98+
const baggageItemsAfterSecondUpdate = await makeRequestAndGetBaggageItems(page);
99+
expect(baggageItemsAfterSecondUpdate).toEqual([
100+
'sentry-environment=production',
101+
'sentry-public_key=public',
102+
'sentry-release=1.1.1',
103+
'sentry-sample_rate=1',
104+
'sentry-sampled=true',
105+
`sentry-trace_id=${traceId}`,
106+
'sentry-transaction=updated-root-span-2',
107+
]);
108+
109+
// 9
110+
const errorEnvelopeTraceHeaderAfterSecondUpdate = await captureErrorAndGetEnvelopeTraceHeader(page);
111+
expect(errorEnvelopeTraceHeaderAfterSecondUpdate).toEqual({
112+
environment: 'production',
113+
public_key: 'public',
114+
release: '1.1.1',
115+
sample_rate: '1',
116+
sampled: 'true',
117+
trace_id: traceId,
118+
transaction: 'updated-root-span-2',
119+
});
120+
121+
// 10
122+
const txnEventPromise = getMultipleSentryEnvelopeRequests<EventAndTraceHeader>(
123+
page,
124+
1,
125+
{ envelopeType: 'transaction' },
126+
eventAndTraceHeaderRequestParser,
127+
);
128+
129+
await page.locator('#btnEndSpan').click();
130+
131+
const [txnEvent, txnEnvelopeTraceHeader] = (await txnEventPromise)[0];
132+
expect(txnEnvelopeTraceHeader).toEqual({
133+
environment: 'production',
134+
public_key: 'public',
135+
release: '1.1.1',
136+
sample_rate: '1',
137+
sampled: 'true',
138+
trace_id: traceId,
139+
transaction: 'updated-root-span-2',
140+
});
141+
142+
expect(txnEvent.transaction).toEqual('updated-root-span-2');
143+
144+
// 11
145+
const baggageItemsAfterEnd = await makeRequestAndGetBaggageItems(page);
146+
expect(baggageItemsAfterEnd).toEqual([
147+
'sentry-environment=production',
148+
'sentry-public_key=public',
149+
'sentry-release=1.1.1',
150+
`sentry-trace_id=${traceId}`,
151+
]);
152+
153+
// 12
154+
const errorEnvelopeTraceHeaderAfterEnd = await captureErrorAndGetEnvelopeTraceHeader(page);
155+
expect(errorEnvelopeTraceHeaderAfterEnd).toEqual({
156+
environment: 'production',
157+
public_key: 'public',
158+
release: '1.1.1',
159+
trace_id: traceId,
160+
});
161+
});
162+
163+
async function makeRequestAndGetBaggageItems(page: Page): Promise<string[]> {
164+
const requestPromise = page.waitForRequest('https://example.com/*');
165+
await page.locator('#btnMakeRequest').click();
166+
const request = await requestPromise;
167+
168+
const baggage = await request.headerValue('baggage');
169+
170+
return baggage?.split(',').sort() ?? [];
171+
}
172+
173+
async function captureErrorAndGetEnvelopeTraceHeader(page: Page): Promise<DynamicSamplingContext | undefined> {
174+
const errorEventPromise = getMultipleSentryEnvelopeRequests<EventAndTraceHeader>(
175+
page,
176+
1,
177+
{ envelopeType: 'event' },
178+
eventAndTraceHeaderRequestParser,
179+
);
180+
181+
await page.locator('#btnCaptureError').click();
182+
183+
const [, errorEnvelopeTraceHeader] = (await errorEventPromise)[0];
184+
return errorEnvelopeTraceHeader;
185+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const errorBtn = document.getElementById('errorBtn');
2+
errorBtn.addEventListener('click', () => {
3+
throw new Error(`Sentry Test Error ${Math.random()}`);
4+
});
5+
6+
const fetchBtn = document.getElementById('fetchBtn');
7+
fetchBtn.addEventListener('click', async () => {
8+
await fetch('http://example.com');
9+
});
10+
11+
const xhrBtn = document.getElementById('xhrBtn');
12+
xhrBtn.addEventListener('click', () => {
13+
const xhr = new XMLHttpRequest();
14+
xhr.open('GET', 'http://example.com');
15+
xhr.send();
16+
});

0 commit comments

Comments
 (0)