Skip to content

Commit 009ce34

Browse files
committed
fix(replay): Ensure we do not try to flush when we force stop replay
1 parent 0bade5d commit 009ce34

File tree

6 files changed

+86
-8
lines changed

6 files changed

+86
-8
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
</head>
6+
<body>
7+
<button onclick="console.log('Test log')" id="button1">Click me</button>
8+
<button onclick="console.log('Test log 2')" id="button2">Click me</button>
9+
</body>
10+
</html>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { expect } from '@playwright/test';
2+
3+
import { sentryTest } from '../../../utils/fixtures';
4+
import {
5+
getReplaySnapshot,
6+
REPLAY_DEFAULT_FLUSH_MAX_DELAY,
7+
shouldSkipReplayTest,
8+
waitForReplayRequest,
9+
} from '../../../utils/replayHelpers';
10+
11+
sentryTest('should stop recording when running into eventBuffer error', async ({ getLocalTestPath, page }) => {
12+
if (shouldSkipReplayTest()) {
13+
sentryTest.skip();
14+
}
15+
16+
let called = 0;
17+
18+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
19+
called++;
20+
21+
return route.fulfill({
22+
status: 200,
23+
});
24+
});
25+
26+
const url = await getLocalTestPath({ testDir: __dirname });
27+
await page.goto(url);
28+
29+
await waitForReplayRequest(page);
30+
31+
expect(called).toBe(1);
32+
const replay = await getReplaySnapshot(page);
33+
expect(replay._isEnabled).toBe(true);
34+
35+
called = 0;
36+
37+
/**
38+
* We test the following here:
39+
* 1. First click should add an event (so the eventbuffer is not empty)
40+
* 2. Second click should throw an error in eventBuffer (which should lead to stopping the replay)
41+
* 3. Nothing should be sent to API, as we stop the replay due to the eventBuffer error.
42+
*/
43+
await page.evaluate(`
44+
window._count = 0;
45+
window._addEvent = window.Replay._replay.eventBuffer.addEvent.bind(window.Replay._replay.eventBuffer);
46+
window.Replay._replay.eventBuffer.addEvent = (...args) => {
47+
window._count++;
48+
if (window._count === 2) {
49+
throw new Error('provoked error');
50+
}
51+
window._addEvent(...args);
52+
};
53+
`);
54+
55+
void page.click('#button1');
56+
void page.click('#button2');
57+
58+
// Should immediately skip retrying and just cancel, no backoff
59+
// This waitForTimeout call should be okay, as we're not checking for any
60+
// further network requests afterwards.
61+
await page.waitForTimeout(REPLAY_DEFAULT_FLUSH_MAX_DELAY + 100);
62+
63+
expect(called).toBe(0);
64+
65+
const replay2 = await getReplaySnapshot(page);
66+
67+
expect(replay2._isEnabled).toBe(false);
68+
});

packages/replay/src/integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ Sentry.init({ replaysOnErrorSampleRate: ${errorSampleRate} })`,
263263
return Promise.resolve();
264264
}
265265

266-
return this._replay.stop();
266+
return this._replay.stop({ forceFlush: this._replay.recordingMode === 'session' });
267267
}
268268

269269
/**

packages/replay/src/replay.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ export class ReplayContainer implements ReplayContainerInterface {
368368
* Currently, this needs to be manually called (e.g. for tests). Sentry SDK
369369
* does not support a teardown
370370
*/
371-
public async stop(reason?: string): Promise<void> {
371+
public async stop({ forceFlush = false, reason }: { forceFlush?: boolean; reason?: string } = {}): Promise<void> {
372372
if (!this._isEnabled) {
373373
return;
374374
}
@@ -388,7 +388,7 @@ export class ReplayContainer implements ReplayContainerInterface {
388388
this._debouncedFlush.cancel();
389389
// See comment above re: `_isEnabled`, we "force" a flush, ignoring the
390390
// `_isEnabled` state of the plugin since it was disabled above.
391-
if (this.recordingMode === 'session') {
391+
if (forceFlush) {
392392
await this._flush({ force: true });
393393
}
394394

@@ -777,7 +777,7 @@ export class ReplayContainer implements ReplayContainerInterface {
777777
this.session = session;
778778

779779
if (!this.session.sampled) {
780-
void this.stop('session not refreshed');
780+
void this.stop({ reason: 'session not refreshed' });
781781
return false;
782782
}
783783

@@ -1099,7 +1099,7 @@ export class ReplayContainer implements ReplayContainerInterface {
10991099
// This means we retried 3 times and all of them failed,
11001100
// or we ran into a problem we don't want to retry, like rate limiting.
11011101
// In this case, we want to completely stop the replay - otherwise, we may get inconsistent segments
1102-
void this.stop('sendReplay');
1102+
void this.stop({ reason: 'sendReplay' });
11031103

11041104
const client = getCurrentHub().getClient();
11051105

@@ -1223,7 +1223,7 @@ export class ReplayContainer implements ReplayContainerInterface {
12231223

12241224
// Stop replay if over the mutation limit
12251225
if (overMutationLimit) {
1226-
void this.stop('mutationLimit');
1226+
void this.stop({ reason: 'mutationLimit', forceFlush: this.recordingMode === 'session' });
12271227
return false;
12281228
}
12291229

packages/replay/src/types/replay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ export interface ReplayContainer {
451451
getContext(): InternalEventContext;
452452
initializeSampling(): void;
453453
start(): void;
454-
stop(reason?: string): Promise<void>;
454+
stop(options?: { reason?: string; forceflush?: boolean }): Promise<void>;
455455
pause(): void;
456456
resume(): void;
457457
startRecording(): void;

packages/replay/src/util/addEvent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export async function addEvent(
7171
const reason = error && error instanceof EventBufferSizeExceededError ? 'addEventSizeExceeded' : 'addEvent';
7272

7373
__DEBUG_BUILD__ && logger.error(error);
74-
await replay.stop(reason);
74+
await replay.stop({ reason });
7575

7676
const client = getCurrentHub().getClient();
7777

0 commit comments

Comments
 (0)