From 88781b19ea5fc8361c25608d3b86de04939a0c0d Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 30 May 2023 09:20:56 -0400 Subject: [PATCH 1/3] fix(replay): Ignore max session life for buffered sessions Theres an edge case where a buffered session becomes expired, an error comes in, the link from error to replay is lost because a new session is created due to the session being expired. We should either ignore the max session life for a buffered session, or possibly check/refresh session when an error comes in. --- packages/replay/src/replay.ts | 11 ++- packages/replay/src/session/getSession.ts | 8 ++- .../errorSampleRate-delayFlush.test.ts | 52 +++++++++----- .../test/integration/errorSampleRate.test.ts | 67 ++++++++++++++----- .../test/unit/session/getSession.test.ts | 59 ++++++++++++++++ 5 files changed, 159 insertions(+), 38 deletions(-) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index 53f263b5fa3f..f20006c8f79c 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -417,6 +417,15 @@ export class ReplayContainer implements ReplayContainerInterface { // Once this session ends, we do not want to refresh it if (this.session) { this.session.shouldRefresh = false; + + // It's possible that the session lifespan is > max session lifespan + // because we have been buffering (which ignores expiration given that + // `shouldRefresh` is true). Since we flip `shouldRefresh`, the session + // could be considered expired due to lifespan. Update session start date + // to the earliest event in buffer, or current timestamp. + this._updateUserActivity(); + this._updateSessionActivity(); + this.session.started = Date.now(); this._maybeSaveSession(); } @@ -657,7 +666,7 @@ export class ReplayContainer implements ReplayContainerInterface { stickySession: Boolean(this._options.stickySession), currentSession: this.session, sessionSampleRate: this._options.sessionSampleRate, - allowBuffering: this._options.errorSampleRate > 0, + allowBuffering: this._options.errorSampleRate > 0 || this.recordingMode === 'buffer', }); // If session was newly created (i.e. was not loaded from storage), then diff --git a/packages/replay/src/session/getSession.ts b/packages/replay/src/session/getSession.ts index ff993887e64b..73554a8860de 100644 --- a/packages/replay/src/session/getSession.ts +++ b/packages/replay/src/session/getSession.ts @@ -34,11 +34,13 @@ export function getSession({ // within "max session time"). const isExpired = isSessionExpired(session, timeouts); - if (!isExpired) { + if (!isExpired || (allowBuffering && session.shouldRefresh)) { return { type: 'saved', session }; } else if (!session.shouldRefresh) { - // In this case, stop - // This is the case if we have an error session that is completed (=triggered an error) + // This is the case if we have an error session that is completed + // (=triggered an error). Session will continue as session-based replay, + // and when this session is expired, it will not be renewed until user + // reloads. const discardedSession = makeSession({ sampled: false }); return { type: 'new', session: discardedSession }; } else { diff --git a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts index 20645b1b85a4..304f5454d28f 100644 --- a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts +++ b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts @@ -600,7 +600,7 @@ describe('Integration | errorSampleRate with delayed flush', () => { await waitForBufferFlush(); - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 20); + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + DEFAULT_FLUSH_MIN_DELAY + 80); // Does not capture mouse click expect(replay).toHaveSentReplay({ @@ -662,7 +662,8 @@ describe('Integration | errorSampleRate with delayed flush', () => { expect(replay.isEnabled()).toBe(false); }); - it('stops replay when session exceeds max length', async () => { + it('stops replay when session exceeds max length after latest captured error', async () => { + const sessionId = replay.session?.id; jest.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; @@ -674,38 +675,57 @@ describe('Integration | errorSampleRate with delayed flush', () => { jest.runAllTimers(); await new Promise(process.nextTick); - captureException(new Error('testing')); + jest.advanceTimersByTime(2 * MAX_SESSION_LIFE); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); - await new Promise(process.nextTick); - - expect(replay).not.toHaveLastSentReplay(); + captureException(new Error('testing')); - // Wait a bit, shortly before session expires - jest.advanceTimersByTime(MAX_SESSION_LIFE - 1000); + // Flush due to exception await new Promise(process.nextTick); + await waitForFlush(); - mockRecord._emitter(TEST_EVENT); - replay.triggerUserActivity(); + expect(replay.session?.id).toBe(sessionId); + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 0 }, + }); - expect(replay).toHaveLastSentReplay(); + // This comes from `startRecording()` in `sendBufferedReplayOrFlush()` + await waitForFlush(); + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 1 }, + recordingData: JSON.stringify([ + { + data: { + isCheckout: true, + }, + timestamp: BASE_TIMESTAMP + 2 * MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 40, + type: 2, + }, + ]), + }); // Now wait after session expires - should stop recording mockRecord.takeFullSnapshot.mockClear(); (getCurrentHub().getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); - jest.advanceTimersByTime(10_000); + jest.advanceTimersByTime(MAX_SESSION_LIFE); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); - replay.triggerUserActivity(); - - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + jest.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); expect(replay.isEnabled()).toBe(false); + + // Once the session is stopped after capturing a replay already + // (buffer-mode), another error will not trigger a new replay + captureException(new Error('testing')); + + await new Promise(process.nextTick); + jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await new Promise(process.nextTick); + expect(replay).not.toHaveLastSentReplay(); }); }); diff --git a/packages/replay/test/integration/errorSampleRate.test.ts b/packages/replay/test/integration/errorSampleRate.test.ts index 3145ba37e7f9..178a9cfe9072 100644 --- a/packages/replay/test/integration/errorSampleRate.test.ts +++ b/packages/replay/test/integration/errorSampleRate.test.ts @@ -432,6 +432,9 @@ describe('Integration | errorSampleRate', () => { ['MAX_SESSION_LIFE', MAX_SESSION_LIFE], ['SESSION_IDLE_EXPIRE_DURATION', SESSION_IDLE_EXPIRE_DURATION], ])('continues buffering replay if session had no error and exceeds %s', async (_label, waitTime) => { + const oldSessionId = replay.session?.id; + expect(oldSessionId).toBeDefined(); + expect(replay).not.toHaveLastSentReplay(); // Idle for given time @@ -475,13 +478,24 @@ describe('Integration | errorSampleRate', () => { jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); - expect(replay).toHaveLastSentReplay({ + expect(replay.session?.id).toBe(oldSessionId); + + // Flush of buffered events + expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ replay_type: 'buffer', }), }); + // Checkout from `startRecording` + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 1 }, + replayEventPayload: expect.objectContaining({ + replay_type: 'buffer', + }), + }); + expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); expect(replay.recordingMode).toBe('session'); @@ -491,6 +505,9 @@ describe('Integration | errorSampleRate', () => { // Should behave the same as above test it('stops replay if user has been idle for more than SESSION_IDLE_EXPIRE_DURATION and does not start a new session thereafter', async () => { + const oldSessionId = replay.session?.id; + expect(oldSessionId).toBeDefined(); + // Idle for 15 minutes jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION + 1); @@ -517,14 +534,24 @@ describe('Integration | errorSampleRate', () => { await new Promise(process.nextTick); jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); + expect(replay.session?.id).toBe(oldSessionId); - expect(replay).toHaveLastSentReplay({ + // buffered events + expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ replay_type: 'buffer', }), }); + // `startRecording` full checkout + expect(replay).toHaveLastSentReplay({ + recordingPayloadHeader: { segment_id: 1 }, + replayEventPayload: expect.objectContaining({ + replay_type: 'buffer', + }), + }); + expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); expect(replay.recordingMode).toBe('session'); @@ -605,7 +632,7 @@ describe('Integration | errorSampleRate', () => { jest.advanceTimersByTime(20); await new Promise(process.nextTick); - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 20); + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 100); // Does not capture mouse click expect(replay).toHaveSentReplay({ @@ -667,7 +694,8 @@ describe('Integration | errorSampleRate', () => { expect(replay.isEnabled()).toBe(false); }); - it('stops replay when session exceeds max length', async () => { + it('stops replay when session exceeds max length after latest captured error', async () => { + const sessionId = replay.session?.id; jest.setSystemTime(BASE_TIMESTAMP); const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; @@ -679,37 +707,40 @@ describe('Integration | errorSampleRate', () => { jest.runAllTimers(); await new Promise(process.nextTick); + jest.advanceTimersByTime(2 * MAX_SESSION_LIFE); + captureException(new Error('testing')); - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + // Flush due to exception await new Promise(process.nextTick); - expect(replay).not.toHaveLastSentReplay(); - - // Wait a bit, shortly before session expires - jest.advanceTimersByTime(MAX_SESSION_LIFE - 1000); + jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); await new Promise(process.nextTick); - - mockRecord._emitter(TEST_EVENT); - replay.triggerUserActivity(); - + expect(replay.session?.id).toBe(sessionId); expect(replay).toHaveLastSentReplay(); - // Now wait after session expires - should stop recording + // Now wait after session expires - should re-start into buffering mode mockRecord.takeFullSnapshot.mockClear(); (getCurrentHub().getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); - jest.advanceTimersByTime(10_000); + jest.advanceTimersByTime(MAX_SESSION_LIFE); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); - replay.triggerUserActivity(); - - jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + jest.runAllTimers(); await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); expect(replay.isEnabled()).toBe(false); + + // Once the session is stopped after capturing a replay already + // (buffer-mode), another error should trigger a new replay + captureException(new Error('testing')); + + await new Promise(process.nextTick); + jest.advanceTimersByTime(DEFAULT_FLUSH_MIN_DELAY); + await new Promise(process.nextTick); + expect(replay).not.toHaveLastSentReplay(); }); }); diff --git a/packages/replay/test/unit/session/getSession.test.ts b/packages/replay/test/unit/session/getSession.test.ts index 2905e1bd72d6..aa3110d114f2 100644 --- a/packages/replay/test/unit/session/getSession.test.ts +++ b/packages/replay/test/unit/session/getSession.test.ts @@ -229,4 +229,63 @@ describe('Unit | session | getSession', () => { expect(session.id).toBe('test_session_uuid_2'); expect(session.segmentId).toBe(0); }); + + it('re-uses the same "buffer" session if it is expired and has never sent a buffered replay', function () { + const { type, session } = getSession({ + timeouts: { + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, + stickySession: false, + ...SAMPLE_OPTIONS, + currentSession: makeSession({ + id: 'test_session_uuid_2', + lastActivity: +new Date() - MAX_SESSION_LIFE - 1, + started: +new Date() - MAX_SESSION_LIFE - 1, + segmentId: 0, + sampled: 'buffer', + }), + allowBuffering: true, + }); + + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).not.toHaveBeenCalled(); + + expect(type).toBe('saved'); + expect(session.id).toBe('test_session_uuid_2'); + expect(session.sampled).toBe('buffer'); + expect(session.segmentId).toBe(0); + }); + + it('creates a new session if it is expired and it was a "buffer" session that has sent a replay', function () { + const currentSession = makeSession({ + id: 'test_session_uuid_2', + lastActivity: +new Date() - MAX_SESSION_LIFE - 1, + started: +new Date() - MAX_SESSION_LIFE - 1, + segmentId: 0, + sampled: 'buffer', + }); + currentSession.shouldRefresh = false; + + const { type, session } = getSession({ + timeouts: { + sessionIdlePause: SESSION_IDLE_PAUSE_DURATION, + sessionIdleExpire: 1000, + maxSessionLife: MAX_SESSION_LIFE, + }, + stickySession: false, + ...SAMPLE_OPTIONS, + currentSession, + allowBuffering: true, + }); + + expect(FetchSession.fetchSession).not.toHaveBeenCalled(); + expect(CreateSession.createSession).not.toHaveBeenCalled(); + + expect(type).toBe('new'); + expect(session.id).not.toBe('test_session_uuid_2'); + expect(session.sampled).toBe(false); + expect(session.segmentId).toBe(0); + }); }); From 13cfd6d158360f9c030e7a961907485d4426944e Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 1 Jun 2023 10:20:40 -0400 Subject: [PATCH 2/3] add/update tests + comments --- packages/replay/src/replay.ts | 19 +++-- .../replay/src/util/handleRecordingEmit.ts | 4 + .../errorSampleRate-delayFlush.test.ts | 79 ++++++++++++++++++- .../test/integration/errorSampleRate.test.ts | 4 +- 4 files changed, 92 insertions(+), 14 deletions(-) diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index f20006c8f79c..1d16b0f39233 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -396,6 +396,8 @@ export class ReplayContainer implements ReplayContainerInterface { return this.flushImmediate(); } + const activityTime = Date.now(); + // Allow flush to complete before resuming as a session recording, otherwise // the checkout from `startRecording` may be included in the payload. // Prefer to keep the error replay as a separate (and smaller) segment @@ -419,13 +421,16 @@ export class ReplayContainer implements ReplayContainerInterface { this.session.shouldRefresh = false; // It's possible that the session lifespan is > max session lifespan - // because we have been buffering (which ignores expiration given that - // `shouldRefresh` is true). Since we flip `shouldRefresh`, the session - // could be considered expired due to lifespan. Update session start date - // to the earliest event in buffer, or current timestamp. - this._updateUserActivity(); - this._updateSessionActivity(); - this.session.started = Date.now(); + // because we have been buffering beyond max session lifespan (we ignore + // expiration given that `shouldRefresh` is true). Since we flip + // `shouldRefresh`, the session could be considered expired due to + // lifespan, which is not what we want. Update session start date to be + // the current timestamp, so that session is not considered to be + // expired. This means that max replay duration can be MAX_SESSION_LIFE + + // (length of buffer), which we are ok with. + this._updateUserActivity(activityTime); + this._updateSessionActivity(activityTime); + this.session.started = activityTime; this._maybeSaveSession(); } diff --git a/packages/replay/src/util/handleRecordingEmit.ts b/packages/replay/src/util/handleRecordingEmit.ts index 3a9dcc211edd..d2e8b30283d1 100644 --- a/packages/replay/src/util/handleRecordingEmit.ts +++ b/packages/replay/src/util/handleRecordingEmit.ts @@ -89,6 +89,10 @@ export function getHandleRecordingEmit(replay: ReplayContainer): RecordingEmitCa // a previous session ID. In this case, we want to buffer events // for a set amount of time before flushing. This can help avoid // capturing replays of users that immediately close the window. + // TODO: We should check `recordingMode` here and do nothing if it's + // buffer, instead of checking inside of timeout, this will make our + // tests a bit cleaner as we will need to wait on the delay in order to + // do nothing. setTimeout(() => replay.conditionalFlush(), options._experiments.delayFlushOnCheckout); // Cancel any previously debounced flushes to ensure there are no [near] diff --git a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts index 304f5454d28f..dd6bb03f8467 100644 --- a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts +++ b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts @@ -573,6 +573,7 @@ describe('Integration | errorSampleRate with delayed flush', () => { it('has correct timestamps when error occurs much later than initial pageload/checkout', async () => { const ELAPSED = BUFFER_CHECKOUT_TIME; + const TICK = 20; const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP, type: 3 }; mockRecord._emitter(TEST_EVENT); @@ -593,26 +594,29 @@ describe('Integration | errorSampleRate with delayed flush', () => { const optionsEvent = createOptionsEvent(replay); jest.runAllTimers(); - jest.advanceTimersByTime(20); await new Promise(process.nextTick); + expect(replay).not.toHaveLastSentReplay(); + captureException(new Error('testing')); await waitForBufferFlush(); - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + DEFAULT_FLUSH_MIN_DELAY + 80); + // See comments in `handleRecordingEmit.ts`, we perform a setTimeout into a + // noop when it can be skipped altogether + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + DEFAULT_FLUSH_MIN_DELAY + TICK + TICK); // Does not capture mouse click expect(replay).toHaveSentReplay({ recordingPayloadHeader: { segment_id: 0 }, replayEventPayload: expect.objectContaining({ // Make sure the old performance event is thrown out - replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + 20) / 1000, + replay_start_timestamp: (BASE_TIMESTAMP + ELAPSED + TICK) / 1000, }), recordingData: JSON.stringify([ { data: { isCheckout: true }, - timestamp: BASE_TIMESTAMP + ELAPSED + 20, + timestamp: BASE_TIMESTAMP + ELAPSED + TICK, type: 2, }, optionsEvent, @@ -727,6 +731,73 @@ describe('Integration | errorSampleRate with delayed flush', () => { await new Promise(process.nextTick); expect(replay).not.toHaveLastSentReplay(); }); + + it('does not stop replay based on earliest event in buffer', async () => { + jest.setSystemTime(BASE_TIMESTAMP); + + const TEST_EVENT = { data: {}, timestamp: BASE_TIMESTAMP - 60000, type: 3 }; + mockRecord._emitter(TEST_EVENT); + + expect(mockRecord.takeFullSnapshot).not.toHaveBeenCalled(); + expect(replay).not.toHaveLastSentReplay(); + + jest.runAllTimers(); + await new Promise(process.nextTick); + + expect(replay).not.toHaveLastSentReplay(); + captureException(new Error('testing')); + + await waitForBufferFlush(); + + expect(replay).toHaveLastSentReplay(); + + // Flush from calling `stopRecording` + await waitForFlush(); + + // Now wait after session expires - should stop recording + mockRecord.takeFullSnapshot.mockClear(); + (getCurrentHub().getClient()!.getTransport()!.send as unknown as jest.SpyInstance).mockClear(); + + expect(replay).not.toHaveLastSentReplay(); + + const TICKS = 80; + + // We advance time so that we are on the border of expiring, taking into + // account that TEST_EVENT timestamp is 60000 ms before BASE_TIMESTAMP. The + // 3 DEFAULT_FLUSH_MIN_DELAY is to account for the `waitForFlush` that has + // happened, and for the next two that will happen. The first following + // `waitForFlush` does not expire session, but the following one will. + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 60000 - (3 * DEFAULT_FLUSH_MIN_DELAY) - TICKS); + await new Promise(process.nextTick); + + mockRecord._emitter(TEST_EVENT); + expect(replay).not.toHaveLastSentReplay(); + await waitForFlush(); + + expect(replay).not.toHaveLastSentReplay(); + expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); + expect(replay.isEnabled()).toBe(true); + + mockRecord._emitter(TEST_EVENT); + expect(replay).not.toHaveLastSentReplay(); + await waitForFlush(); + + expect(replay).not.toHaveLastSentReplay(); + expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); + expect(replay.isEnabled()).toBe(true); + + // It's hard to test, but if we advance the below time less 1 ms, it should + // be enabled, but we can't trigger a session check via flush without + // incurring another DEFAULT_FLUSH_MIN_DELAY timeout. + jest.advanceTimersByTime(60000 - DEFAULT_FLUSH_MIN_DELAY) + mockRecord._emitter(TEST_EVENT); + expect(replay).not.toHaveLastSentReplay(); + await waitForFlush(); + + expect(replay).not.toHaveLastSentReplay(); + expect(mockRecord.takeFullSnapshot).toHaveBeenCalledTimes(0); + expect(replay.isEnabled()).toBe(false); + }); }); /** diff --git a/packages/replay/test/integration/errorSampleRate.test.ts b/packages/replay/test/integration/errorSampleRate.test.ts index 178a9cfe9072..ea1825dd8429 100644 --- a/packages/replay/test/integration/errorSampleRate.test.ts +++ b/packages/replay/test/integration/errorSampleRate.test.ts @@ -622,17 +622,15 @@ describe('Integration | errorSampleRate', () => { const optionsEvent = createOptionsEvent(replay); jest.runAllTimers(); - jest.advanceTimersByTime(20); await new Promise(process.nextTick); captureException(new Error('testing')); await new Promise(process.nextTick); jest.runAllTimers(); - jest.advanceTimersByTime(20); await new Promise(process.nextTick); - expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 100); + expect(replay.session?.started).toBe(BASE_TIMESTAMP + ELAPSED + 40); // Does not capture mouse click expect(replay).toHaveSentReplay({ From e6876bcd962655e4d6b1d8320fd47f9e0bd9ca3a Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 1 Jun 2023 15:24:30 -0400 Subject: [PATCH 3/3] lint --- .../test/integration/errorSampleRate-delayFlush.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts index dd6bb03f8467..f691d8e953c1 100644 --- a/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts +++ b/packages/replay/test/integration/errorSampleRate-delayFlush.test.ts @@ -767,7 +767,7 @@ describe('Integration | errorSampleRate with delayed flush', () => { // 3 DEFAULT_FLUSH_MIN_DELAY is to account for the `waitForFlush` that has // happened, and for the next two that will happen. The first following // `waitForFlush` does not expire session, but the following one will. - jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 60000 - (3 * DEFAULT_FLUSH_MIN_DELAY) - TICKS); + jest.advanceTimersByTime(SESSION_IDLE_EXPIRE_DURATION - 60000 - 3 * DEFAULT_FLUSH_MIN_DELAY - TICKS); await new Promise(process.nextTick); mockRecord._emitter(TEST_EVENT); @@ -789,7 +789,7 @@ describe('Integration | errorSampleRate with delayed flush', () => { // It's hard to test, but if we advance the below time less 1 ms, it should // be enabled, but we can't trigger a session check via flush without // incurring another DEFAULT_FLUSH_MIN_DELAY timeout. - jest.advanceTimersByTime(60000 - DEFAULT_FLUSH_MIN_DELAY) + jest.advanceTimersByTime(60000 - DEFAULT_FLUSH_MIN_DELAY); mockRecord._emitter(TEST_EVENT); expect(replay).not.toHaveLastSentReplay(); await waitForFlush();