diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js index 43248f09b63d..f44669962912 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js index bd0421b32761..7c200c542c56 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts index fbd30098773a..85b7663dabc0 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withAbortController/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event as SentryEvent } from '@sentry/core'; import { sentryTest } from '../../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../../utils/helpers'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForErrorRequest } from '../../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../../utils/spanUtils'; sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -14,7 +14,7 @@ sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page } // never fulfil this route because we abort the request as part of the test }); - const transactionEventPromise = getFirstSentryEnvelopeRequest(page); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'with-abort-controller'); const hasAbortedFetchPromise = new Promise(resolve => { page.on('console', msg => { @@ -29,10 +29,13 @@ sentryTest('should handle aborted fetch calls', async ({ getLocalTestUrl, page } await page.locator('[data-test-id=start-button]').click(); await page.locator('[data-test-id=abort-button]').click(); - const transactionEvent = await transactionEventPromise; + await spanPromise; + const eventPromise = waitForErrorRequest(page); + await page.evaluate(() => (window as any).Sentry.captureMessage('After aborted fetch')); + const event = envelopeRequestParser(await eventPromise); // assert that fetch calls do not return undefined - const fetchBreadcrumbs = transactionEvent.breadcrumbs?.filter( + const fetchBreadcrumbs = event.breadcrumbs?.filter( ({ category, data }) => category === 'fetch' && data === undefined, ); expect(fetchBreadcrumbs).toHaveLength(0); diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js index fd831d3f7c14..8584933da0fa 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js index 7d53e07a4c0c..a20cf0664d13 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withDisabledDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js index 2739a4aeea64..5eafca47503b 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/fetch/withHeadersOnlyDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js index 43248f09b63d..f44669962912 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js index fd831d3f7c14..8584933da0fa 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/httpclient/xhr/withDataCollection/init.js @@ -4,7 +4,6 @@ import { httpClientIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [httpClientIntegration()], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/init.js rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/init.js diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts similarity index 89% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts index c3218f27e98a..9491bc7feab6 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts @@ -1,3 +1,4 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; @@ -22,7 +23,7 @@ sentryTest('beforeSendSpan applies changes to streamed span', async ({ getLocalT spanId: '456', }, attributes: { - 'sentry.link.type': { type: 'string', value: 'custom_link' }, + [SENTRY_LINK_TYPE]: { type: 'string', value: 'custom_link' }, }, }, ]); diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts rename to dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts b/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts index ae9db5f3064d..2c42ced57737 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/captureMessage/simple_message_no_stacktrace/test.ts @@ -1,12 +1,11 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequestOnUrl } from '../../../../utils/helpers'; sentryTest('does not capture a stack trace if `attachStackTrace` is `false`', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = envelopeRequestParser(await waitForErrorRequestOnUrl(page, url)); expect(eventData.message).toBe('foo'); expect(eventData.level).toBe('info'); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts index 4d6d78f0eea1..7820d0108fc2 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/attributes/test.ts @@ -1,20 +1,14 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('sends an empty string attribute', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'child_span'); + await page.goto(url); - const childSpan = transaction.spans?.[0]; - expect(childSpan?.data?.someAttribute).toBe(''); + const span = await spanPromise; + expect(span.attributes.someAttribute).toEqual({ type: 'string', value: '' }); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js new file mode 100644 index 000000000000..527fad0d6f15 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js new file mode 100644 index 000000000000..35d41b55e69d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js @@ -0,0 +1,14 @@ +async function run() { + Sentry.startSpan({ name: 'parent_span' }, () => { + Sentry.startSpan({ name: 'child_span' }, () => { + // whatever a user would do here + }); + + // unfinished spans are filtered out + Sentry.startInactiveSpan({ name: 'span_4' }); + }); +} + +(async () => { + await run(); +})(); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts new file mode 100644 index 000000000000..d3819b797a11 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts @@ -0,0 +1,52 @@ +import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest( + 'sends a transaction in an envelope with manual origin and custom source', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + const attributes = transaction.contexts?.trace?.data; + expect(attributes).toEqual({ + [SENTRY_ORIGIN]: 'manual', + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, + [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', + }); + + expect(transaction.transaction_info?.source).toBe('custom'); + + expect(transaction.transaction).toBe('parent_span'); + expect(transaction.spans).toBeDefined(); + }, +); + +sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + expect(transaction.spans).toHaveLength(1); + + const span_1 = transaction.spans?.[0]; + expect(span_1?.description).toBe('child_span'); + expect(span_1?.parent_span_id).toEqual(transaction?.contexts?.trace?.span_id); + expect(span_1?.origin).toEqual('manual'); + expect(span_1?.data?.[SENTRY_ORIGIN]).toEqual('manual'); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js similarity index 77% rename from dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js rename to dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js index aaafd3396f14..e546c7cc6a94 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js @@ -4,7 +4,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1.0, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js index 35d41b55e69d..afe2527d8c7e 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/subject.js @@ -1,14 +1,15 @@ -async function run() { - Sentry.startSpan({ name: 'parent_span' }, () => { - Sentry.startSpan({ name: 'child_span' }, () => { - // whatever a user would do here - }); - - // unfinished spans are filtered out - Sentry.startInactiveSpan({ name: 'span_4' }); +Sentry.startSpan({ name: 'test-span', op: 'test' }, () => { + Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => { + // noop }); -} -(async () => { - await run(); -})(); + const inactiveSpan = Sentry.startInactiveSpan({ name: 'test-inactive-span' }); + inactiveSpan.end(); + + Sentry.startSpanManual({ name: 'test-manual-span' }, span => { + // 2 = SPAN_STATUS_ERROR. The message must be preserved as the `sentry.status.message` + // attribute on the streamed span, since v2 statuses are reduced to ok/error. + span.setStatus({ code: 2, message: 'Connection Refused' }); + span.end(); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts index 824220e5133f..ba2404e747b5 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts @@ -1,52 +1,297 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + URL_FULL, + SENTRY_SEGMENT_NAME_SOURCE, + SENTRY_SEGMENT_ID, + SENTRY_SEGMENT_NAME, + SENTRY_SDK_NAME, + SENTRY_SDK_VERSION, + SENTRY_TRACE_LIFECYCLE, + USER_AGENT_ORIGINAL, + SENTRY_ENVIRONMENT, + SENTRY_SDK_INTEGRATIONS, + SENTRY_STATUS_MESSAGE, + SENTRY_OP, + SENTRY_ORIGIN, +} from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/browser'; +import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; - -sentryTest( - 'sends a transaction in an envelope with manual origin and custom source', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); - - const attributes = transaction.contexts?.trace?.data; - expect(attributes).toEqual({ - [SENTRY_ORIGIN]: 'manual', - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1, - [SENTRY_SEGMENT_NAME_SOURCE]: 'custom', - }); - - expect(transaction.transaction_info?.source).toBe('custom'); - - expect(transaction.transaction).toBe('parent_span'); - expect(transaction.spans).toBeDefined(); - }, -); - -sentryTest('should report finished spans as children of the root transaction', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; + +sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + const spanEnvelopePromise = waitForStreamedSpanEnvelope(page); const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); + await page.goto(url); + + const spanEnvelope = await spanEnvelopePromise; + + const envelopeHeader = spanEnvelope[0]; + const envelopeItem = spanEnvelope[1]; + const spans = envelopeItem[0][1].items; + + expect(envelopeHeader).toEqual({ + sdk: { + name: 'sentry.javascript.browser', + version: SDK_VERSION, + }, + sent_at: expect.any(String), + trace: { + environment: 'production', + public_key: 'public', + sample_rand: expect.any(String), + sample_rate: '1', + sampled: 'true', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + transaction: 'test-span', + }, + }); + + const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); + const traceId = envelopeHeader.trace!.trace_id; + + expect(Number.isNaN(numericSampleRand)).toBe(false); + + expect(envelopeItem).toEqual([ + [ + { content_type: 'application/vnd.sentry.items.span.v2+json', item_count: 4, type: 'span' }, + { + version: 2, + ingest_settings: { infer_ip: 'auto', infer_user_agent: 'auto' }, + items: expect.any(Array), + }, + ], + ]); - expect(transaction.spans).toHaveLength(1); + const segmentSpanId = spans.find(s => !!s.is_segment)?.span_id; + expect(segmentSpanId).toBeDefined(); - const span_1 = transaction.spans?.[0]; - expect(span_1?.description).toBe('child_span'); - expect(span_1?.parent_span_id).toEqual(transaction?.contexts?.trace?.span_id); - expect(span_1?.origin).toEqual('manual'); - expect(span_1?.data?.['sentry.origin']).toEqual('manual'); + expect(spans).toEqual([ + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_OP]: { + type: 'string', + value: 'test-child', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-child-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-inactive-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_STATUS_MESSAGE]: { + type: 'string', + value: 'Connection Refused', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + }, + end_timestamp: expect.any(Number), + is_segment: false, + name: 'test-manual-span', + parent_span_id: segmentSpanId, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + start_timestamp: expect.any(Number), + status: 'error', + trace_id: traceId, + }, + { + attributes: { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [CULTURE_CALENDAR]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_LOCALE]: { + type: 'string', + value: expect.any(String), + }, + [CULTURE_TIMEZONE]: { + type: 'string', + value: expect.any(String), + }, + [USER_AGENT_ORIGINAL]: { + type: 'string', + value: expect.any(String), + }, + [URL_FULL]: { + type: 'string', + value: expect.any(String), + }, + [SENTRY_OP]: { + type: 'string', + value: 'test', + }, + [SENTRY_ORIGIN]: { + type: 'string', + value: 'manual', + }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { + type: 'integer', + value: 1, + }, + [SENTRY_SDK_NAME]: { + type: 'string', + value: 'sentry.javascript.browser', + }, + [SENTRY_SDK_VERSION]: { + type: 'string', + value: SDK_VERSION, + }, + [SENTRY_SDK_INTEGRATIONS]: { + type: 'array', + value: expect.arrayContaining(['SpanStreaming']), + }, + [SENTRY_SEGMENT_ID]: { + type: 'string', + value: segmentSpanId, + }, + [SENTRY_SEGMENT_NAME]: { + type: 'string', + value: 'test-span', + }, + [SENTRY_SEGMENT_NAME_SOURCE]: { + type: 'string', + value: 'custom', + }, + [SENTRY_ENVIRONMENT]: { + type: 'string', + value: 'production', + }, + [SENTRY_TRACE_LIFECYCLE]: { + type: 'string', + value: 'stream', + }, + }, + end_timestamp: expect.any(Number), + is_segment: true, + name: 'test-span', + span_id: segmentSpanId, + start_timestamp: expect.any(Number), + status: 'ok', + trace_id: traceId, + }, + ]); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js new file mode 100644 index 000000000000..527fad0d6f15 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js new file mode 100644 index 000000000000..c7aa1cd72dfe --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js @@ -0,0 +1,7 @@ +const chicken = {}; +const egg = { contains: chicken }; +chicken.lays = egg; + +Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken, count: 42 } }, () => { + Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken, count: 42 } }, () => undefined); +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts new file mode 100644 index 000000000000..d794f101112d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts @@ -0,0 +1,28 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest( + 'normalizes circular attributes on root and child spans with static tracing', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const event = envelopeRequestParser(await waitForTransactionRequestOnUrl(page, url)); + + expect(event.transaction).toBe('circular_object_test_transaction'); + expect(event.contexts?.trace?.data).toMatchObject({ + chicken: { lays: { contains: '[Circular ~]' } }, + count: 42, + }); + expect(event.spans).toHaveLength(1); + expect(event.spans?.[0]).toMatchObject({ + op: 'circular_object_test_span', + parent_span_id: event.contexts?.trace?.span_id, + data: { chicken: { lays: { contains: '[Circular ~]' } }, count: 42 }, + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js index b5ee8624b124..c7aa1cd72dfe 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/subject.js @@ -2,6 +2,6 @@ const chicken = {}; const egg = { contains: chicken }; chicken.lays = egg; -Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken } }, () => { - Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken } }, () => undefined); +Sentry.startSpan({ name: 'circular_object_test_transaction', attributes: { chicken, count: 42 } }, () => { + Sentry.startSpan({ op: 'circular_object_test_span', attributes: { chicken, count: 42 } }, () => undefined); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts index 41d49b6351ea..7860439b7707 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts @@ -1,29 +1,23 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('should be able to handle circular data', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest( + 'drops circular object attributes while preserving supported attributes on root and child spans', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const url = await getLocalTestUrl({ testDir: __dirname }); + const rootPromise = waitForStreamedSpan(page, span => span.name === 'circular_object_test_transaction'); + const childPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'circular_object_test_span'); + await page.goto(url); - const url = await getLocalTestUrl({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.type).toBe('transaction'); - expect(eventData.transaction).toBe('circular_object_test_transaction'); - - expect(eventData.contexts).toMatchObject({ - trace: { - data: { chicken: { lays: { contains: '[Circular ~]' } } }, - }, - }); - - expect(eventData?.spans?.[0]).toMatchObject({ - data: { chicken: { lays: { contains: '[Circular ~]' } } }, - op: 'circular_object_test_span', - }); - - await new Promise(resolve => setTimeout(resolve, 2000)); -}); + const [rootSpan, childSpan] = await Promise.all([rootPromise, childPromise]); + expect(rootSpan.is_segment).toBe(true); + expect(childSpan.parent_span_id).toBe(rootSpan.span_id); + expect(rootSpan.attributes.count).toEqual({ type: 'integer', value: 42 }); + expect(childSpan.attributes.count).toEqual({ type: 'integer', value: 42 }); + expect(rootSpan.attributes.chicken).toBeUndefined(); + expect(childSpan.attributes.chicken).toBeUndefined(); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts index d8cecd5f09c7..c70b05e99fea 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-reject/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should capture a promise rejection within an async startSpan callback', @@ -11,17 +11,18 @@ sentryTest( } const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); await page.goto(url); const clickPromise = page.getByText('Button 1').click(); - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('ok'); expect(err?.exception?.values?.[0]?.value).toBe( 'Non-Error promise rejection captured with value: Async Promise Rejection', diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts index 79ecf0f6e9e8..42c87027807f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw-not-awaited/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( "should capture a thrown error within an async startSpan callback that's not awaited properly", @@ -9,7 +9,8 @@ sentryTest( if (shouldSkipTracingTest()) { sentryTest.skip(); } - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -17,11 +18,11 @@ sentryTest( const clickPromise = page.getByText('Button 1').click(); // awaiting both events simultaneously to avoid race conditions - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Async Thrown Error'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts index 8f527b4e7238..ce66c78553bc 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-async-throw/test.ts @@ -1,13 +1,14 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { envelopeRequestParser, waitForErrorRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('should capture a thrown error within an async startSpan callback', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const envelopePromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); @@ -15,10 +16,10 @@ sentryTest('should capture a thrown error within an async startSpan callback', a const clickPromise = page.getByText('Button 1').click(); // awaiting both events simultaneously to avoid race conditions - const [, events] = await Promise.all([clickPromise, envelopePromise]); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [, span, errorRequest] = await Promise.all([clickPromise, spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Async Thrown Error'); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts index 9880598b9294..2a1dc368e98f 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts @@ -1,11 +1,13 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; +import type * as SentryBrowser from '@sentry/browser'; import { sentryTest } from '../../../../utils/fixtures'; import { - getMultipleSentryEnvelopeRequests, + envelopeRequestParser, + waitForErrorRequest, runScriptInSandbox, shouldSkipTracingTest, } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should capture an error within a sync startSpan callback', @@ -21,10 +23,20 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const errorEventsPromise = getMultipleSentryEnvelopeRequests(page, 2); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'parent_span'); + const errorPromise = waitForErrorRequest(page); await page.goto(url); + // Force a span envelope to arrive before the error so the error waiter must ignore it. + const precedingSpanPromise = waitForStreamedSpan(page, span => span.name === 'preceding_span'); + await page.evaluate(async () => { + const Sentry = (window as typeof window & { Sentry: typeof SentryBrowser }).Sentry; + Sentry.startSpan({ name: 'preceding_span' }, () => undefined); + await Sentry.flush(); + }); + await precedingSpanPromise; + await runScriptInSandbox(page, { content: ` function run() { @@ -37,12 +49,11 @@ sentryTest( `, }); - const events = await errorEventsPromise; - - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); + const [span, errorRequest] = await Promise.all([spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - expect(txn).toMatchObject({ transaction: 'parent_span' }); + expect(span.name).toBe('parent_span'); + expect(span.status).toBe('error'); expect(err?.exception?.values?.[0]?.value).toBe('Sync Error'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js index 527fad0d6f15..3364667c960c 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1.0, normalizeDepth: 10, diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts index b3edbfac7dcf..b233cae07f97 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-in-scope/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should send manually started parallel root spans outside of root context', @@ -12,21 +12,21 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBeDefined(); expect(trace2Id).toBeDefined(); @@ -34,7 +34,7 @@ sentryTest( // We use the same traceID from the root propagation context here expect(trace1Id).toBe(trace2Id); - expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); - expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); + expect(transaction1.parent_span_id).toBeUndefined(); + expect(transaction2.parent_span_id).toBeUndefined(); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts index 62cdeb9bf7c9..49d52dce3c61 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans-with-parentSpanId/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'should send manually started parallel root spans in root context with parentSpanId', @@ -12,26 +12,26 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBe('12345678901234567890123456789012'); expect(trace2Id).toBe('12345678901234567890123456789012'); - expect(transaction1.contexts?.trace?.parent_span_id).toBe('1234567890123456'); - expect(transaction2.contexts?.trace?.parent_span_id).toBe('1234567890123456'); + expect(transaction1.parent_span_id).toBe('1234567890123456'); + expect(transaction2.parent_span_id).toBe('1234567890123456'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts index 084b7100fa9b..1c1af189cc77 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/parallel-root-spans/test.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('should send manually started parallel root spans in root context', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -10,21 +10,21 @@ sentryTest('should send manually started parallel root spans in root context', a const url = await getLocalTestUrl({ testDir: __dirname }); - const transaction1ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_1'); - const transaction2ReqPromise = waitForTransactionRequest(page, event => event.transaction === 'test_span_2'); + const transaction1ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_1'); + const transaction2ReqPromise = waitForStreamedSpan(page, event => event.name === 'test_span_2'); await page.goto(url); const [transaction1Req, transaction2Req] = await Promise.all([transaction1ReqPromise, transaction2ReqPromise]); - const transaction1 = envelopeRequestParser(transaction1Req); - const transaction2 = envelopeRequestParser(transaction2Req); + const transaction1 = transaction1Req; + const transaction2 = transaction2Req; expect(transaction1).toBeDefined(); expect(transaction2).toBeDefined(); - const trace1Id = transaction1.contexts?.trace?.trace_id; - const trace2Id = transaction2.contexts?.trace?.trace_id; + const trace1Id = transaction1.trace_id; + const trace2Id = transaction2.trace_id; expect(trace1Id).toBeDefined(); expect(trace2Id).toBeDefined(); @@ -32,6 +32,6 @@ sentryTest('should send manually started parallel root spans in root context', a // We use the same traceID from the root propagation context here expect(trace1Id).toBe(trace2Id); - expect(transaction1.contexts?.trace?.parent_span_id).toBeUndefined(); - expect(transaction2.contexts?.trace?.parent_span_id).toBeUndefined(); + expect(transaction1.parent_span_id).toBeUndefined(); + expect(transaction2.parent_span_id).toBeUndefined(); }); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js new file mode 100644 index 000000000000..5f6b23836f8e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + // setMeasurement is a legacy transaction API without a span v2 representation. + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + tracesSampleRate: 1.0, + normalizeDepth: 10, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts index 2604470d0a50..8c257a2645cc 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/test.ts @@ -3,19 +3,22 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; -sentryTest('should attach measurement to transaction', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest( + 'static tracing compatibility: should attach measurement to transaction', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const url = await getLocalTestUrl({ testDir: __dirname }); - const event = await getFirstSentryEnvelopeRequest(page, url); + const url = await getLocalTestUrl({ testDir: __dirname }); + const event = await getFirstSentryEnvelopeRequest(page, url); - expect(event.measurements?.['metric.foo'].value).toBe(42); - expect(event.measurements?.['metric.bar'].value).toBe(1337); - expect(event.measurements?.['metric.baz'].value).toBe(1); + expect(event.measurements?.['metric.foo'].value).toBe(42); + expect(event.measurements?.['metric.bar'].value).toBe(1337); + expect(event.measurements?.['metric.baz'].value).toBe(1); - expect(event.measurements?.['metric.foo'].unit).toBe('ms'); - expect(event.measurements?.['metric.bar'].unit).toBe('nanoseconds'); - expect(event.measurements?.['metric.baz'].unit).toBe(''); -}); + expect(event.measurements?.['metric.foo'].unit).toBe('ms'); + expect(event.measurements?.['metric.bar'].unit).toBe('nanoseconds'); + expect(event.measurements?.['metric.baz'].unit).toBe(''); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js deleted file mode 100644 index afe2527d8c7e..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js +++ /dev/null @@ -1,15 +0,0 @@ -Sentry.startSpan({ name: 'test-span', op: 'test' }, () => { - Sentry.startSpan({ name: 'test-child-span', op: 'test-child' }, () => { - // noop - }); - - const inactiveSpan = Sentry.startInactiveSpan({ name: 'test-inactive-span' }); - inactiveSpan.end(); - - Sentry.startSpanManual({ name: 'test-manual-span' }, span => { - // 2 = SPAN_STATUS_ERROR. The message must be preserved as the `sentry.status.message` - // attribute on the streamed span, since v2 statuses are reduced to ok/error. - span.setStatus({ code: 2, message: 'Connection Refused' }); - span.end(); - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts deleted file mode 100644 index 5cc3bd34f817..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { expect } from '@playwright/test'; -import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; -import { - SENTRY_SEGMENT_NAME_SOURCE, - SENTRY_SEGMENT_ID, - SENTRY_SEGMENT_NAME, - SENTRY_SDK_NAME, - SENTRY_SDK_VERSION, - SENTRY_TRACE_LIFECYCLE, - USER_AGENT_ORIGINAL, - SENTRY_STATUS_MESSAGE, - SENTRY_ENVIRONMENT, - SENTRY_SDK_INTEGRATIONS, - SENTRY_OP, - SENTRY_ORIGIN, -} from '@sentry/conventions/attributes'; - -sentryTest( - 'sends a streamed span envelope if spanStreamingIntegration is enabled', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const spanEnvelopePromise = waitForStreamedSpanEnvelope(page); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const spanEnvelope = await spanEnvelopePromise; - - const envelopeHeader = spanEnvelope[0]; - const envelopeItem = spanEnvelope[1]; - const spans = envelopeItem[0][1].items; - - expect(envelopeHeader).toEqual({ - sdk: { - name: 'sentry.javascript.browser', - version: SDK_VERSION, - }, - sent_at: expect.any(String), - trace: { - environment: 'production', - public_key: 'public', - sample_rand: expect.any(String), - sample_rate: '1', - sampled: 'true', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - transaction: 'test-span', - }, - }); - - const numericSampleRand = parseFloat(envelopeHeader.trace!.sample_rand!); - const traceId = envelopeHeader.trace!.trace_id; - - expect(Number.isNaN(numericSampleRand)).toBe(false); - - expect(envelopeItem).toEqual([ - [ - { content_type: 'application/vnd.sentry.items.span.v2+json', item_count: 4, type: 'span' }, - { - version: 2, - ingest_settings: { infer_ip: 'auto', infer_user_agent: 'auto' }, - items: expect.any(Array), - }, - ], - ]); - - const segmentSpanId = spans.find(s => !!s.is_segment)?.span_id; - expect(segmentSpanId).toBeDefined(); - - expect(spans).toEqual([ - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_OP]: { - type: 'string', - value: 'test-child', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-child-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-inactive-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_STATUS_MESSAGE]: { - type: 'string', - value: 'Connection Refused', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - }, - end_timestamp: expect.any(Number), - is_segment: false, - name: 'test-manual-span', - parent_span_id: segmentSpanId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - start_timestamp: expect.any(Number), - status: 'error', - trace_id: traceId, - }, - { - attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - 'culture.calendar': { - type: 'string', - value: expect.any(String), - }, - 'culture.locale': { - type: 'string', - value: expect.any(String), - }, - 'culture.timezone': { - type: 'string', - value: expect.any(String), - }, - [USER_AGENT_ORIGINAL]: { - type: 'string', - value: expect.any(String), - }, - 'url.full': { - type: 'string', - value: expect.any(String), - }, - [SENTRY_OP]: { - type: 'string', - value: 'test', - }, - [SENTRY_ORIGIN]: { - type: 'string', - value: 'manual', - }, - [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { - type: 'integer', - value: 1, - }, - [SENTRY_SDK_NAME]: { - type: 'string', - value: 'sentry.javascript.browser', - }, - [SENTRY_SDK_VERSION]: { - type: 'string', - value: SDK_VERSION, - }, - [SENTRY_SDK_INTEGRATIONS]: { - type: 'array', - value: expect.arrayContaining(['SpanStreaming']), - }, - [SENTRY_SEGMENT_ID]: { - type: 'string', - value: segmentSpanId, - }, - [SENTRY_SEGMENT_NAME]: { - type: 'string', - value: 'test-span', - }, - [SENTRY_SEGMENT_NAME_SOURCE]: { - type: 'string', - value: 'custom', - }, - [SENTRY_ENVIRONMENT]: { - type: 'string', - value: 'production', - }, - [SENTRY_TRACE_LIFECYCLE]: { - type: 'string', - value: 'stream', - }, - }, - end_timestamp: expect.any(Number), - is_segment: true, - name: 'test-span', - span_id: segmentSpanId, - start_timestamp: expect.any(Number), - status: 'ok', - trace_id: traceId, - }, - ]); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts index 44008a93a3e9..ed6ab723310c 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/thenable-with-extra-methods/test.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -8,7 +9,7 @@ sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getL } const url = await getLocalTestUrl({ testDir: __dirname }); - const transactionPromise = waitForTransactionRequest(page); + const spanPromise = waitForStreamedSpan(page, span => span.name === 'test-jqxhr'); await page.goto(url); @@ -27,9 +28,9 @@ sentryTest('preserves extra methods on real jQuery jqXHR objects', async ({ getL const testError = await page.evaluate(() => (window as any).jqXHRTestError); expect(testError).toBeNull(); - const transaction = envelopeRequestParser(await transactionPromise); - expect(transaction.transaction).toBe('test-jqxhr'); - expect(transaction.spans).toBeDefined(); + const span = await spanPromise; + expect(span.name).toBe('test-jqxhr'); + expect(span.is_segment).toBe(true); }); sentryTest('aborted request rejects promise correctly', async ({ getLocalTestUrl, page }) => { diff --git a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js index bd0421b32761..7c200c542c56 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts index f95829febe1d..3b269806fc84 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/bindScopeToEmitter/test.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipCdnBundleTest, - shouldSkipTracingTest, - waitForTransactionRequest, -} from '../../../utils/helpers'; +import { shouldSkipCdnBundleTest, shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('bindScopeToEmitter runs listeners with the bound scope active', async ({ getLocalTestUrl, page }) => { // `bindScopeToEmitter` is not exported from the CDN bundles, only from npm. @@ -13,19 +9,19 @@ sentryTest('bindScopeToEmitter runs listeners with the bound scope active', asyn sentryTest.skip(); } - const req = waitForTransactionRequest(page, e => e.transaction === 'parent'); + const rootSpanPromise = waitForStreamedSpan(page, span => span.name === 'parent'); + const childSpanPromise = waitForStreamedSpan(page, span => span.name === 'child-bound'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const parentEvent = envelopeRequestParser(await req); - const parentSpanId = parentEvent.contexts?.trace?.span_id; - const parentTraceId = parentEvent.contexts?.trace?.trace_id; + const [parentEvent, childBound] = await Promise.all([rootSpanPromise, childSpanPromise]); + const parentSpanId = parentEvent.span_id; + const parentTraceId = parentEvent.trace_id; expect(parentSpanId).toMatch(/[a-f\d]{16}/); // The listener fired while no span was active, yet `child-bound` is nested under `parent` // because the parent scope was bound to the emitter. - const childBound = parentEvent.spans?.find(s => s.description === 'child-bound'); expect(childBound).toBeDefined(); expect(childBound?.parent_span_id).toBe(parentSpanId); expect(childBound?.trace_id).toBe(parentTraceId); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js index fdca84ba5c42..3595ea724749 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegrationShim/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', sampleRate: 1, integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js index 65cfbab96ee2..b2280b70e307 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts index f53e4ef0615f..ecca9689b964 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/full-tracing/test.ts @@ -1,13 +1,15 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, shouldSkipTracingTest, waitForErrorRequest, - waitForTransactionRequest, } from '../../../../utils/helpers'; +import { + collectStreamedSpans, + waitForStreamedSpanAndTraceHeader, + waitForStreamedSpanAndTraceHeaderOnUrl, +} from '../../../../utils/spanUtils'; const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const SAMPLED_SPAN_ID = '1234567890123456'; @@ -28,26 +30,24 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - // Discard the initial pageload transaction. - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === SAMPLED_TRACE_ID, + span => span.is_segment && span.name === 'continued-sampled', ); await page.locator('#sampled').click(); - const req = await transactionPromise; - const transaction = eventAndTraceHeaderRequestParser(req); - const traceContext = transaction[0].contexts?.trace; + const span = await spanPromise; + const traceContext = span[0]; - expect(traceContext?.trace_id).toBe(SAMPLED_TRACE_ID); - expect(traceContext?.parent_span_id).toBe(SAMPLED_SPAN_ID); - expect(transaction[0].transaction).toBe('continued-sampled'); + expect(traceContext.trace_id).toBe(SAMPLED_TRACE_ID); + expect(traceContext.parent_span_id).toBe(SAMPLED_SPAN_ID); + expect(span[0].name).toBe('continued-sampled'); // The incoming positive sampling decision is honored regardless of local config. - expect(transaction[1]?.sampled).toBe('true'); + expect(span[1]?.sampled).toBe('true'); // Outgoing request carries the continued trace. const outgoingRequest = await outgoingRequestPromise; @@ -58,7 +58,7 @@ sentryTest( ); sentryTest( - 'continueTrace continues an unsampled incoming trace without emitting a transaction', + 'continueTrace continues an unsampled incoming trace without emitting a span', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -71,11 +71,11 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - // The captured error carries the continued (unsampled) trace even though no transaction is sent. const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#unsampled').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -85,6 +85,8 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${UNSAMPLED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === UNSAMPLED_TRACE_ID)).toBe(false); }, ); @@ -101,20 +103,18 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - await getFirstSentryEnvelopeRequest(page, url, eventAndTraceHeaderRequestParser); + await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - // With tracesSampleRate=1 the deferred decision resolves to sampled, so a transaction is emitted - // on the continued trace id. - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === DEFERRED_TRACE_ID, + span => span.is_segment && span.name === 'continued-deferred', ); await page.locator('#deferred').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - expect(transaction[0].contexts?.trace?.trace_id).toBe(DEFERRED_TRACE_ID); - expect(transaction[0].transaction).toBe('continued-deferred'); + const span = await spanPromise; + expect(span[0].trace_id).toBe(DEFERRED_TRACE_ID); + expect(span[0].name).toBe('continued-deferred'); }, ); @@ -129,21 +129,20 @@ sentryTest('continueTrace with no incoming trace starts a fresh trace', async ({ return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - const [pageloadEvent] = await getFirstSentryEnvelopeRequest( + const [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceId = pageloadEvent.trace_id; + + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - url, - eventAndTraceHeaderRequestParser, + span => span.is_segment && span.name === 'continued-noTrace', ); - const pageloadTraceId = pageloadEvent.contexts?.trace?.trace_id; - - const transactionPromise = waitForTransactionRequest(page, event => event.transaction === 'continued-noTrace'); await page.locator('#noTrace').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - const traceId = transaction[0].contexts?.trace?.trace_id; + const span = await spanPromise; + const traceId = span[0].trace_id; expect(traceId).toMatch(/^[a-f0-9]{32}$/); expect(traceId).not.toBe(pageloadTraceId); - expect(transaction[0].contexts?.trace).not.toHaveProperty('parent_span_id'); + expect(span[0]).not.toHaveProperty('parent_span_id'); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js index 06a40a8e55ec..5e9c86105ae7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts index 77956fb134bf..e7321abb38ee 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracesSampleRate-zero/test.ts @@ -4,8 +4,8 @@ import { eventAndTraceHeaderRequestParser, shouldSkipTracingTest, waitForErrorRequest, - waitForTransactionRequest, } from '../../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpanAndTraceHeader } from '../../../../utils/spanUtils'; const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const SAMPLED_SPAN_ID = '1234567890123456'; @@ -26,18 +26,17 @@ sentryTest( return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({}) }); }); - // With tracesSampleRate=0 there is no pageload transaction, so we only wait for the continued one. - const transactionPromise = waitForTransactionRequest( + const spanPromise = waitForStreamedSpanAndTraceHeader( page, - event => event.contexts?.trace?.trace_id === SAMPLED_TRACE_ID, + span => span.is_segment && span.name === 'continued-sampled', ); await page.goto(url); await page.locator('#sampled').click(); - const transaction = eventAndTraceHeaderRequestParser(await transactionPromise); - expect(transaction[0].contexts?.trace?.trace_id).toBe(SAMPLED_TRACE_ID); - expect(transaction[0].contexts?.trace?.parent_span_id).toBe(SAMPLED_SPAN_ID); + const span = await spanPromise; + expect(span[0].trace_id).toBe(SAMPLED_TRACE_ID); + expect(span[0].parent_span_id).toBe(SAMPLED_SPAN_ID); const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); @@ -46,7 +45,7 @@ sentryTest( ); sentryTest( - 'continueTrace does not emit a transaction for a deferred decision with tracesSampleRate=0', + 'continueTrace does not emit a span for a deferred decision with tracesSampleRate=0', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -61,9 +60,9 @@ sentryTest( await page.goto(url); - // The captured error carries the continued trace even though no transaction is sent. const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#deferred').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -73,6 +72,8 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${DEFERRED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === DEFERRED_TRACE_ID)).toBe(false); }, ); @@ -94,6 +95,7 @@ sentryTest( const errorPromise = waitForErrorRequest(page); + const spans = collectStreamedSpans(page); await page.locator('#unsampled').click(); const [errorEvent] = eventAndTraceHeaderRequestParser(await errorPromise); @@ -102,5 +104,7 @@ sentryTest( const outgoingRequest = await outgoingRequestPromise; const headers = await outgoingRequest.allHeaders(); expect(headers['sentry-trace']).toMatch(new RegExp(`^${UNSAMPLED_TRACE_ID}-[a-f0-9]{16}-0$`)); + await page.evaluate(() => (window as any).Sentry.flush()); + expect(spans.some(span => span.trace_id === UNSAMPLED_TRACE_ID)).toBe(false); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js index 9eb615a288dd..79d41d4ce43c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/init.js @@ -5,7 +5,6 @@ window.Sentry = Sentry; // In the browser, "tracing without performance" (TwP) means enabling `browserTracingIntegration` // but not setting `tracesSampleRate`. Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts index e9ebd9dcec57..bbf746fdc4df 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/continueTrace/tracing-without-performance/test.ts @@ -10,7 +10,7 @@ const SAMPLED_TRACE_ID = '12345678901234567890123456789012'; const UNSAMPLED_TRACE_ID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; const DEFERRED_TRACE_ID = 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'; -// In TwP mode no transactions are emitted, so each variant is observed via the captured error event +// In TwP mode no spans are emitted, so each variant is observed via the captured error event // (which carries the continued trace on its `contexts.trace`) and the outgoing request headers. const VARIANTS = [ { button: 'sampled', traceId: SAMPLED_TRACE_ID, sampledFlag: '-1' }, 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 index 01c1dc121daf..b6c2bad12033 100644 --- 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 @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration({ instrumentNavigation: false, instrumentPageLoad: false })], tracesSampleRate: 1, 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 index 7d33497a988b..23ec0ce9edda 100644 --- 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 @@ -8,6 +8,7 @@ import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest, } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeader } from '../../../utils/spanUtils'; sentryTest('updates the DSC when the txn name is updated and high-quality', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -38,7 +39,7 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn 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 + 11. Make another request and check that it starts its own segment in the same trace 12. Capture an error and check that envelope trace header has no span information */ @@ -133,16 +134,9 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn }); // 10 - const txnEventPromise = getMultipleSentryEnvelopeRequests( - page, - 1, - { envelopeType: 'transaction' }, - eventAndTraceHeaderRequestParser, - ); - + const spanPromise = waitForStreamedSpanAndTraceHeader(page, span => span.name === 'updated-root-span-2'); await page.locator('#btnEndSpan').click(); - - const [txnEvent, txnEnvelopeTraceHeader] = (await txnEventPromise)[0]; + const [span, txnEnvelopeTraceHeader] = await spanPromise; expect(txnEnvelopeTraceHeader).toEqual({ environment: 'production', public_key: 'public', @@ -154,16 +148,28 @@ sentryTest('updates the DSC when the txn name is updated and high-quality', asyn sample_rand: expect.any(String), }); - expect(txnEvent.transaction).toEqual('updated-root-span-2'); + expect(span.name).toEqual('updated-root-span-2'); // 11 + const requestSpanPromise = waitForStreamedSpanAndTraceHeader( + page, + span => span.name === 'GET sentry-test-site.example', + ); const baggageItemsAfterEnd = await makeRequestAndGetBaggageItems(page); expect(baggageItemsAfterEnd).toEqual([ 'sentry-environment=production', 'sentry-public_key=public', 'sentry-release=1.1.1', + expect.stringMatching(/sentry-sample_rand=0\.\d+/), + 'sentry-sample_rate=1', + 'sentry-sampled=true', `sentry-trace_id=${traceId}`, + 'sentry-transaction=GET%20sentry-test-site.example', ]); + const [requestSpan] = await requestSpanPromise; + expect(requestSpan.is_segment).toBe(true); + expect(requestSpan.trace_id).toBe(traceId); + expect(requestSpan).not.toHaveProperty('parent_span_id'); // 12 const errorEnvelopeTraceHeaderAfterEnd = await captureErrorAndGetEnvelopeTraceHeader(page); diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js index 66fe57da4d6f..376c14f7b559 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/init.js @@ -4,7 +4,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], environment: 'production', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts index 23043ebc770d..9e146d46bfac 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header-transaction-name/test.ts @@ -1,11 +1,7 @@ import { expect } from '@playwright/test'; -import type { EventEnvelopeHeaders } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../utils/spanUtils'; sentryTest( 'should only include transaction name if source is better than an unparameterized URL', @@ -16,13 +12,13 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); + const [, traceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ + expect(traceHeader).toBeDefined(); + expect(traceHeader).toEqual({ environment: 'production', sample_rate: '1', - transaction: expect.stringContaining('/index.html'), + transaction: 'Pageload', trace_id: expect.stringMatching(/[a-f\d]{32}/), public_key: 'public', sampled: 'true', diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js index b90217ce3c39..79b4b0c89555 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: [/.*/], diff --git a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts index 7617b498efb2..242bf9ff462d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/envelope-header/test.ts @@ -1,14 +1,10 @@ import { expect } from '@playwright/test'; -import type { EventEnvelopeHeaders } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../utils/spanUtils'; sentryTest( - 'should send dynamic sampling context data in trace envelope header of a transaction envelope', + 'should send dynamic sampling context data in trace envelope header of a streamed span envelope', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); @@ -16,14 +12,14 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const envHeader = await getFirstSentryEnvelopeRequest(page, url, envelopeHeaderRequestParser); + const [, traceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); // In this test, we don't expect trace.transaction to be present because without a custom routing instrumentation // we for now don't have parameterization. This might change in the future but for now the only way of having // transaction in DSC with the default browserTracingIntegration is when the transaction name is set manually. // This scenario is covered in another integration test (envelope-header-transaction-name). - expect(envHeader.trace).toBeDefined(); - expect(envHeader.trace).toEqual({ + expect(traceHeader).toBeDefined(); + expect(traceHeader).toEqual({ environment: 'production', sample_rate: '1', trace_id: expect.stringMatching(/[a-f\d]{32}/), diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js similarity index 54% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js index 94db849f5fde..10e5ac1b84eb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/init.js @@ -4,10 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false }), - Sentry.spanStreamingIntegration(), - ], + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false })], tracesSampleRate: 1, sendClientReports: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts similarity index 63% rename from dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts index 81d41ada7ccc..634068358415 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts @@ -1,10 +1,11 @@ +import { SENTRY_ORIGIN, SENTRY_OP, URL_DOMAIN } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; import { getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest( - 'sends http.client span for fetch requests without an active span when span streaming is enabled', + 'sends http.client span for fetch requests without an active span with default span streaming', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); @@ -24,9 +25,11 @@ sentryTest( const span = await spanPromise; + expect(span.is_segment).toBe(true); + expect(span).not.toHaveProperty('parent_span_id'); expect(span.name).toBe('GET sentry-test-site.example'); - expect(span.attributes['sentry.origin']).toEqual({ type: 'string', value: 'auto.http.browser' }); - expect(span.attributes['sentry.op']).toEqual({ type: 'string', value: 'http.client' }); - expect(span.attributes['url.domain']).toEqual({ type: 'string', value: 'sentry-test-site.example' }); + expect(span.attributes[SENTRY_ORIGIN]).toEqual({ type: 'string', value: 'auto.http.browser' }); + expect(span.attributes[SENTRY_OP]).toEqual({ type: 'string', value: 'http.client' }); + expect(span.attributes[URL_DOMAIN]).toEqual({ type: 'string', value: 'sentry-test-site.example' }); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js new file mode 100644 index 000000000000..9e5c6fa2f2e9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js @@ -0,0 +1,50 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [ + Sentry.browserTracingIntegration({ + idleTimeout: 3000, + finalTimeout: 3000, + childSpanTimeout: 3000, + }), + ], + ignoreSpans: [/ignore/], + tracesSampleRate: 1, + debug: true, +}); + +const waitFor = time => new Promise(resolve => setTimeout(resolve, time)); + +Sentry.startSpanManual( + { + name: 'take-me', + }, + async span => { + await waitFor(500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me', + }, + async span => { + await waitFor(1500); + span.end(); + }, +); + +Sentry.startSpanManual( + { + name: 'ignore-me-too', + }, + async span => { + await waitFor(2500); + span.end(); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts new file mode 100644 index 000000000000..bc752c9cdf41 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; + +sentryTest( + 'adjusts the end timestamp of the root idle span if child spans are ignored', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const eventData = envelopeRequestParser(await pageloadRequestPromise); + + const { start_timestamp: startTimestamp, timestamp: endTimestamp } = eventData; + const durationSeconds = endTimestamp! - startTimestamp!; + + const spans = eventData.spans || []; + + expect(durationSeconds).toBeGreaterThan(0); + expect(durationSeconds).toBeLessThan(1.5); + + expect(spans.some(span => span.description === 'take-me')).toBe(true); + expect(spans.some(span => span.description?.includes('ignore-me'))).toBe(false); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js similarity index 82% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js index f57cb5bfaa57..d4000b23ae24 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [{ attributes: { 'http.response.status_code': 200 } }], tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/attributes/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/attributes/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js similarity index 82% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js index 086383ccdd54..4346f1d8f878 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [/ignore/, { op: 'ignored-op' }], parentSpanIsAlwaysRootSpan: false, tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/child/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/child/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js similarity index 76% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js index 588769fbea94..df2361332e75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [/ignored-click-listener/], parentSpanIsAlwaysRootSpan: false, tracePropagationTargets: ['sentry-test-external.io'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-child/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-child/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js similarity index 76% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js index 788e05781740..2bd0d43829a3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [{ attributes: { 'sentry.op': 'http.client' } }], tracePropagationTargets: ['sentry-test-external.io'], tracesSampleRate: 0, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-http-client/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-http-client/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js similarity index 74% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js index 616ae3d942f6..8233baa577e3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], ignoreSpans: [{ op: 'pageload' }], tracePropagationTargets: ['sentry-test-external.io'], tracesSampleRate: 0, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/template.html b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/template.html rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/continued-trace-segment/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/continued-trace-segment/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js index 9e5c6fa2f2e9..385a1cdf1df5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [ Sentry.browserTracingIntegration({ diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js similarity index 79% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js index 3a710324f0e1..6f5aa2608b79 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], ignoreSpans: [/ignore/], tracesSampleRate: 1, debug: true, diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/subject.js b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-streamed/segment/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/segment/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts index bc752c9cdf41..aca9af0055e6 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/ignoreSpans/test.ts @@ -1,6 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest( 'adjusts the end timestamp of the root idle span if child spans are ignored', @@ -9,21 +10,22 @@ sentryTest( sentryTest.skip(); } - const pageloadRequestPromise = waitForTransactionRequest(page, event => event.contexts?.trace?.op === 'pageload'); + const spans = collectStreamedSpans(page); + const pageloadRequestPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const eventData = envelopeRequestParser(await pageloadRequestPromise); + const eventData = await pageloadRequestPromise; - const { start_timestamp: startTimestamp, timestamp: endTimestamp } = eventData; + const { start_timestamp: startTimestamp, end_timestamp: endTimestamp } = eventData; const durationSeconds = endTimestamp! - startTimestamp!; - const spans = eventData.spans || []; + await page.evaluate(() => (window as any).Sentry.flush()); expect(durationSeconds).toBeGreaterThan(0); expect(durationSeconds).toBeLessThan(1.5); - expect(spans.some(span => span.description === 'take-me')).toBe(true); - expect(spans.some(span => span.description?.includes('ignore-me'))).toBe(false); + expect(spans.some(span => span.name === 'take-me')).toBe(true); + expect(spans.some(span => span.name?.includes('ignore-me'))).toBe(false); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js similarity index 75% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js rename to dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js index 9afcee48dc4a..bd0421b32761 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts new file mode 100644 index 000000000000..063569d95996 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts @@ -0,0 +1,77 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; +import type { SpanJSON, TransactionEvent } from '@sentry/core'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; + +sentryTest('should link spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); + const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); + const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); + + const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; + const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + + expect(rootSpan1.transaction).toBe('rootSpan1'); + expect(rootSpan1.spans).toEqual([]); + + expect(rootSpan2.transaction).toBe('rootSpan2'); + expect(rootSpan2.spans).toEqual([]); + + expect(rootSpan2.contexts?.trace?.links?.length).toBe(1); + expect(rootSpan2.contexts?.trace?.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + sampled: true, + span_id: rootSpan1_spanId, + trace_id: rootSpan1_traceId, + }); +}); + +sentryTest('should link spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); + const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); + const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + + const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; + const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + + const [childSpan_3_1, childSpan_3_2] = rootSpan3.spans as [SpanJSON, SpanJSON]; + const rootSpan3_traceId = rootSpan3.contexts?.trace?.trace_id as string; + const rootSpan3_spanId = rootSpan3.contexts?.trace?.span_id as string; + + expect(rootSpan3.transaction).toBe('rootSpan3'); + + expect(childSpan_3_1.description).toBe('childSpan3.1'); + expect(childSpan_3_1.links?.length).toBe(1); + expect(childSpan_3_1.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, + sampled: true, + span_id: rootSpan1_spanId, + trace_id: rootSpan1_traceId, + }); + + expect(childSpan_3_2.description).toBe('childSpan3.2'); + expect(childSpan_3_2.links?.[0]).toMatchObject({ + sampled: true, + span_id: rootSpan3_spanId, + trace_id: rootSpan3_traceId, + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts deleted file mode 100644 index bafa95aee03f..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../utils/helpers'; -import { waitForStreamedSpan, waitForStreamedSpans } from '../../../utils/spanUtils'; - -sentryTest('links spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); - const rootSpan2Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan2' && !!s.is_segment); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const rootSpan1 = await rootSpan1Promise; - const rootSpan2 = await rootSpan2Promise; - - expect(rootSpan1.name).toBe('rootSpan1'); - expect(rootSpan1.links).toBeUndefined(); - - expect(rootSpan2.name).toBe('rootSpan2'); - expect(rootSpan2.links).toHaveLength(1); - expect(rootSpan2.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, - sampled: true, - span_id: rootSpan1.span_id, - trace_id: rootSpan1.trace_id, - }); -}); - -sentryTest('links spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); - const rootSpan3SpansPromise = waitForStreamedSpans(page, spans => - spans.some(s => s.name === 'rootSpan3' && s.is_segment), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const rootSpan1 = await rootSpan1Promise; - const rootSpan3Spans = await rootSpan3SpansPromise; - - const rootSpan3 = rootSpan3Spans.find(s => s.name === 'rootSpan3')!; - const childSpan1 = rootSpan3Spans.find(s => s.name === 'childSpan3.1')!; - const childSpan2 = rootSpan3Spans.find(s => s.name === 'childSpan3.2')!; - - expect(rootSpan3.name).toBe('rootSpan3'); - - expect(childSpan1.name).toBe('childSpan3.1'); - expect(childSpan1.links).toHaveLength(1); - expect(childSpan1.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, - sampled: true, - span_id: rootSpan1.span_id, - trace_id: rootSpan1.trace_id, - }); - - expect(childSpan2.name).toBe('childSpan3.2'); - expect(childSpan2.links?.[0]).toMatchObject({ - sampled: true, - span_id: rootSpan3.span_id, - trace_id: rootSpan3.trace_id, - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts index 9c556335651e..e7f009c0e911 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink/test.ts @@ -1,76 +1,66 @@ -import { expect } from '@playwright/test'; -import type { SpanJSON, TransactionEvent } from '@sentry/core'; -import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; - -sentryTest('should link spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; +sentryTest('links spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); + const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); + const rootSpan2Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan2' && !!s.is_segment); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; - expect(rootSpan2.transaction).toBe('rootSpan2'); - expect(rootSpan2.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(rootSpan1.links).toBeUndefined(); - expect(rootSpan2.contexts?.trace?.links?.length).toBe(1); - expect(rootSpan2.contexts?.trace?.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': 'previous_trace' }, + expect(rootSpan2.name).toBe('rootSpan2'); + expect(rootSpan2.links).toHaveLength(1); + expect(rootSpan2.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, - span_id: rootSpan1_spanId, - trace_id: rootSpan1_traceId, + span_id: rootSpan1.span_id, + trace_id: rootSpan1.trace_id, }); }); +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpan } from '../../../utils/spanUtils'; -sentryTest('should link spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('links spans with addLink() in nested startSpan() calls', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, s => s.name === 'rootSpan1' && !!s.is_segment); + const rootSpan3Promise = waitForStreamedSpan(page, span => span.name === 'rootSpan3' && span.is_segment); + const childSpan1Promise = waitForStreamedSpan(page, span => span.name === 'childSpan3.1'); + const childSpan2Promise = waitForStreamedSpan(page, span => span.name === 'childSpan3.2'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); - - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - - const [childSpan_3_1, childSpan_3_2] = rootSpan3.spans as [SpanJSON, SpanJSON]; - const rootSpan3_traceId = rootSpan3.contexts?.trace?.trace_id as string; - const rootSpan3_spanId = rootSpan3.contexts?.trace?.span_id as string; + const rootSpan1 = await rootSpan1Promise; + const [rootSpan3, childSpan1, childSpan2] = await Promise.all([ + rootSpan3Promise, + childSpan1Promise, + childSpan2Promise, + ]); - expect(rootSpan3.transaction).toBe('rootSpan3'); + expect(rootSpan3.name).toBe('rootSpan3'); - expect(childSpan_3_1.description).toBe('childSpan3.1'); - expect(childSpan_3_1.links?.length).toBe(1); - expect(childSpan_3_1.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': 'previous_trace' }, + expect(childSpan1.name).toBe('childSpan3.1'); + expect(childSpan1.links).toHaveLength(1); + expect(childSpan1.links?.[0]).toMatchObject({ + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, - span_id: rootSpan1_spanId, - trace_id: rootSpan1_traceId, + span_id: rootSpan1.span_id, + trace_id: rootSpan1.trace_id, }); - expect(childSpan_3_2.description).toBe('childSpan3.2'); - expect(childSpan_3_2.links?.[0]).toMatchObject({ + expect(childSpan2.name).toBe('childSpan3.2'); + expect(childSpan2.links?.[0]).toMatchObject({ sampled: true, - span_id: rootSpan3_spanId, - trace_id: rootSpan3_traceId, + span_id: rootSpan3.span_id, + trace_id: rootSpan3.trace_id, }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts index 529eae04ae03..56aaebf93cde 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLinks/test.ts @@ -1,47 +1,50 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { SpanJSON, TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('should link spans with addLinks() in trace context', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan1'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan3Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan3'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; + const rootSpan3 = await rootSpan3Promise; - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; + const rootSpan1_traceId = rootSpan1.trace_id as string; + const rootSpan1_spanId = rootSpan1.span_id as string; - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan1.span_id)).toEqual([]); - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - expect(rootSpan2.transaction).toBe('rootSpan2'); - expect(rootSpan2.spans).toEqual([]); + expect(rootSpan2.name).toBe('rootSpan2'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan2.span_id)).toEqual([]); - expect(rootSpan3.transaction).toBe('rootSpan3'); - expect(rootSpan3.spans).toEqual([]); - expect(rootSpan3.contexts?.trace?.links?.length).toBe(2); - expect(rootSpan3.contexts?.trace?.links).toEqual([ + expect(rootSpan3.name).toBe('rootSpan3'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan3.span_id)).toEqual([]); + expect(rootSpan3.links?.length).toBe(2); + expect(rootSpan3.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, @@ -54,37 +57,40 @@ sentryTest('should link spans with addLinks() in nested startSpan() calls', asyn sentryTest.skip(); } - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan4Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan4'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan4Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan4'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan4 = envelopeRequestParser(await rootSpan4Promise); + const rootSpan2 = await rootSpan2Promise; + const rootSpan4 = await rootSpan4Promise; - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - const [childSpan_4_1, childSpan_4_2] = rootSpan4.spans as [SpanJSON, SpanJSON]; - const rootSpan4_traceId = rootSpan4.contexts?.trace?.trace_id as string; - const rootSpan4_spanId = rootSpan4.contexts?.trace?.span_id as string; + const childSpan_4_1 = allSpans.find(span => span.name === 'childSpan4.1')!; + const childSpan_4_2 = allSpans.find(span => span.name === 'childSpan4.2')!; + const rootSpan4_traceId = rootSpan4.trace_id as string; + const rootSpan4_spanId = rootSpan4.span_id as string; - expect(rootSpan4.transaction).toBe('rootSpan4'); + expect(rootSpan4.name).toBe('rootSpan4'); - expect(childSpan_4_1.description).toBe('childSpan4.1'); + expect(childSpan_4_1.name).toBe('childSpan4.1'); expect(childSpan_4_1.links).toBe(undefined); - expect(childSpan_4_2.description).toBe('childSpan4.2'); + expect(childSpan_4_2.name).toBe('childSpan4.2'); expect(childSpan_4_2.links?.length).toBe(2); expect(childSpan_4_2.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan4_spanId, trace_id: rootSpan4_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js index 848d72e6bfdc..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts index c2a2ed02f0e6..c07214170d5c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts @@ -1,45 +1,48 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; -import type { TransactionEvent } from '@sentry/core'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('should link spans by adding "links" to span options', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { sentryTest.skip(); } - const rootSpan1Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan1'); - const rootSpan2Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan2'); - const rootSpan3Promise = waitForTransactionRequest(page, event => event.transaction === 'rootSpan3'); + const rootSpan1Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan1'); + const rootSpan2Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan2'); + const rootSpan3Promise = waitForStreamedSpan(page, event => event.name === 'rootSpan3'); + const allSpans = collectStreamedSpans(page); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const rootSpan1 = envelopeRequestParser(await rootSpan1Promise); - const rootSpan2 = envelopeRequestParser(await rootSpan2Promise); - const rootSpan3 = envelopeRequestParser(await rootSpan3Promise); + const rootSpan1 = await rootSpan1Promise; + const rootSpan2 = await rootSpan2Promise; + const rootSpan3 = await rootSpan3Promise; - const rootSpan1_traceId = rootSpan1.contexts?.trace?.trace_id as string; - const rootSpan1_spanId = rootSpan1.contexts?.trace?.span_id as string; - const rootSpan2_traceId = rootSpan2.contexts?.trace?.trace_id as string; - const rootSpan2_spanId = rootSpan2.contexts?.trace?.span_id as string; + const rootSpan1_traceId = rootSpan1.trace_id as string; + const rootSpan1_spanId = rootSpan1.span_id as string; + const rootSpan2_traceId = rootSpan2.trace_id as string; + const rootSpan2_spanId = rootSpan2.span_id as string; - expect(rootSpan1.transaction).toBe('rootSpan1'); - expect(rootSpan1.spans).toEqual([]); + expect(rootSpan1.name).toBe('rootSpan1'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan1.span_id)).toEqual([]); - expect(rootSpan3.transaction).toBe('rootSpan3'); - expect(rootSpan3.spans?.length).toBe(1); - expect(rootSpan3.spans?.[0].description).toBe('childSpan3.1'); + expect(rootSpan3.name).toBe('rootSpan3'); + expect(allSpans.filter(span => span.parent_span_id === rootSpan3.span_id)?.length).toBe(1); + expect(allSpans.find(span => span.name === 'childSpan3.1')?.name).toBe('childSpan3.1'); - expect(rootSpan3.contexts?.trace?.links?.length).toBe(2); - expect(rootSpan3.contexts?.trace?.links).toEqual([ + expect(rootSpan3.links?.length).toBe(2); + expect(rootSpan3.links).toEqual([ { + attributes: {}, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js new file mode 100644 index 000000000000..d4062aa6eb8f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js @@ -0,0 +1,16 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], + tracesSampleRate: 1, +}); + +Sentry.startSpan({ name: 'parent' }, () => { + for (let i = 0; i < 5000; i++) { + Sentry.startInactiveSpan({ name: `child ${i}` }).end(); + } +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts new file mode 100644 index 000000000000..5a5c6f851c69 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts @@ -0,0 +1,16 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequestOnUrl } from '../../../utils/helpers'; + +sentryTest('limits static transactions to 1000 child spans', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + + const url = await getLocalTestUrl({ testDir: __dirname }); + const req = await waitForTransactionRequestOnUrl(page, url); + const transaction = envelopeRequestParser(req); + + expect(transaction.spans).toHaveLength(1000); + expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 0' })); + expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 999' })); + expect(transaction.spans).not.toContainEqual(expect.objectContaining({ description: 'child 1000' })); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js index d4062aa6eb8f..162bc4a04cd9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [], tracesSampleRate: 1, diff --git a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts index 328c5c364c87..f5a05ad0854c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/maxSpans/test.ts @@ -1,20 +1,20 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequestOnUrl } from '../../../utils/helpers'; - -sentryTest('it limits spans to 1000', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../utils/spanUtils'; +sentryTest('streams all children without the static 1000-span limit', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); + const spans = collectStreamedSpans(page); + const rootPromise = waitForStreamedSpan(page, span => span.name === 'parent'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); + const root = await rootPromise; + await page.evaluate(() => (window as any).Sentry.flush()); - const req = await waitForTransactionRequestOnUrl(page, url); - const transaction = envelopeRequestParser(req); - - expect(transaction.spans).toHaveLength(1000); - expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 0' })); - expect(transaction.spans).toContainEqual(expect.objectContaining({ description: 'child 999' })); - expect(transaction.spans).not.toContainEqual(expect.objectContaining({ description: 'child 1000' })); + const children = spans.filter(span => span.parent_span_id === root.span_id); + expect(children).toHaveLength(5000); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 0' })); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 999' })); + expect(children).toContainEqual(expect.objectContaining({ name: 'child 4999' })); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/init.js b/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/init.js index 19670513eef9..b8f186bc2896 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/test.ts b/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/test.ts index e49b258b4ff2..d166f200ff28 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/test.ts @@ -1,6 +1,8 @@ +import { URL_FULL } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { collectStreamedSpans, getSpanOp, waitForStreamedSpan } from '../../../utils/spanUtils'; sentryTest('should attribute spans to their originating microfrontend', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -11,27 +13,29 @@ sentryTest('should attribute spans to their originating microfrontend', async ({ const url = await getLocalTestUrl({ testDir: __dirname }); - const reqPromise = waitForTransactionRequest(page, event => { - const spans = event.spans || []; - return ( - spans.some(s => s.description?.includes('/api/todos/1')) && - spans.some(s => s.description?.includes('/api/todos/2')) && - spans.some(s => s.description?.includes('/api/todos/3')) && - spans.some(s => s.description?.includes('/api/shell-config')) - ); - }); - + const spans = collectStreamedSpans(page); + const rootSpanPromise = waitForStreamedSpan(page, span => span.is_segment && getSpanOp(span) === 'pageload'); await page.goto(url); - - const req = await reqPromise; - const event = envelopeRequestParser(req); - const httpSpans = event.spans?.filter(({ op }) => op === 'http.client') || []; + await rootSpanPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const httpSpans = spans.filter(span => getSpanOp(span) === 'http.client'); + expect(httpSpans).toHaveLength(4); // Each MFE's fetch is attributed via withScope + spanStart hook - expect(httpSpans.find(s => s.description?.includes('/api/todos/1'))?.data?.['mfe.name']).toBe('mfe-header'); - expect(httpSpans.find(s => s.description?.includes('/api/todos/2'))?.data?.['mfe.name']).toBe('mfe-one'); - expect(httpSpans.find(s => s.description?.includes('/api/todos/3'))?.data?.['mfe.name']).toBe('mfe-two'); + expect( + httpSpans.find(s => s.attributes[URL_FULL]?.value?.toString().includes('/api/todos/1'))?.attributes['mfe.name'], + ).toEqual({ type: 'string', value: 'mfe-header' }); + expect( + httpSpans.find(s => s.attributes[URL_FULL]?.value?.toString().includes('/api/todos/2'))?.attributes['mfe.name'], + ).toEqual({ type: 'string', value: 'mfe-one' }); + expect( + httpSpans.find(s => s.attributes[URL_FULL]?.value?.toString().includes('/api/todos/3'))?.attributes['mfe.name'], + ).toEqual({ type: 'string', value: 'mfe-two' }); // Shell span has no MFE tag - expect(httpSpans.find(s => s.description?.includes('/api/shell-config'))?.data?.['mfe.name']).toBeUndefined(); + expect( + httpSpans.find(s => s.attributes[URL_FULL]?.value?.toString().includes('/api/shell-config'))?.attributes[ + 'mfe.name' + ], + ).toBeUndefined(); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/init.js b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/init.js rename to dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/init.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/subject.js b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/test.ts b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js similarity index 75% rename from dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js index 9afcee48dc4a..bd0421b32761 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts similarity index 52% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts index c9f48a4ebefd..2019c78c837f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts @@ -1,26 +1,26 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpans } from '../../../../utils/spanUtils'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => s.name === 'checkout-flow' && s.is_segment)); + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const spans = await spansPromise; - const checkoutSpan = spans.find(s => s.name === 'checkout-flow'); - const checkoutSpanId = checkoutSpan?.span_id; + const checkoutEvent = envelopeRequestParser(await req); + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); - expect(spans.filter(s => !s.is_segment)).toHaveLength(3); + expect(checkoutEvent.spans).toHaveLength(3); - const checkoutStep1 = spans.find(s => s.name === 'checkout-step-1'); - const checkoutStep11 = spans.find(s => s.name === 'checkout-step-1-1'); - const checkoutStep2 = spans.find(s => s.name === 'checkout-step-2'); + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); expect(checkoutStep1).toBeDefined(); expect(checkoutStep11).toBeDefined(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js similarity index 78% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/init.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js index 5b4cff73e95d..58be3b8dc82c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js @@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1, parentSpanIsAlwaysRootSpan: false, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts similarity index 53% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/test.ts rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts index f0ce77d4e00c..5132178d1981 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts @@ -1,37 +1,36 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpans } from '../../../../utils/spanUtils'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; sentryTest( 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } - const checkoutSpansPromise = waitForStreamedSpans(page, spans => - spans.some(s => s.name === 'checkout-flow' && s.is_segment), - ); + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const checkoutSpans = await checkoutSpansPromise; + const checkoutEvent = envelopeRequestParser(await req); + const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); - const checkoutSpan = checkoutSpans.find(s => s.name === 'checkout-flow'); - const postCheckoutSpan = checkoutSpans.find(s => s.name === 'post-checkout'); - - const checkoutSpanId = checkoutSpan?.span_id; - const postCheckoutSpanId = postCheckoutSpan?.span_id; + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); expect(postCheckoutSpanId).toMatch(/[a-f\d]{16}/); - expect(checkoutSpans.filter(s => !s.is_segment)).toHaveLength(5); + expect(checkoutEvent.spans).toHaveLength(4); + expect(postCheckoutEvent.spans).toHaveLength(1); - const checkoutStep1 = checkoutSpans.find(s => s.name === 'checkout-step-1'); - const checkoutStep2 = checkoutSpans.find(s => s.name === 'checkout-step-2'); - const checkoutStep21 = checkoutSpans.find(s => s.name === 'checkout-step-2-1'); - const checkoutStep3 = checkoutSpans.find(s => s.name === 'checkout-step-3'); + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); + const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); expect(checkoutStep1).toBeDefined(); expect(checkoutStep2).toBeDefined(); @@ -51,7 +50,7 @@ sentryTest( // post-checkout trace is started as a new trace because ending checkoutSpan removes the active // span on the scope - const postCheckoutStep1 = checkoutSpans.find(s => s.name === 'post-checkout-1'); + const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); expect(postCheckoutStep1).toBeDefined(); expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js similarity index 75% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js index 9afcee48dc4a..bd0421b32761 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/test.ts new file mode 100644 index 000000000000..cea01f304691 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/test.ts @@ -0,0 +1,52 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'nested calls to setActiveSpanInBrowser still parent to root span by default', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const checkoutEvent = envelopeRequestParser(await req); + const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); + + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; + + expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); + expect(postCheckoutSpanId).toMatch(/[a-f\d]{16}/); + + expect(checkoutEvent.spans).toHaveLength(4); + expect(postCheckoutEvent.spans).toHaveLength(1); + + const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); + const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); + const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); + + expect(checkoutStep1).toBeDefined(); + expect(checkoutStep2).toBeDefined(); + expect(checkoutStep21).toBeDefined(); + expect(checkoutStep3).toBeDefined(); + + expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); + + // despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the + // root span due to this being default behaviour in browser environments + expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId); + + const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); + expect(postCheckoutStep1).toBeDefined(); + expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/test.ts deleted file mode 100644 index 37a76f81152d..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest( - 'nested calls to setActiveSpanInBrowser still parent to root span by default', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const checkoutSpansPromise = waitForStreamedSpans(page, spans => - spans.some(s => s.name === 'checkout-flow' && s.is_segment), - ); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const checkoutSpans = await checkoutSpansPromise; - - const checkoutSpan = checkoutSpans.find(s => s.name === 'checkout-flow'); - const postCheckoutSpan = checkoutSpans.find(s => s.name === 'post-checkout'); - - const checkoutSpanId = checkoutSpan?.span_id; - const postCheckoutSpanId = postCheckoutSpan?.span_id; - - expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); - expect(postCheckoutSpanId).toMatch(/[a-f\d]{16}/); - - expect(checkoutSpans.filter(s => !s.is_segment)).toHaveLength(5); - - const checkoutStep1 = checkoutSpans.find(s => s.name === 'checkout-step-1'); - const checkoutStep2 = checkoutSpans.find(s => s.name === 'checkout-step-2'); - const checkoutStep21 = checkoutSpans.find(s => s.name === 'checkout-step-2-1'); - const checkoutStep3 = checkoutSpans.find(s => s.name === 'checkout-step-3'); - - expect(checkoutStep1).toBeDefined(); - expect(checkoutStep2).toBeDefined(); - expect(checkoutStep21).toBeDefined(); - expect(checkoutStep3).toBeDefined(); - - expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); - expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); - expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); - - // despite 2-1 being called within 2 AND setting 2 as active span, it's still parented to the - // root span due to this being default behaviour in browser environments - expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId); - - const postCheckoutStep1 = checkoutSpans.find(s => s.name === 'post-checkout-1'); - expect(postCheckoutStep1).toBeDefined(); - expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js index bd0421b32761..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts index 2019c78c837f..ab996fc17d4c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/default/test.ts @@ -1,26 +1,28 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); - const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + const spans = collectStreamedSpans(page); + const rootPromise = waitForStreamedSpan(page, span => span.name === 'checkout-flow' && span.is_segment); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const checkoutEvent = envelopeRequestParser(await req); - const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + await rootPromise; + await page.evaluate(() => (window as any).Sentry.flush()); + const checkoutSpan = spans.find(s => s.name === 'checkout-flow'); + const checkoutSpanId = checkoutSpan?.span_id; expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); - expect(checkoutEvent.spans).toHaveLength(3); + expect(spans.filter(s => !s.is_segment)).toHaveLength(3); - const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); - const checkoutStep11 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1-1'); - const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); + const checkoutStep1 = spans.find(s => s.name === 'checkout-step-1'); + const checkoutStep11 = spans.find(s => s.name === 'checkout-step-1-1'); + const checkoutStep2 = spans.find(s => s.name === 'checkout-step-2'); expect(checkoutStep1).toBeDefined(); expect(checkoutStep11).toBeDefined(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js index 58be3b8dc82c..27b13c52906f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/init.js @@ -3,8 +3,8 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], tracesSampleRate: 1, parentSpanIsAlwaysRootSpan: false, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts index 5132178d1981..12baff69f5f8 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested-parentAlwaysRoot/test.ts @@ -1,36 +1,38 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); - const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); - const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); + const checkoutSpans = collectStreamedSpans(page); + const checkoutPromise = waitForStreamedSpan(page, span => span.name === 'checkout-flow' && span.is_segment); + const postCheckoutPromise = waitForStreamedSpan(page, span => span.name === 'post-checkout' && span.is_segment); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const checkoutEvent = envelopeRequestParser(await req); - const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); + await Promise.all([checkoutPromise, postCheckoutPromise]); + await page.evaluate(() => (window as any).Sentry.flush()); - const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; - const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; + const checkoutSpan = checkoutSpans.find(s => s.name === 'checkout-flow'); + const postCheckoutSpan = checkoutSpans.find(s => s.name === 'post-checkout'); + + const checkoutSpanId = checkoutSpan?.span_id; + const postCheckoutSpanId = postCheckoutSpan?.span_id; expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); expect(postCheckoutSpanId).toMatch(/[a-f\d]{16}/); - expect(checkoutEvent.spans).toHaveLength(4); - expect(postCheckoutEvent.spans).toHaveLength(1); + expect(checkoutSpans.filter(s => !s.is_segment)).toHaveLength(5); - const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); - const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); - const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); - const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); + const checkoutStep1 = checkoutSpans.find(s => s.name === 'checkout-step-1'); + const checkoutStep2 = checkoutSpans.find(s => s.name === 'checkout-step-2'); + const checkoutStep21 = checkoutSpans.find(s => s.name === 'checkout-step-2-1'); + const checkoutStep3 = checkoutSpans.find(s => s.name === 'checkout-step-3'); expect(checkoutStep1).toBeDefined(); expect(checkoutStep2).toBeDefined(); @@ -50,7 +52,7 @@ sentryTest( // post-checkout trace is started as a new trace because ending checkoutSpan removes the active // span on the scope - const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); + const postCheckoutStep1 = checkoutSpans.find(s => s.name === 'post-checkout-1'); expect(postCheckoutStep1).toBeDefined(); expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js index bd0421b32761..3ec6adbbdb75 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/init.js @@ -3,7 +3,7 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts index cea01f304691..686a64079db9 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive/nested/test.ts @@ -1,36 +1,38 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { collectStreamedSpans, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'nested calls to setActiveSpanInBrowser still parent to root span by default', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); - const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); - const postCheckoutReq = waitForTransactionRequest(page, e => e.transaction === 'post-checkout'); + const checkoutSpans = collectStreamedSpans(page); + const checkoutPromise = waitForStreamedSpan(page, span => span.name === 'checkout-flow' && span.is_segment); + const postCheckoutPromise = waitForStreamedSpan(page, span => span.name === 'post-checkout' && span.is_segment); const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const checkoutEvent = envelopeRequestParser(await req); - const postCheckoutEvent = envelopeRequestParser(await postCheckoutReq); + await Promise.all([checkoutPromise, postCheckoutPromise]); + await page.evaluate(() => (window as any).Sentry.flush()); - const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; - const postCheckoutSpanId = postCheckoutEvent.contexts?.trace?.span_id; + const checkoutSpan = checkoutSpans.find(s => s.name === 'checkout-flow'); + const postCheckoutSpan = checkoutSpans.find(s => s.name === 'post-checkout'); + + const checkoutSpanId = checkoutSpan?.span_id; + const postCheckoutSpanId = postCheckoutSpan?.span_id; expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); expect(postCheckoutSpanId).toMatch(/[a-f\d]{16}/); - expect(checkoutEvent.spans).toHaveLength(4); - expect(postCheckoutEvent.spans).toHaveLength(1); + expect(checkoutSpans.filter(s => !s.is_segment)).toHaveLength(5); - const checkoutStep1 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-1'); - const checkoutStep2 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2'); - const checkoutStep21 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-2-1'); - const checkoutStep3 = checkoutEvent.spans?.find(s => s.description === 'checkout-step-3'); + const checkoutStep1 = checkoutSpans.find(s => s.name === 'checkout-step-1'); + const checkoutStep2 = checkoutSpans.find(s => s.name === 'checkout-step-2'); + const checkoutStep21 = checkoutSpans.find(s => s.name === 'checkout-step-2-1'); + const checkoutStep3 = checkoutSpans.find(s => s.name === 'checkout-step-3'); expect(checkoutStep1).toBeDefined(); expect(checkoutStep2).toBeDefined(); @@ -45,7 +47,7 @@ sentryTest( // root span due to this being default behaviour in browser environments expect(checkoutStep21?.parent_span_id).toBe(checkoutSpanId); - const postCheckoutStep1 = postCheckoutEvent.spans?.find(s => s.description === 'post-checkout-1'); + const postCheckoutStep1 = checkoutSpans.find(s => s.name === 'post-checkout-1'); expect(postCheckoutStep1).toBeDefined(); expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/init.js b/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/init.js index cbf6c3a35415..dbefdbdfcda5 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampleRate: '1', }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/test.ts index 207aa7517ac7..e078da24e465 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/test.ts @@ -1,6 +1,8 @@ +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequestOnUrl } from '../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../utils/spanUtils'; sentryTest('parses a string sample rate', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -9,8 +11,7 @@ sentryTest('parses a string sample rate', async ({ getLocalTestUrl, page }) => { const url = await getLocalTestUrl({ testDir: __dirname }); - const req = await waitForTransactionRequestOnUrl(page, url); - const eventData = envelopeRequestParser(req); + const [span] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(eventData.contexts?.trace?.data?.['sentry.sample_rate']).toStrictEqual(1); + expect(span.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]).toEqual({ type: 'integer', value: 1 }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js index 8718d9d354eb..30317eda63ef 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/init.js @@ -5,7 +5,6 @@ import { feedbackIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/init.js similarity index 89% rename from dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/init.js index 3dd77207e103..8718d9d354eb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/init.js @@ -5,8 +5,9 @@ import { feedbackIntegration } from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ + traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), feedbackIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/subject.js new file mode 100644 index 000000000000..eead800a75a5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/subject.js @@ -0,0 +1,23 @@ +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://sentry-test-site.example'); +}); + +const xhrBtn = document.getElementById('xhrBtn'); +xhrBtn.addEventListener('click', () => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://sentry-test-site.example'); + xhr.send(); +}); + +const spanAndFetchBtn = document.getElementById('spanAndFetchBtn'); +spanAndFetchBtn.addEventListener('click', () => { + Sentry.startSpan({ name: 'custom-root-span' }, async () => { + await fetch('http://sentry-test-site.example'); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/template.html b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/template.html new file mode 100644 index 000000000000..87e7bf5efbea --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/test.ts new file mode 100644 index 000000000000..2f7749685ba8 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/test.ts @@ -0,0 +1,363 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { + eventAndTraceHeaderRequestParser, + getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, + shouldSkipFeedbackTest, + shouldSkipTracingTest, +} from '../../../../utils/helpers'; + +sentryTest('creates a new trace and sample_rand on each navigation', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await getFirstSentryEnvelopeRequest(page, url); + + const [navigation1Event, navigation1TraceHeader] = await getFirstSentryEnvelopeRequest( + page, + `${url}#foo`, + eventAndTraceHeaderRequestParser, + ); + const [navigation2Event, navigation2TraceHeader] = await getFirstSentryEnvelopeRequest( + page, + `${url}#bar`, + eventAndTraceHeaderRequestParser, + ); + + const navigation1TraceContext = navigation1Event.contexts?.trace; + const navigation2TraceContext = navigation2Event.contexts?.trace; + + expect(navigation1Event.type).toEqual('transaction'); + expect(navigation2Event.type).toEqual('transaction'); + + expect(navigation1TraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigation1TraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigation1TraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigation1TraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + expect(navigation2TraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigation2TraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigation2TraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigation2TraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + expect(navigation1TraceContext?.trace_id).not.toEqual(navigation2TraceContext?.trace_id); + expect(navigation1TraceHeader?.sample_rand).not.toEqual(navigation2TraceHeader?.sample_rand); +}); + +sentryTest('error after navigation has navigation traceId', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( + page, + `${url}#foo`, + eventAndTraceHeaderRequestParser, + ); + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(navigationEvent.type).toEqual('transaction'); + + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigationTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const errorEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + await page.locator('#errorBtn').click(); + const [errorEvent, errorTraceHeader] = await errorEventPromise; + + expect(errorEvent.type).toEqual(undefined); + + const errorTraceContext = errorEvent.contexts?.trace; + expect(errorTraceContext).toEqual({ + trace_id: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(errorTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); +}); + +sentryTest('error during navigation has new navigation traceId', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( + page, + 2, + undefined, + eventAndTraceHeaderRequestParser, + ); + + await page.goto(`${url}#foo`); + await page.locator('#errorBtn').click(); + const envelopes = await envelopeRequestsPromise; + + const [navigationEvent, navigationTraceHeader] = envelopes.find(envelope => envelope[0].type === 'transaction')!; + const [errorEvent, errorTraceHeader] = envelopes.find(envelope => !envelope[0].type)!; + + expect(navigationEvent.type).toEqual('transaction'); + expect(errorEvent.type).toEqual(undefined); + + const navigationTraceContext = navigationEvent?.contexts?.trace; + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigationTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const errorTraceContext = errorEvent?.contexts?.trace; + expect(errorTraceContext).toEqual({ + trace_id: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + + expect(errorTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); +}); + +sentryTest( + 'outgoing fetch request during navigation has navigation traceId in headers', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('http://sentry-test-site.example/**', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); + + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const navigationEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); + await page.goto(`${url}#foo`); + await page.locator('#fetchBtn').click(); + const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ + navigationEventPromise, + requestPromise, + ]); + + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(navigationEvent.type).toEqual('transaction'); + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigationTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const headers = request.headers(); + + // sampling decision is propagated from active span sampling decision + const navigationTraceId = navigationTraceContext?.trace_id; + expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); + expect(headers['baggage']).toEqual( + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`, + ); + }, +); + +sentryTest( + 'outgoing XHR request during navigation has navigation traceId in headers', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('http://sentry-test-site.example/**', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); + + // ensure navigation transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const navigationEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); + await page.goto(`${url}#foo`); + await page.locator('#xhrBtn').click(); + const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ + navigationEventPromise, + requestPromise, + ]); + + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(navigationEvent.type).toEqual('transaction'); + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigationTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const headers = request.headers(); + + // sampling decision is propagated from active span sampling decision + const navigationTraceId = navigationTraceContext?.trace_id; + expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); + expect(headers['baggage']).toEqual( + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`, + ); + }, +); + +sentryTest( + 'user feedback event after navigation has navigation traceId in headers', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); + + // ensure pageload transaction is finished + await getFirstSentryEnvelopeRequest(page, url); + + const navigationEvent = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); + + const navigationTraceContext = navigationEvent.contexts?.trace; + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + const feedbackEventsPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'feedback' }); + + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const [feedbackEvent] = await feedbackEventsPromise; + + expect(feedbackEvent.type).toEqual('feedback'); + + const feedbackTraceContext = feedbackEvent.contexts?.trace; + + expect(feedbackTraceContext).toMatchObject({ + trace_id: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/test.ts deleted file mode 100644 index 3c2cafaa0430..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/test.ts +++ /dev/null @@ -1,317 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; -import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipFeedbackTest, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; - -sentryTest('creates a new trace and sample_rand on each navigation', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - // Wait for and skip the initial pageload span - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigation1SpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - await page.goto(`${url}#foo`); - const navigation1SpanEnvelope = await navigation1SpanEnvelopePromise; - - const navigation2SpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - await page.goto(`${url}#bar`); - const navigation2SpanEnvelope = await navigation2SpanEnvelopePromise; - - const navigation1TraceId = navigation1SpanEnvelope[0].trace?.trace_id; - const navigation1SampleRand = navigation1SpanEnvelope[0].trace?.sample_rand; - const navigation2TraceId = navigation2SpanEnvelope[0].trace?.trace_id; - const navigation2SampleRand = navigation2SpanEnvelope[0].trace?.sample_rand; - - const navigation1Span = navigation1SpanEnvelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - const navigation2Span = navigation2SpanEnvelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - - expect(getSpanOp(navigation1Span)).toEqual('navigation'); - expect(navigation1TraceId).toMatch(/^[\da-f]{32}$/); - expect(navigation1Span.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigation1Span.parent_span_id).toBeUndefined(); - - expect(navigation1SpanEnvelope[0].trace).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigation1TraceId, - sample_rand: expect.any(String), - }); - - expect(getSpanOp(navigation2Span)).toEqual('navigation'); - expect(navigation2TraceId).toMatch(/^[\da-f]{32}$/); - expect(navigation2Span.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigation2Span.parent_span_id).toBeUndefined(); - - expect(navigation2SpanEnvelope[0].trace).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigation2TraceId, - sample_rand: expect.any(String), - }); - - expect(navigation1TraceId).not.toEqual(navigation2TraceId); - expect(navigation1SampleRand).not.toEqual(navigation2SampleRand); -}); - -sentryTest('error after navigation has navigation traceId', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - // ensure pageload span is finished - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - await page.goto(`${url}#foo`); - const [navigationSpan, navigationSpanEnvelope] = await Promise.all([ - navigationSpanPromise, - navigationSpanEnvelopePromise, - ]); - - const navigationTraceId = navigationSpan.trace_id; - - expect(getSpanOp(navigationSpan)).toEqual('navigation'); - expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); - expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigationSpan.parent_span_id).toBeUndefined(); - - expect(navigationSpanEnvelope[0].trace).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceId, - sample_rand: expect.any(String), - }); - - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); - await page.locator('#errorBtn').click(); - const [errorEvent, errorTraceHeader] = await errorEventPromise; - - expect(errorEvent.type).toEqual(undefined); - - const errorTraceContext = errorEvent.contexts?.trace; - expect(errorTraceContext).toEqual({ - trace_id: navigationTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(errorTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceId, - sample_rand: expect.any(String), - }); -}); - -sentryTest('error during navigation has new navigation traceId', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - // ensure pageload span is finished - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); - - await page.goto(`${url}#foo`); - await page.locator('#errorBtn').click(); - const [navigationSpan, [errorEvent, errorTraceHeader]] = await Promise.all([ - navigationSpanPromise, - errorEventPromise, - ]); - - expect(getSpanOp(navigationSpan)).toEqual('navigation'); - expect(errorEvent.type).toEqual(undefined); - - const navigationTraceId = navigationSpan.trace_id; - expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); - expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigationSpan.parent_span_id).toBeUndefined(); - - const errorTraceContext = errorEvent?.contexts?.trace; - expect(errorTraceContext).toEqual({ - trace_id: navigationTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - - expect(errorTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceId, - sample_rand: expect.any(String), - }); -}); - -sentryTest( - 'outgoing fetch request during navigation has navigation traceId in headers', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://sentry-test-site.example/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - // ensure pageload span is finished - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); - await page.goto(`${url}#foo`); - await page.locator('#fetchBtn').click(); - const [navigationSpanEnvelope, request] = await Promise.all([navigationSpanEnvelopePromise, requestPromise]); - - const navigationTraceId = navigationSpanEnvelope[0].trace?.trace_id; - const sampleRand = navigationSpanEnvelope[0].trace?.sample_rand; - - expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); - - const headers = request.headers(); - - // sampling decision is propagated from active span sampling decision - expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, - ); - }, -); - -sentryTest( - 'outgoing XHR request during navigation has navigation traceId in headers', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://sentry-test-site.example/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - // ensure navigation span is finished - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), - ); - const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); - await page.goto(`${url}#foo`); - await page.locator('#xhrBtn').click(); - const [navigationSpanEnvelope, request] = await Promise.all([navigationSpanEnvelopePromise, requestPromise]); - - const navigationTraceId = navigationSpanEnvelope[0].trace?.trace_id; - const sampleRand = navigationSpanEnvelope[0].trace?.sample_rand; - - expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); - - const headers = request.headers(); - - // sampling decision is propagated from active span sampling decision - expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); - expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, - ); - }, -); - -sentryTest( - 'user feedback event after navigation has navigation traceId in headers', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest() || shouldSkipFeedbackTest()); - - const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - - // ensure pageload span is finished - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - await pageloadSpanPromise; - - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - await page.goto(`${url}#foo`); - const navigationSpan = await navigationSpanPromise; - - const navigationTraceId = navigationSpan.trace_id; - expect(getSpanOp(navigationSpan)).toEqual('navigation'); - expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); - expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigationSpan.parent_span_id).toBeUndefined(); - - const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); - - await page.getByText('Report a Bug').click(); - expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); - await page.locator('[name="name"]').fill('Jane Doe'); - await page.locator('[name="email"]').fill('janedoe@example.org'); - await page.locator('[name="message"]').fill('my example feedback'); - await page.locator('[data-sentry-feedback] .btn--primary').click(); - - const feedbackEvent = await feedbackEventPromise; - - expect(feedbackEvent.type).toEqual('feedback'); - - const feedbackTraceContext = feedbackEvent.contexts?.trace; - - expect(feedbackTraceContext).toMatchObject({ - trace_id: navigationTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/init.js similarity index 89% rename from dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/init.js index 3dd77207e103..30317eda63ef 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/init.js @@ -6,7 +6,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), feedbackIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts index 0255507287d3..37bb7cb62f8c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation/test.ts @@ -1,117 +1,117 @@ import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, + waitForErrorRequest, } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; sentryTest('creates a new trace and sample_rand on each navigation', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - await getFirstSentryEnvelopeRequest(page, url); + // Wait for and skip the initial pageload span + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const [navigation1Event, navigation1TraceHeader] = await getFirstSentryEnvelopeRequest( + const navigation1SpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); - const [navigation2Event, navigation2TraceHeader] = await getFirstSentryEnvelopeRequest( + await page.goto(`${url}#foo`); + const navigation1SpanEnvelope = await navigation1SpanEnvelopePromise; + + const navigation2SpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - `${url}#bar`, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); + await page.goto(`${url}#bar`); + const navigation2SpanEnvelope = await navigation2SpanEnvelopePromise; - const navigation1TraceContext = navigation1Event.contexts?.trace; - const navigation2TraceContext = navigation2Event.contexts?.trace; + const navigation1TraceId = navigation1SpanEnvelope[0].trace?.trace_id; + const navigation1SampleRand = navigation1SpanEnvelope[0].trace?.sample_rand; + const navigation2TraceId = navigation2SpanEnvelope[0].trace?.trace_id; + const navigation2SampleRand = navigation2SpanEnvelope[0].trace?.sample_rand; - expect(navigation1Event.type).toEqual('transaction'); - expect(navigation2Event.type).toEqual('transaction'); + const navigation1Span = navigation1SpanEnvelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; + const navigation2Span = navigation2SpanEnvelope[1][0][1].items.find(s => getSpanOp(s) === 'navigation')!; - expect(navigation1TraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigation1TraceContext).not.toHaveProperty('parent_span_id'); + expect(getSpanOp(navigation1Span)).toEqual('navigation'); + expect(navigation1TraceId).toMatch(/^[\da-f]{32}$/); + expect(navigation1Span.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigation1Span.parent_span_id).toBeUndefined(); - expect(navigation1TraceHeader).toEqual({ + expect(navigation1SpanEnvelope[0].trace).toEqual({ environment: 'production', public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigation1TraceContext?.trace_id, + trace_id: navigation1TraceId, sample_rand: expect.any(String), }); - expect(navigation2TraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigation2TraceContext).not.toHaveProperty('parent_span_id'); + expect(getSpanOp(navigation2Span)).toEqual('navigation'); + expect(navigation2TraceId).toMatch(/^[\da-f]{32}$/); + expect(navigation2Span.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigation2Span.parent_span_id).toBeUndefined(); - expect(navigation2TraceHeader).toEqual({ + expect(navigation2SpanEnvelope[0].trace).toEqual({ environment: 'production', public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigation2TraceContext?.trace_id, + trace_id: navigation2TraceId, sample_rand: expect.any(String), }); - expect(navigation1TraceContext?.trace_id).not.toEqual(navigation2TraceContext?.trace_id); - expect(navigation1TraceHeader?.sample_rand).not.toEqual(navigation2TraceHeader?.sample_rand); + expect(navigation1TraceId).not.toEqual(navigation2TraceId); + expect(navigation1SampleRand).not.toEqual(navigation2SampleRand); }); sentryTest('error after navigation has navigation traceId', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - // ensure pageload transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + // ensure pageload span is finished + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); + const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); - const navigationTraceContext = navigationEvent.contexts?.trace; + await page.goto(`${url}#foo`); + const [navigationSpan, navigationSpanEnvelope] = await Promise.all([ + navigationSpanPromise, + navigationSpanEnvelopePromise, + ]); - expect(navigationEvent.type).toEqual('transaction'); + const navigationTraceId = navigationSpan.trace_id; - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + expect(getSpanOp(navigationSpan)).toEqual('navigation'); + expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); + expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigationSpan.parent_span_id).toBeUndefined(); - expect(navigationTraceHeader).toEqual({ + expect(navigationSpanEnvelope[0].trace).toEqual({ environment: 'production', public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, sample_rand: expect.any(String), }); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; @@ -119,7 +119,7 @@ sentryTest('error after navigation has navigation traceId', async ({ getLocalTes const errorTraceContext = errorEvent.contexts?.trace; expect(errorTraceContext).toEqual({ - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); expect(errorTraceHeader).toEqual({ @@ -127,58 +127,42 @@ sentryTest('error after navigation has navigation traceId', async ({ getLocalTes public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, sample_rand: expect.any(String), }); }); sentryTest('error during navigation has new navigation traceId', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - // ensure pageload transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + // ensure pageload span is finished + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( - page, - 2, - undefined, - eventAndTraceHeaderRequestParser, - ); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.goto(`${url}#foo`); await page.locator('#errorBtn').click(); - const envelopes = await envelopeRequestsPromise; - - const [navigationEvent, navigationTraceHeader] = envelopes.find(envelope => envelope[0].type === 'transaction')!; - const [errorEvent, errorTraceHeader] = envelopes.find(envelope => !envelope[0].type)!; + const [navigationSpan, [errorEvent, errorTraceHeader]] = await Promise.all([ + navigationSpanPromise, + errorEventPromise, + ]); - expect(navigationEvent.type).toEqual('transaction'); + expect(getSpanOp(navigationSpan)).toEqual('navigation'); expect(errorEvent.type).toEqual(undefined); - const navigationTraceContext = navigationEvent?.contexts?.trace; - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + const navigationTraceId = navigationSpan.trace_id; + expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); + expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigationSpan.parent_span_id).toBeUndefined(); const errorTraceContext = errorEvent?.contexts?.trace; expect(errorTraceContext).toEqual({ - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); @@ -187,7 +171,7 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, sample_rand: expect.any(String), }); }); @@ -195,9 +179,7 @@ sentryTest('error during navigation has new navigation traceId', async ({ getLoc sentryTest( 'outgoing fetch request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -209,48 +191,31 @@ sentryTest( }); }); - // ensure pageload transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + // ensure pageload span is finished + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const navigationEventPromise = getFirstSentryEnvelopeRequest( + const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - undefined, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(`${url}#foo`); await page.locator('#fetchBtn').click(); - const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ - navigationEventPromise, - requestPromise, - ]); + const [navigationSpanEnvelope, request] = await Promise.all([navigationSpanEnvelopePromise, requestPromise]); - const navigationTraceContext = navigationEvent.contexts?.trace; + const navigationTraceId = navigationSpanEnvelope[0].trace?.trace_id; + const sampleRand = navigationSpanEnvelope[0].trace?.sample_rand; - expect(navigationEvent.type).toEqual('transaction'); - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); const headers = request.headers(); // sampling decision is propagated from active span sampling decision - const navigationTraceId = navigationTraceContext?.trace_id; expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`, + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, ); }, ); @@ -258,9 +223,7 @@ sentryTest( sentryTest( 'outgoing XHR request during navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -272,48 +235,31 @@ sentryTest( }); }); - // ensure navigation transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + // ensure navigation span is finished + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const navigationEventPromise = getFirstSentryEnvelopeRequest( + const navigationSpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - undefined, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'navigation'), ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(`${url}#foo`); await page.locator('#xhrBtn').click(); - const [[navigationEvent, navigationTraceHeader], request] = await Promise.all([ - navigationEventPromise, - requestPromise, - ]); + const [navigationSpanEnvelope, request] = await Promise.all([navigationSpanEnvelopePromise, requestPromise]); - const navigationTraceContext = navigationEvent.contexts?.trace; + const navigationTraceId = navigationSpanEnvelope[0].trace?.trace_id; + const sampleRand = navigationSpanEnvelope[0].trace?.sample_rand; - expect(navigationEvent.type).toEqual('transaction'); - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); const headers = request.headers(); // sampling decision is propagated from active span sampling decision - const navigationTraceId = navigationTraceContext?.trace_id; expect(headers['sentry-trace']).toMatch(new RegExp(`^${navigationTraceId}-[0-9a-f]{16}-1$`)); expect(headers['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${navigationTraceHeader?.sample_rand},sentry-sample_rate=1`, + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${navigationTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, ); }, ); @@ -321,26 +267,26 @@ sentryTest( sentryTest( 'user feedback event after navigation has navigation traceId in headers', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest() || shouldSkipFeedbackTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest() || shouldSkipFeedbackTest()); const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - // ensure pageload transaction is finished - await getFirstSentryEnvelopeRequest(page, url); + // ensure pageload span is finished + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + await pageloadSpanPromise; - const navigationEvent = await getFirstSentryEnvelopeRequest(page, `${url}#foo`); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); + await page.goto(`${url}#foo`); + const navigationSpan = await navigationSpanPromise; - const navigationTraceContext = navigationEvent.contexts?.trace; - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + const navigationTraceId = navigationSpan.trace_id; + expect(getSpanOp(navigationSpan)).toEqual('navigation'); + expect(navigationTraceId).toMatch(/^[\da-f]{32}$/); + expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigationSpan.parent_span_id).toBeUndefined(); - const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + const feedbackEventsPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'feedback' }); await page.getByText('Report a Bug').click(); expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); @@ -349,14 +295,14 @@ sentryTest( await page.locator('[name="message"]').fill('my example feedback'); await page.locator('[data-sentry-feedback] .btn--primary').click(); - const feedbackEvent = await feedbackEventPromise; + const [feedbackEvent] = await feedbackEventsPromise; expect(feedbackEvent.type).toEqual('feedback'); const feedbackTraceContext = feedbackEvent.contexts?.trace; expect(feedbackTraceContext).toMatchObject({ - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); }, diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-headers/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-headers/test.ts index 76e015dcb07a..efe6897d497e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-headers/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-headers/test.ts @@ -1,11 +1,8 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; -import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; const META_TAG_TRACE_ID = '12345678901234567890123456789012'; const META_TAG_PARENT_SPAN_ID = '1234567890123456'; @@ -28,23 +25,13 @@ sentryTest( }, }); - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationSpan, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const navigationTraceContext = navigationEvent.contexts?.trace; - - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + expect(pageloadSpan.is_segment).toBe(true); + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -61,25 +48,26 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.type).toEqual('transaction'); - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', + expect(navigationSpan.is_segment).toBe(true); + expect(navigationSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation' } }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), }); // navigation span is head of trace, so there's no parent span: - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + expect(navigationSpan).not.toHaveProperty('parent_span_id'); expect(navigationTraceHeader).toEqual({ environment: 'production', public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationSpan?.trace_id, sample_rand: expect.any(String), }); - expect(pageloadTraceContext?.trace_id).not.toEqual(navigationTraceContext?.trace_id); + expect(pageloadSpan?.trace_id).not.toEqual(navigationSpan?.trace_id); expect(pageloadTraceHeader?.sample_rand).not.toEqual(navigationTraceHeader?.sample_rand); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts index c66d8793e610..5d81177d2515 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-meta/test.ts @@ -1,14 +1,19 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, + waitForErrorRequest, } from '../../../../utils/helpers'; +import { + getSpanOp, + waitForStreamedSpanAndTraceHeader, + waitForStreamedSpanAndTraceHeaderOnUrl, +} from '../../../../utils/spanUtils'; const META_TAG_TRACE_ID = '12345678901234567890123456789012'; const META_TAG_PARENT_SPAN_ID = '1234567890123456'; @@ -24,23 +29,13 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); - - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const navigationTraceContext = navigationEvent.contexts?.trace; + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationSpan, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + expect(pageloadSpan.is_segment).toBe(true); + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -57,25 +52,26 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.type).toEqual('transaction'); - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', + expect(navigationSpan.is_segment).toBe(true); + expect(navigationSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation' } }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), }); // navigation span is head of trace, so there's no parent span: - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + expect(navigationSpan).not.toHaveProperty('parent_span_id'); expect(navigationTraceHeader).toEqual({ environment: 'production', public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: navigationTraceContext?.trace_id, + trace_id: navigationSpan?.trace_id, sample_rand: expect.any(String), }); - expect(pageloadTraceContext?.trace_id).not.toEqual(navigationTraceContext?.trace_id); + expect(pageloadSpan?.trace_id).not.toEqual(navigationSpan?.trace_id); expect(pageloadTraceHeader?.sample_rand).not.toEqual(navigationTraceHeader?.sample_rand); }, ); @@ -87,14 +83,11 @@ sentryTest('error after tag pageload has pageload traceId', async ({ getL const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(pageloadEvent.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -111,11 +104,7 @@ sentryTest('error after tag pageload has pageload traceId', async ({ getL sample_rand: '0.42', }); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; @@ -145,24 +134,22 @@ sentryTest('error during tag pageload has pageload traceId', async ({ get const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( + const pageloadPromise = waitForStreamedSpanAndTraceHeader( page, - 2, - undefined, - eventAndTraceHeaderRequestParser, + span => span.is_segment && getSpanOp(span) === 'pageload', ); + const errorPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.goto(url); await page.locator('#errorBtn').click(); - const envelopes = await envelopeRequestsPromise; - - const [pageloadEvent, pageloadTraceHeader] = envelopes.find( - eventAndHeader => eventAndHeader[0].type === 'transaction', - )!; - const [errorEvent, errorTraceHeader] = envelopes.find(eventAndHeader => !eventAndHeader[0].type)!; - - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', + const [[pageloadSpan, pageloadTraceHeader], [errorEvent, errorTraceHeader]] = await Promise.all([ + pageloadPromise, + errorPromise, + ]); + + expect(pageloadSpan.is_segment).toBe(true); + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -215,19 +202,19 @@ sentryTest( }); }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadSpanPromise = waitForStreamedSpanAndTraceHeader( page, - undefined, - eventAndTraceHeaderRequestParser, + span => span.is_segment && getSpanOp(span) === 'pageload', ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(url); await page.locator('#fetchBtn').click(); - const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + const [[pageloadSpan, pageloadTraceHeader], request] = await Promise.all([pageloadSpanPromise, requestPromise]); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadSpan.is_segment).toBe(true); + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -270,19 +257,19 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadSpanPromise = waitForStreamedSpanAndTraceHeader( page, - undefined, - eventAndTraceHeaderRequestParser, + span => span.is_segment && getSpanOp(span) === 'pageload', ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(url); await page.locator('#xhrBtn').click(); - const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + const [[pageloadSpan, pageloadTraceHeader], request] = await Promise.all([pageloadSpanPromise, requestPromise]); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadSpan.is_segment).toBe(true); + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), @@ -315,17 +302,17 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - const pageloadEvent = await getFirstSentryEnvelopeRequest(page, url); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, parent_span_id: META_TAG_PARENT_SPAN_ID, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); - const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + const feedbackEventsPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'feedback' }); await page.getByText('Report a Bug').click(); expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); @@ -334,7 +321,7 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', await page.locator('[name="message"]').fill('my example feedback'); await page.locator('[data-sentry-feedback] .btn--primary').click(); - const feedbackEvent = await feedbackEventPromise; + const [feedbackEvent] = await feedbackEventsPromise; const feedbackTraceContext = feedbackEvent.contexts?.trace; expect(feedbackEvent.type).toEqual('feedback'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/init.js new file mode 100644 index 000000000000..8718d9d354eb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/init.js @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/subject.js new file mode 100644 index 000000000000..eead800a75a5 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/subject.js @@ -0,0 +1,23 @@ +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://sentry-test-site.example'); +}); + +const xhrBtn = document.getElementById('xhrBtn'); +xhrBtn.addEventListener('click', () => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', 'http://sentry-test-site.example'); + xhr.send(); +}); + +const spanAndFetchBtn = document.getElementById('spanAndFetchBtn'); +spanAndFetchBtn.addEventListener('click', () => { + Sentry.startSpan({ name: 'custom-root-span' }, async () => { + await fetch('http://sentry-test-site.example'); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/template.html b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/template.html new file mode 100644 index 000000000000..87e7bf5efbea --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/template.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/test.ts new file mode 100644 index 000000000000..f65ed9c13e3a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/test.ts @@ -0,0 +1,484 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { + eventAndTraceHeaderRequestParser, + getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, + shouldSkipFeedbackTest, + shouldSkipTracingTest, +} from '../../../../utils/helpers'; + +sentryTest( + 'should create a new trace for a navigation after the initial pageload', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( + page, + url, + eventAndTraceHeaderRequestParser, + ); + const [navigationEvent, navigationTraceHeaders] = await getFirstSentryEnvelopeRequest( + page, + `${url}#foo`, + eventAndTraceHeaderRequestParser, + ); + + const pageloadTraceContext = pageloadEvent.contexts?.trace; + const navigationTraceContext = navigationEvent.contexts?.trace; + + expect(pageloadEvent.type).toEqual('transaction'); + expect(navigationEvent.type).toEqual('transaction'); + + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + expect(pageloadTraceHeaders).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + expect(navigationTraceContext).toMatchObject({ + op: 'navigation', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); + + expect(navigationTraceHeaders).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: navigationTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + expect(pageloadTraceContext?.span_id).not.toEqual(navigationTraceContext?.span_id); + }, +); + +sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( + page, + url, + eventAndTraceHeaderRequestParser, + ); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + expect(pageloadTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const errorEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + await page.locator('#errorBtn').click(); + const [errorEvent, errorTraceHeader] = await errorEventPromise; + + const errorTraceContext = errorEvent.contexts?.trace; + expect(errorEvent.type).toEqual(undefined); + + expect(errorTraceContext).toEqual({ + trace_id: pageloadTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + + expect(errorTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); +}); + +sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( + page, + 2, + undefined, + eventAndTraceHeaderRequestParser, + ); + await page.goto(url); + await page.locator('#errorBtn').click(); + const envelopes = await envelopeRequestsPromise; + + const [pageloadEvent, pageloadTraceHeader] = envelopes.find( + eventAndHeader => eventAndHeader[0].type === 'transaction', + )!; + const [errorEvent, errorTraceHeader] = envelopes.find(eventAndHeader => !eventAndHeader[0].type)!; + + const pageloadTraceContext = pageloadEvent?.contexts?.trace; + + expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + expect(pageloadTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const errorTraceContext = errorEvent?.contexts?.trace; + + expect(errorEvent.type).toEqual(undefined); + expect(errorTraceContext).toEqual({ + trace_id: pageloadTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + + expect(errorTraceHeader).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); +}); + +sentryTest( + 'outgoing fetch request during pageload has pageload traceId in headers', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('http://sentry-test-site.example/**', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); + + const pageloadEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); + await page.goto(url); + await page.locator('#fetchBtn').click(); + const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + + const pageloadTraceContext = pageloadEvent.contexts?.trace; + const pageloadTraceId = pageloadTraceContext?.trace_id; + + expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-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, + sample_rand: expect.any(String), + }); + + 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']).toBe( + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`, + ); + }, +); + +sentryTest( + 'outgoing XHR request during pageload has pageload traceId in headers', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('http://sentry-test-site.example/**', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); + + const pageloadEventPromise = getFirstSentryEnvelopeRequest( + page, + undefined, + eventAndTraceHeaderRequestParser, + ); + const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); + await page.goto(url); + await page.locator('#xhrBtn').click(); + const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + + const pageloadTraceContext = pageloadEvent.contexts?.trace; + const pageloadTraceId = pageloadTraceContext?.trace_id; + + expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-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, + sample_rand: expect.any(String), + }); + + 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']).toBe( + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`, + ); + }, +); + +// 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://sentry-test-site.example/**', 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://sentry-test-site.example/*'); +// 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://sentry-test-site.example/**', 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://sentry-test-site.example/**'); +// 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(); + } + + const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); + + const pageloadEvent = await getFirstSentryEnvelopeRequest(page, url); + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + const feedbackEventsPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'feedback' }); + + await page.getByText('Report a Bug').click(); + expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + await page.locator('[data-sentry-feedback] .btn--primary').click(); + + const [feedbackEvent] = await feedbackEventsPromise; + + expect(feedbackEvent.type).toEqual('feedback'); + + const feedbackTraceContext = feedbackEvent.contexts?.trace; + + expect(feedbackTraceContext).toMatchObject({ + trace_id: pageloadTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/test.ts deleted file mode 100644 index 646d4be6fa56..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; -import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipFeedbackTest, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; - -sentryTest('creates a new trace for a navigation after the initial pageload', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const pageloadSpan = await pageloadSpanPromise; - - page.goto(`${url}#foo`); - - const navigationSpan = await navigationSpanPromise; - - expect(getSpanOp(pageloadSpan)).toEqual('pageload'); - expect(pageloadSpan.trace_id).toMatch(/^[\da-f]{32}$/); - expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(pageloadSpan.parent_span_id).toBeUndefined(); - - expect(getSpanOp(navigationSpan)).toEqual('navigation'); - expect(navigationSpan.trace_id).toMatch(/^[\da-f]{32}$/); - expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(navigationSpan.parent_span_id).toBeUndefined(); - - expect(pageloadSpan.span_id).not.toEqual(navigationSpan.span_id); - expect(pageloadSpan.trace_id).not.toEqual(navigationSpan.trace_id); -}); - -sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - - const url = await getLocalTestUrl({ testDir: __dirname }); - await page.goto(url); - - const pageloadSpan = await pageloadSpanPromise; - const pageloadTraceId = pageloadSpan.trace_id; - - expect(getSpanOp(pageloadSpan)).toEqual('pageload'); - expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(pageloadSpan.parent_span_id).toBeUndefined(); - - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); - await page.locator('#errorBtn').click(); - const [errorEvent, errorTraceHeader] = await errorEventPromise; - - const errorTraceContext = errorEvent.contexts?.trace; - expect(errorEvent.type).toEqual(undefined); - - expect(errorTraceContext).toEqual({ - trace_id: pageloadTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - - expect(errorTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceId, - sample_rand: expect.any(String), - }); -}); - -sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); - - await page.goto(url); - await page.locator('#errorBtn').click(); - const [pageloadSpan, [errorEvent, errorTraceHeader]] = await Promise.all([pageloadSpanPromise, errorEventPromise]); - - const pageloadTraceId = pageloadSpan.trace_id; - - expect(getSpanOp(pageloadSpan)).toEqual('pageload'); - expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(pageloadSpan.parent_span_id).toBeUndefined(); - - const errorTraceContext = errorEvent?.contexts?.trace; - expect(errorEvent.type).toEqual(undefined); - - expect(errorTraceContext).toEqual({ - trace_id: pageloadTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - - expect(errorTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceId, - sample_rand: expect.any(String), - }); -}); - -sentryTest( - 'outgoing fetch request during pageload has pageload traceId in headers', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://sentry-test-site.example/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const pageloadSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); - await page.goto(url); - await page.locator('#fetchBtn').click(); - const [pageloadSpanEnvelope, request] = await Promise.all([pageloadSpanEnvelopePromise, requestPromise]); - - const pageloadTraceId = pageloadSpanEnvelope[0].trace?.trace_id; - const sampleRand = pageloadSpanEnvelope[0].trace?.sample_rand; - - expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - - 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']).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, - ); - }, -); - -sentryTest( - 'outgoing XHR request during pageload has pageload traceId in headers', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://sentry-test-site.example/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const pageloadSpanEnvelopePromise = waitForStreamedSpanEnvelope( - page, - env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), - ); - const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); - await page.goto(url); - await page.locator('#xhrBtn').click(); - const [pageloadSpanEnvelope, request] = await Promise.all([pageloadSpanEnvelopePromise, requestPromise]); - - const pageloadTraceId = pageloadSpanEnvelope[0].trace?.trace_id; - const sampleRand = pageloadSpanEnvelope[0].trace?.sample_rand; - - expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - - 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']).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, - ); - }, -); - -sentryTest('user feedback event after pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest() || shouldSkipFeedbackTest()); - - const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const pageloadSpan = await pageloadSpanPromise; - const pageloadTraceId = pageloadSpan.trace_id; - - expect(getSpanOp(pageloadSpan)).toEqual('pageload'); - expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(pageloadSpan.parent_span_id).toBeUndefined(); - - const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); - - await page.getByText('Report a Bug').click(); - expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); - await page.locator('[name="name"]').fill('Jane Doe'); - await page.locator('[name="email"]').fill('janedoe@example.org'); - await page.locator('[name="message"]').fill('my example feedback'); - await page.locator('[data-sentry-feedback] .btn--primary').click(); - - const feedbackEvent = await feedbackEventPromise; - - expect(feedbackEvent.type).toEqual('feedback'); - - const feedbackTraceContext = feedbackEvent.contexts?.trace; - - expect(feedbackTraceContext).toMatchObject({ - trace_id: pageloadTraceId, - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/init.js new file mode 100644 index 000000000000..30317eda63ef --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/init.js @@ -0,0 +1,12 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, +}); 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 e6edd02cf360..c6038e48826b 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 @@ -1,113 +1,61 @@ import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, + waitForErrorRequest, } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; -sentryTest( - 'should create a new trace for a navigation after the initial pageload', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +sentryTest('creates a new trace for a navigation after the initial pageload', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); - const url = await getLocalTestUrl({ testDir: __dirname }); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const navigationSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'navigation'); - const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const [navigationEvent, navigationTraceHeaders] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const navigationTraceContext = navigationEvent.contexts?.trace; + const pageloadSpan = await pageloadSpanPromise; - expect(pageloadEvent.type).toEqual('transaction'); - expect(navigationEvent.type).toEqual('transaction'); + await page.goto(`${url}#foo`); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); - - expect(pageloadTraceHeaders).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + const navigationSpan = await navigationSpanPromise; - expect(navigationTraceContext).toMatchObject({ - op: 'navigation', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - - expect(navigationTraceHeaders).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: navigationTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + expect(getSpanOp(pageloadSpan)).toEqual('pageload'); + expect(pageloadSpan.trace_id).toMatch(/^[\da-f]{32}$/); + expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(pageloadSpan.parent_span_id).toBeUndefined(); - expect(pageloadTraceContext?.span_id).not.toEqual(navigationTraceContext?.span_id); - }, -); + expect(getSpanOp(navigationSpan)).toEqual('navigation'); + expect(navigationSpan.trace_id).toMatch(/^[\da-f]{32}$/); + expect(navigationSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(navigationSpan.parent_span_id).toBeUndefined(); + + expect(pageloadSpan.span_id).not.toEqual(navigationSpan.span_id); + expect(pageloadSpan.trace_id).not.toEqual(navigationSpan.trace_id); +}); sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); + + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + const pageloadSpan = await pageloadSpanPromise; + const pageloadTraceId = pageloadSpan.trace_id; - expect(pageloadTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + expect(getSpanOp(pageloadSpan)).toEqual('pageload'); + expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); + expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(pageloadSpan.parent_span_id).toBeUndefined(); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; @@ -115,7 +63,7 @@ sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl expect(errorEvent.type).toEqual(undefined); expect(errorTraceContext).toEqual({ - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); @@ -124,57 +72,35 @@ sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadTraceId, sample_rand: expect.any(String), }); }); sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); - const envelopeRequestsPromise = getMultipleSentryEnvelopeRequests( - page, - 2, - undefined, - eventAndTraceHeaderRequestParser, - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); + await page.goto(url); await page.locator('#errorBtn').click(); - const envelopes = await envelopeRequestsPromise; - - const [pageloadEvent, pageloadTraceHeader] = envelopes.find( - eventAndHeader => eventAndHeader[0].type === 'transaction', - )!; - const [errorEvent, errorTraceHeader] = envelopes.find(eventAndHeader => !eventAndHeader[0].type)!; - - const pageloadTraceContext = pageloadEvent?.contexts?.trace; + const [pageloadSpan, [errorEvent, errorTraceHeader]] = await Promise.all([pageloadSpanPromise, errorEventPromise]); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + const pageloadTraceId = pageloadSpan.trace_id; - expect(pageloadTraceHeader).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, - sample_rand: expect.any(String), - }); + expect(getSpanOp(pageloadSpan)).toEqual('pageload'); + expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); + expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(pageloadSpan.parent_span_id).toBeUndefined(); const errorTraceContext = errorEvent?.contexts?.trace; - expect(errorEvent.type).toEqual(undefined); + expect(errorTraceContext).toEqual({ - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); @@ -183,7 +109,7 @@ sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUr public_key: 'public', sample_rate: '1', sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadTraceId, sample_rand: expect.any(String), }); }); @@ -191,9 +117,7 @@ sentryTest('error during pageload has pageload traceId', async ({ getLocalTestUr sentryTest( 'outgoing fetch request during pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -205,42 +129,26 @@ sentryTest( }); }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadSpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - undefined, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(url); await page.locator('#fetchBtn').click(); - const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + const [pageloadSpanEnvelope, request] = await Promise.all([pageloadSpanEnvelopePromise, requestPromise]); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const pageloadTraceId = pageloadTraceContext?.trace_id; + const pageloadTraceId = pageloadSpanEnvelope[0].trace?.trace_id; + const sampleRand = pageloadSpanEnvelope[0].trace?.sample_rand; - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-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, - sample_rand: expect.any(String), - }); + expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); 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']).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`, + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, ); }, ); @@ -248,9 +156,7 @@ sentryTest( sentryTest( 'outgoing XHR request during pageload has pageload traceId in headers', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -262,207 +168,46 @@ sentryTest( }); }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadSpanEnvelopePromise = waitForStreamedSpanEnvelope( page, - undefined, - eventAndTraceHeaderRequestParser, + env => !!env[1][0][1].items.find(s => getSpanOp(s) === 'pageload'), ); const requestPromise = page.waitForRequest('http://sentry-test-site.example/*'); await page.goto(url); await page.locator('#xhrBtn').click(); - const [[pageloadEvent, pageloadTraceHeader], request] = await Promise.all([pageloadEventPromise, requestPromise]); + const [pageloadSpanEnvelope, request] = await Promise.all([pageloadSpanEnvelopePromise, requestPromise]); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const pageloadTraceId = pageloadTraceContext?.trace_id; + const pageloadTraceId = pageloadSpanEnvelope[0].trace?.trace_id; + const sampleRand = pageloadSpanEnvelope[0].trace?.sample_rand; - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-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, - sample_rand: expect.any(String), - }); + expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); 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']).toBe( - `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${pageloadTraceHeader?.sample_rand},sentry-sample_rate=1`, + `sentry-environment=production,sentry-public_key=public,sentry-trace_id=${pageloadTraceId},sentry-sampled=true,sentry-sample_rand=${sampleRand},sentry-sample_rate=1`, ); }, ); -// 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://sentry-test-site.example/**', 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://sentry-test-site.example/*'); -// 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://sentry-test-site.example/**', 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://sentry-test-site.example/**'); -// 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(); - } + sentryTest.skip(shouldSkipTracingTest() || shouldSkipFeedbackTest()); const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - const pageloadEvent = await getFirstSentryEnvelopeRequest(page, url); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + const pageloadSpan = await pageloadSpanPromise; + const pageloadTraceId = pageloadSpan.trace_id; - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + expect(getSpanOp(pageloadSpan)).toEqual('pageload'); + expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); + expect(pageloadSpan.span_id).toMatch(/^[\da-f]{16}$/); + expect(pageloadSpan.parent_span_id).toBeUndefined(); - const feedbackEventPromise = getFirstSentryEnvelopeRequest(page); + const feedbackEventsPromise = getMultipleSentryEnvelopeRequests(page, 1, { envelopeType: 'feedback' }); await page.getByText('Report a Bug').click(); expect(await page.locator(':visible:text-is("Report a Bug")').count()).toEqual(1); @@ -471,14 +216,14 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', await page.locator('[name="message"]').fill('my example feedback'); await page.locator('[data-sentry-feedback] .btn--primary').click(); - const feedbackEvent = await feedbackEventPromise; + const [feedbackEvent] = await feedbackEventsPromise; expect(feedbackEvent.type).toEqual('feedback'); const feedbackTraceContext = feedbackEvent.contexts?.trace; expect(feedbackTraceContext).toMatchObject({ - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadTraceId, span_id: expect.stringMatching(/^[\da-f]{16}$/), }); }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-multiple-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-multiple-spans/test.ts index cf17b26aee87..f9c3d5deca85 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-multiple-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-multiple-spans/test.ts @@ -1,12 +1,11 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - getMultipleSentryEnvelopeRequests, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; + type StreamedSpanAndTraceHeader, + waitForStreamedSpanAndTraceHeader, + waitForStreamedSpanAndTraceHeaderOnUrl, +} from '../../../../utils/spanUtils'; sentryTest( 'every root span created within a single `startNewTrace` callback shares the one new trace', @@ -17,44 +16,37 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const pageloadTraceId = pageloadEvent.contexts?.trace?.trace_id; + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceId = pageloadSpan?.trace_id; expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); - const transactionPromises = getMultipleSentryEnvelopeRequests( - page, - 3, - { envelopeType: 'transaction' }, - eventAndTraceHeaderRequestParser, + const spanPromises = Promise.all( + [1, 2, 3].map(index => waitForStreamedSpanAndTraceHeader(page, span => span.name === `new-trace-span-${index}`)), ); await page.locator('#newTrace').click(); - const transactions = await transactionPromises; + const spans = await spanPromises; - const byName = (name: string): EventAndTraceHeader => transactions.find(([event]) => event.transaction === name)!; + const byName = (name: string): StreamedSpanAndTraceHeader => spans.find(([span]) => span.name === name)!; const [span1] = byName('new-trace-span-1'); const [span2] = byName('new-trace-span-2'); const [span3] = byName('new-trace-span-3'); - const newTraceId = span1.contexts?.trace?.trace_id; + const newTraceId = span1.trace_id; expect(newTraceId).toMatch(/^[\da-f]{32}$/); // All three root spans share the one new trace id ... - expect(span2.contexts?.trace?.trace_id).toBe(newTraceId); - expect(span3.contexts?.trace?.trace_id).toBe(newTraceId); + expect(span2.trace_id).toBe(newTraceId); + expect(span3.trace_id).toBe(newTraceId); // ... which is a fresh trace, not the pageload trace. expect(newTraceId).not.toBe(pageloadTraceId); // They are independent root spans, not parented to one another. - expect(span1.contexts?.trace).not.toHaveProperty('parent_span_id'); - expect(span2.contexts?.trace).not.toHaveProperty('parent_span_id'); - expect(span3.contexts?.trace).not.toHaveProperty('parent_span_id'); + expect(span1).not.toHaveProperty('parent_span_id'); + expect(span2).not.toHaveProperty('parent_span_id'); + expect(span3).not.toHaveProperty('parent_span_id'); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/init.js new file mode 100644 index 000000000000..8718d9d354eb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/init.js @@ -0,0 +1,13 @@ +import * as Sentry from '@sentry/browser'; +// Import this separately so that generatePlugin can handle it for CDN scenarios +import { feedbackIntegration } from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration(), feedbackIntegration()], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/subject.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/subject.js rename to dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/subject.js diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/template.html b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html similarity index 100% rename from dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/template.html rename to dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/test.ts new file mode 100644 index 000000000000..40cc8a64d75f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/test.ts @@ -0,0 +1,109 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import type { EventAndTraceHeader } from '../../../../utils/helpers'; +import { + eventAndTraceHeaderRequestParser, + getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, + shouldSkipTracingTest, +} from '../../../../utils/helpers'; + +sentryTest( + 'creates a new trace if `startNewTrace` is called and leaves old trace valid outside the callback', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + await page.route('http://sentry-test-site.example/**', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({}), + }); + }); + + const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( + page, + url, + eventAndTraceHeaderRequestParser, + ); + + const pageloadTraceContext = pageloadEvent.contexts?.trace; + + expect(pageloadEvent.type).toEqual('transaction'); + + expect(pageloadTraceContext).toMatchObject({ + op: 'pageload', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + + expect(pageloadTraceHeaders).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: pageloadTraceContext?.trace_id, + sample_rand: expect.any(String), + }); + + const transactionPromises = getMultipleSentryEnvelopeRequests( + page, + 2, + { envelopeType: 'transaction' }, + eventAndTraceHeaderRequestParser, + ); + + await page.locator('#newTrace').click(); + await page.locator('#oldTrace').click(); + + const [txnEvent1, txnEvent2] = await transactionPromises; + + const [newTraceTransactionEvent, newTraceTransactionTraceHeaders] = + txnEvent1[0].transaction === 'new-trace' ? txnEvent1 : txnEvent2; + const [oldTraceTransactionEvent, oldTraceTransactionTraceHeaders] = + txnEvent1[0].transaction === 'old-trace' ? txnEvent1 : txnEvent2; + + const newTraceTransactionTraceContext = newTraceTransactionEvent.contexts?.trace; + expect(newTraceTransactionTraceContext).toMatchObject({ + op: 'ui.interaction.click', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + + expect(newTraceTransactionTraceHeaders).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: newTraceTransactionTraceContext?.trace_id, + transaction: 'new-trace', + sample_rand: expect.any(String), + }); + + const oldTraceTransactionEventTraceContext = oldTraceTransactionEvent.contexts?.trace; + expect(oldTraceTransactionEventTraceContext).toMatchObject({ + op: 'ui.interaction.click', + trace_id: expect.stringMatching(/^[\da-f]{32}$/), + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + + expect(oldTraceTransactionTraceHeaders).toEqual({ + environment: 'production', + public_key: 'public', + sample_rate: '1', + sampled: 'true', + trace_id: oldTraceTransactionTraceHeaders?.trace_id, + sample_rand: expect.any(String), + // transaction: 'old-trace', <-- this is not in the DSC because the DSC is continued from the pageload transaction + // which does not have a `transaction` field because its source is URL. + }); + + expect(oldTraceTransactionEventTraceContext?.trace_id).toEqual(pageloadTraceContext?.trace_id); + expect(newTraceTransactionTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/test.ts deleted file mode 100644 index 07ac4b0c8cd3..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; - -sentryTest( - 'creates a new trace if `startNewTrace` is called and leaves old trace valid outside the callback', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.route('http://sentry-test-site.example/**', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({}), - }); - }); - - const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); - await page.goto(url); - const pageloadSpan = await pageloadSpanPromise; - - const newTraceSpanPromise = waitForStreamedSpan(page, span => span.name === 'new-trace'); - const oldTraceSpanPromise = waitForStreamedSpan(page, span => span.name === 'old-trace'); - - await page.locator('#newTrace').click(); - await page.locator('#oldTrace').click(); - - const [newTraceSpan, oldTraceSpan] = await Promise.all([newTraceSpanPromise, oldTraceSpanPromise]); - - expect(getSpanOp(newTraceSpan)).toEqual('ui.interaction.click'); - expect(newTraceSpan.trace_id).toMatch(/^[\da-f]{32}$/); - expect(newTraceSpan.span_id).toMatch(/^[\da-f]{16}$/); - - expect(getSpanOp(oldTraceSpan)).toEqual('ui.interaction.click'); - expect(oldTraceSpan.trace_id).toMatch(/^[\da-f]{32}$/); - expect(oldTraceSpan.span_id).toMatch(/^[\da-f]{16}$/); - - expect(oldTraceSpan.trace_id).toEqual(pageloadSpan.trace_id); - expect(newTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/init.js similarity index 71% rename from dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/init.js rename to dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/init.js index 187e07624fdf..b2280b70e307 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/init.js @@ -4,7 +4,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration(), Sentry.spanStreamingIntegration()], + integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], tracesSampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/test.ts index 40cc8a64d75f..07ac4b0c8cd3 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace/test.ts @@ -1,19 +1,12 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; -import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - getMultipleSentryEnvelopeRequests, - shouldSkipTracingTest, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; sentryTest( 'creates a new trace if `startNewTrace` is called and leaves old trace valid outside the callback', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } + sentryTest.skip(shouldSkipTracingTest()); const url = await getLocalTestUrl({ testDir: __dirname }); @@ -25,85 +18,27 @@ sentryTest( }); }); - const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); + const pageloadSpanPromise = waitForStreamedSpan(page, span => getSpanOp(span) === 'pageload'); + await page.goto(url); + const pageloadSpan = await pageloadSpanPromise; - const pageloadTraceContext = pageloadEvent.contexts?.trace; - - expect(pageloadEvent.type).toEqual('transaction'); - - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); - - expect(pageloadTraceHeaders).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, - sample_rand: expect.any(String), - }); - - const transactionPromises = getMultipleSentryEnvelopeRequests( - page, - 2, - { envelopeType: 'transaction' }, - eventAndTraceHeaderRequestParser, - ); + const newTraceSpanPromise = waitForStreamedSpan(page, span => span.name === 'new-trace'); + const oldTraceSpanPromise = waitForStreamedSpan(page, span => span.name === 'old-trace'); await page.locator('#newTrace').click(); await page.locator('#oldTrace').click(); - const [txnEvent1, txnEvent2] = await transactionPromises; + const [newTraceSpan, oldTraceSpan] = await Promise.all([newTraceSpanPromise, oldTraceSpanPromise]); - const [newTraceTransactionEvent, newTraceTransactionTraceHeaders] = - txnEvent1[0].transaction === 'new-trace' ? txnEvent1 : txnEvent2; - const [oldTraceTransactionEvent, oldTraceTransactionTraceHeaders] = - txnEvent1[0].transaction === 'old-trace' ? txnEvent1 : txnEvent2; + expect(getSpanOp(newTraceSpan)).toEqual('ui.interaction.click'); + expect(newTraceSpan.trace_id).toMatch(/^[\da-f]{32}$/); + expect(newTraceSpan.span_id).toMatch(/^[\da-f]{16}$/); - const newTraceTransactionTraceContext = newTraceTransactionEvent.contexts?.trace; - expect(newTraceTransactionTraceContext).toMatchObject({ - op: 'ui.interaction.click', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - - expect(newTraceTransactionTraceHeaders).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: newTraceTransactionTraceContext?.trace_id, - transaction: 'new-trace', - sample_rand: expect.any(String), - }); - - const oldTraceTransactionEventTraceContext = oldTraceTransactionEvent.contexts?.trace; - expect(oldTraceTransactionEventTraceContext).toMatchObject({ - op: 'ui.interaction.click', - trace_id: expect.stringMatching(/^[\da-f]{32}$/), - span_id: expect.stringMatching(/^[\da-f]{16}$/), - }); - - expect(oldTraceTransactionTraceHeaders).toEqual({ - environment: 'production', - public_key: 'public', - sample_rate: '1', - sampled: 'true', - trace_id: oldTraceTransactionTraceHeaders?.trace_id, - sample_rand: expect.any(String), - // transaction: 'old-trace', <-- this is not in the DSC because the DSC is continued from the pageload transaction - // which does not have a `transaction` field because its source is URL. - }); + expect(getSpanOp(oldTraceSpan)).toEqual('ui.interaction.click'); + expect(oldTraceSpan.trace_id).toMatch(/^[\da-f]{32}$/); + expect(oldTraceSpan.span_id).toMatch(/^[\da-f]{16}$/); - expect(oldTraceTransactionEventTraceContext?.trace_id).toEqual(pageloadTraceContext?.trace_id); - expect(newTraceTransactionTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(oldTraceSpan.trace_id).toEqual(pageloadSpan.trace_id); + expect(newTraceSpan.trace_id).not.toEqual(pageloadSpan.trace_id); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js index 9e1a8762f0b1..d32f77f4cb6b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/init.js @@ -15,7 +15,6 @@ crypto.randomUUID = function randomUUID() { }; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], tracePropagationTargets: ['http://sentry-test-site.example'], diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/test.ts index dc3d75f54799..17f822e26a2a 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTraceSampling/test.ts @@ -1,12 +1,9 @@ +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; -import { - eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, - shouldSkipTracingTest, - waitForTransactionRequest, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { waitForStreamedSpanAndTraceHeader, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest( 'new trace started with `startNewTrace` is sampled according to the `tracesSampler`', @@ -25,65 +22,56 @@ sentryTest( }); }); - const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); + const [pageloadSpan, pageloadTraceHeaders] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + expect(pageloadSpan.is_segment).toBe(true); - expect(pageloadEvent.type).toEqual('transaction'); - - expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + expect(pageloadSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'pageload' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'double', value: 0.5 }, + }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), - data: { - 'sentry.sample_rate': 0.5, - }, }); - expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); + expect(pageloadSpan).not.toHaveProperty('parent_span_id'); expect(pageloadTraceHeaders).toEqual({ environment: 'production', public_key: 'public', sample_rate: '0.5', sampled: 'true', - trace_id: pageloadTraceContext?.trace_id, + trace_id: pageloadSpan?.trace_id, sample_rand: '0.45', }); - const transactionPromise = waitForTransactionRequest(page, event => { - return event.transaction === 'new-trace'; - }); + const spanPromise = waitForStreamedSpanAndTraceHeader(page, span => span.name === 'new-trace'); await page.locator('#newTrace').click(); - const [newTraceTransactionEvent, newTraceTransactionTraceHeaders] = eventAndTraceHeaderRequestParser( - await transactionPromise, - ); + const [newTraceSpan, newTraceHeaders] = await spanPromise; - const newTraceTransactionTraceContext = newTraceTransactionEvent.contexts?.trace; - expect(newTraceTransactionTraceContext).toMatchObject({ - op: 'ui.interaction.click', + expect(newTraceSpan).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ + [SENTRY_OP]: { type: 'string', value: 'ui.interaction.click' }, + [SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: { type: 'double', value: 0.9 }, + }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), - data: { - 'sentry.sample_rate': 0.9, - }, }); - expect(newTraceTransactionTraceHeaders).toEqual({ + expect(newTraceHeaders).toEqual({ environment: 'production', public_key: 'public', sample_rate: '0.9', sampled: 'true', - trace_id: newTraceTransactionTraceContext?.trace_id, + trace_id: newTraceSpan?.trace_id, transaction: 'new-trace', sample_rand: '0.85', }); - expect(newTraceTransactionTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(newTraceSpan?.trace_id).not.toEqual(pageloadSpan?.trace_id); }, ); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/init.js index f6aa87028094..94222159056c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', // in browser TwP means not setting tracesSampleRate but adding browserTracingIntegration, dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/test.ts index 3d0262b717d9..b0737b5c9fe7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-headers/test.ts @@ -1,10 +1,9 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import type { EventAndTraceHeader } from '../../../../utils/helpers'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, shouldSkipTracingTest, + waitForErrorRequest, } from '../../../../utils/helpers'; const META_TAG_TRACE_ID = '12345678901234567890123456789012'; @@ -25,11 +24,7 @@ sentryTest('error on initial page has traceId from server timing headers', async }); await page.goto(url); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-propagateTraceparent/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-propagateTraceparent/init.js index 59e00870be1d..b5b7d0354f77 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-propagateTraceparent/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance-propagateTraceparent/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', // in browser TwP means not setting tracesSampleRate but adding browserTracingIntegration, dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/init.js index f6aa87028094..94222159056c 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', // in browser TwP means not setting tracesSampleRate but adding browserTracingIntegration, dsn: 'https://public@dsn.ingest.sentry.io/1337', integrations: [Sentry.browserTracingIntegration()], diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/test.ts index 51cc61e0de0c..126f1eb99bff 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/tracing-without-performance/test.ts @@ -5,6 +5,7 @@ import { eventAndTraceHeaderRequestParser, getFirstSentryEnvelopeRequest, shouldSkipTracingTest, + waitForErrorRequest, } from '../../../../utils/helpers'; const META_TAG_TRACE_ID = '12345678901234567890123456789012'; @@ -20,11 +21,7 @@ sentryTest('error on initial page has traceId from meta tag', async ({ getLocalT const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; @@ -53,11 +50,7 @@ sentryTest('error has new traceId after navigation', async ({ getLocalTestUrl, p const url = await getLocalTestUrl({ testDir: __dirname }); await page.goto(url); - const errorEventPromise = getFirstSentryEnvelopeRequest( - page, - undefined, - eventAndTraceHeaderRequestParser, - ); + const errorEventPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.locator('#errorBtn').click(); const [errorEvent, errorTraceHeader] = await errorEventPromise; diff --git a/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/init.js b/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/init.js index 1c46615de295..9eeaf599e7c2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/init.js @@ -3,7 +3,6 @@ import * as Sentry from '@sentry/browser'; window.Sentry = Sentry; Sentry.init({ - traceLifecycle: 'static', dsn: 'https://public@dsn.ingest.sentry.io/1337', tracesSampler: ({ attributes }) => { return attributes?.['sentry.op'] === 'custom.op' ? 1 : 0; diff --git a/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/test.ts b/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/test.ts index 9d5bc57bc5c2..4828dcd58af7 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/tracesSampler/op-attribute/test.ts @@ -1,10 +1,7 @@ import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; -import { - envelopeRequestParser, - shouldSkipTracingTest, - waitForTransactionRequestOnUrl, -} from '../../../../utils/helpers'; +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpanAndTraceHeaderOnUrl } from '../../../../utils/spanUtils'; sentryTest('tracesSampler can sample based on the `sentry.op` attribute', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -15,10 +12,9 @@ sentryTest('tracesSampler can sample based on the `sentry.op` attribute', async // The sampler drops `other.op` and keeps `custom.op`, so the only transaction // that arrives is the one whose op the sampler saw in the attributes. - const req = await waitForTransactionRequestOnUrl(page, url); - const transactionEvent = envelopeRequestParser(req); + const [span] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(transactionEvent.type).toBe('transaction'); - expect(transactionEvent.transaction).toBe('span-with-sampled-op'); - expect(transactionEvent.contexts?.trace?.op).toBe('custom.op'); + expect(span.is_segment).toBe(true); + expect(span.name).toBe('span-with-sampled-op'); + expect(getSpanOp(span)).toBe('custom.op'); }); diff --git a/dev-packages/browser-integration-tests/utils/helpers.ts b/dev-packages/browser-integration-tests/utils/helpers.ts index 97c40002e305..05b5aebcf5f4 100644 --- a/dev-packages/browser-integration-tests/utils/helpers.ts +++ b/dev-packages/browser-integration-tests/utils/helpers.ts @@ -215,12 +215,12 @@ export function waitForErrorRequest(page: Page, callback?: (event: SentryEvent) } try { - const event = envelopeRequestParser(req); - - if (event.type) { + if (getEnvelopeType(req) !== 'event') { return false; } + const event = envelopeRequestParser(req); + if (callback) { return callback(event); } diff --git a/dev-packages/browser-integration-tests/utils/spanUtils.ts b/dev-packages/browser-integration-tests/utils/spanUtils.ts index 532f2ce5c526..4a10f9069e8c 100644 --- a/dev-packages/browser-integration-tests/utils/spanUtils.ts +++ b/dev-packages/browser-integration-tests/utils/spanUtils.ts @@ -1,5 +1,5 @@ import type { Page } from '@playwright/test'; -import type { SerializedStreamedSpan, StreamedSpanEnvelope } from '@sentry/core'; +import type { DynamicSamplingContext, SerializedStreamedSpan, StreamedSpanEnvelope } from '@sentry/core'; import { properFullEnvelopeParser } from './helpers'; /** @@ -131,3 +131,42 @@ export function getSpanOp(span: SerializedStreamedSpan): string | undefined { export function getSpansFromEnvelope(envelope: StreamedSpanEnvelope): SerializedStreamedSpan[] { return envelope[1][0][1].items; } + +export type StreamedSpanAndTraceHeader = [SerializedStreamedSpan, Partial | undefined]; + +export async function waitForStreamedSpanAndTraceHeader( + page: Page, + callback: (span: SerializedStreamedSpan) => boolean = span => span.is_segment, +): Promise { + const envelope = await waitForStreamedSpanEnvelope(page, envelope => envelope[1][0][1].items.some(callback)); + const span = envelope[1][0][1].items.find(callback)!; + return [span, envelope[0].trace]; +} + +export async function waitForStreamedSpanAndTraceHeaderOnUrl( + page: Page, + url: string, + callback?: (span: SerializedStreamedSpan) => boolean, +): Promise { + const [spanAndTraceHeader] = await Promise.all([waitForStreamedSpanAndTraceHeader(page, callback), page.goto(url)]); + return spanAndTraceHeader; +} + +export function collectStreamedSpans(page: Page): SerializedStreamedSpan[] { + const spans: SerializedStreamedSpan[] = []; + page.on('request', request => { + if (!request.postData()) { + return; + } + try { + const envelope = properFullEnvelopeParser(request); + const header = envelope[1][0][0]; + if (header.type === 'span' && header.content_type === 'application/vnd.sentry.items.span.v2+json') { + spans.push(...envelope[1][0][1].items); + } + } catch { + // Other requests may contain bodies which are not envelopes. + } + }); + return spans; +}