@@ -63,6 +63,7 @@ import org.mockito.kotlin.anyOrNull
6363import org.mockito.kotlin.argThat
6464import org.mockito.kotlin.check
6565import org.mockito.kotlin.doAnswer
66+ import org.mockito.kotlin.doReturn
6667import org.mockito.kotlin.eq
6768import org.mockito.kotlin.mock
6869import org.mockito.kotlin.never
@@ -969,6 +970,104 @@ class ReplayIntegrationTest {
969970 assertFalse(replay.isDebugMaskingOverlayEnabled)
970971 }
971972
973+ @Test
974+ fun `snapshot observer is invoked with bitmap and metadata` () {
975+ var callbackInvoked = false
976+ var receivedTimestamp = 0L
977+ var receivedScreen: String? = null
978+ var receivedBitmap: Bitmap ? = null
979+
980+ val captureStrategy =
981+ mock<CaptureStrategy > {
982+ doAnswer {
983+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
984+ fixture.replayCache,
985+ 1720693523997 ,
986+ )
987+ }
988+ .whenever(mock)
989+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
990+ }
991+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
992+
993+ fixture.scopes.configureScope { it.screen = " MainActivity" }
994+ replay.register(fixture.scopes, fixture.options)
995+ replay.start()
996+
997+ replay.snapshotObserver = ReplaySnapshotObserver { bitmap, frameTimestamp, screenName ->
998+ callbackInvoked = true
999+ receivedTimestamp = frameTimestamp
1000+ receivedScreen = screenName
1001+ receivedBitmap = bitmap
1002+ }
1003+
1004+ val copyBitmap = mock<Bitmap >()
1005+ val sourceBitmap =
1006+ mock<Bitmap > {
1007+ on { config } doReturn ARGB_8888
1008+ on { copy(any(), any()) } doReturn copyBitmap
1009+ }
1010+ replay.onScreenshotRecorded(sourceBitmap)
1011+
1012+ assertTrue(callbackInvoked)
1013+ assertEquals(1720693523997 , receivedTimestamp)
1014+ assertEquals(" MainActivity" , receivedScreen)
1015+ assertEquals(copyBitmap, receivedBitmap)
1016+ }
1017+
1018+ @Test
1019+ fun `snapshot observer exception does not prevent frame storage` () {
1020+ val captureStrategy =
1021+ mock<CaptureStrategy > {
1022+ doAnswer {
1023+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
1024+ fixture.replayCache,
1025+ 1720693523997 ,
1026+ )
1027+ }
1028+ .whenever(mock)
1029+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
1030+ }
1031+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
1032+
1033+ replay.register(fixture.scopes, fixture.options)
1034+ replay.start()
1035+
1036+ replay.snapshotObserver = ReplaySnapshotObserver { _, _, _ -> throw RuntimeException (" test" ) }
1037+
1038+ val sourceBitmap =
1039+ mock<Bitmap > {
1040+ on { config } doReturn ARGB_8888
1041+ on { copy(any(), any()) } doReturn mock<Bitmap >()
1042+ }
1043+ replay.onScreenshotRecorded(sourceBitmap)
1044+
1045+ verify(fixture.replayCache).addFrame(any<Bitmap >(), any(), anyOrNull())
1046+ }
1047+
1048+ @Test
1049+ fun `snapshot observer is not invoked when null` () {
1050+ val captureStrategy =
1051+ mock<CaptureStrategy > {
1052+ doAnswer {
1053+ ((it.arguments[1 ] as ReplayCache .(frameTimestamp: Long ) -> Unit )).invoke(
1054+ fixture.replayCache,
1055+ 1720693523997 ,
1056+ )
1057+ }
1058+ .whenever(mock)
1059+ .onScreenshotRecorded(anyOrNull<Bitmap >(), any())
1060+ }
1061+ val replay = fixture.getSut(context, replayCaptureStrategyProvider = { captureStrategy })
1062+
1063+ replay.register(fixture.scopes, fixture.options)
1064+ replay.start()
1065+
1066+ replay.onScreenshotRecorded(mock<Bitmap >())
1067+
1068+ verify(fixture.replayCache).addFrame(any<Bitmap >(), any(), anyOrNull())
1069+ }
1070+
9721071 private fun getSessionCaptureStrategy (options : SentryOptions ): SessionCaptureStrategy =
9731072 SessionCaptureStrategy (
9741073 options,
0 commit comments