@@ -573,6 +573,7 @@ describe('Integration | errorSampleRate with delayed flush', () => {
573573
574574 it ( 'has correct timestamps when error occurs much later than initial pageload/checkout' , async ( ) => {
575575 const ELAPSED = BUFFER_CHECKOUT_TIME ;
576+ const TICK = 20 ;
576577 const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 3 } ;
577578 mockRecord . _emitter ( TEST_EVENT ) ;
578579
@@ -593,26 +594,29 @@ describe('Integration | errorSampleRate with delayed flush', () => {
593594 const optionsEvent = createOptionsEvent ( replay ) ;
594595
595596 jest . runAllTimers ( ) ;
596- jest . advanceTimersByTime ( 20 ) ;
597597 await new Promise ( process . nextTick ) ;
598598
599+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
600+
599601 captureException ( new Error ( 'testing' ) ) ;
600602
601603 await waitForBufferFlush ( ) ;
602604
603- expect ( replay . session ?. started ) . toBe ( BASE_TIMESTAMP + ELAPSED + 20 ) ;
605+ // See comments in `handleRecordingEmit.ts`, we perform a setTimeout into a
606+ // noop when it can be skipped altogether
607+ expect ( replay . session ?. started ) . toBe ( BASE_TIMESTAMP + ELAPSED + DEFAULT_FLUSH_MIN_DELAY + TICK + TICK ) ;
604608
605609 // Does not capture mouse click
606610 expect ( replay ) . toHaveSentReplay ( {
607611 recordingPayloadHeader : { segment_id : 0 } ,
608612 replayEventPayload : expect . objectContaining ( {
609613 // Make sure the old performance event is thrown out
610- replay_start_timestamp : ( BASE_TIMESTAMP + ELAPSED + 20 ) / 1000 ,
614+ replay_start_timestamp : ( BASE_TIMESTAMP + ELAPSED + TICK ) / 1000 ,
611615 } ) ,
612616 recordingData : JSON . stringify ( [
613617 {
614618 data : { isCheckout : true } ,
615- timestamp : BASE_TIMESTAMP + ELAPSED + 20 ,
619+ timestamp : BASE_TIMESTAMP + ELAPSED + TICK ,
616620 type : 2 ,
617621 } ,
618622 optionsEvent ,
@@ -662,7 +666,8 @@ describe('Integration | errorSampleRate with delayed flush', () => {
662666 expect ( replay . isEnabled ( ) ) . toBe ( false ) ;
663667 } ) ;
664668
665- it ( 'stops replay when session exceeds max length' , async ( ) => {
669+ it ( 'stops replay when session exceeds max length after latest captured error' , async ( ) => {
670+ const sessionId = replay . session ?. id ;
666671 jest . setSystemTime ( BASE_TIMESTAMP ) ;
667672
668673 const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 3 } ;
@@ -674,34 +679,120 @@ describe('Integration | errorSampleRate with delayed flush', () => {
674679 jest . runAllTimers ( ) ;
675680 await new Promise ( process . nextTick ) ;
676681
682+ jest . advanceTimersByTime ( 2 * MAX_SESSION_LIFE ) ;
683+
677684 captureException ( new Error ( 'testing' ) ) ;
678685
679- jest . advanceTimersByTime ( DEFAULT_FLUSH_MIN_DELAY ) ;
686+ // Flush due to exception
687+ await new Promise ( process . nextTick ) ;
688+ await waitForFlush ( ) ;
689+
690+ expect ( replay . session ?. id ) . toBe ( sessionId ) ;
691+ expect ( replay ) . toHaveLastSentReplay ( {
692+ recordingPayloadHeader : { segment_id : 0 } ,
693+ } ) ;
694+
695+ // This comes from `startRecording()` in `sendBufferedReplayOrFlush()`
696+ await waitForFlush ( ) ;
697+ expect ( replay ) . toHaveLastSentReplay ( {
698+ recordingPayloadHeader : { segment_id : 1 } ,
699+ recordingData : JSON . stringify ( [
700+ {
701+ data : {
702+ isCheckout : true ,
703+ } ,
704+ timestamp : BASE_TIMESTAMP + 2 * MAX_SESSION_LIFE + DEFAULT_FLUSH_MIN_DELAY + 40 ,
705+ type : 2 ,
706+ } ,
707+ ] ) ,
708+ } ) ;
709+
710+ // Now wait after session expires - should stop recording
711+ mockRecord . takeFullSnapshot . mockClear ( ) ;
712+ ( getCurrentHub ( ) . getClient ( ) ! . getTransport ( ) ! . send as unknown as jest . SpyInstance < any > ) . mockClear ( ) ;
713+
714+ jest . advanceTimersByTime ( MAX_SESSION_LIFE ) ;
715+ await new Promise ( process . nextTick ) ;
716+
717+ mockRecord . _emitter ( TEST_EVENT ) ;
718+ jest . runAllTimers ( ) ;
680719 await new Promise ( process . nextTick ) ;
681720
682721 expect ( replay ) . not . toHaveLastSentReplay ( ) ;
722+ expect ( mockRecord . takeFullSnapshot ) . toHaveBeenCalledTimes ( 0 ) ;
723+ expect ( replay . isEnabled ( ) ) . toBe ( false ) ;
724+
725+ // Once the session is stopped after capturing a replay already
726+ // (buffer-mode), another error will not trigger a new replay
727+ captureException ( new Error ( 'testing' ) ) ;
683728
684- // Wait a bit, shortly before session expires
685- jest . advanceTimersByTime ( MAX_SESSION_LIFE - 1000 ) ;
686729 await new Promise ( process . nextTick ) ;
730+ jest . advanceTimersByTime ( DEFAULT_FLUSH_MIN_DELAY ) ;
731+ await new Promise ( process . nextTick ) ;
732+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
733+ } ) ;
687734
735+ it ( 'does not stop replay based on earliest event in buffer' , async ( ) => {
736+ jest . setSystemTime ( BASE_TIMESTAMP ) ;
737+
738+ const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP - 60000 , type : 3 } ;
688739 mockRecord . _emitter ( TEST_EVENT ) ;
689- replay . triggerUserActivity ( ) ;
740+
741+ expect ( mockRecord . takeFullSnapshot ) . not . toHaveBeenCalled ( ) ;
742+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
743+
744+ jest . runAllTimers ( ) ;
745+ await new Promise ( process . nextTick ) ;
746+
747+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
748+ captureException ( new Error ( 'testing' ) ) ;
749+
750+ await waitForBufferFlush ( ) ;
690751
691752 expect ( replay ) . toHaveLastSentReplay ( ) ;
692753
754+ // Flush from calling `stopRecording`
755+ await waitForFlush ( ) ;
756+
693757 // Now wait after session expires - should stop recording
694758 mockRecord . takeFullSnapshot . mockClear ( ) ;
695759 ( getCurrentHub ( ) . getClient ( ) ! . getTransport ( ) ! . send as unknown as jest . SpyInstance < any > ) . mockClear ( ) ;
696760
697- jest . advanceTimersByTime ( 10_000 ) ;
761+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
762+
763+ const TICKS = 80 ;
764+
765+ // We advance time so that we are on the border of expiring, taking into
766+ // account that TEST_EVENT timestamp is 60000 ms before BASE_TIMESTAMP. The
767+ // 3 DEFAULT_FLUSH_MIN_DELAY is to account for the `waitForFlush` that has
768+ // happened, and for the next two that will happen. The first following
769+ // `waitForFlush` does not expire session, but the following one will.
770+ jest . advanceTimersByTime ( SESSION_IDLE_EXPIRE_DURATION - 60000 - 3 * DEFAULT_FLUSH_MIN_DELAY - TICKS ) ;
698771 await new Promise ( process . nextTick ) ;
699772
700773 mockRecord . _emitter ( TEST_EVENT ) ;
701- replay . triggerUserActivity ( ) ;
774+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
775+ await waitForFlush ( ) ;
702776
703- jest . advanceTimersByTime ( DEFAULT_FLUSH_MIN_DELAY ) ;
704- await new Promise ( process . nextTick ) ;
777+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
778+ expect ( mockRecord . takeFullSnapshot ) . toHaveBeenCalledTimes ( 0 ) ;
779+ expect ( replay . isEnabled ( ) ) . toBe ( true ) ;
780+
781+ mockRecord . _emitter ( TEST_EVENT ) ;
782+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
783+ await waitForFlush ( ) ;
784+
785+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
786+ expect ( mockRecord . takeFullSnapshot ) . toHaveBeenCalledTimes ( 0 ) ;
787+ expect ( replay . isEnabled ( ) ) . toBe ( true ) ;
788+
789+ // It's hard to test, but if we advance the below time less 1 ms, it should
790+ // be enabled, but we can't trigger a session check via flush without
791+ // incurring another DEFAULT_FLUSH_MIN_DELAY timeout.
792+ jest . advanceTimersByTime ( 60000 - DEFAULT_FLUSH_MIN_DELAY ) ;
793+ mockRecord . _emitter ( TEST_EVENT ) ;
794+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
795+ await waitForFlush ( ) ;
705796
706797 expect ( replay ) . not . toHaveLastSentReplay ( ) ;
707798 expect ( mockRecord . takeFullSnapshot ) . toHaveBeenCalledTimes ( 0 ) ;
0 commit comments