@@ -573,6 +573,7 @@ describe('Integration | errorSampleRate with delayed flush', () => {
573
573
574
574
it ( 'has correct timestamps when error occurs much later than initial pageload/checkout' , async ( ) => {
575
575
const ELAPSED = BUFFER_CHECKOUT_TIME ;
576
+ const TICK = 20 ;
576
577
const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 3 } ;
577
578
mockRecord . _emitter ( TEST_EVENT ) ;
578
579
@@ -593,26 +594,29 @@ describe('Integration | errorSampleRate with delayed flush', () => {
593
594
const optionsEvent = createOptionsEvent ( replay ) ;
594
595
595
596
jest . runAllTimers ( ) ;
596
- jest . advanceTimersByTime ( 20 ) ;
597
597
await new Promise ( process . nextTick ) ;
598
598
599
+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
600
+
599
601
captureException ( new Error ( 'testing' ) ) ;
600
602
601
603
await waitForBufferFlush ( ) ;
602
604
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 ) ;
604
608
605
609
// Does not capture mouse click
606
610
expect ( replay ) . toHaveSentReplay ( {
607
611
recordingPayloadHeader : { segment_id : 0 } ,
608
612
replayEventPayload : expect . objectContaining ( {
609
613
// 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 ,
611
615
} ) ,
612
616
recordingData : JSON . stringify ( [
613
617
{
614
618
data : { isCheckout : true } ,
615
- timestamp : BASE_TIMESTAMP + ELAPSED + 20 ,
619
+ timestamp : BASE_TIMESTAMP + ELAPSED + TICK ,
616
620
type : 2 ,
617
621
} ,
618
622
optionsEvent ,
@@ -662,7 +666,8 @@ describe('Integration | errorSampleRate with delayed flush', () => {
662
666
expect ( replay . isEnabled ( ) ) . toBe ( false ) ;
663
667
} ) ;
664
668
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 ;
666
671
jest . setSystemTime ( BASE_TIMESTAMP ) ;
667
672
668
673
const TEST_EVENT = { data : { } , timestamp : BASE_TIMESTAMP , type : 3 } ;
@@ -674,34 +679,120 @@ describe('Integration | errorSampleRate with delayed flush', () => {
674
679
jest . runAllTimers ( ) ;
675
680
await new Promise ( process . nextTick ) ;
676
681
682
+ jest . advanceTimersByTime ( 2 * MAX_SESSION_LIFE ) ;
683
+
677
684
captureException ( new Error ( 'testing' ) ) ;
678
685
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 ( ) ;
680
719
await new Promise ( process . nextTick ) ;
681
720
682
721
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' ) ) ;
683
728
684
- // Wait a bit, shortly before session expires
685
- jest . advanceTimersByTime ( MAX_SESSION_LIFE - 1000 ) ;
686
729
await new Promise ( process . nextTick ) ;
730
+ jest . advanceTimersByTime ( DEFAULT_FLUSH_MIN_DELAY ) ;
731
+ await new Promise ( process . nextTick ) ;
732
+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
733
+ } ) ;
687
734
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 } ;
688
739
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 ( ) ;
690
751
691
752
expect ( replay ) . toHaveLastSentReplay ( ) ;
692
753
754
+ // Flush from calling `stopRecording`
755
+ await waitForFlush ( ) ;
756
+
693
757
// Now wait after session expires - should stop recording
694
758
mockRecord . takeFullSnapshot . mockClear ( ) ;
695
759
( getCurrentHub ( ) . getClient ( ) ! . getTransport ( ) ! . send as unknown as jest . SpyInstance < any > ) . mockClear ( ) ;
696
760
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 ) ;
698
771
await new Promise ( process . nextTick ) ;
699
772
700
773
mockRecord . _emitter ( TEST_EVENT ) ;
701
- replay . triggerUserActivity ( ) ;
774
+ expect ( replay ) . not . toHaveLastSentReplay ( ) ;
775
+ await waitForFlush ( ) ;
702
776
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 ( ) ;
705
796
706
797
expect ( replay ) . not . toHaveLastSentReplay ( ) ;
707
798
expect ( mockRecord . takeFullSnapshot ) . toHaveBeenCalledTimes ( 0 ) ;
0 commit comments