Skip to content

Commit 441efad

Browse files
committed
add helpers
1 parent 82bdbf6 commit 441efad

File tree

3 files changed

+168
-4
lines changed

3 files changed

+168
-4
lines changed

packages/integration-tests/utils/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export const envelopeParser = (request: Request | null): unknown[] => {
1717
});
1818
};
1919

20-
export const envelopeRequestParser = (request: Request | null): Event => {
21-
return envelopeParser(request)[2] as Event;
20+
export const envelopeRequestParser = (request: Request | null, envelopeIndex = 2): Event => {
21+
return envelopeParser(request)[envelopeIndex] as Event;
2222
};
2323

2424
export const envelopeHeaderRequestParser = (request: Request | null): EventEnvelopeHeaders => {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { expect } from '@playwright/test';
3+
import { SDK_VERSION } from '@sentry/browser';
4+
import type { ReplayEvent } from '@sentry/types';
5+
6+
const DEFAULT_REPLAY_EVENT = {
7+
type: 'replay_event',
8+
timestamp: expect.any(Number),
9+
error_ids: [],
10+
trace_ids: [],
11+
urls: [expect.stringContaining('/dist/index.html')],
12+
replay_id: expect.stringMatching(/\w{32}/),
13+
replay_start_timestamp: expect.any(Number),
14+
segment_id: 0,
15+
replay_type: 'session',
16+
event_id: expect.stringMatching(/\w{32}/),
17+
environment: 'production',
18+
sdk: {
19+
integrations: [
20+
'InboundFilters',
21+
'FunctionToString',
22+
'TryCatch',
23+
'Breadcrumbs',
24+
'GlobalHandlers',
25+
'LinkedErrors',
26+
'Dedupe',
27+
'HttpContext',
28+
'Replay',
29+
],
30+
version: SDK_VERSION,
31+
name: 'sentry.javascript.browser',
32+
},
33+
sdkProcessingMetadata: {},
34+
request: {
35+
url: expect.stringContaining('/dist/index.html'),
36+
headers: {
37+
'User-Agent': expect.stringContaining(''),
38+
},
39+
},
40+
platform: 'javascript',
41+
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
42+
};
43+
44+
/**
45+
* Creates a ReplayEvent object with the default values merged with the customExpectedReplayEvent.
46+
* This is useful for testing multi-segment replays to not repeat most of the properties that don't change
47+
* throughout the replay segments.
48+
*
49+
* Note: The benfit of this approach over expect.objectContaining is that,
50+
* we'll catch if properties we expect to stay the same actually change.
51+
*
52+
* @param customExpectedReplayEvent overwrite the default values with custom values (e.g. segment_id)
53+
*/
54+
export function getExpectedReplayEvent(customExpectedReplayEvent: Partial<ReplayEvent> & Record<string, unknown> = {}) {
55+
return {
56+
...DEFAULT_REPLAY_EVENT,
57+
...customExpectedReplayEvent,
58+
};
59+
}
60+
61+
/* This is how we expect different kinds of navigation performance span to look: */
62+
63+
export const expectedNavigationPerformanceSpan = {
64+
op: 'navigation.navigate',
65+
description: '',
66+
startTimestamp: expect.any(Number),
67+
endTimestamp: expect.any(Number),
68+
data: {
69+
duration: expect.any(Number),
70+
size: expect.any(Number),
71+
},
72+
};
73+
74+
export const expectedMemoryPerformanceSpan = {
75+
op: 'memory',
76+
description: 'memory',
77+
startTimestamp: expect.any(Number),
78+
endTimestamp: expect.any(Number),
79+
data: {
80+
memory: {
81+
jsHeapSizeLimit: expect.any(Number),
82+
totalJSHeapSize: expect.any(Number),
83+
usedJSHeapSize: expect.any(Number),
84+
},
85+
},
86+
};
87+
88+
export const expectedLCPPerformanceSpan = {
89+
op: 'largest-contentful-paint',
90+
description: 'largest-contentful-paint',
91+
startTimestamp: expect.any(Number),
92+
endTimestamp: expect.any(Number),
93+
data: {
94+
duration: expect.any(Number),
95+
nodeId: expect.any(Number),
96+
size: expect.any(Number),
97+
},
98+
};
99+
100+
export const expectedFCPPerformanceSpan = {
101+
op: 'paint',
102+
description: 'first-contentful-paint',
103+
startTimestamp: expect.any(Number),
104+
endTimestamp: expect.any(Number),
105+
};
106+
107+
export const expectedFPPerformanceSpan = {
108+
op: 'paint',
109+
description: 'first-paint',
110+
startTimestamp: expect.any(Number),
111+
endTimestamp: expect.any(Number),
112+
};

packages/integration-tests/utils/replayHelpers.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import type { ReplayContainer } from '@sentry/replay/build/npm/types/types';
2-
import type { Event, ReplayEvent } from '@sentry/types';
1+
import type { RecordingEvent, ReplayContainer } from '@sentry/replay/build/npm/types/types';
2+
import type { Breadcrumb, Event, ReplayEvent } from '@sentry/types';
33
import type { Page, Request } from 'playwright';
44

55
import { envelopeRequestParser } from './helpers';
66

7+
type CustomRecordingEvent = { tag: string; payload: Record<string, unknown> };
8+
type PerformanceSpan = {
9+
op: string;
10+
description: string;
11+
startTimestamp: number;
12+
endTimestamp: number;
13+
data: Record<string, number>;
14+
};
15+
716
/**
817
* Waits for a replay request to be sent by the page and returns it.
918
*
@@ -56,3 +65,46 @@ export async function getReplaySnapshot(page: Page): Promise<ReplayContainer> {
5665
}
5766

5867
export const REPLAY_DEFAULT_FLUSH_MAX_DELAY = 5_000;
68+
69+
export function getReplayEvent(replayRequest: Request): ReplayEvent {
70+
const event = envelopeRequestParser(replayRequest);
71+
if (!isReplayEvent(event)) {
72+
throw new Error('Request is not a replay event');
73+
}
74+
return event;
75+
}
76+
77+
/**
78+
* Takes an uncompressed replay request and returns the custom recording events,
79+
* i.e. the events we emit as type 5 rrweb events
80+
*
81+
* @param replayRequest
82+
* @returns an object containing the replay breadcrumbs and performance spans
83+
*/
84+
export function getCustomRecordingEvents(replayRequest: Request): {
85+
breadcrumbs: Breadcrumb[];
86+
performanceSpans: PerformanceSpan[];
87+
} {
88+
const recordingEvents = envelopeRequestParser(replayRequest, 5) as RecordingEvent[];
89+
90+
const breadcrumbs = getReplayBreadcrumbs(recordingEvents);
91+
const performanceSpans = getReplayPerformanceSpans(recordingEvents);
92+
return { breadcrumbs, performanceSpans };
93+
}
94+
95+
function getAllCustomRrwebRecordingEvents(recordingEvents: RecordingEvent[]): CustomRecordingEvent[] {
96+
return recordingEvents.filter(event => event.type === 5).map(event => event.data as CustomRecordingEvent);
97+
}
98+
99+
function getReplayBreadcrumbs(recordingEvents: RecordingEvent[], category?: string): Breadcrumb[] {
100+
return getAllCustomRrwebRecordingEvents(recordingEvents)
101+
.filter(data => data.tag === 'breadcrumb')
102+
.map(data => data.payload)
103+
.filter(payload => !category || payload.category === category);
104+
}
105+
106+
function getReplayPerformanceSpans(recordingEvents: RecordingEvent[]): PerformanceSpan[] {
107+
return getAllCustomRrwebRecordingEvents(recordingEvents)
108+
.filter(data => data.tag === 'performanceSpan')
109+
.map(data => data.payload) as PerformanceSpan[];
110+
}

0 commit comments

Comments
 (0)