@@ -18,6 +18,8 @@ import io.sentry.SentryEvent
1818import io.sentry.SentryIntegrationPackageStorage
1919import io.sentry.SentryOptions
2020import io.sentry.SentryReplayEvent.ReplayType
21+ import io.sentry.SentryReplayOptions
22+ import io.sentry.TypeCheckHint
2123import io.sentry.android.replay.ReplayCache.Companion.ONGOING_SEGMENT
2224import io.sentry.android.replay.ReplayCache.Companion.SEGMENT_KEY_BIT_RATE
2325import io.sentry.android.replay.ReplayCache.Companion.SEGMENT_KEY_FRAME_RATE
@@ -63,6 +65,7 @@ import org.mockito.kotlin.anyOrNull
6365import org.mockito.kotlin.argThat
6466import org.mockito.kotlin.check
6567import org.mockito.kotlin.doAnswer
68+ import org.mockito.kotlin.doReturn
6669import org.mockito.kotlin.eq
6770import org.mockito.kotlin.mock
6871import org.mockito.kotlin.never
@@ -969,6 +972,106 @@ class ReplayIntegrationTest {
969972 assertFalse(replay.isDebugMaskingOverlayEnabled)
970973 }
971974
975+ @Test
976+ fun `snapshot observer is invoked with bitmap and metadata` () {
977+ var callbackInvoked = false
978+ var receivedTimestamp = 0L
979+ var receivedScreen: String? = null
980+ var receivedBitmap: Bitmap ? = null
981+
982+ val captureStrategy =
983+ mock<CaptureStrategy > {
984+ doAnswer {
985+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
986+ fixture.replayCache,
987+ 1720693523997 ,
988+ )
989+ }
990+ .whenever(mock)
991+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
992+ }
993+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
994+
995+ fixture.scopes.configureScope { it.screen = " MainActivity" }
996+ replay.register(fixture.scopes, fixture.options)
997+ replay.start()
998+
999+ fixture.options.sessionReplay.snapshotObserver =
1000+ SentryReplayOptions .ReplaySnapshotObserver { hint, frameTimestamp, screenName ->
1001+ callbackInvoked = true
1002+ receivedTimestamp = frameTimestamp
1003+ receivedScreen = screenName
1004+ receivedBitmap = hint.getAs(TypeCheckHint .REPLAY_FRAME_BITMAP , Bitmap ::class .java)
1005+ }
1006+
1007+ val copyBitmap = mock<Bitmap >()
1008+ val sourceBitmap =
1009+ mock<Bitmap > {
1010+ on { config } doReturn ARGB_8888
1011+ on { copy(any(), any()) } doReturn copyBitmap
1012+ }
1013+ replay.onScreenshotRecorded(sourceBitmap)
1014+
1015+ assertTrue(callbackInvoked)
1016+ assertEquals(1720693523997 , receivedTimestamp)
1017+ assertEquals(" MainActivity" , receivedScreen)
1018+ assertEquals(copyBitmap, receivedBitmap)
1019+ }
1020+
1021+ @Test
1022+ fun `snapshot observer exception does not prevent frame storage` () {
1023+ val captureStrategy =
1024+ mock<CaptureStrategy > {
1025+ doAnswer {
1026+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
1027+ fixture.replayCache,
1028+ 1720693523997 ,
1029+ )
1030+ }
1031+ .whenever(mock)
1032+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
1033+ }
1034+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
1035+
1036+ replay.register(fixture.scopes, fixture.options)
1037+ replay.start()
1038+
1039+ fixture.options.sessionReplay.snapshotObserver =
1040+ SentryReplayOptions .ReplaySnapshotObserver { _, _, _ -> throw RuntimeException (" test" ) }
1041+
1042+ val sourceBitmap =
1043+ mock<Bitmap > {
1044+ on { config } doReturn ARGB_8888
1045+ on { copy(any(), any()) } doReturn mock<Bitmap >()
1046+ }
1047+ replay.onScreenshotRecorded(sourceBitmap)
1048+
1049+ verify(fixture.replayCache).addFrame(any<Bitmap >(), any(), anyOrNull())
1050+ }
1051+
1052+ @Test
1053+ fun `snapshot observer is not invoked when null` () {
1054+ val captureStrategy =
1055+ mock<CaptureStrategy > {
1056+ doAnswer {
1057+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
1058+ fixture.replayCache,
1059+ 1720693523997 ,
1060+ )
1061+ }
1062+ .whenever(mock)
1063+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
1064+ }
1065+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
1066+
1067+ replay.register(fixture.scopes, fixture.options)
1068+ replay.start()
1069+
1070+ replay.onScreenshotRecorded(mock<Bitmap >())
1071+
1072+ verify(fixture.replayCache).addFrame(any<Bitmap >(), any(), anyOrNull())
1073+ }
1074+
9721075 private fun getSessionCaptureStrategy (options : SentryOptions ): SessionCaptureStrategy =
9731076 SessionCaptureStrategy (
9741077 options,
0 commit comments