From 711d62db4ec1f2ab8dbb5cba9b17d7bd928064e7 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 30 Sep 2026 13:11:30 +0200 Subject: [PATCH 1/5] test(browser): port trace semantics to span streaming Co-Authored-By: GPT-6 --- .../integrations/httpclient/fetch/init.js | 1 - .../fetch/withAbortController/init.js | 1 - .../fetch/withAbortController/test.ts | 13 +- .../fetch/withDataCollection/init.js | 1 - .../fetch/withDisabledDataCollection/init.js | 1 - .../withHeadersOnlyDataCollection/init.js | 1 - .../suites/integrations/httpclient/init.js | 1 - .../httpclient/xhr/withDataCollection/init.js | 1 - .../init.js | 0 .../test.ts | 0 .../public-api/beforeSendTransaction/init.js | 24 - .../public-api/beforeSendTransaction/test.ts | 32 -- .../public-api/startSpan/attributes/test.ts | 20 +- .../startSpan/{streamed => basic}/init.js | 2 +- .../public-api/startSpan/basic/subject.js | 25 +- .../suites/public-api/startSpan/basic/test.ts | 330 ++++++++++++-- .../startSpan/circular_data/test.ts | 35 +- .../startSpan/error-async-reject/test.ts | 15 +- .../error-async-throw-not-awaited/test.ts | 15 +- .../startSpan/error-async-throw/test.ts | 15 +- .../public-api/startSpan/error-sync/test.ts | 17 +- .../suites/public-api/startSpan/init.js | 1 - .../parallel-root-spans-in-scope/test.ts | 20 +- .../test.ts | 20 +- .../startSpan/parallel-root-spans/test.ts | 20 +- .../startSpan/setMeasurement/init.js | 11 + .../startSpan/setMeasurement/test.ts | 29 +- .../public-api/startSpan/streamed/subject.js | 15 - .../public-api/startSpan/streamed/test.ts | 295 ------------ .../thenable-with-extra-methods/test.ts | 11 +- .../suites/tracing/bindScopeToEmitter/init.js | 1 - .../suites/tracing/bindScopeToEmitter/test.ts | 18 +- .../browserTracingIntegrationShim/init.js | 1 - .../continueTrace/full-tracing/init.js | 1 - .../continueTrace/full-tracing/test.ts | 67 ++- .../tracesSampleRate-zero/init.js | 1 - .../tracesSampleRate-zero/test.ts | 22 +- .../tracing-without-performance/init.js | 1 - .../tracing-without-performance/test.ts | 2 +- .../tracing/dsc-txn-name-update/init.js | 1 - .../tracing/dsc-txn-name-update/test.ts | 28 +- .../envelope-header-transaction-name/init.js | 1 - .../envelope-header-transaction-name/test.ts | 16 +- .../suites/tracing/envelope-header/init.js | 1 - .../suites/tracing/envelope-header/test.ts | 16 +- .../tracing/http-client-span-streamed/init.js | 13 - .../init.js | 1 - .../subject.js | 0 .../test.ts | 4 +- .../attributes/init.js | 2 +- .../attributes/subject.js | 0 .../attributes/test.ts | 0 .../child/init.js | 2 +- .../child/subject.js | 0 .../child/test.ts | 0 .../continued-trace-child/init.js | 2 +- .../continued-trace-child/subject.js | 0 .../continued-trace-child/template.html | 0 .../continued-trace-child/test.ts | 0 .../continued-trace-http-client/init.js | 2 +- .../continued-trace-http-client/subject.js | 0 .../continued-trace-http-client/template.html | 0 .../continued-trace-http-client/test.ts | 0 .../continued-trace-segment/init.js | 2 +- .../continued-trace-segment/subject.js | 0 .../continued-trace-segment/template.html | 0 .../continued-trace-segment/test.ts | 0 .../suites/tracing/ignoreSpans/init.js | 1 - .../segment/init.js | 2 +- .../segment/subject.js | 0 .../segment/test.ts | 0 .../suites/tracing/ignoreSpans/test.ts | 16 +- .../tracing/linking-addLink-streamed/init.js | 9 - .../linking-addLink-streamed/subject.js | 28 -- .../tracing/linking-addLink-streamed/test.ts | 66 --- .../suites/tracing/linking-addLink/init.js | 1 - .../suites/tracing/linking-addLink/test.ts | 93 ++-- .../suites/tracing/linking-addLinks/init.js | 1 - .../suites/tracing/linking-addLinks/test.ts | 73 +-- .../tracing/linking-spanOptions/init.js | 1 - .../tracing/linking-spanOptions/test.ts | 42 +- .../suites/tracing/maxSpans/init.js | 1 - .../suites/tracing/maxSpans/test.ts | 26 +- .../microfrontend-span-attribution/init.js | 1 - .../microfrontend-span-attribution/test.ts | 41 +- .../no-parent-span-client-report/subject.js | 1 - .../no-parent-span-client-report/test.ts | 45 -- .../setSpanActive-streamed/default/init.js | 9 - .../setSpanActive-streamed/default/subject.js | 14 - .../setSpanActive-streamed/default/test.ts | 35 -- .../nested-parentAlwaysRoot/init.js | 10 - .../nested-parentAlwaysRoot/subject.js | 22 - .../nested-parentAlwaysRoot/test.ts | 58 --- .../setSpanActive-streamed/nested/init.js | 9 - .../setSpanActive-streamed/nested/subject.js | 22 - .../setSpanActive-streamed/nested/test.ts | 53 --- .../tracing/setSpanActive/default/init.js | 2 +- .../tracing/setSpanActive/default/test.ts | 24 +- .../nested-parentAlwaysRoot/init.js | 2 +- .../nested-parentAlwaysRoot/test.ts | 36 +- .../tracing/setSpanActive/nested/init.js | 2 +- .../tracing/setSpanActive/nested/test.ts | 36 +- .../suites/tracing/stringSampleRate/init.js | 1 - .../suites/tracing/stringSampleRate/test.ts | 8 +- .../suites/tracing/trace-lifetime/init.js | 1 - .../navigation-streamed/test.ts | 317 ------------- .../init.js | 2 +- .../tracing/trace-lifetime/navigation/test.ts | 303 ++++++------- .../trace-lifetime/pageload-headers/test.ts | 34 +- .../trace-lifetime/pageload-meta/test.ts | 108 ++--- .../trace-lifetime/pageload-streamed/test.ts | 237 ---------- .../{pageload-streamed => pageload}/init.js | 2 +- .../tracing/trace-lifetime/pageload/test.ts | 420 ++++-------------- .../startNewTrace-multiple-spans/test.ts | 42 +- .../startNewTrace-streamed/subject.js | 15 - .../startNewTrace-streamed/template.html | 10 - .../startNewTrace-streamed/test.ts | 44 -- .../init.js | 2 +- .../trace-lifetime/startNewTrace/test.ts | 99 +---- .../startNewTraceSampling/init.js | 1 - .../startNewTraceSampling/test.ts | 55 +-- .../init.js | 1 - .../test.ts | 9 +- .../init.js | 1 - .../tracing-without-performance/init.js | 1 - .../tracing-without-performance/test.ts | 13 +- .../tracesSampler/op-attribute/init.js | 1 - .../tracesSampler/op-attribute/test.ts | 16 +- .../utils/spanUtils.ts | 41 +- 129 files changed, 1095 insertions(+), 2648 deletions(-) rename dev-packages/browser-integration-tests/suites/public-api/{beforeSendSpan-streamed => beforeSendSpan}/init.js (100%) rename dev-packages/browser-integration-tests/suites/public-api/{beforeSendSpan-streamed => beforeSendSpan}/test.ts (100%) delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts rename dev-packages/browser-integration-tests/suites/public-api/startSpan/{streamed => basic}/init.js (77%) create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/setMeasurement/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/streamed/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js rename dev-packages/browser-integration-tests/suites/tracing/{no-parent-span-client-report => http-client-span}/init.js (91%) rename dev-packages/browser-integration-tests/suites/tracing/{http-client-span-streamed => http-client-span}/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{http-client-span-streamed => http-client-span}/test.ts (90%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/attributes/init.js (82%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/attributes/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/attributes/test.ts (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/child/init.js (82%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/child/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/child/test.ts (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-child/init.js (76%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-child/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-child/template.html (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-child/test.ts (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-http-client/init.js (76%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-http-client/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-http-client/template.html (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-http-client/test.ts (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-segment/init.js (74%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-segment/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-segment/template.html (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/continued-trace-segment/test.ts (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/segment/init.js (79%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/segment/subject.js (100%) rename dev-packages/browser-integration-tests/suites/tracing/{ignoreSpans-streamed => ignoreSpans}/segment/test.ts (100%) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-streamed/test.ts rename dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/{navigation-streamed => navigation}/init.js (89%) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-streamed/test.ts rename dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/{pageload-streamed => pageload}/init.js (89%) delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/subject.js delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/template.html delete mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/test.ts rename dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/{startNewTrace-streamed => startNewTrace}/init.js (71%) 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 100% 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 diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js deleted file mode 100644 index e4206b38e808..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/init.js +++ /dev/null @@ -1,24 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - traceLifecycle: 'static', - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.browserTracingIntegration()], - beforeSendTransaction: transactionEvent => { - const op = transactionEvent.contexts.trace.op; - if (op === 'pageload' || op === 'navigation') { - // use whatever logic you want to set the name - transactionEvent.transaction = 'customName'; - - transactionEvent.transaction_info.source = 'route'; - transactionEvent.contexts.trace.data = { - ...transactionEvent.contexts.trace.data, - ['sentry.segment.name.source']: 'route', - }; - } - return transactionEvent; - }, - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts deleted file mode 100644 index f5b5a96608f1..000000000000 --- a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction/test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; -import { expect } from '@playwright/test'; - -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../utils/helpers'; - -sentryTest( - 'allows modification of the transaction name and source but overwrites source to custom', - async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const eventData = await getFirstSentryEnvelopeRequest(page, url); - - expect(eventData.type).toBe('transaction'); - - // user-changed name - expect(eventData.transaction).toBe('customName'); - - // Despite the user setting the source to 'route', the SDK detects that the txn name was changed - // and therefore sets the transaction_info.source to 'custom'. This is not ideal but also not easily changeable. - // Given that Relay doesn't differentiate between 'source' and 'route', we'll keep this as-is for now. - expect(eventData.transaction_info?.source).toBe('custom'); - - // This stays the same but it has no effect on Relay. - expect(eventData.contexts?.trace?.data?.[SENTRY_SEGMENT_NAME_SOURCE]).toBe('route'); - }, -); 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/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..ac4dfdd9a57d 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,7 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [Sentry.spanStreamingIntegration()], + integrations: [], 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..701e0c09fa55 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,292 @@ -import { SENTRY_SEGMENT_NAME_SOURCE, SENTRY_ORIGIN } from '@sentry/conventions/attributes'; +import { + 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/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data/test.ts index 41d49b6351ea..845688d9ec49 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,18 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; -import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('should be able to handle circular data', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } +import { shouldSkipTracingTest } from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; +sentryTest('handles circular attributes on root and child spans', async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest()); 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', - }); + 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); - 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.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..26557b337b35 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,12 @@ import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; 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,7 +22,8 @@ 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); @@ -37,12 +39,11 @@ sentryTest( `, }); - const events = await errorEventsPromise; + const [span, errorRequest] = await Promise.all([spanPromise, errorPromise]); + const err = envelopeRequestParser(errorRequest); - const txn = events.find(event => event.type === 'transaction'); - const err = events.find(event => !event.type); - - 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-streamed/init.js deleted file mode 100644 index 94db849f5fde..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span-streamed/init.js +++ /dev/null @@ -1,13 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - integrations: [ - Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false }), - Sentry.spanStreamingIntegration(), - ], - tracesSampleRate: 1, - sendClientReports: true, -}); 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/http-client-span/init.js similarity index 91% 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/http-client-span/init.js index 4c93751dc394..10e5ac1b84eb 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/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({ instrumentPageLoad: false, instrumentNavigation: false })], tracesSampleRate: 1, 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 90% 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..ddfa111e0428 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 @@ -4,7 +4,7 @@ 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,6 +24,8 @@ 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' }); 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/linking-addLink-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js deleted file mode 100644 index 9afcee48dc4a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - 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-streamed/subject.js deleted file mode 100644 index 510fb07540ad..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-streamed/subject.js +++ /dev/null @@ -1,28 +0,0 @@ -// REGULAR --- -const rootSpan1 = Sentry.startInactiveSpan({ name: 'rootSpan1' }); -rootSpan1.end(); - -Sentry.startSpan({ name: 'rootSpan2' }, rootSpan2 => { - rootSpan2.addLink({ - context: rootSpan1.spanContext(), - attributes: { 'sentry.link.type': 'previous_trace' }, - }); -}); - -// NESTED --- -Sentry.startSpan({ name: 'rootSpan3' }, async rootSpan3 => { - Sentry.startSpan({ name: 'childSpan3.1' }, async childSpan1 => { - childSpan1.addLink({ - context: rootSpan1.spanContext(), - attributes: { 'sentry.link.type': 'previous_trace' }, - }); - - childSpan1.end(); - }); - - Sentry.startSpan({ name: 'childSpan3.2' }, async childSpan2 => { - childSpan2.addLink({ context: rootSpan3.spanContext() }); - - childSpan2.end(); - }); -}); 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..6c9c66214961 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,65 @@ -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(); - } +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..409715a2371e 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,49 @@ 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 +56,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..1aef61bb6d57 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,47 @@ 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/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..5c78e2edf36e 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,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('should attribute spans to their originating microfrontend', async ({ getLocalTestUrl, page }) => { if (shouldSkipTracingTest()) { @@ -11,27 +12,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/subject.js b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/subject.js deleted file mode 100644 index 6ba8011d77ac..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/subject.js +++ /dev/null @@ -1 +0,0 @@ -fetch('http://sentry-test-site.example/api/test'); 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/test.ts deleted file mode 100644 index 2672d41c17fb..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report/test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { expect } from '@playwright/test'; -import type { ClientReport } from '@sentry/core'; -import { sentryTest } from '../../../utils/fixtures'; -import { - envelopeRequestParser, - hidePage, - shouldSkipTracingTest, - testingCdnBundle, - waitForClientReportRequest, -} from '../../../utils/helpers'; - -sentryTest( - 'records no_parent_span client report for fetch requests without an active span', - async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle()); - - await page.route('http://sentry-test-site.example/api/test', route => { - route.fulfill({ - status: 200, - body: 'ok', - headers: { 'Content-Type': 'text/plain' }, - }); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - const clientReportPromise = waitForClientReportRequest(page, report => { - return report.discarded_events.some(e => e.reason === 'no_parent_span'); - }); - - await page.goto(url); - - await hidePage(page); - - const clientReport = envelopeRequestParser(await clientReportPromise); - - expect(clientReport.discarded_events).toEqual([ - { - category: 'span', - quantity: 1, - reason: 'no_parent_span', - }, - ]); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js deleted file mode 100644 index 9afcee48dc4a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - 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-streamed/default/subject.js deleted file mode 100644 index 0ce39588eb1b..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/subject.js +++ /dev/null @@ -1,14 +0,0 @@ -const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); -Sentry.setActiveSpanInBrowser(checkoutSpan); - -Sentry.startSpan({ name: 'checkout-step-1' }, () => { - Sentry.startSpan({ name: 'checkout-step-1-1' }, () => { - // ... ` - }); -}); - -Sentry.startSpan({ name: 'checkout-step-2' }, () => { - // ... ` -}); - -checkoutSpan.end(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts deleted file mode 100644 index c9f48a4ebefd..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/default/test.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; -import { waitForStreamedSpans } from '../../../../utils/spanUtils'; - -sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { - sentryTest.skip(shouldSkipTracingTest()); - - const spansPromise = waitForStreamedSpans(page, spans => spans.some(s => s.name === 'checkout-flow' && s.is_segment)); - - 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; - expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); - - expect(spans.filter(s => !s.is_segment)).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'); - - expect(checkoutStep1).toBeDefined(); - expect(checkoutStep11).toBeDefined(); - expect(checkoutStep2).toBeDefined(); - - expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); - expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); - - // despite 1-1 being called within 1, it's still parented to the root span - // due to this being default behaviour in browser environments - expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId); -}); 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-streamed/nested-parentAlwaysRoot/init.js deleted file mode 100644 index 5b4cff73e95d..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/init.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - 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-streamed/nested-parentAlwaysRoot/subject.js deleted file mode 100644 index dc601cbf4d30..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/subject.js +++ /dev/null @@ -1,22 +0,0 @@ -const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); -Sentry.setActiveSpanInBrowser(checkoutSpan); - -Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); - -const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); -Sentry.setActiveSpanInBrowser(checkoutStep2); - -Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { - // ... ` -}); -checkoutStep2.end(); - -Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); - -checkoutSpan.end(); - -Sentry.startSpan({ name: 'post-checkout' }, () => { - Sentry.startSpan({ name: 'post-checkout-1' }, () => { - // ... ` - }); -}); 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-streamed/nested-parentAlwaysRoot/test.ts deleted file mode 100644 index f0ce77d4e00c..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested-parentAlwaysRoot/test.ts +++ /dev/null @@ -1,58 +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 with parentSpanIsAlwaysRootSpan=false result in correct parenting', - 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); - - // with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because - // 2 was the active span when 2-1 was started - expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id); - - // since the parent of three is `checkoutSpan`, we correctly reset - // the active span to `checkoutSpan` after 2 ended - expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); - - // 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'); - expect(postCheckoutStep1).toBeDefined(); - expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js deleted file mode 100644 index 9afcee48dc4a..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/init.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; - -Sentry.init({ - 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-streamed/nested/subject.js deleted file mode 100644 index dc601cbf4d30..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-streamed/nested/subject.js +++ /dev/null @@ -1,22 +0,0 @@ -const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); -Sentry.setActiveSpanInBrowser(checkoutSpan); - -Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); - -const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); -Sentry.setActiveSpanInBrowser(checkoutStep2); - -Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { - // ... ` -}); -checkoutStep2.end(); - -Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); - -checkoutSpan.end(); - -Sentry.startSpan({ name: 'post-checkout' }, () => { - Sentry.startSpan({ name: 'post-checkout-1' }, () => { - // ... ` - }); -}); 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..5e8d14dab378 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,7 @@ 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 +10,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['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/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/navigation-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/navigation-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/navigation-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..469c433a3f13 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,116 @@ -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(); - } + 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); }); +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + eventAndTraceHeaderRequestParser, + getFirstSentryEnvelopeRequest, + shouldSkipFeedbackTest, + shouldSkipTracingTest, + waitForErrorRequest, +} from '../../../../utils/helpers'; +import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; 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 +118,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 +126,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 +170,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 +178,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 +190,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 +222,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 +234,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,24 +266,24 @@ 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); @@ -356,7 +301,7 @@ sentryTest( 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..a12736638e5c 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,7 @@ 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 +24,16 @@ sentryTest( }, }); - const [pageloadEvent, pageloadTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); - const [navigationEvent, navigationTraceHeader] = await getFirstSentryEnvelopeRequest( - page, - `${url}#foo`, - eventAndTraceHeaderRequestParser, - ); + const [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationEvent, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const navigationTraceContext = navigationEvent.contexts?.trace; + const pageloadTraceContext = pageloadEvent; + const navigationTraceContext = navigationEvent; - expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadEvent.is_segment).toBe(true); expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + 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,9 +50,10 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.type).toEqual('transaction'); + expect(navigationEvent.is_segment).toBe(true); expect(navigationTraceContext).toMatchObject({ - op: 'navigation', + 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}$/), }); 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..63a775942f00 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,18 @@ 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 +28,16 @@ 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 [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationEvent, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - const pageloadTraceContext = pageloadEvent.contexts?.trace; - const navigationTraceContext = navigationEvent.contexts?.trace; + const pageloadTraceContext = pageloadEvent; + const navigationTraceContext = navigationEvent; - expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadEvent.is_segment).toBe(true); expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + 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,9 +54,10 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.type).toEqual('transaction'); + expect(navigationEvent.is_segment).toBe(true); expect(navigationTraceContext).toMatchObject({ - op: 'navigation', + 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}$/), }); @@ -87,14 +85,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 [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(pageloadEvent.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadEvent).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 +106,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 +136,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 [[pageloadEvent, pageloadTraceHeader], [errorEvent, errorTraceHeader]] = await Promise.all([ + pageloadPromise, + errorPromise, + ]); + + expect(pageloadEvent.is_segment).toBe(true); + expect(pageloadEvent).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 +204,19 @@ sentryTest( }); }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadEventPromise = 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]); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadEvent.is_segment).toBe(true); + expect(pageloadEvent).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 +259,19 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadEventPromise = getFirstSentryEnvelopeRequest( + const pageloadEventPromise = 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]); - expect(pageloadEvent.type).toEqual('transaction'); - expect(pageloadEvent?.contexts?.trace).toMatchObject({ - op: 'pageload', + expect(pageloadEvent.is_segment).toBe(true); + expect(pageloadEvent).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,11 +304,12 @@ 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 [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceContext = pageloadEvent; expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + 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}$/), 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-streamed/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/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/pageload/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/pageload/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/pageload/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload/test.ts index e6edd02cf360..36bf0f63dec7 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,60 @@ +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); +}); 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'; - -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); - }, -); +import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; 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 +62,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 +71,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 [pageloadSpan, [errorEvent, errorTraceHeader]] = await Promise.all([pageloadSpanPromise, errorEventPromise]); - const pageloadTraceContext = pageloadEvent?.contexts?.trace; + const pageloadTraceId = pageloadSpan.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: 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 +108,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 +116,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 +128,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 +155,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,205 +167,44 @@ 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); @@ -478,7 +222,7 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', 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..29158b38c5c6 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 [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceId = pageloadEvent?.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-streamed/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/subject.js deleted file mode 100644 index 3bb1e489ccb6..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/subject.js +++ /dev/null @@ -1,15 +0,0 @@ -const newTraceBtn = document.getElementById('newTrace'); -newTraceBtn.addEventListener('click', async () => { - Sentry.startNewTrace(() => { - Sentry.startSpan({ op: 'ui.interaction.click', name: 'new-trace' }, async () => { - await fetch('http://sentry-test-site.example'); - }); - }); -}); - -const oldTraceBtn = document.getElementById('oldTrace'); -oldTraceBtn.addEventListener('click', async () => { - Sentry.startSpan({ op: 'ui.interaction.click', name: 'old-trace' }, async () => { - await fetch('http://sentry-test-site.example'); - }); -}); 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-streamed/template.html deleted file mode 100644 index f78960343dd0..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-streamed/template.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - 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..ebb8c46808da 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,7 @@ 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,23 +20,20 @@ sentryTest( }); }); - const [pageloadEvent, pageloadTraceHeaders] = await getFirstSentryEnvelopeRequest( - page, - url, - eventAndTraceHeaderRequestParser, - ); + const [pageloadEvent, pageloadTraceHeaders] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const pageloadTraceContext = pageloadEvent.contexts?.trace; + const pageloadTraceContext = pageloadEvent; - expect(pageloadEvent.type).toEqual('transaction'); + expect(pageloadEvent.is_segment).toBe(true); expect(pageloadTraceContext).toMatchObject({ - op: 'pageload', + is_segment: true, + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'pageload' }, + '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'); @@ -54,36 +46,33 @@ sentryTest( 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', + const newTraceSpanContext = newTraceSpan; + expect(newTraceSpanContext).toMatchObject({ + is_segment: true, + attributes: expect.objectContaining({ + 'sentry.op': { type: 'string', value: 'ui.interaction.click' }, + '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: newTraceSpanContext?.trace_id, transaction: 'new-trace', sample_rand: '0.85', }); - expect(newTraceTransactionTraceContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(newTraceSpanContext?.trace_id).not.toEqual(pageloadTraceContext?.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/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; +} From cb42e89add284282c3d60d1b5ae811c4e7411900 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 7 Oct 2026 13:21:30 +0200 Subject: [PATCH 2/5] test(browser): retain static trace lifecycle counterparts Co-Authored-By: GPT-6 --- .../beforeSendTransaction-static/init.js | 24 + .../beforeSendTransaction-static/test.ts | 32 ++ .../public-api/startSpan/basic-static/init.js | 10 + .../startSpan/basic-static/subject.js | 14 + .../public-api/startSpan/basic-static/test.ts | 52 ++ .../suites/tracing/ignoreSpans-static/init.js | 50 ++ .../suites/tracing/ignoreSpans-static/test.ts | 29 ++ .../tracing/linking-addLink-static/init.js | 10 + .../tracing/linking-addLink-static/subject.js | 28 + .../tracing/linking-addLink-static/test.ts | 76 +++ .../setSpanActive-static/default/init.js | 9 + .../setSpanActive-static/default/subject.js | 14 + .../setSpanActive-static/default/test.ts | 35 ++ .../nested-parentAlwaysRoot/init.js | 10 + .../nested-parentAlwaysRoot/subject.js | 22 + .../nested-parentAlwaysRoot/test.ts | 57 +++ .../setSpanActive-static/nested/init.js | 9 + .../setSpanActive-static/nested/subject.js | 22 + .../setSpanActive-static/nested/test.ts | 52 ++ .../trace-lifetime/navigation-static/init.js | 13 + .../navigation-static/subject.js | 23 + .../navigation-static/template.html | 12 + .../trace-lifetime/navigation-static/test.ts | 363 +++++++++++++ .../trace-lifetime/pageload-static/init.js | 13 + .../trace-lifetime/pageload-static/subject.js | 23 + .../pageload-static/template.html | 12 + .../trace-lifetime/pageload-static/test.ts | 484 ++++++++++++++++++ .../startNewTrace-static/init.js | 13 + .../startNewTrace-static/subject.js | 15 + .../startNewTrace-static/template.html | 10 + .../startNewTrace-static/test.ts | 109 ++++ 31 files changed, 1645 insertions(+) create mode 100644 dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/ignoreSpans-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html create mode 100644 dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js new file mode 100644 index 000000000000..e4206b38e808 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/init.js @@ -0,0 +1,24 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + beforeSendTransaction: transactionEvent => { + const op = transactionEvent.contexts.trace.op; + if (op === 'pageload' || op === 'navigation') { + // use whatever logic you want to set the name + transactionEvent.transaction = 'customName'; + + transactionEvent.transaction_info.source = 'route'; + transactionEvent.contexts.trace.data = { + ...transactionEvent.contexts.trace.data, + ['sentry.segment.name.source']: 'route', + }; + } + return transactionEvent; + }, + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts new file mode 100644 index 000000000000..f5b5a96608f1 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/public-api/beforeSendTransaction-static/test.ts @@ -0,0 +1,32 @@ +import { SENTRY_SEGMENT_NAME_SOURCE } from '@sentry/conventions/attributes'; +import { expect } from '@playwright/test'; + +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../utils/fixtures'; +import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../utils/helpers'; + +sentryTest( + 'allows modification of the transaction name and source but overwrites source to custom', + async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const eventData = await getFirstSentryEnvelopeRequest(page, url); + + expect(eventData.type).toBe('transaction'); + + // user-changed name + expect(eventData.transaction).toBe('customName'); + + // Despite the user setting the source to 'route', the SDK detects that the txn name was changed + // and therefore sets the transaction_info.source to 'custom'. This is not ideal but also not easily changeable. + // Given that Relay doesn't differentiate between 'source' and 'route', we'll keep this as-is for now. + expect(eventData.transaction_info?.source).toBe('custom'); + + // This stays the same but it has no effect on Relay. + expect(eventData.contexts?.trace?.data?.[SENTRY_SEGMENT_NAME_SOURCE]).toBe('route'); + }, +); 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..824220e5133f --- /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/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/linking-addLink-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js new file mode 100644 index 000000000000..848d72e6bfdc --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-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', + integrations: [], + tracesSampleRate: 1, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js new file mode 100644 index 000000000000..510fb07540ad --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/subject.js @@ -0,0 +1,28 @@ +// REGULAR --- +const rootSpan1 = Sentry.startInactiveSpan({ name: 'rootSpan1' }); +rootSpan1.end(); + +Sentry.startSpan({ name: 'rootSpan2' }, rootSpan2 => { + rootSpan2.addLink({ + context: rootSpan1.spanContext(), + attributes: { 'sentry.link.type': 'previous_trace' }, + }); +}); + +// NESTED --- +Sentry.startSpan({ name: 'rootSpan3' }, async rootSpan3 => { + Sentry.startSpan({ name: 'childSpan3.1' }, async childSpan1 => { + childSpan1.addLink({ + context: rootSpan1.spanContext(), + attributes: { 'sentry.link.type': 'previous_trace' }, + }); + + childSpan1.end(); + }); + + Sentry.startSpan({ name: 'childSpan3.2' }, async childSpan2 => { + childSpan2.addLink({ context: rootSpan3.spanContext() }); + + childSpan2.end(); + }); +}); 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..9c556335651e --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts @@ -0,0 +1,76 @@ +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/setSpanActive-static/default/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js new file mode 100644 index 000000000000..bd0421b32761 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/init.js @@ -0,0 +1,9 @@ +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/setSpanActive-static/default/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/subject.js new file mode 100644 index 000000000000..0ce39588eb1b --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/subject.js @@ -0,0 +1,14 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => { + Sentry.startSpan({ name: 'checkout-step-1-1' }, () => { + // ... ` + }); +}); + +Sentry.startSpan({ name: 'checkout-step-2' }, () => { + // ... ` +}); + +checkoutSpan.end(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts new file mode 100644 index 000000000000..2019c78c837f --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/default/test.ts @@ -0,0 +1,35 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest('sets an inactive span active and adds child spans to it', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + const req = waitForTransactionRequest(page, e => e.transaction === 'checkout-flow'); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const checkoutEvent = envelopeRequestParser(await req); + const checkoutSpanId = checkoutEvent.contexts?.trace?.span_id; + expect(checkoutSpanId).toMatch(/[a-f\d]{16}/); + + expect(checkoutEvent.spans).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'); + + expect(checkoutStep1).toBeDefined(); + expect(checkoutStep11).toBeDefined(); + expect(checkoutStep2).toBeDefined(); + + expect(checkoutStep1?.parent_span_id).toBe(checkoutSpanId); + expect(checkoutStep2?.parent_span_id).toBe(checkoutSpanId); + + // despite 1-1 being called within 1, it's still parented to the root span + // due to this being default behaviour in browser environments + expect(checkoutStep11?.parent_span_id).toBe(checkoutSpanId); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/init.js new file mode 100644 index 000000000000..58be3b8dc82c --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/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, + parentSpanIsAlwaysRootSpan: false, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js new file mode 100644 index 000000000000..dc601cbf4d30 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/subject.js @@ -0,0 +1,22 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); + +const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); +Sentry.setActiveSpanInBrowser(checkoutStep2); + +Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { + // ... ` +}); +checkoutStep2.end(); + +Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); + +checkoutSpan.end(); + +Sentry.startSpan({ name: 'post-checkout' }, () => { + Sentry.startSpan({ name: 'post-checkout-1' }, () => { + // ... ` + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts new file mode 100644 index 000000000000..5132178d1981 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested-parentAlwaysRoot/test.ts @@ -0,0 +1,57 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, shouldSkipTracingTest, waitForTransactionRequest } from '../../../../utils/helpers'; + +sentryTest( + 'nested calls to setActiveSpanInBrowser with parentSpanIsAlwaysRootSpan=false result in correct parenting', + 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); + + // with parentSpanIsAlwaysRootSpan=false, 2-1 is parented to 2 because + // 2 was the active span when 2-1 was started + expect(checkoutStep21?.parent_span_id).toBe(checkoutStep2?.span_id); + + // since the parent of three is `checkoutSpan`, we correctly reset + // the active span to `checkoutSpan` after 2 ended + expect(checkoutStep3?.parent_span_id).toBe(checkoutSpanId); + + // 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'); + expect(postCheckoutStep1).toBeDefined(); + expect(postCheckoutStep1?.parent_span_id).toBe(postCheckoutSpanId); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js new file mode 100644 index 000000000000..bd0421b32761 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/init.js @@ -0,0 +1,9 @@ +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/setSpanActive-static/nested/subject.js b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/subject.js new file mode 100644 index 000000000000..dc601cbf4d30 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/setSpanActive-static/nested/subject.js @@ -0,0 +1,22 @@ +const checkoutSpan = Sentry.startInactiveSpan({ name: 'checkout-flow' }); +Sentry.setActiveSpanInBrowser(checkoutSpan); + +Sentry.startSpan({ name: 'checkout-step-1' }, () => {}); + +const checkoutStep2 = Sentry.startInactiveSpan({ name: 'checkout-step-2' }); +Sentry.setActiveSpanInBrowser(checkoutStep2); + +Sentry.startSpan({ name: 'checkout-step-2-1' }, () => { + // ... ` +}); +checkoutStep2.end(); + +Sentry.startSpan({ name: 'checkout-step-3' }, () => {}); + +checkoutSpan.end(); + +Sentry.startSpan({ name: 'post-checkout' }, () => { + Sentry.startSpan({ name: 'post-checkout-1' }, () => { + // ... ` + }); +}); 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/trace-lifetime/navigation-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/init.js new file mode 100644 index 000000000000..8718d9d354eb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-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/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..0255507287d3 --- /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 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: navigationTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); + }, +); 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..e6edd02cf360 --- /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 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: pageloadTraceContext?.trace_id, + span_id: expect.stringMatching(/^[\da-f]{16}$/), + }); +}); 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-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/subject.js new file mode 100644 index 000000000000..3bb1e489ccb6 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/subject.js @@ -0,0 +1,15 @@ +const newTraceBtn = document.getElementById('newTrace'); +newTraceBtn.addEventListener('click', async () => { + Sentry.startNewTrace(() => { + Sentry.startSpan({ op: 'ui.interaction.click', name: 'new-trace' }, async () => { + await fetch('http://sentry-test-site.example'); + }); + }); +}); + +const oldTraceBtn = document.getElementById('oldTrace'); +oldTraceBtn.addEventListener('click', async () => { + Sentry.startSpan({ op: 'ui.interaction.click', name: 'old-trace' }, async () => { + await fetch('http://sentry-test-site.example'); + }); +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html new file mode 100644 index 000000000000..f78960343dd0 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/startNewTrace-static/template.html @@ -0,0 +1,10 @@ + + + + + + + + + + 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); + }, +); From 8f261e4eba76c5f5141e2a11e684153c6eec65ba Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 7 Oct 2026 14:50:31 +0200 Subject: [PATCH 3/5] test(browser): tighten streamed span assertions Co-Authored-By: GPT-6 --- .../suites/public-api/beforeSendSpan/test.ts | 3 +- .../public-api/startSpan/basic-static/test.ts | 2 +- .../suites/public-api/startSpan/basic/test.ts | 21 +++++++++----- .../startSpan/circular_data-static/init.js | 10 +++++++ .../startSpan/circular_data-static/subject.js | 7 +++++ .../startSpan/circular_data-static/test.ts | 28 ++++++++++++++++++ .../startSpan/circular_data/subject.js | 4 +-- .../startSpan/circular_data/test.ts | 29 +++++++++++-------- .../suites/tracing/http-client-span/test.ts | 7 +++-- .../tracing/linking-addLink-static/test.ts | 5 ++-- .../suites/tracing/linking-addLink/test.ts | 5 ++-- .../suites/tracing/linking-addLinks/test.ts | 5 ++-- .../tracing/linking-spanOptions/test.ts | 3 +- .../microfrontend-span-attribution/test.ts | 9 +++--- .../suites/tracing/stringSampleRate/test.ts | 3 +- .../trace-lifetime/navigation-static/test.ts | 4 +-- .../tracing/trace-lifetime/navigation/test.ts | 6 ++-- .../trace-lifetime/pageload-headers/test.ts | 5 ++-- .../trace-lifetime/pageload-meta/test.ts | 21 +++++++------- .../trace-lifetime/pageload-static/test.ts | 4 +-- .../tracing/trace-lifetime/pageload/test.ts | 6 ++-- .../startNewTraceSampling/test.ts | 10 ++++--- 22 files changed, 132 insertions(+), 65 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/public-api/startSpan/circular_data-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/beforeSendSpan/test.ts b/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/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/startSpan/basic-static/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic-static/test.ts index 824220e5133f..d3819b797a11 100644 --- 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 @@ -48,5 +48,5 @@ sentryTest('should report finished spans as children of the root transaction', a 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(span_1?.data?.[SENTRY_ORIGIN]).toEqual('manual'); }); 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 701e0c09fa55..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,4 +1,9 @@ import { + SENTRY_IS_LOCALHOST, + CULTURE_CALENDAR, + CULTURE_LOCALE, + CULTURE_TIMEZONE, + URL_FULL, SENTRY_SEGMENT_NAME_SOURCE, SENTRY_SEGMENT_ID, SENTRY_SEGMENT_NAME, @@ -71,7 +76,7 @@ sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl expect(spans).toEqual([ { attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, [SENTRY_OP]: { type: 'string', value: 'test-child', @@ -120,7 +125,7 @@ sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl }, { attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual', @@ -165,7 +170,7 @@ sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl }, { attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, [SENTRY_ORIGIN]: { type: 'string', value: 'manual', @@ -214,16 +219,16 @@ sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl }, { attributes: { - 'sentry.is_localhost': { value: false, type: 'boolean' }, - 'culture.calendar': { + [SENTRY_IS_LOCALHOST]: { value: false, type: 'boolean' }, + [CULTURE_CALENDAR]: { type: 'string', value: expect.any(String), }, - 'culture.locale': { + [CULTURE_LOCALE]: { type: 'string', value: expect.any(String), }, - 'culture.timezone': { + [CULTURE_TIMEZONE]: { type: 'string', value: expect.any(String), }, @@ -231,7 +236,7 @@ sentryTest('sends a streamed span envelope by default', async ({ getLocalTestUrl type: 'string', value: expect.any(String), }, - 'url.full': { + [URL_FULL]: { type: 'string', value: expect.any(String), }, 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 845688d9ec49..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 @@ -3,16 +3,21 @@ import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../utils/helpers'; import { getSpanOp, waitForStreamedSpan } from '../../../../utils/spanUtils'; -sentryTest('handles circular 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); +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 [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.chicken).toBeUndefined(); - expect(childSpan.attributes.chicken).toBeUndefined(); -}); + 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/tracing/http-client-span/test.ts b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts index ddfa111e0428..634068358415 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/http-client-span/test.ts @@ -1,3 +1,4 @@ +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'; @@ -27,8 +28,8 @@ sentryTest( 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/linking-addLink-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/test.ts index 9c556335651e..063569d95996 100644 --- 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 @@ -1,3 +1,4 @@ +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'; @@ -28,7 +29,7 @@ sentryTest('should link spans with addLink() in trace context', async ({ getLoca expect(rootSpan2.contexts?.trace?.links?.length).toBe(1); expect(rootSpan2.contexts?.trace?.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': 'previous_trace' }, + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, @@ -61,7 +62,7 @@ sentryTest('should link spans with addLink() in nested startSpan() calls', async 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' }, + attributes: { [SENTRY_LINK_TYPE]: 'previous_trace' }, sampled: true, span_id: rootSpan1_spanId, trace_id: rootSpan1_traceId, 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 6c9c66214961..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,3 +1,4 @@ +import { SENTRY_LINK_TYPE } from '@sentry/conventions/attributes'; sentryTest('links spans with addLink() in trace context', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); @@ -16,7 +17,7 @@ sentryTest('links spans with addLink() in trace context', async ({ getLocalTestU expect(rootSpan2.name).toBe('rootSpan2'); expect(rootSpan2.links).toHaveLength(1); expect(rootSpan2.links?.[0]).toMatchObject({ - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan1.span_id, trace_id: rootSpan1.trace_id, @@ -50,7 +51,7 @@ sentryTest('links spans with addLink() in nested startSpan() calls', async ({ ge 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' } }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan1.span_id, trace_id: rootSpan1.trace_id, 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 409715a2371e..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,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'; @@ -43,7 +44,7 @@ sentryTest('should link spans with addLinks() in trace context', async ({ getLoc trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': { type: 'string', value: 'previous_trace' } }, + attributes: { [SENTRY_LINK_TYPE]: { type: 'string', value: 'previous_trace' } }, sampled: true, span_id: rootSpan2_spanId, trace_id: rootSpan2_traceId, @@ -89,7 +90,7 @@ sentryTest('should link spans with addLinks() in nested startSpan() calls', asyn trace_id: rootSpan4_traceId, }, { - attributes: { 'sentry.link.type': { type: 'string', value: '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/test.ts b/dev-packages/browser-integration-tests/suites/tracing/linking-spanOptions/test.ts index 1aef61bb6d57..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,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'; @@ -41,7 +42,7 @@ sentryTest('should link spans by adding "links" to span options', async ({ getLo trace_id: rootSpan1_traceId, }, { - attributes: { 'sentry.link.type': { type: 'string', value: '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/microfrontend-span-attribution/test.ts b/dev-packages/browser-integration-tests/suites/tracing/microfrontend-span-attribution/test.ts index 5c78e2edf36e..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,3 +1,4 @@ +import { URL_FULL } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; @@ -22,18 +23,18 @@ sentryTest('should attribute spans to their originating microfrontend', async ({ // Each MFE's fetch is attributed via withScope + spanStart hook expect( - httpSpans.find(s => s.attributes['url.full']?.value?.toString().includes('/api/todos/1'))?.attributes['mfe.name'], + 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'], + 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'], + 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.attributes['url.full']?.value?.toString().includes('/api/shell-config'))?.attributes[ + 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/stringSampleRate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/stringSampleRate/test.ts index 5e8d14dab378..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,3 +1,4 @@ +import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '@sentry/core'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../utils/helpers'; @@ -12,5 +13,5 @@ sentryTest('parses a string sample rate', async ({ getLocalTestUrl, page }) => { const [span] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(span.attributes['sentry.sample_rate']).toEqual({ type: 'integer', value: 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/navigation-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/navigation-static/test.ts index 0255507287d3..2f7749685ba8 100644 --- 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 @@ -340,7 +340,7 @@ sentryTest( }); expect(navigationTraceContext).not.toHaveProperty('parent_span_id'); - 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,7 +349,7 @@ 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'); 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 469c433a3f13..8e03851921ba 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 @@ -66,7 +66,7 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, waitForErrorRequest, @@ -285,7 +285,7 @@ sentryTest( 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); @@ -294,7 +294,7 @@ 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'); 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 a12736638e5c..55fdfc79dcf5 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,3 +1,4 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import { sentryTest } from '../../../../utils/fixtures'; import { shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -33,7 +34,7 @@ sentryTest( expect(pageloadEvent.is_segment).toBe(true); expect(pageloadTraceContext).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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}$/), @@ -53,7 +54,7 @@ sentryTest( expect(navigationEvent.is_segment).toBe(true); expect(navigationTraceContext).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'navigation' } }), + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation' } }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), }); 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 63a775942f00..618d29d104e5 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,9 +1,10 @@ +import { SENTRY_OP } from '@sentry/conventions/attributes'; import { expect } from '@playwright/test'; import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, waitForErrorRequest, @@ -37,7 +38,7 @@ sentryTest( expect(pageloadEvent.is_segment).toBe(true); expect(pageloadTraceContext).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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,7 +58,7 @@ sentryTest( expect(navigationEvent.is_segment).toBe(true); expect(navigationTraceContext).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'navigation' } }), + attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'navigation' } }), trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), }); @@ -89,7 +90,7 @@ sentryTest('error after tag pageload has pageload traceId', async ({ getL expect(pageloadEvent).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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}$/), @@ -151,7 +152,7 @@ sentryTest('error during tag pageload has pageload traceId', async ({ get expect(pageloadEvent.is_segment).toBe(true); expect(pageloadEvent).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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}$/), @@ -216,7 +217,7 @@ sentryTest( expect(pageloadEvent.is_segment).toBe(true); expect(pageloadEvent).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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}$/), @@ -271,7 +272,7 @@ sentryTest( expect(pageloadEvent.is_segment).toBe(true); expect(pageloadEvent).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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}$/), @@ -309,13 +310,13 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', expect(pageloadTraceContext).toMatchObject({ is_segment: true, - attributes: expect.objectContaining({ 'sentry.op': { type: 'string', value: 'pageload' } }), + 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); @@ -324,7 +325,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/test.ts b/dev-packages/browser-integration-tests/suites/tracing/trace-lifetime/pageload-static/test.ts index e6edd02cf360..f65ed9c13e3a 100644 --- 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 @@ -462,7 +462,7 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', }); expect(pageloadTraceContext).not.toHaveProperty('parent_span_id'); - 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,7 +471,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; expect(feedbackEvent.type).toEqual('feedback'); 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 36bf0f63dec7..8b5cf3eae64a 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 @@ -31,7 +31,7 @@ import type { Event } from '@sentry/core'; import { sentryTest } from '../../../../utils/fixtures'; import { eventAndTraceHeaderRequestParser, - getFirstSentryEnvelopeRequest, + getMultipleSentryEnvelopeRequests, shouldSkipFeedbackTest, shouldSkipTracingTest, waitForErrorRequest, @@ -206,7 +206,7 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', 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); @@ -215,7 +215,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; expect(feedbackEvent.type).toEqual('feedback'); 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 ebb8c46808da..c819e9b149fd 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,3 +1,5 @@ +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 { shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -29,8 +31,8 @@ sentryTest( expect(pageloadTraceContext).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'pageload' }, - 'sentry.sample_rate': { type: 'double', value: 0.5 }, + [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}$/), @@ -56,8 +58,8 @@ sentryTest( expect(newTraceSpanContext).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ - 'sentry.op': { type: 'string', value: 'ui.interaction.click' }, - 'sentry.sample_rate': { type: 'double', value: 0.9 }, + [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}$/), From 73634c706195b175cce8299b59179a33aae10ec5 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Wed, 7 Oct 2026 15:12:06 +0200 Subject: [PATCH 4/5] test(browser): filter error waits by envelope item type Co-Authored-By: GPT-6 --- .../simple_message_no_stacktrace/test.ts | 5 ++--- .../suites/public-api/startSpan/error-sync/test.ts | 10 ++++++++++ .../browser-integration-tests/utils/helpers.ts | 6 +++--- 3 files changed, 15 insertions(+), 6 deletions(-) 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/error-sync/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/error-sync/test.ts index 26557b337b35..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,4 +1,5 @@ import { expect } from '@playwright/test'; +import type * as SentryBrowser from '@sentry/browser'; import { sentryTest } from '../../../../utils/fixtures'; import { envelopeRequestParser, @@ -27,6 +28,15 @@ sentryTest( 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() { 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); } From d474bbad40a2f632858ba7f7a1124858f915bb07 Mon Sep 17 00:00:00 2001 From: Martin Sonnberger Date: Thu, 8 Oct 2026 17:03:50 +0200 Subject: [PATCH 5/5] code review --- .../suites/public-api/startSpan/basic/init.js | 1 - .../tracing/linking-addLink-static/init.js | 1 - .../suites/tracing/maxSpans-static/init.js | 16 ++++++ .../suites/tracing/maxSpans-static/test.ts | 16 ++++++ .../init.js | 11 ++++ .../subject.js | 1 + .../test.ts | 45 ++++++++++++++++ .../tracing/trace-lifetime/navigation/test.ts | 23 ++++---- .../trace-lifetime/pageload-headers/test.ts | 21 ++++---- .../trace-lifetime/pageload-meta/test.ts | 52 +++++++++---------- .../tracing/trace-lifetime/pageload/test.ts | 25 ++++----- .../startNewTrace-multiple-spans/test.ts | 4 +- .../startNewTraceSampling/test.ts | 19 +++---- 13 files changed, 157 insertions(+), 78 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/maxSpans-static/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/init.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js create mode 100644 dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/init.js index ac4dfdd9a57d..e546c7cc6a94 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/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: [], tracesSampleRate: 1.0, debug: true, }); diff --git a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js index 848d72e6bfdc..bd0421b32761 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js +++ b/dev-packages/browser-integration-tests/suites/tracing/linking-addLink-static/init.js @@ -5,6 +5,5 @@ 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-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/no-parent-span-client-report-static/init.js b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/init.js new file mode 100644 index 000000000000..4c93751dc394 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/init.js @@ -0,0 +1,11 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + traceLifecycle: 'static', + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration({ instrumentPageLoad: false, instrumentNavigation: false })], + tracesSampleRate: 1, + sendClientReports: true, +}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js new file mode 100644 index 000000000000..6ba8011d77ac --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/subject.js @@ -0,0 +1 @@ +fetch('http://sentry-test-site.example/api/test'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts new file mode 100644 index 000000000000..2672d41c17fb --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/no-parent-span-client-report-static/test.ts @@ -0,0 +1,45 @@ +import { expect } from '@playwright/test'; +import type { ClientReport } from '@sentry/core'; +import { sentryTest } from '../../../utils/fixtures'; +import { + envelopeRequestParser, + hidePage, + shouldSkipTracingTest, + testingCdnBundle, + waitForClientReportRequest, +} from '../../../utils/helpers'; + +sentryTest( + 'records no_parent_span client report for fetch requests without an active span', + async ({ getLocalTestUrl, page }) => { + sentryTest.skip(shouldSkipTracingTest() || testingCdnBundle()); + + await page.route('http://sentry-test-site.example/api/test', route => { + route.fulfill({ + status: 200, + body: 'ok', + headers: { 'Content-Type': 'text/plain' }, + }); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const clientReportPromise = waitForClientReportRequest(page, report => { + return report.discarded_events.some(e => e.reason === 'no_parent_span'); + }); + + await page.goto(url); + + await hidePage(page); + + const clientReport = envelopeRequestParser(await clientReportPromise); + + expect(clientReport.discarded_events).toEqual([ + { + category: 'span', + quantity: 1, + reason: 'no_parent_span', + }, + ]); + }, +); 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 8e03851921ba..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,3 +1,15 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + eventAndTraceHeaderRequestParser, + 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 }) => { sentryTest.skip(shouldSkipTracingTest()); @@ -61,17 +73,6 @@ sentryTest('creates a new trace and sample_rand on each navigation', async ({ ge expect(navigation1TraceId).not.toEqual(navigation2TraceId); expect(navigation1SampleRand).not.toEqual(navigation2SampleRand); }); -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import { - eventAndTraceHeaderRequestParser, - getMultipleSentryEnvelopeRequests, - shouldSkipFeedbackTest, - shouldSkipTracingTest, - waitForErrorRequest, -} from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; sentryTest('error after navigation has navigation traceId', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); 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 55fdfc79dcf5..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 @@ -25,14 +25,11 @@ sentryTest( }, }); - const [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const [navigationEvent, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationSpan, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - const pageloadTraceContext = pageloadEvent; - const navigationTraceContext = navigationEvent; - - expect(pageloadEvent.is_segment).toBe(true); - expect(pageloadTraceContext).toMatchObject({ + 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, @@ -51,26 +48,26 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.is_segment).toBe(true); - expect(navigationTraceContext).toMatchObject({ + 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 618d29d104e5..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 @@ -29,14 +29,11 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const [navigationEvent, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [navigationSpan, navigationTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, `${url}#foo`); - const pageloadTraceContext = pageloadEvent; - const navigationTraceContext = navigationEvent; - - expect(pageloadEvent.is_segment).toBe(true); - expect(pageloadTraceContext).toMatchObject({ + 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, @@ -55,26 +52,26 @@ sentryTest( sample_rand: '0.42', }); - expect(navigationEvent.is_segment).toBe(true); - expect(navigationTraceContext).toMatchObject({ + 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); }, ); @@ -86,9 +83,9 @@ sentryTest('error after tag pageload has pageload traceId', async ({ getL const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [pageloadSpan, pageloadTraceHeader] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(pageloadEvent).toMatchObject({ + expect(pageloadSpan).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, @@ -144,13 +141,13 @@ sentryTest('error during tag pageload has pageload traceId', async ({ get const errorPromise = waitForErrorRequest(page).then(eventAndTraceHeaderRequestParser); await page.goto(url); await page.locator('#errorBtn').click(); - const [[pageloadEvent, pageloadTraceHeader], [errorEvent, errorTraceHeader]] = await Promise.all([ + const [[pageloadSpan, pageloadTraceHeader], [errorEvent, errorTraceHeader]] = await Promise.all([ pageloadPromise, errorPromise, ]); - expect(pageloadEvent.is_segment).toBe(true); - expect(pageloadEvent).toMatchObject({ + 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, @@ -205,17 +202,17 @@ sentryTest( }); }); - const pageloadEventPromise = waitForStreamedSpanAndTraceHeader( + const pageloadSpanPromise = waitForStreamedSpanAndTraceHeader( page, 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.is_segment).toBe(true); - expect(pageloadEvent).toMatchObject({ + 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, @@ -260,17 +257,17 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const pageloadEventPromise = waitForStreamedSpanAndTraceHeader( + const pageloadSpanPromise = waitForStreamedSpanAndTraceHeader( page, 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.is_segment).toBe(true); - expect(pageloadEvent).toMatchObject({ + 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, @@ -305,10 +302,9 @@ sentryTest('user feedback event after pageload has pageload traceId in headers', const url = await getLocalTestUrl({ testDir: __dirname, handleLazyLoadedFeedback: true }); - const [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const pageloadTraceContext = pageloadEvent; + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - expect(pageloadTraceContext).toMatchObject({ + expect(pageloadSpan).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' } }), trace_id: META_TAG_TRACE_ID, 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 8b5cf3eae64a..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,3 +1,15 @@ +import { expect } from '@playwright/test'; +import type { Event } from '@sentry/core'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + eventAndTraceHeaderRequestParser, + getMultipleSentryEnvelopeRequests, + shouldSkipFeedbackTest, + shouldSkipTracingTest, + waitForErrorRequest, +} 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()); @@ -9,7 +21,7 @@ sentryTest('creates a new trace for a navigation after the initial pageload', as const pageloadSpan = await pageloadSpanPromise; - page.goto(`${url}#foo`); + await page.goto(`${url}#foo`); const navigationSpan = await navigationSpanPromise; @@ -26,17 +38,6 @@ sentryTest('creates a new trace for a navigation after the initial pageload', as expect(pageloadSpan.span_id).not.toEqual(navigationSpan.span_id); expect(pageloadSpan.trace_id).not.toEqual(navigationSpan.trace_id); }); -import { expect } from '@playwright/test'; -import type { Event } from '@sentry/core'; -import { sentryTest } from '../../../../utils/fixtures'; -import { - eventAndTraceHeaderRequestParser, - getMultipleSentryEnvelopeRequests, - shouldSkipFeedbackTest, - shouldSkipTracingTest, - waitForErrorRequest, -} from '../../../../utils/helpers'; -import { getSpanOp, waitForStreamedSpan, waitForStreamedSpanEnvelope } from '../../../../utils/spanUtils'; sentryTest('error after pageload has pageload traceId', async ({ getLocalTestUrl, page }) => { sentryTest.skip(shouldSkipTracingTest()); 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 29158b38c5c6..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 @@ -16,8 +16,8 @@ sentryTest( const url = await getLocalTestUrl({ testDir: __dirname }); - const [pageloadEvent] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const pageloadTraceId = pageloadEvent?.trace_id; + const [pageloadSpan] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const pageloadTraceId = pageloadSpan?.trace_id; expect(pageloadTraceId).toMatch(/^[\da-f]{32}$/); const spanPromises = Promise.all( 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 c819e9b149fd..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 @@ -22,13 +22,11 @@ sentryTest( }); }); - const [pageloadEvent, pageloadTraceHeaders] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); + const [pageloadSpan, pageloadTraceHeaders] = await waitForStreamedSpanAndTraceHeaderOnUrl(page, url); - const pageloadTraceContext = pageloadEvent; + expect(pageloadSpan.is_segment).toBe(true); - expect(pageloadEvent.is_segment).toBe(true); - - expect(pageloadTraceContext).toMatchObject({ + expect(pageloadSpan).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'pageload' }, @@ -37,14 +35,14 @@ sentryTest( trace_id: expect.stringMatching(/^[\da-f]{32}$/), span_id: expect.stringMatching(/^[\da-f]{16}$/), }); - 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', }); @@ -54,8 +52,7 @@ sentryTest( const [newTraceSpan, newTraceHeaders] = await spanPromise; - const newTraceSpanContext = newTraceSpan; - expect(newTraceSpanContext).toMatchObject({ + expect(newTraceSpan).toMatchObject({ is_segment: true, attributes: expect.objectContaining({ [SENTRY_OP]: { type: 'string', value: 'ui.interaction.click' }, @@ -70,11 +67,11 @@ sentryTest( public_key: 'public', sample_rate: '0.9', sampled: 'true', - trace_id: newTraceSpanContext?.trace_id, + trace_id: newTraceSpan?.trace_id, transaction: 'new-trace', sample_rand: '0.85', }); - expect(newTraceSpanContext?.trace_id).not.toEqual(pageloadTraceContext?.trace_id); + expect(newTraceSpan?.trace_id).not.toEqual(pageloadSpan?.trace_id); }, );