Skip to content

Commit

Permalink
test(browser): Add test for current DSC transaction name updating beh…
Browse files Browse the repository at this point in the history
…avior (#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.
  • Loading branch information
Lms24 authored Oct 14, 2024
1 parent 0ccf8ce commit 50ab948
Show file tree
Hide file tree
Showing 11 changed files with 599 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
integrations: [Sentry.browserTracingIntegration({ instrumentNavigation: false, instrumentPageLoad: false })],
tracesSampleRate: 1,
tracePropagationTargets: ['example.com'],
release: '1.1.1',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const btnStartSpan = document.getElementById('btnStartSpan');
const btnUpdateName = document.getElementById('btnUpdateName');
const btnMakeRequest = document.getElementById('btnMakeRequest');
const btnCaptureError = document.getElementById('btnCaptureError');
const btnEndSpan = document.getElementById('btnEndSpan');

btnStartSpan.addEventListener('click', () => {
Sentry.startSpanManual(
{ name: 'test-root-span', attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url' } },
async span => {
window.__traceId = span.spanContext().traceId;
await new Promise(resolve => {
btnEndSpan.addEventListener('click', resolve);
});
span.end();
},
);
});

let updateCnt = 0;
btnUpdateName.addEventListener('click', () => {
const span = Sentry.getActiveSpan();
const rootSpan = Sentry.getRootSpan(span);
rootSpan.updateName(`updated-root-span-${++updateCnt}`);
rootSpan.setAttribute(Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'route');
});

btnMakeRequest.addEventListener('click', () => {
fetch('https://example.com/api');
});

btnCaptureError.addEventListener('click', () => {
Sentry.captureException(new Error('test-error'));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<button id="btnStartSpan">Start Span</button>
<button id="btnUpdateName">Update Name</button>
<button id="btnMakeRequest">Make Request</button>
<button id="btnCaptureError">Capture Error</button>
<button id="btnEndSpan">End Span</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
import { expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import type { DynamicSamplingContext } from '@sentry/types';
import { sentryTest } from '../../../utils/fixtures';
import type { EventAndTraceHeader } from '../../../utils/helpers';
import {
eventAndTraceHeaderRequestParser,
getMultipleSentryEnvelopeRequests,
shouldSkipTracingTest,
} from '../../../utils/helpers';

sentryTest('updates the DSC when the txn name is updated and high-quality', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest()) {
sentryTest.skip();
}

const url = await getLocalTestUrl({ testDir: __dirname });

await page.goto(url);

/*
Test Steps:
1. Start new span with LQ txn name (source: url)
2. Make request and check that baggage has no transaction name
3. Capture error and check that envelope trace header has no transaction name
4. Update span name and source to HQ (source: route)
5. Make request and check that baggage has HQ txn name
6. Capture error and check that envelope trace header has HQ txn name
7. Update span name again with HQ name (source: route)
8. Make request and check that baggage has updated HQ txn name
9. Capture error and check that envelope trace header has updated HQ txn name
10. End span and check that envelope trace header has updated HQ txn name
11. Make another request and check that there's no span information in baggage
12. Capture an error and check that envelope trace header has no span information
*/

// 1
await page.locator('#btnStartSpan').click();
const traceId = await page.evaluate(() => {
return (window as any).__traceId;
});

expect(traceId).toMatch(/^[0-9a-f]{32}$/);

// 2
const baggageItems = await makeRequestAndGetBaggageItems(page);
expect(baggageItems).toEqual([
'sentry-environment=production',
'sentry-public_key=public',
'sentry-release=1.1.1',
'sentry-sample_rate=1',
'sentry-sampled=true',
`sentry-trace_id=${traceId}`,
]);

// 3
const errorEnvelopeTraceHeader = await captureErrorAndGetEnvelopeTraceHeader(page);
expect(errorEnvelopeTraceHeader).toEqual({
environment: 'production',
public_key: 'public',
release: '1.1.1',
sample_rate: '1',
sampled: 'true',
trace_id: traceId,
});

// 4
await page.locator('#btnUpdateName').click();

// 5
const baggageItemsAfterUpdate = await makeRequestAndGetBaggageItems(page);
expect(baggageItemsAfterUpdate).toEqual([
'sentry-environment=production',
'sentry-public_key=public',
'sentry-release=1.1.1',
'sentry-sample_rate=1',
'sentry-sampled=true',
`sentry-trace_id=${traceId}`,
'sentry-transaction=updated-root-span-1',
]);

// 6
const errorEnvelopeTraceHeaderAfterUpdate = await captureErrorAndGetEnvelopeTraceHeader(page);
expect(errorEnvelopeTraceHeaderAfterUpdate).toEqual({
environment: 'production',
public_key: 'public',
release: '1.1.1',
sample_rate: '1',
sampled: 'true',
trace_id: traceId,
transaction: 'updated-root-span-1',
});

// 7
await page.locator('#btnUpdateName').click();

// 8
const baggageItemsAfterSecondUpdate = await makeRequestAndGetBaggageItems(page);
expect(baggageItemsAfterSecondUpdate).toEqual([
'sentry-environment=production',
'sentry-public_key=public',
'sentry-release=1.1.1',
'sentry-sample_rate=1',
'sentry-sampled=true',
`sentry-trace_id=${traceId}`,
'sentry-transaction=updated-root-span-2',
]);

// 9
const errorEnvelopeTraceHeaderAfterSecondUpdate = await captureErrorAndGetEnvelopeTraceHeader(page);
expect(errorEnvelopeTraceHeaderAfterSecondUpdate).toEqual({
environment: 'production',
public_key: 'public',
release: '1.1.1',
sample_rate: '1',
sampled: 'true',
trace_id: traceId,
transaction: 'updated-root-span-2',
});

// 10
const txnEventPromise = getMultipleSentryEnvelopeRequests<EventAndTraceHeader>(
page,
1,
{ envelopeType: 'transaction' },
eventAndTraceHeaderRequestParser,
);

await page.locator('#btnEndSpan').click();

const [txnEvent, txnEnvelopeTraceHeader] = (await txnEventPromise)[0];
expect(txnEnvelopeTraceHeader).toEqual({
environment: 'production',
public_key: 'public',
release: '1.1.1',
sample_rate: '1',
sampled: 'true',
trace_id: traceId,
transaction: 'updated-root-span-2',
});

expect(txnEvent.transaction).toEqual('updated-root-span-2');

// 11
const baggageItemsAfterEnd = await makeRequestAndGetBaggageItems(page);
expect(baggageItemsAfterEnd).toEqual([
'sentry-environment=production',
'sentry-public_key=public',
'sentry-release=1.1.1',
`sentry-trace_id=${traceId}`,
]);

// 12
const errorEnvelopeTraceHeaderAfterEnd = await captureErrorAndGetEnvelopeTraceHeader(page);
expect(errorEnvelopeTraceHeaderAfterEnd).toEqual({
environment: 'production',
public_key: 'public',
release: '1.1.1',
trace_id: traceId,
});
});

async function makeRequestAndGetBaggageItems(page: Page): Promise<string[]> {
const requestPromise = page.waitForRequest('https://example.com/*');
await page.locator('#btnMakeRequest').click();
const request = await requestPromise;

const baggage = await request.headerValue('baggage');

return baggage?.split(',').sort() ?? [];
}

async function captureErrorAndGetEnvelopeTraceHeader(page: Page): Promise<DynamicSamplingContext | undefined> {
const errorEventPromise = getMultipleSentryEnvelopeRequests<EventAndTraceHeader>(
page,
1,
{ envelopeType: 'event' },
eventAndTraceHeaderRequestParser,
);

await page.locator('#btnCaptureError').click();

const [, errorEnvelopeTraceHeader] = (await errorEventPromise)[0];
return errorEnvelopeTraceHeader;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const errorBtn = document.getElementById('errorBtn');
errorBtn.addEventListener('click', () => {
throw new Error(`Sentry Test Error ${Math.random()}`);
});

const fetchBtn = document.getElementById('fetchBtn');
fetchBtn.addEventListener('click', async () => {
await fetch('http://example.com');
});

const xhrBtn = document.getElementById('xhrBtn');
xhrBtn.addEventListener('click', () => {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://example.com');
xhr.send();
});
Loading

0 comments on commit 50ab948

Please sign in to comment.