Skip to content

Commit 22661b1

Browse files
Merge b484b07 into 952dd05
2 parents 952dd05 + b484b07 commit 22661b1

File tree

7 files changed

+280
-15
lines changed

7 files changed

+280
-15
lines changed

samples/react-native/e2e/captureMessage.test.android.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { describe, it, beforeAll, expect, afterAll } from '@jest/globals';
2-
import { Envelope } from '@sentry/core';
2+
import { Envelope, EventItem } from '@sentry/core';
33
import { device } from 'detox';
44
import {
55
createSentryServer,
66
containingEvent,
77
} from './utils/mockedSentryServer';
8-
import { HEADER, ITEMS } from './utils/consts';
98
import { tap } from './utils/tap';
9+
import { getItemOfTypeFrom } from './utils/event';
1010

1111
describe('Capture message', () => {
1212
let sentryServer = createSentryServer();
@@ -27,9 +27,7 @@ describe('Capture message', () => {
2727
});
2828

2929
it('envelope contains message event', async () => {
30-
const item = (envelope[ITEMS] as [{ type?: string }, unknown][]).find(
31-
i => i[HEADER].type === 'event',
32-
);
30+
const item = getItemOfTypeFrom<EventItem>(envelope, 'event');
3331

3432
expect(item).toEqual([
3533
{

samples/react-native/e2e/captureMessage.test.ios.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { describe, it, beforeAll, expect, afterAll } from '@jest/globals';
2-
import { Envelope } from '@sentry/core';
2+
import { Envelope, EventItem } from '@sentry/core';
33
import { device } from 'detox';
44
import {
55
createSentryServer,
66
containingEvent,
77
} from './utils/mockedSentryServer';
8-
import { HEADER, ITEMS } from './utils/consts';
98
import { tap } from './utils/tap';
9+
import { getItemOfTypeFrom } from './utils/event';
1010

1111
describe('Capture message', () => {
1212
let sentryServer = createSentryServer();
@@ -27,9 +27,7 @@ describe('Capture message', () => {
2727
});
2828

2929
it('envelope contains message event', async () => {
30-
const item = (envelope[ITEMS] as [{ type?: string }, unknown][]).find(
31-
i => i[HEADER].type === 'event',
32-
);
30+
const item = getItemOfTypeFrom<EventItem>(envelope, 'event');
3331

3432
expect(item).toEqual([
3533
{
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { describe, it, beforeAll, expect, afterAll } from '@jest/globals';
2+
import { Envelope, EventItem } from '@sentry/core';
3+
import { device } from 'detox';
4+
import {
5+
createSentryServer,
6+
containingTransactionWithName,
7+
} from './utils/mockedSentryServer';
8+
import { tap } from './utils/tap';
9+
import { sleep } from './utils/sleep';
10+
import { getItemOfTypeFrom } from './utils/event';
11+
12+
describe('Capture transaction', () => {
13+
let sentryServer = createSentryServer();
14+
sentryServer.start();
15+
16+
const getErrorsEnvelope = () =>
17+
sentryServer.getEnvelope(containingTransactionWithName('Errors'));
18+
19+
const getTrackerEnvelope = () =>
20+
sentryServer.getEnvelope(containingTransactionWithName('Tracker'));
21+
22+
beforeAll(async () => {
23+
await device.launchApp();
24+
25+
const waitForPerformanceTransaction = sentryServer.waitForEnvelope(
26+
containingTransactionWithName('Tracker'), // The last created and sent transaction
27+
);
28+
29+
await sleep(500);
30+
await tap('Performance'); // Bottom tab
31+
await sleep(200);
32+
await tap('Auto Tracing Example'); // Screen with Full Display
33+
34+
await waitForPerformanceTransaction;
35+
});
36+
37+
afterAll(async () => {
38+
await sentryServer.close();
39+
});
40+
41+
it('envelope contains transaction context', async () => {
42+
const item = getItemOfTypeFrom<EventItem>(
43+
getErrorsEnvelope(),
44+
'transaction',
45+
);
46+
47+
expect(item).toEqual([
48+
expect.objectContaining({
49+
length: expect.any(Number),
50+
type: 'transaction',
51+
}),
52+
expect.objectContaining({
53+
platform: 'javascript',
54+
contexts: expect.objectContaining({
55+
trace: {
56+
data: {
57+
'route.has_been_seen': false,
58+
'route.key': expect.stringMatching(/^ErrorsScreen/),
59+
'route.name': 'ErrorsScreen',
60+
'sentry.idle_span_finish_reason': 'idleTimeout',
61+
'sentry.op': 'ui.load',
62+
'sentry.origin': 'auto.app.start',
63+
'sentry.sample_rate': 1,
64+
'sentry.source': 'component',
65+
},
66+
op: 'ui.load',
67+
origin: 'auto.app.start',
68+
span_id: expect.any(String),
69+
trace_id: expect.any(String),
70+
},
71+
}),
72+
}),
73+
]);
74+
});
75+
76+
it('contains app start measurements', async () => {
77+
const item = getItemOfTypeFrom<EventItem>(
78+
getErrorsEnvelope(),
79+
'transaction',
80+
);
81+
82+
expect(item?.[1]).toEqual(
83+
expect.objectContaining({
84+
measurements: expect.objectContaining({
85+
app_start_warm: {
86+
unit: 'millisecond',
87+
value: expect.any(Number),
88+
},
89+
time_to_initial_display: {
90+
unit: 'millisecond',
91+
value: expect.any(Number),
92+
},
93+
}),
94+
}),
95+
);
96+
});
97+
98+
it('contains time to initial display measurements', async () => {
99+
const item = getItemOfTypeFrom<EventItem>(
100+
await getErrorsEnvelope(),
101+
'transaction',
102+
);
103+
104+
expect(item?.[1]).toEqual(
105+
expect.objectContaining({
106+
measurements: expect.objectContaining({
107+
time_to_initial_display: {
108+
unit: 'millisecond',
109+
value: expect.any(Number),
110+
},
111+
}),
112+
}),
113+
);
114+
});
115+
116+
it('contains JS stall measurements', async () => {
117+
const item = getItemOfTypeFrom<EventItem>(
118+
await getErrorsEnvelope(),
119+
'transaction',
120+
);
121+
122+
expect(item?.[1]).toEqual(
123+
expect.objectContaining({
124+
measurements: expect.objectContaining({
125+
stall_count: {
126+
unit: 'none',
127+
value: expect.any(Number),
128+
},
129+
stall_longest_time: {
130+
unit: 'millisecond',
131+
value: expect.any(Number),
132+
},
133+
stall_total_time: {
134+
unit: 'millisecond',
135+
value: expect.any(Number),
136+
},
137+
}),
138+
}),
139+
);
140+
});
141+
142+
it('contains native frames measurements', async () => {
143+
const item = getItemOfTypeFrom<EventItem>(
144+
getErrorsEnvelope(),
145+
'transaction',
146+
);
147+
148+
expect(item?.[1]).toEqual(
149+
expect.objectContaining({
150+
measurements: expect.objectContaining({
151+
frames_frozen: {
152+
unit: 'none',
153+
value: 0, // Should we force 0 in e2e tests?
154+
},
155+
frames_slow: {
156+
unit: 'none',
157+
value: expect.any(Number),
158+
},
159+
frames_total: {
160+
unit: 'none',
161+
value: expect.any(Number),
162+
},
163+
}),
164+
}),
165+
);
166+
});
167+
168+
it('contains time to display measurements', async () => {
169+
const item = getItemOfTypeFrom<EventItem>(
170+
getTrackerEnvelope(),
171+
'transaction',
172+
);
173+
174+
expect(item?.[1]).toEqual(
175+
expect.objectContaining({
176+
measurements: expect.objectContaining({
177+
time_to_initial_display: {
178+
unit: 'millisecond',
179+
value: expect.any(Number),
180+
},
181+
time_to_full_display: {
182+
unit: 'millisecond',
183+
value: expect.any(Number),
184+
},
185+
}),
186+
}),
187+
);
188+
});
189+
190+
it('contains at least one xhr breadcrumb of request to the tracker endpoint', async () => {
191+
const item = getItemOfTypeFrom<EventItem>(
192+
getTrackerEnvelope(),
193+
'transaction',
194+
);
195+
196+
expect(item?.[1]).toEqual(
197+
expect.objectContaining({
198+
breadcrumbs: expect.arrayContaining([
199+
expect.objectContaining({
200+
category: 'xhr',
201+
data: {
202+
end_timestamp: expect.any(Number),
203+
method: 'GET',
204+
response_body_size: expect.any(Number),
205+
start_timestamp: expect.any(Number),
206+
status_code: expect.any(Number),
207+
url: expect.stringContaining('api.covid19api.com/summary'),
208+
},
209+
level: 'info',
210+
timestamp: expect.any(Number),
211+
type: 'http',
212+
}),
213+
]),
214+
}),
215+
);
216+
});
217+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Envelope, EnvelopeItem } from '@sentry/core';
2+
import { HEADER, ITEMS } from './consts';
3+
4+
export function getItemOfTypeFrom<T extends EnvelopeItem>(
5+
envelope: Envelope,
6+
type: string,
7+
): T | undefined {
8+
return (envelope[ITEMS] as [{ type?: string }, unknown][]).find(
9+
i => i[HEADER].type === type,
10+
) as T | undefined;
11+
}

samples/react-native/e2e/utils/mockedSentryServer.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { IncomingMessage, ServerResponse, createServer } from 'node:http';
22
import { createGunzip } from 'node:zlib';
3-
import { Envelope } from '@sentry/core';
3+
import { Envelope, EnvelopeItem } from '@sentry/core';
44
import { parseEnvelope } from './parseEnvelope';
5+
import { Event } from '@sentry/core';
56

67
type RecordedRequest = {
78
path: string | undefined;
@@ -16,6 +17,7 @@ export function createSentryServer({ port = 8961 } = {}): {
1617
) => Promise<Envelope>;
1718
close: () => Promise<void>;
1819
start: () => void;
20+
getEnvelope: (predicate: (envelope: Envelope) => boolean) => Envelope;
1921
} {
2022
let onNextRequestCallback: (request: RecordedRequest) => void = () => {};
2123
const requests: RecordedRequest[] = [];
@@ -74,11 +76,47 @@ export function createSentryServer({ port = 8961 } = {}): {
7476
server.close(() => resolve());
7577
});
7678
},
79+
getEnvelope: (predicate: (envelope: Envelope) => boolean) => {
80+
const envelope = requests.find(
81+
request => request.envelope && predicate(request.envelope),
82+
)?.envelope;
83+
84+
if (!envelope) {
85+
throw new Error('Envelope not found');
86+
}
87+
88+
return envelope;
89+
},
7790
};
7891
}
7992

8093
export function containingEvent(envelope: Envelope) {
81-
return envelope[1].some(
82-
item => (item[0] as { type?: string }).type === 'event',
83-
);
94+
return envelope[1].some(item => itemHeaderIsType(item[0], 'event'));
95+
}
96+
97+
export function containingTransactionWithName(name: string) {
98+
return (envelope: Envelope) =>
99+
envelope[1].some(
100+
item =>
101+
itemHeaderIsType(item[0], 'transaction') &&
102+
itemBodyIsEvent(item[1]) &&
103+
item[1].transaction &&
104+
item[1].transaction.includes(name),
105+
);
106+
}
107+
108+
export function itemBodyIsEvent(itemBody: EnvelopeItem[1]): itemBody is Event {
109+
return typeof itemBody === 'object' && 'event_id' in itemBody;
110+
}
111+
112+
export function itemHeaderIsType(itemHeader: EnvelopeItem[0], type: string) {
113+
if (typeof itemHeader !== 'object' || !('type' in itemHeader)) {
114+
return false;
115+
}
116+
117+
if (itemHeader.type !== type) {
118+
return false;
119+
}
120+
121+
return true;
84122
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function sleep(ms: number) {
2+
return new Promise(resolve => setTimeout(resolve, ms));
3+
}

samples/react-native/sentry.options.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"dsn": "https://[email protected]/5428561",
2+
"dsn": "http://key@localhost:8961/123456",
33
"debug": true,
44
"environment": "dev",
55
"enableUserInteractionTracing": true,

0 commit comments

Comments
 (0)