diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js
new file mode 100644
index 000000000000..9c7cdb7e11b6
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/init.js
@@ -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',
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/subject.js b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/subject.js
new file mode 100644
index 000000000000..163082710a13
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/subject.js
@@ -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'));
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/template.html b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/template.html
new file mode 100644
index 000000000000..9ad1d0cfe584
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/template.html
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts
new file mode 100644
index 000000000000..e8c21a66647f
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/dsc-txn-name-update/test.ts
@@ -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(
+ 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 {
+ 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 {
+ const errorEventPromise = getMultipleSentryEnvelopeRequests(
+ page,
+ 1,
+ { envelopeType: 'event' },
+ eventAndTraceHeaderRequestParser,
+ );
+
+ await page.locator('#btnCaptureError').click();
+
+ const [, errorEnvelopeTraceHeader] = (await errorEventPromise)[0];
+ return errorEnvelopeTraceHeader;
+}
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/subject.js
new file mode 100644
index 000000000000..9528f861a723
--- /dev/null
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/subject.js
@@ -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();
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
index 2d0933002e7f..5ef3cb81ad28 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts
@@ -294,6 +294,149 @@ sentryTest(
},
);
+// sentryTest(
+// 'outgoing fetch request after pageload has pageload traceId in headers',
+// async ({ getLocalTestUrl, page }) => {
+// if (shouldSkipTracingTest()) {
+// sentryTest.skip();
+// }
+
+// const url = await getLocalTestUrl({ testDir: __dirname });
+
+// await page.route('http://example.com/**', route => {
+// return route.fulfill({
+// status: 200,
+// contentType: 'application/json',
+// body: JSON.stringify({}),
+// });
+// });
+
+// const pageloadEventPromise = getFirstSentryEnvelopeRequest(
+// page,
+// undefined,
+// eventAndTraceHeaderRequestParser,
+// );
+// await page.goto(url);
+// const [pageloadEvent, pageloadTraceHeader] = await pageloadEventPromise;
+
+// const pageloadTraceContext = pageloadEvent.contexts?.trace;
+// const pageloadTraceId = pageloadTraceContext?.trace_id;
+
+// expect(pageloadEvent.type).toEqual('transaction');
+// expect(pageloadTraceContext).toMatchObject({
+// op: 'pageload',
+// trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
+// span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
+// });
+// expect(pageloadTraceContext).not.toHaveProperty('parent_span_id');
+
+// expect(pageloadTraceHeader).toEqual({
+// environment: 'production',
+// public_key: 'public',
+// sample_rate: '1',
+// sampled: 'true',
+// trace_id: pageloadTraceId,
+// });
+
+// const requestPromise = page.waitForRequest('http://example.com/*');
+// await page.locator('#xhrBtn').click();
+// const request = await requestPromise;
+
+// const headers = request.headers();
+
+// // sampling decision is propagated from active span sampling decision
+// expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
+// expect(headers['baggage']).toEqual(
+// `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`,
+// );
+// },
+// )
+
+// sentryTest(
+// 'custom span and request headers after pageload have pageload traceId ',
+// async ({ getLocalTestUrl, page }) => {
+// if (shouldSkipTracingTest()) {
+// sentryTest.skip();
+// }
+
+// const url = await getLocalTestUrl({ testDir: __dirname });
+
+// await page.route('http://example.com/**', route => {
+// return route.fulfill({
+// status: 200,
+// contentType: 'application/json',
+// body: JSON.stringify({}),
+// });
+// });
+
+// const pageloadEventPromise = getFirstSentryEnvelopeRequest(
+// page,
+// undefined,
+// eventAndTraceHeaderRequestParser,
+// );
+
+// await page.goto(url);
+
+// const [pageloadEvent, pageloadTraceHeader] = await pageloadEventPromise;
+
+// const pageloadTraceContext = pageloadEvent.contexts?.trace;
+// const pageloadTraceId = pageloadTraceContext?.trace_id;
+
+// expect(pageloadEvent.type).toEqual('transaction');
+// expect(pageloadTraceContext).toMatchObject({
+// op: 'pageload',
+// trace_id: expect.stringMatching(/^[0-9a-f]{32}$/),
+// span_id: expect.stringMatching(/^[0-9a-f]{16}$/),
+// });
+// expect(pageloadTraceContext).not.toHaveProperty('parent_span_id');
+
+// expect(pageloadTraceHeader).toEqual({
+// environment: 'production',
+// public_key: 'public',
+// sample_rate: '1',
+// sampled: 'true',
+// trace_id: pageloadTraceId,
+// });
+
+// const requestPromise = page.waitForRequest('http://example.com/**');
+// const customTransactionEventPromise = getFirstSentryEnvelopeRequest(
+// page,
+// undefined,
+// eventAndTraceHeaderRequestParser,
+// );
+
+// await page.locator('#spanAndFetchBtn').click();
+
+// const [[customTransactionEvent, customTransactionTraceHeader], request] = await Promise.all([
+// customTransactionEventPromise,
+// requestPromise,
+// ]);
+
+// const customTransactionTraceContext = customTransactionEvent.contexts?.trace;
+
+// expect(customTransactionEvent.type).toEqual('transaction');
+// expect(customTransactionTraceContext).toMatchObject({
+// trace_id: pageloadTraceId,
+// });
+
+// expect(customTransactionTraceHeader).toEqual({
+// environment: 'production',
+// public_key: 'public',
+// sample_rate: '1',
+// sampled: 'true',
+// trace_id: pageloadTraceId,
+// });
+
+// const headers = request.headers();
+
+// // sampling decision is propagated from active span sampling decision
+// expect(headers['sentry-trace']).toMatch(new RegExp(`^${pageloadTraceId}-[0-9a-f]{16}-1$`));
+// expect(headers['baggage']).toEqual(
+// `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sample_rate=1,sentry-sampled=true`,
+// );
+// },
+// );
+
sentryTest('user feedback event after pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => {
if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) {
sentryTest.skip();
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/subject.js
index 9528f861a723..a2f6271463ce 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/subject.js
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/subject.js
@@ -14,3 +14,10 @@ xhrBtn.addEventListener('click', () => {
xhr.open('GET', 'http://example.com');
xhr.send();
});
+
+const spanAndFetchBtn = document.getElementById('spanAndFetchBtn');
+spanAndFetchBtn.addEventListener('click', () => {
+ Sentry.startSpan({ name: 'custom-root-span' }, async () => {
+ await fetch('http://example.com');
+ });
+});
diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/template.html b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/template.html
index a3c17f442605..a112e5c46771 100644
--- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/template.html
+++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/template.html
@@ -7,5 +7,6 @@
+