Skip to content

Commit eeb83da

Browse files
fix(tracing): ReactNativeTracing and initial navigation spans have to be created after integrations setup (#4000)
1 parent d838577 commit eeb83da

File tree

4 files changed

+96
-12
lines changed

4 files changed

+96
-12
lines changed

src/js/tracing/integrations/nativeFrames.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ export const nativeFramesIntegration = (): Integration => {
7373

7474
NATIVE.enableNativeFramesTracking();
7575

76-
// TODO: Ensure other integrations like ReactNativeTracing and ReactNavigation create spans after all integration are setup.
7776
client.on('spanStart', _onSpanStart);
7877
client.on('spanEnd', _onSpanFinish);
7978
logger.log('[ReactNativeTracing] Native frames instrumentation initialized.');

src/js/tracing/reactnativetracing.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ export const reactNativeTracingIntegration = (
111111
};
112112

113113
const setup = (client: Client): void => {
114+
addDefaultOpForSpanFrom(client);
115+
116+
instrumentOutgoingRequests({
117+
traceFetch: finalOptions.traceFetch,
118+
traceXHR: finalOptions.traceXHR,
119+
shouldCreateSpanForRequest: finalOptions.shouldCreateSpanForRequest,
120+
tracePropagationTargets: client.getOptions().tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS,
121+
});
122+
};
123+
124+
const afterAllSetup = (): void => {
114125
if (finalOptions.routingInstrumentation) {
115126
const idleNavigationSpanOptions = {
116127
finalTimeout: finalOptions.finalTimeoutMs,
@@ -139,15 +150,6 @@ export const reactNativeTracingIntegration = (
139150
} else {
140151
logger.log(`[${INTEGRATION_NAME}] Not instrumenting route changes as routingInstrumentation has not been set.`);
141152
}
142-
143-
addDefaultOpForSpanFrom(client);
144-
145-
instrumentOutgoingRequests({
146-
traceFetch: finalOptions.traceFetch,
147-
traceXHR: finalOptions.traceXHR,
148-
shouldCreateSpanForRequest: finalOptions.shouldCreateSpanForRequest,
149-
tracePropagationTargets: client.getOptions().tracePropagationTargets || DEFAULT_TRACE_PROPAGATION_TARGETS,
150-
});
151153
};
152154

153155
const processEvent = (event: Event): Event => {
@@ -160,6 +162,7 @@ export const reactNativeTracingIntegration = (
160162
return {
161163
name: INTEGRATION_NAME,
162164
setup,
165+
afterAllSetup,
163166
processEvent,
164167
options: finalOptions,
165168
state,

test/tracing/reactnativetracing.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ describe('ReactNativeTracing', () => {
129129
});
130130

131131
integration.setup(client);
132+
integration.afterAllSetup(client);
132133
// wait for internal promises to resolve, fetch app start data from mocked native
133134
await Promise.resolve();
134135

test/tracing/reactnavigation.test.ts

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-disable deprecation/deprecation */
22
/* eslint-disable @typescript-eslint/no-explicit-any */
33
import { getCurrentScope, getGlobalScope, getIsolationScope, SentrySpan, setCurrentClient } from '@sentry/core';
4-
import type { StartSpanOptions } from '@sentry/types';
4+
import type { Event, Measurements, StartSpanOptions } from '@sentry/types';
55

6-
import { reactNativeTracingIntegration } from '../../src/js';
6+
import { nativeFramesIntegration, reactNativeTracingIntegration } from '../../src/js';
77
import { DEFAULT_NAVIGATION_SPAN_NAME } from '../../src/js/tracing/reactnativetracing';
88
import type { NavigationRoute } from '../../src/js/tracing/reactnavigation';
99
import { ReactNavigationInstrumentation } from '../../src/js/tracing/reactnavigation';
@@ -21,13 +21,15 @@ import {
2121
} from '../../src/js/tracing/semanticAttributes';
2222
import { RN_GLOBAL_OBJ } from '../../src/js/utils/worldwide';
2323
import { getDefaultTestClientOptions, TestClient } from '../mocks/client';
24+
import { NATIVE } from '../mockWrapper';
2425
import { createMockNavigationAndAttachTo } from './reactnavigationutils';
2526

2627
const dummyRoute = {
2728
name: 'Route',
2829
key: '0',
2930
};
3031

32+
jest.mock('../../src/js/wrapper.ts', () => jest.requireActual('../mockWrapper.ts'));
3133
jest.useFakeTimers({ advanceTimers: true });
3234

3335
class MockNavigationContainer {
@@ -82,6 +84,85 @@ describe('ReactNavigationInstrumentation', () => {
8284
);
8385
});
8486

87+
describe('initial navigation span is created after all integrations are setup', () => {
88+
let rnTracing: ReturnType<typeof reactNativeTracingIntegration>;
89+
90+
beforeEach(() => {
91+
const startFrames = {
92+
totalFrames: 100,
93+
slowFrames: 20,
94+
frozenFrames: 5,
95+
};
96+
const finishFrames = {
97+
totalFrames: 200,
98+
slowFrames: 40,
99+
frozenFrames: 10,
100+
};
101+
NATIVE.fetchNativeFrames.mockResolvedValueOnce(startFrames).mockResolvedValueOnce(finishFrames);
102+
103+
const rNavigation = new ReactNavigationInstrumentation({
104+
routeChangeTimeoutMs: 200,
105+
});
106+
mockNavigation = createMockNavigationAndAttachTo(rNavigation);
107+
108+
rnTracing = reactNativeTracingIntegration({
109+
routingInstrumentation: rNavigation,
110+
});
111+
});
112+
113+
test('initial navigation span contains native frames when nativeFrames integration is after react native tracing', async () => {
114+
const options = getDefaultTestClientOptions({
115+
enableNativeFramesTracking: true,
116+
enableStallTracking: false,
117+
tracesSampleRate: 1.0,
118+
integrations: [rnTracing, nativeFramesIntegration()],
119+
enableAppStartTracking: false,
120+
});
121+
client = new TestClient(options);
122+
setCurrentClient(client);
123+
client.init();
124+
125+
// Flush the init transaction, must be async to allow for the native start frames to be fetched
126+
await jest.runOnlyPendingTimersAsync();
127+
await client.flush();
128+
129+
expectInitNavigationSpanWithNativeFrames(client.event);
130+
});
131+
132+
test('initial navigation span contains native frames when nativeFrames integration is before react native tracing', async () => {
133+
const options = getDefaultTestClientOptions({
134+
enableNativeFramesTracking: true,
135+
enableStallTracking: false,
136+
tracesSampleRate: 1.0,
137+
integrations: [nativeFramesIntegration(), rnTracing],
138+
enableAppStartTracking: false,
139+
});
140+
client = new TestClient(options);
141+
setCurrentClient(client);
142+
client.init();
143+
144+
// Flush the init transaction, must be async to allow for the native start frames to be fetched
145+
await jest.runOnlyPendingTimersAsync();
146+
await client.flush();
147+
148+
expectInitNavigationSpanWithNativeFrames(client.event);
149+
});
150+
151+
function expectInitNavigationSpanWithNativeFrames(event: Event): void {
152+
expect(event).toEqual(
153+
expect.objectContaining<Event>({
154+
type: 'transaction',
155+
transaction: 'Initial Screen',
156+
measurements: expect.objectContaining<Measurements>({
157+
frames_total: expect.toBeObject(),
158+
frames_slow: expect.toBeObject(),
159+
frames_frozen: expect.toBeObject(),
160+
}),
161+
}),
162+
);
163+
}
164+
});
165+
85166
test('transaction sent on navigation', async () => {
86167
setupTestClient();
87168
jest.runOnlyPendingTimers(); // Flush the init transaction

0 commit comments

Comments
 (0)