Skip to content

Commit df4f4ab

Browse files
authored
feat(replay): Do not capture replays < 5 seconds (GA) (#8277)
1 parent 4edc429 commit df4f4ab

File tree

13 files changed

+222
-1070
lines changed

13 files changed

+222
-1070
lines changed

packages/browser-integration-tests/suites/replay/bufferMode/test.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ sentryTest(
2525
let errorEventId: string | undefined;
2626
const reqPromise0 = waitForReplayRequest(page, 0);
2727
const reqPromise1 = waitForReplayRequest(page, 1);
28-
const reqPromise2 = waitForReplayRequest(page, 2);
2928
const reqErrorPromise = waitForErrorRequest(page);
3029

3130
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
@@ -101,18 +100,14 @@ sentryTest(
101100

102101
// Switches to session mode and then goes to background
103102
const req1 = await reqPromise1;
104-
const req2 = await reqPromise2;
105-
expect(callsToSentry).toBeGreaterThanOrEqual(5);
103+
expect(callsToSentry).toBeGreaterThanOrEqual(4);
106104

107105
const event0 = getReplayEvent(req0);
108106
const content0 = getReplayRecordingContent(req0);
109107

110108
const event1 = getReplayEvent(req1);
111109
const content1 = getReplayRecordingContent(req1);
112110

113-
const event2 = getReplayEvent(req2);
114-
const content2 = getReplayRecordingContent(req2);
115-
116111
expect(event0).toEqual(
117112
getExpectedReplayEvent({
118113
error_ids: [errorEventId!],
@@ -157,17 +152,7 @@ sentryTest(
157152

158153
// From switching to session mode
159154
expect(content1.fullSnapshots).toHaveLength(1);
160-
161-
expect(event2).toEqual(
162-
getExpectedReplayEvent({
163-
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
164-
segment_id: 2,
165-
urls: [],
166-
}),
167-
);
168-
169-
expect(content2.fullSnapshots).toHaveLength(0);
170-
expect(content2.breadcrumbs).toEqual(expect.arrayContaining([expectedClickBreadcrumb]));
155+
expect(content1.breadcrumbs).toEqual(expect.arrayContaining([expectedClickBreadcrumb]));
171156
},
172157
);
173158

packages/browser-integration-tests/suites/replay/fileInput/test.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ sentryTest(
2525
}
2626

2727
const reqPromise0 = waitForReplayRequest(page, 0);
28-
const reqPromise1 = waitForReplayRequest(page, 1);
2928

3029
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
3130
return route.fulfill({
@@ -39,7 +38,7 @@ sentryTest(
3938

4039
await page.goto(url);
4140

42-
await reqPromise0;
41+
const res = await reqPromise0;
4342

4443
await page.setInputFiles('#file-input', {
4544
name: 'file.csv',
@@ -49,9 +48,7 @@ sentryTest(
4948

5049
await forceFlushReplay();
5150

52-
const res1 = await reqPromise1;
53-
54-
const snapshots = getIncrementalRecordingSnapshots(res1).filter(isInputMutation);
51+
const snapshots = getIncrementalRecordingSnapshots(res).filter(isInputMutation);
5552

5653
expect(snapshots).toEqual([]);
5754
},

packages/browser-integration-tests/suites/replay/largeMutations/defaultOptions/test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ sentryTest(
1111
}
1212

1313
const reqPromise0 = waitForReplayRequest(page, 0);
14-
const reqPromise0b = waitForReplayRequest(page, 1);
1514

1615
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
1716
return route.fulfill({
@@ -24,10 +23,7 @@ sentryTest(
2423
const url = await getLocalTestPath({ testDir: __dirname });
2524

2625
await page.goto(url);
27-
await forceFlushReplay();
2826
const res0 = await reqPromise0;
29-
await reqPromise0b;
30-
// A second request is sent right after initial snapshot, we want to wait for that to settle before we continue
3127

3228
const reqPromise1 = waitForReplayRequest(page);
3329

packages/browser-integration-tests/suites/replay/largeMutations/mutationLimit/test.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ sentryTest(
1616
}
1717

1818
const reqPromise0 = waitForReplayRequest(page, 0);
19-
const reqPromise0b = waitForReplayRequest(page, 1);
2019

2120
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
2221
return route.fulfill({
@@ -30,8 +29,6 @@ sentryTest(
3029

3130
await page.goto(url);
3231
const res0 = await reqPromise0;
33-
await reqPromise0b;
34-
// A second request is sent right after initial snapshot, we want to wait for that to settle before we continue
3532

3633
const reqPromise1 = waitForReplayRequest(page);
3734

packages/browser-integration-tests/suites/replay/slowClick/mutation/test.ts

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect } from '@playwright/test';
33
import { sentryTest } from '../../../../utils/fixtures';
44
import { getCustomRecordingEvents, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers';
55

6-
sentryTest('mutation after threshold results in slow click', async ({ getLocalTestUrl, page }) => {
6+
sentryTest('mutation after threshold results in slow click', async ({ forceFlushReplay, getLocalTestUrl, page }) => {
77
if (shouldSkipReplayTest()) {
88
sentryTest.skip();
99
}
@@ -21,6 +21,7 @@ sentryTest('mutation after threshold results in slow click', async ({ getLocalTe
2121
const url = await getLocalTestUrl({ testDir: __dirname });
2222

2323
await page.goto(url);
24+
await forceFlushReplay();
2425
await reqPromise0;
2526

2627
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
@@ -125,59 +126,63 @@ sentryTest('multiple clicks are counted', async ({ getLocalTestUrl, page }) => {
125126
expect(slowClickBreadcrumbs[0]?.data?.timeAfterClickMs).toBeLessThan(3100);
126127
});
127128

128-
sentryTest('immediate mutation does not trigger slow click', async ({ browserName, getLocalTestUrl, page }) => {
129-
// This test seems to only be flakey on firefox
130-
if (shouldSkipReplayTest() || ['firefox'].includes(browserName)) {
131-
sentryTest.skip();
132-
}
133-
134-
const reqPromise0 = waitForReplayRequest(page, 0);
135-
136-
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
137-
return route.fulfill({
138-
status: 200,
139-
contentType: 'application/json',
140-
body: JSON.stringify({ id: 'test-id' }),
129+
sentryTest(
130+
'immediate mutation does not trigger slow click',
131+
async ({ forceFlushReplay, browserName, getLocalTestUrl, page }) => {
132+
// This test seems to only be flakey on firefox
133+
if (shouldSkipReplayTest() || ['firefox'].includes(browserName)) {
134+
sentryTest.skip();
135+
}
136+
137+
const reqPromise0 = waitForReplayRequest(page, 0);
138+
139+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
140+
return route.fulfill({
141+
status: 200,
142+
contentType: 'application/json',
143+
body: JSON.stringify({ id: 'test-id' }),
144+
});
141145
});
142-
});
143146

144-
const url = await getLocalTestUrl({ testDir: __dirname });
147+
const url = await getLocalTestUrl({ testDir: __dirname });
145148

146-
await page.goto(url);
147-
await reqPromise0;
149+
await page.goto(url);
150+
await forceFlushReplay();
151+
await reqPromise0;
148152

149-
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
150-
const { breadcrumbs } = getCustomRecordingEvents(res);
153+
const reqPromise1 = waitForReplayRequest(page, (event, res) => {
154+
const { breadcrumbs } = getCustomRecordingEvents(res);
151155

152-
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
153-
});
154-
155-
await page.click('#mutationButtonImmediately');
156-
157-
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
156+
return breadcrumbs.some(breadcrumb => breadcrumb.category === 'ui.click');
157+
});
158158

159-
expect(breadcrumbs).toEqual([
160-
{
161-
category: 'ui.click',
162-
data: {
163-
node: {
164-
attributes: {
165-
id: 'mutationButtonImmediately',
159+
await page.click('#mutationButtonImmediately');
160+
161+
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
162+
163+
expect(breadcrumbs).toEqual([
164+
{
165+
category: 'ui.click',
166+
data: {
167+
node: {
168+
attributes: {
169+
id: 'mutationButtonImmediately',
170+
},
171+
id: expect.any(Number),
172+
tagName: 'button',
173+
textContent: '******* ******** ***********',
166174
},
167-
id: expect.any(Number),
168-
tagName: 'button',
169-
textContent: '******* ******** ***********',
175+
nodeId: expect.any(Number),
170176
},
171-
nodeId: expect.any(Number),
177+
message: 'body > button#mutationButtonImmediately',
178+
timestamp: expect.any(Number),
179+
type: 'default',
172180
},
173-
message: 'body > button#mutationButtonImmediately',
174-
timestamp: expect.any(Number),
175-
type: 'default',
176-
},
177-
]);
178-
});
181+
]);
182+
},
183+
);
179184

180-
sentryTest('inline click handler does not trigger slow click', async ({ getLocalTestUrl, page }) => {
185+
sentryTest('inline click handler does not trigger slow click', async ({ forceFlushReplay, getLocalTestUrl, page }) => {
181186
if (shouldSkipReplayTest()) {
182187
sentryTest.skip();
183188
}
@@ -195,6 +200,7 @@ sentryTest('inline click handler does not trigger slow click', async ({ getLocal
195200
const url = await getLocalTestUrl({ testDir: __dirname });
196201

197202
await page.goto(url);
203+
await forceFlushReplay();
198204
await reqPromise0;
199205

200206
const reqPromise1 = waitForReplayRequest(page, (event, res) => {

packages/browser-integration-tests/suites/replay/throttleBreadcrumbs/test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,19 @@ sentryTest(
2626
const url = await getLocalTestUrl({ testDir: __dirname });
2727

2828
await page.goto(url);
29-
await reqPromise0;
29+
await forceFlushReplay();
30+
const res0 = getCustomRecordingEvents(await reqPromise0);
3031

3132
await page.click('[data-console]');
3233
await forceFlushReplay();
3334

34-
const { breadcrumbs } = getCustomRecordingEvents(await reqPromise1);
35+
const res1 = getCustomRecordingEvents(await reqPromise1);
3536

36-
// 1 click breadcrumb + 1 throttled breadcrumb is why console logs are less
37-
// than throttle limit
38-
expect(breadcrumbs.length).toBe(THROTTLE_LIMIT);
37+
const breadcrumbs = [...res0.breadcrumbs, ...res1.breadcrumbs];
38+
const spans = [...res0.performanceSpans, ...res1.performanceSpans];
3939
expect(breadcrumbs.filter(breadcrumb => breadcrumb.category === 'replay.throttled').length).toBe(1);
40+
// replay.throttled breadcrumb does *not* use the throttledAddEvent as we
41+
// alwants want that breadcrumb to be present in replay
42+
expect(breadcrumbs.length + spans.length).toBe(THROTTLE_LIMIT + 1);
4043
},
4144
);

packages/e2e-tests/test-applications/standard-frontend-react/tests/fixtures/ReplayRecordingData.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -95,26 +95,6 @@ export const ReplayRecordingData = [
9595
},
9696
timestamp: expect.any(Number),
9797
},
98-
{
99-
type: 5,
100-
timestamp: expect.any(Number),
101-
data: {
102-
tag: 'performanceSpan',
103-
payload: {
104-
op: 'memory',
105-
description: 'memory',
106-
startTimestamp: expect.any(Number),
107-
endTimestamp: expect.any(Number),
108-
data: {
109-
memory: {
110-
jsHeapSizeLimit: expect.any(Number),
111-
totalJSHeapSize: expect.any(Number),
112-
usedJSHeapSize: expect.any(Number),
113-
},
114-
},
115-
},
116-
},
117-
},
11898
{
11999
type: 3,
120100
data: {
@@ -155,8 +135,6 @@ export const ReplayRecordingData = [
155135
data: { source: 5, text: 'Capture Exception', isChecked: false, id: 16 },
156136
timestamp: expect.any(Number),
157137
},
158-
],
159-
[
160138
{
161139
type: 5,
162140
timestamp: expect.any(Number),

packages/replay/src/replay.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,13 @@ export class ReplayContainer implements ReplayContainerInterface {
547547
return this.flushImmediate();
548548
}
549549

550+
/**
551+
* Flush using debounce flush
552+
*/
553+
public flush(): Promise<void> {
554+
return this._debouncedFlush() as Promise<void>;
555+
}
556+
550557
/**
551558
* Always flush via `_debouncedFlush` so that we do not have flushes triggered
552559
* from calling both `flush` and `_debouncedFlush`. Otherwise, there could be

packages/replay/src/types/replay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
194194
_experiments: Partial<{
195195
captureExceptions: boolean;
196196
traceInternals: boolean;
197-
delayFlushOnCheckout: number;
198197
}>;
199198
}
200199

@@ -438,6 +437,7 @@ export interface ReplayContainer {
438437
stopRecording(): boolean;
439438
sendBufferedReplayOrFlush(options?: SendBufferedReplayOptions): Promise<void>;
440439
conditionalFlush(): Promise<void>;
440+
flush(): Promise<void>;
441441
flushImmediate(): Promise<void>;
442442
cancelFlush(): void;
443443
triggerUserActivity(): void;

packages/replay/src/util/handleRecordingEmit.ts

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -80,41 +80,12 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa
8080
}
8181
}
8282

83-
const options = replay.getOptions();
84-
85-
// TODO: We want this as an experiment so that we can test
86-
// internally and create metrics before making this the default
87-
if (options._experiments.delayFlushOnCheckout) {
83+
if (replay.recordingMode === 'session') {
8884
// If the full snapshot is due to an initial load, we will not have
8985
// a previous session ID. In this case, we want to buffer events
9086
// for a set amount of time before flushing. This can help avoid
9187
// capturing replays of users that immediately close the window.
92-
// TODO: We should check `recordingMode` here and do nothing if it's
93-
// buffer, instead of checking inside of timeout, this will make our
94-
// tests a bit cleaner as we will need to wait on the delay in order to
95-
// do nothing.
96-
setTimeout(() => replay.conditionalFlush(), options._experiments.delayFlushOnCheckout);
97-
98-
// Cancel any previously debounced flushes to ensure there are no [near]
99-
// simultaneous flushes happening. The latter request should be
100-
// insignificant in this case, so wait for additional user interaction to
101-
// trigger a new flush.
102-
//
103-
// This can happen because there's no guarantee that a recording event
104-
// happens first. e.g. a mouse click can happen and trigger a debounced
105-
// flush before the checkout.
106-
replay.cancelFlush();
107-
108-
return true;
109-
}
110-
111-
// Flush immediately so that we do not miss the first segment, otherwise
112-
// it can prevent loading on the UI. This will cause an increase in short
113-
// replays (e.g. opening and closing a tab quickly), but these can be
114-
// filtered on the UI.
115-
if (replay.recordingMode === 'session') {
116-
// We want to ensure the worker is ready, as otherwise we'd always send the first event uncompressed
117-
void replay.flushImmediate();
88+
void replay.flush();
11889
}
11990

12091
return true;

packages/replay/test/integration/coreHandlers/handleAfterSendEvent.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,13 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => {
148148

149149
jest.runAllTimers();
150150
await new Promise(process.nextTick);
151-
152151
// Send twice, one for the error & one right after for the session conversion
152+
expect(mockSend).toHaveBeenCalledTimes(1);
153+
154+
jest.runAllTimers();
155+
await new Promise(process.nextTick);
153156
expect(mockSend).toHaveBeenCalledTimes(2);
157+
154158
// This is removed now, because it has been converted to a "session" session
155159
expect(Array.from(replay.getContext().errorIds)).toEqual([]);
156160
expect(replay.isEnabled()).toBe(true);

0 commit comments

Comments
 (0)