diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/init.js deleted file mode 100644 index 6d4dd43801b8..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/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.browserTracingIntegration(), Sentry.browserTracingIntegration()], - tracesSampleRate: 1, -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts deleted file mode 100644 index 1789bdf76c12..000000000000 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/multiple-integrations/test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { expect } from '@playwright/test'; -import { sentryTest } from '../../../../utils/fixtures'; -import { shouldSkipTracingTest } from '../../../../utils/helpers'; - -sentryTest('warns if multiple integrations are used', async ({ getLocalTestUrl, page }) => { - if (shouldSkipTracingTest()) { - sentryTest.skip(); - } - - const msgs: string[] = []; - - page.on('console', msg => { - msgs.push(msg.text()); - }); - - const url = await getLocalTestUrl({ testDir: __dirname }); - - await page.goto(url); - - expect(msgs).toEqual(['Multiple browserTracingIntegration instances are not supported.']); -}); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js new file mode 100644 index 000000000000..0a301839c169 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/init.js @@ -0,0 +1,14 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['http://sentry-test-site.example'], + tracesSampleRate: 1, + autoSessionTracking: false, +}); + +// fetch directly after init +fetch('http://sentry-test-site.example/0'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts new file mode 100644 index 000000000000..7d11168acb1a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch-immediate/test.ts @@ -0,0 +1,40 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../../utils/fixtures'; +import { + envelopeRequestParser, + shouldSkipTracingTest, + waitForTransactionRequestOnUrl, +} from '../../../../utils/helpers'; + +sentryTest('should create spans for fetch requests called directly after init', async ({ getLocalTestUrl, page }) => { + if (shouldSkipTracingTest()) { + sentryTest.skip(); + } + + await page.route('http://sentry-test-site.example/*', route => route.fulfill({ body: 'ok' })); + + const url = await getLocalTestUrl({ testDir: __dirname }); + + const req = await waitForTransactionRequestOnUrl(page, url); + const tracingEvent = envelopeRequestParser(req); + + const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); + + expect(requestSpans).toHaveLength(1); + + expect(requestSpans![0]).toMatchObject({ + description: 'GET http://sentry-test-site.example/0', + parent_span_id: tracingEvent.contexts?.trace?.span_id, + span_id: expect.stringMatching(/[a-f0-9]{16}/), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: tracingEvent.contexts?.trace?.trace_id, + data: { + 'http.method': 'GET', + 'http.url': 'http://sentry-test-site.example/0', + url: 'http://sentry-test-site.example/0', + 'server.address': 'sentry-test-site.example', + type: 'fetch', + }, + }); +}); diff --git a/packages/browser/src/tracing/browserTracingIntegration.ts b/packages/browser/src/tracing/browserTracingIntegration.ts index 0a4579f40774..d31fe41742f8 100644 --- a/packages/browser/src/tracing/browserTracingIntegration.ts +++ b/packages/browser/src/tracing/browserTracingIntegration.ts @@ -3,7 +3,6 @@ import type { Client, IntegrationFn, Span, StartSpanOptions, TransactionSource, import { addNonEnumerableProperty, browserPerformanceTimeOrigin, - consoleSandbox, generateTraceId, getClient, getCurrentScope, @@ -233,8 +232,6 @@ const DEFAULT_BROWSER_TRACING_OPTIONS: BrowserTracingOptions = { ...defaultRequestInstrumentationOptions, }; -let _hasBeenInitialized = false; - /** * The Browser Tracing integration automatically instruments browser pageload/navigation * actions as transactions, and captures requests, metrics and errors as spans. @@ -245,14 +242,10 @@ let _hasBeenInitialized = false; * We explicitly export the proper type here, as this has to be extended in some cases. */ export const browserTracingIntegration = ((_options: Partial = {}) => { - if (_hasBeenInitialized) { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn('Multiple browserTracingIntegration instances are not supported.'); - }); - } - - _hasBeenInitialized = true; + const latestRoute: RouteInfo = { + name: undefined, + source: undefined, + }; /** * This is just a small wrapper that makes `document` optional. @@ -260,8 +253,6 @@ export const browserTracingIntegration = ((_options: Partial void); /** Create routing idle transaction. */ function _createRouteSpan(client: Client, startSpanOptions: StartSpanOptions): void { @@ -340,7 +307,9 @@ export const browserTracingIntegration = ((_options: Partial { - _collectWebVitals(); + // This will generally always be defined here, because it is set in `setup()` of the integration + // but technically, it is optional, so we guard here to be extra safe + _collectWebVitals?.(); addPerformanceEntries(span, { recordClsOnPageloadSpan: !enableStandaloneClsSpans }); setActiveIdleSpan(client, undefined); @@ -378,8 +347,29 @@ export const browserTracingIntegration = ((_options: Partial {}); + public constructor(name: string, tag?: string) { this.name = name; this.tag = tag; } - - public setupOnce(): void { - // noop - } } type TestCase = [ @@ -74,6 +72,31 @@ describe('getIntegrationsToSetup', () => { }); expect(integrations.map(i => i.name)).toEqual(expected); }); + + test('it uses passed integration over default intergation', () => { + const integrationDefault = new MockIntegration('ChaseSquirrels'); + const integration1 = new MockIntegration('ChaseSquirrels'); + + const integrations = getIntegrationsToSetup({ + defaultIntegrations: [integrationDefault], + integrations: [integration1], + }); + + expect(integrations).toEqual([integration1]); + }); + + test('it uses last passed integration only', () => { + const integrationDefault = new MockIntegration('ChaseSquirrels'); + const integration1 = new MockIntegration('ChaseSquirrels'); + const integration2 = new MockIntegration('ChaseSquirrels'); + + const integrations = getIntegrationsToSetup({ + defaultIntegrations: [integrationDefault], + integrations: [integration1, integration2], + }); + + expect(integrations).toEqual([integration2]); + }); }); describe('deduping', () => { diff --git a/packages/react/src/reactrouterv6-compat-utils.tsx b/packages/react/src/reactrouterv6-compat-utils.tsx index 97109e191fcf..d9d0cbe45e8c 100644 --- a/packages/react/src/reactrouterv6-compat-utils.tsx +++ b/packages/react/src/reactrouterv6-compat-utils.tsx @@ -236,7 +236,9 @@ export function createReactRouterV6CompatibleTracingIntegration( return { ...integration, - setup() { + setup(client) { + integration.setup(client); + _useEffect = useEffect; _useLocation = useLocation; _useNavigationType = useNavigationType;