From c0f879a6bafb4cd7f12d74967fdccea169e31ff4 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 11:16:30 +0400 Subject: [PATCH 01/12] Improved the screen recorder for creating than 3 minutes videos --- .../marathon/extension/StringExtensions.kt | 4 ++ .../marathon/android/RemoteFileManager.kt | 2 +- .../android/adam/AdamAndroidDevice.kt | 26 ++++++++- .../video/ScreenRecorderTestBatchListener.kt | 41 +++++++++++-- .../extension/ConfigurationExtensions.kt | 5 +- .../marathon/android/RemoteFileManagerTest.kt | 2 +- .../android/VideoConfigurationTest.kt | 58 +++++++++++++++++++ 7 files changed, 127 insertions(+), 11 deletions(-) diff --git a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt index a8be25c84..8b188384a 100644 --- a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt +++ b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt @@ -24,3 +24,7 @@ fun String.escape(): String { } val escapeRegex = "[^a-zA-Z0-9\\.\\#]".toRegex() + +fun String.addFileNumberForVideo(fileNumber: String): String { + return with(this.split(".mp4")) {return@with "${this[0]}$fileNumber.mp4"} +} diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt index 22459c8d0..af4720da0 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt @@ -44,7 +44,7 @@ class RemoteFileManager(private val device: AndroidDevice) { } companion object { - const val MAX_FILENAME = 255 + const val MAX_FILENAME = 254 //we need 1 more char for fileNumber in case of using video attachment > 180 && apiLevel < 34 const val TMP_PATH = "/data/local/tmp" } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt index 4eb5375c8..b8bbb9575 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt @@ -54,6 +54,7 @@ import com.malinskiy.marathon.device.NetworkState import com.malinskiy.marathon.device.file.measureFileTransfer import com.malinskiy.marathon.exceptions.DeviceLostException import com.malinskiy.marathon.execution.TestBatchResults +import com.malinskiy.marathon.extension.addFileNumberForVideo import com.malinskiy.marathon.extension.escape import com.malinskiy.marathon.extension.withTimeout import com.malinskiy.marathon.extension.withTimeoutOrNull @@ -74,6 +75,7 @@ import java.awt.image.BufferedImage import java.io.File import java.time.Duration import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.TimeUnit import kotlin.coroutines.CoroutineContext import com.malinskiy.marathon.android.model.ShellCommandResult as MarathonShellCommandResult @@ -372,7 +374,27 @@ class AdamAndroidDevice( remoteFilePath: String, options: VideoConfiguration ) { - val screenRecorderCommand = options.toScreenRecorderCommand(remoteFilePath) + var secondsRemaining = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits) + if(secondsRemaining <= 180 || apiLevel >= 34) { + startScreenRecorder(remoteFilePath, options) + } else { + var recordsCount = 0 + while(recordsCount == 0 || secondsRemaining>=180) { + startScreenRecorder(remoteFilePath, options, recordsCount.toString()) { + secondsRemaining -= 180 + recordsCount++ + } + } + } + } + + private suspend fun startScreenRecorder( + remoteFilePath: String, + options: VideoConfiguration, + fileNumber: String = "", + finallyBlock: (() -> Unit)? = null + ) { + val screenRecorderCommand = options.toScreenRecorderCommand(remoteFilePath.addFileNumberForVideo(fileNumber), this) try { withTimeoutOrNull(androidConfiguration.timeoutConfiguration.screenrecorder) { val result = client.execute(ShellCommandRequest(screenRecorderCommand), serial = adbSerial) @@ -388,6 +410,8 @@ class AdamAndroidDevice( logger.warn(e) { "screenrecord start was interrupted" } } catch (e: Exception) { logger.error("Unable to start screenrecord", e) + } finally { + finallyBlock?.invoke() } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 709633193..cb5c3ae35 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -10,6 +10,7 @@ import com.malinskiy.marathon.device.DevicePoolId import com.malinskiy.marathon.device.toDeviceInfo import com.malinskiy.marathon.execution.Attachment import com.malinskiy.marathon.execution.AttachmentType +import com.malinskiy.marathon.extension.addFileNumberForVideo import com.malinskiy.marathon.extension.withTimeoutOrNull import com.malinskiy.marathon.io.FileManager import com.malinskiy.marathon.io.FileType @@ -22,6 +23,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.cancelAndJoin +import java.io.File import java.time.Duration import kotlin.system.measureTimeMillis @@ -122,29 +124,56 @@ class ScreenRecorderTestBatchListener( } private suspend fun pullTestVideo(test: Test) { - val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) + val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { - device.safePullFile(remoteFilePath, localVideoFile.toString()) + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) + device.safePullFile(remoteFilePath, localVideoFile.toString()) + localVideoFiles.add(localVideoFile) + } else { + for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId, i.toString()) + device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) + localVideoFiles.add(localVideoFile) + } + } } logger.trace { "Pulling finished in ${millis}ms $remoteFilePath " } - attachmentListeners.forEach { it.onAttachment(test, Attachment(localVideoFile, AttachmentType.VIDEO)) } + attachmentListeners.forEach { + localVideoFiles.forEach { localVideoFile -> + it.onAttachment(test, Attachment(localVideoFile, AttachmentType.VIDEO)) + } + } } /** * This can be called both when test times out and device unavailable */ private suspend fun pullLastBatchVideo(remoteFilePath: String) { - val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) val millis = measureTimeMillis { - device.safePullFile(remoteFilePath, localVideoFile.toString()) + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) + device.safePullFile(remoteFilePath, localVideoFile.toString()) + } else { + for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId, id = i.toString()) + device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) + } + } } logger.trace { "Pulling finished in ${millis}ms $remoteFilePath " } } private suspend fun removeRemoteVideo(remoteFilePath: String) { val millis = measureTimeMillis { - device.fileManager.removeRemotePath(remoteFilePath) + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + device.fileManager.removeRemotePath(remoteFilePath) + } else { + for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i.toString())) + } + } } logger.trace { "Removed file in ${millis}ms $remoteFilePath" } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt index a55a0f5cf..bdd821472 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt @@ -1,12 +1,13 @@ package com.malinskiy.marathon.android.extension +import com.malinskiy.marathon.android.AndroidDevice import com.malinskiy.marathon.android.model.AndroidTestBundle import com.malinskiy.marathon.config.vendor.VendorConfiguration import com.malinskiy.marathon.config.vendor.android.AndroidTestBundleConfiguration import com.malinskiy.marathon.config.vendor.android.VideoConfiguration import java.util.concurrent.TimeUnit -fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String): String { +fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String, device: AndroidDevice? = null): String { val sb = StringBuilder() sb.append("screenrecord") @@ -36,7 +37,7 @@ fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String): String { if (timeLimit > 0) { sb.append("--time-limit ") var seconds = TimeUnit.SECONDS.convert(timeLimit, timeLimitUnits) - if (seconds > 180) { + if (seconds > 180 && ((device?.apiLevel ?: 0) < 34)) { seconds = 180 } sb.append(seconds) diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt index dba6eeb75..f913b1b7a 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt @@ -176,7 +176,7 @@ class RemoteFileManagerTest { emptyList() ), "batch-id" ) - assertThat(actual).isEqualTo("/sdcard/pkg.clazz-testWithAVeryLongNameThatExceeds255CharactersqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERT-batch-id.mp4") + assertThat(actual).isEqualTo("/sdcard/pkg.clazz-testWithAVeryLongNameThatExceeds255CharactersqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWER-batch-id.mp4") } } } diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt index d85223b80..6fd630e93 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt @@ -2,11 +2,34 @@ package com.malinskiy.marathon.android import assertk.assertThat import assertk.assertions.isEqualTo +import com.malinskiy.adam.AndroidDebugBridgeClient +import com.malinskiy.adam.server.junit5.AdbClient +import com.malinskiy.adam.server.junit5.AdbServer +import com.malinskiy.adam.server.junit5.AdbTest +import com.malinskiy.adam.server.stub.AndroidDebugBridgeServer +import com.malinskiy.marathon.android.adam.TestConfigurationFactory +import com.malinskiy.marathon.android.adam.TestDeviceFactory +import com.malinskiy.marathon.android.adam.boot +import com.malinskiy.marathon.android.adam.features import com.malinskiy.marathon.android.extension.toScreenRecorderCommand +import com.malinskiy.marathon.config.vendor.android.FileSyncConfiguration +import com.malinskiy.marathon.config.vendor.android.FileSyncEntry import com.malinskiy.marathon.config.vendor.android.VideoConfiguration +import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.mockito.kotlin.mock +@AdbTest class VideoConfigurationTest { + @AdbClient + lateinit var client: AndroidDebugBridgeClient + + @AdbServer + lateinit var server: AndroidDebugBridgeServer + @Test fun testDefaults() { assertThat(VideoConfiguration().toScreenRecorderCommand("/sdcard/video.mp4")) @@ -18,4 +41,39 @@ class VideoConfigurationTest { assertThat(VideoConfiguration(timeLimit = 200).toScreenRecorderCommand("/sdcard/video.mp4")) .isEqualTo("screenrecord --size 720x1280 --bit-rate 1000000 --time-limit 180 /sdcard/video.mp4") } + + @ParameterizedTest + @MethodSource("apiLevels") + fun testLongVideoDependsOnApiLevel(sdkLevel: Int, expectedTimeLimit: Int) { + val configuration = TestConfigurationFactory.create( + fileSyncConfiguration = FileSyncConfiguration( + mutableSetOf( + FileSyncEntry( + "screenshots" + ) + ) + ) + ) + val device = TestDeviceFactory.create(client, configuration, mock()) + runBlocking { + server.multipleSessions { + serial("emulator-5554") { + boot(sdk = sdkLevel) + } + features("emulator-5554") + } + + device.setup() + } + assertThat(VideoConfiguration(timeLimit = 200).toScreenRecorderCommand("/sdcard/video.mp4", device)) + .isEqualTo("screenrecord --size 720x1280 --bit-rate 1000000 --time-limit $expectedTimeLimit /sdcard/video.mp4") + } + + companion object { + @JvmStatic + fun apiLevels() = listOf( + Arguments.of(33, 180), + Arguments.of(34, 200) + ) + } } From 4adcb17bb73a2cde0630c23830783d434cf2efc2 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 12:18:28 +0400 Subject: [PATCH 02/12] Beautify --- .../com/malinskiy/marathon/extension/StringExtensions.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt index 8b188384a..609aa0801 100644 --- a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt +++ b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt @@ -25,6 +25,4 @@ fun String.escape(): String { val escapeRegex = "[^a-zA-Z0-9\\.\\#]".toRegex() -fun String.addFileNumberForVideo(fileNumber: String): String { - return with(this.split(".mp4")) {return@with "${this[0]}$fileNumber.mp4"} -} +fun String.addFileNumberForVideo(fileNumber: String) = "${this.split(".mp4")[0]}$fileNumber.mp4" From aa07237896fba615d53f470fcef184960279332e Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 14:52:38 +0400 Subject: [PATCH 03/12] Feature toggle has added --- .../config/vendor/android/VideoConfiguration.kt | 3 ++- .../marathon/android/adam/AdamAndroidDevice.kt | 2 +- .../listeners/video/ScreenRecorderTestBatchListener.kt | 6 +++--- .../android/extension/ConfigurationExtensions.kt | 2 +- .../marathon/android/VideoConfigurationTest.kt | 10 ++++++---- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt index bfc3f6759..c0352e054 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt @@ -12,5 +12,6 @@ data class VideoConfiguration( @JsonProperty("height") val height: Int = 1280, @JsonProperty("bitrateMbps") val bitrateMbps: Int = 1, @JsonProperty("timeLimit") val timeLimit: Long = 180, - @JsonProperty("timeLimitUnits") val timeLimitUnits: TimeUnit = TimeUnit.SECONDS + @JsonProperty("timeLimitUnits") val timeLimitUnits: TimeUnit = TimeUnit.SECONDS, + @JsonProperty("increasedTimeLimitFeatureEnabled") val increasedTimeLimitFeatureEnabled: Boolean = false ) diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt index b8bbb9575..49f5824ea 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt @@ -375,7 +375,7 @@ class AdamAndroidDevice( options: VideoConfiguration ) { var secondsRemaining = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits) - if(secondsRemaining <= 180 || apiLevel >= 34) { + if(secondsRemaining <= 180 || apiLevel >= 34 || !options.increasedTimeLimitFeatureEnabled) { startScreenRecorder(remoteFilePath, options) } else { var recordsCount = 0 diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index cb5c3ae35..0149646d5 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -127,7 +127,7 @@ class ScreenRecorderTestBatchListener( val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) device.safePullFile(remoteFilePath, localVideoFile.toString()) localVideoFiles.add(localVideoFile) @@ -152,7 +152,7 @@ class ScreenRecorderTestBatchListener( */ private suspend fun pullLastBatchVideo(remoteFilePath: String) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) device.safePullFile(remoteFilePath, localVideoFile.toString()) } else { @@ -167,7 +167,7 @@ class ScreenRecorderTestBatchListener( private suspend fun removeRemoteVideo(remoteFilePath: String) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { device.fileManager.removeRemotePath(remoteFilePath) } else { for (i in 0 .. (videoConfiguration.timeLimit / 180)) { diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt index bdd821472..8c373b10c 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt @@ -37,7 +37,7 @@ fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String, device: A if (timeLimit > 0) { sb.append("--time-limit ") var seconds = TimeUnit.SECONDS.convert(timeLimit, timeLimitUnits) - if (seconds > 180 && ((device?.apiLevel ?: 0) < 34)) { + if (seconds > 180 && ((device?.apiLevel ?: 0) < 34 || !increasedTimeLimitFeatureEnabled)) { seconds = 180 } sb.append(seconds) diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt index 6fd630e93..2d26c119d 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt @@ -44,7 +44,7 @@ class VideoConfigurationTest { @ParameterizedTest @MethodSource("apiLevels") - fun testLongVideoDependsOnApiLevel(sdkLevel: Int, expectedTimeLimit: Int) { + fun testLongVideoDependsOnApiLevel(sdkLevel: Int, expectedTimeLimit: Int, featureIsEnabled: Boolean) { val configuration = TestConfigurationFactory.create( fileSyncConfiguration = FileSyncConfiguration( mutableSetOf( @@ -65,15 +65,17 @@ class VideoConfigurationTest { device.setup() } - assertThat(VideoConfiguration(timeLimit = 200).toScreenRecorderCommand("/sdcard/video.mp4", device)) + assertThat(VideoConfiguration(timeLimit = 200, increasedTimeLimitFeatureEnabled = featureIsEnabled).toScreenRecorderCommand("/sdcard/video.mp4", device)) .isEqualTo("screenrecord --size 720x1280 --bit-rate 1000000 --time-limit $expectedTimeLimit /sdcard/video.mp4") } companion object { @JvmStatic fun apiLevels() = listOf( - Arguments.of(33, 180), - Arguments.of(34, 200) + Arguments.of(33, 180, true), + Arguments.of(34, 200, true), + Arguments.of(33, 180, false), + Arguments.of(34, 180, false) ) } } From a94214de9fc22ebc3c75f1516af2c16e8cb45aac Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 21:41:55 +0400 Subject: [PATCH 04/12] Fixed the comments --- .../marathon/extension/StringExtensions.kt | 2 - .../marathon/android/RemoteFileManager.kt | 2 +- .../android/adam/AdamAndroidDevice.kt | 2 +- .../listeners/video/ScreenRecorder.kt | 9 ++++ .../video/ScreenRecorderTestBatchListener.kt | 41 +++++++++++++------ .../marathon/android/RemoteFileManagerTest.kt | 2 +- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt index 609aa0801..a8be25c84 100644 --- a/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt +++ b/core/src/main/kotlin/com/malinskiy/marathon/extension/StringExtensions.kt @@ -24,5 +24,3 @@ fun String.escape(): String { } val escapeRegex = "[^a-zA-Z0-9\\.\\#]".toRegex() - -fun String.addFileNumberForVideo(fileNumber: String) = "${this.split(".mp4")[0]}$fileNumber.mp4" diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt index af4720da0..22459c8d0 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/RemoteFileManager.kt @@ -44,7 +44,7 @@ class RemoteFileManager(private val device: AndroidDevice) { } companion object { - const val MAX_FILENAME = 254 //we need 1 more char for fileNumber in case of using video attachment > 180 && apiLevel < 34 + const val MAX_FILENAME = 255 const val TMP_PATH = "/data/local/tmp" } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt index 49f5824ea..c54b99532 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt @@ -41,6 +41,7 @@ import com.malinskiy.marathon.android.RemoteFileManager import com.malinskiy.marathon.android.adam.extension.toShellResult import com.malinskiy.marathon.android.exception.CommandRejectedException import com.malinskiy.marathon.android.exception.InstallException +import com.malinskiy.marathon.android.executor.listeners.video.ScreenRecorder.Companion.addFileNumberForVideo import com.malinskiy.marathon.exceptions.TransferException import com.malinskiy.marathon.execution.listener.LineListener import com.malinskiy.marathon.android.extension.toScreenRecorderCommand @@ -54,7 +55,6 @@ import com.malinskiy.marathon.device.NetworkState import com.malinskiy.marathon.device.file.measureFileTransfer import com.malinskiy.marathon.exceptions.DeviceLostException import com.malinskiy.marathon.execution.TestBatchResults -import com.malinskiy.marathon.extension.addFileNumberForVideo import com.malinskiy.marathon.extension.escape import com.malinskiy.marathon.extension.withTimeout import com.malinskiy.marathon.extension.withTimeoutOrNull diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt index 08491b6c5..7d087e82d 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt @@ -34,5 +34,14 @@ internal class ScreenRecorder( companion object { private val logger = MarathonLogging.logger("ScreenRecorder") + private const val VIDEO_SUFFIX_LENGTH = 41 // "-" + 36 chars UUID of testBatchId + ".mp4" + fun String.addFileNumberForVideo(fileNumber: String): String { + val lastIndexForReplacement = this.length - VIDEO_SUFFIX_LENGTH + var firstIndexForReplacement = lastIndexForReplacement + if (this.length + fileNumber.length > 255) { + firstIndexForReplacement -= fileNumber.length + } + return this.replaceRange(firstIndexForReplacement, lastIndexForReplacement, fileNumber) + } } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 0149646d5..9e7862422 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -3,6 +3,7 @@ package com.malinskiy.marathon.android.executor.listeners.video import com.malinskiy.marathon.android.AndroidDevice import com.malinskiy.marathon.exceptions.TransferException import com.malinskiy.marathon.android.executor.listeners.NoOpTestRunListener +import com.malinskiy.marathon.android.executor.listeners.video.ScreenRecorder.Companion.addFileNumberForVideo import com.malinskiy.marathon.android.model.TestIdentifier import com.malinskiy.marathon.config.ScreenRecordingPolicy import com.malinskiy.marathon.config.vendor.android.VideoConfiguration @@ -10,7 +11,6 @@ import com.malinskiy.marathon.device.DevicePoolId import com.malinskiy.marathon.device.toDeviceInfo import com.malinskiy.marathon.execution.Attachment import com.malinskiy.marathon.execution.AttachmentType -import com.malinskiy.marathon.extension.addFileNumberForVideo import com.malinskiy.marathon.extension.withTimeoutOrNull import com.malinskiy.marathon.io.FileManager import com.malinskiy.marathon.io.FileType @@ -18,6 +18,7 @@ import com.malinskiy.marathon.log.MarathonLogging import com.malinskiy.marathon.report.attachment.AttachmentListener import com.malinskiy.marathon.report.attachment.AttachmentProvider import com.malinskiy.marathon.test.Test +import com.malinskiy.marathon.test.toSafeTestName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob @@ -25,6 +26,8 @@ import kotlinx.coroutines.async import kotlinx.coroutines.cancelAndJoin import java.io.File import java.time.Duration +import java.util.UUID +import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis class ScreenRecorderTestBatchListener( @@ -50,6 +53,9 @@ class ScreenRecorderTestBatchListener( private var hasFailed: Boolean = false private var supervisorJob: Job? = null private var lastRemoteFile: String? = null + private var lastUUID: UUID? = null + + private val testStartTimeMap: MutableMap = mutableMapOf() override suspend fun testStarted(test: TestIdentifier) { hasFailed = false @@ -60,6 +66,11 @@ class ScreenRecorderTestBatchListener( val supervisor = SupervisorJob() supervisorJob = supervisor async(supervisor) { + with(UUID.randomUUID()) { //safety for immediately failed run + lastUUID = this + testStartTimeMap[this.toString()] = System.currentTimeMillis() + } + testStartTimeMap[test.toTest().toSafeTestName()] screenRecorder.run() } } @@ -82,9 +93,12 @@ class ScreenRecorderTestBatchListener( try { if (device.verifyHealthy()) { stop() - lastRemoteFile?.let { - pullLastBatchVideo(it) - removeRemoteVideo(it) + lastRemoteFile?.let { file -> + lastUUID?.let { + val testDuration = testStartTimeMap[it.toString()]!!.calculateTimeDuration() + pullLastBatchVideo(file, testDuration) + removeRemoteVideo(file, testDuration) + } } } } catch (e: InterruptedException) { @@ -112,10 +126,11 @@ class ScreenRecorderTestBatchListener( private suspend fun pullVideo(test: Test) { try { stop() + val testDuration = testStartTimeMap[test.toSafeTestName()]!!.calculateTimeDuration() if (screenRecordingPolicy == ScreenRecordingPolicy.ON_ANY || hasFailed) { - pullTestVideo(test) + pullTestVideo(test, testDuration) } - removeRemoteVideo(device.fileManager.remoteVideoForTest(test, testBatchId)) + removeRemoteVideo(device.fileManager.remoteVideoForTest(test, testBatchId), testDuration) } catch (e: InterruptedException) { logger.warn { "Can't stop recording" } } catch (e: TransferException) { @@ -123,7 +138,7 @@ class ScreenRecorderTestBatchListener( } } - private suspend fun pullTestVideo(test: Test) { + private suspend fun pullTestVideo(test: Test, testDuration: Long) { val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { @@ -132,7 +147,7 @@ class ScreenRecorderTestBatchListener( device.safePullFile(remoteFilePath, localVideoFile.toString()) localVideoFiles.add(localVideoFile) } else { - for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + for (i in 0 .. (testDuration / 180)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId, i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) localVideoFiles.add(localVideoFile) @@ -150,13 +165,13 @@ class ScreenRecorderTestBatchListener( /** * This can be called both when test times out and device unavailable */ - private suspend fun pullLastBatchVideo(remoteFilePath: String) { + private suspend fun pullLastBatchVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) device.safePullFile(remoteFilePath, localVideoFile.toString()) } else { - for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + for (i in 0 .. (testDuration / 180)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId, id = i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) } @@ -165,18 +180,20 @@ class ScreenRecorderTestBatchListener( logger.trace { "Pulling finished in ${millis}ms $remoteFilePath " } } - private suspend fun removeRemoteVideo(remoteFilePath: String) { + private suspend fun removeRemoteVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { device.fileManager.removeRemotePath(remoteFilePath) } else { - for (i in 0 .. (videoConfiguration.timeLimit / 180)) { + for (i in 0 .. (testDuration / 180)) { device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i.toString())) } } } logger.trace { "Removed file in ${millis}ms $remoteFilePath" } } + + private fun Long.calculateTimeDuration() = TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) } private suspend fun AndroidDevice.verifyHealthy(): Boolean { diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt index f913b1b7a..dba6eeb75 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/RemoteFileManagerTest.kt @@ -176,7 +176,7 @@ class RemoteFileManagerTest { emptyList() ), "batch-id" ) - assertThat(actual).isEqualTo("/sdcard/pkg.clazz-testWithAVeryLongNameThatExceeds255CharactersqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWER-batch-id.mp4") + assertThat(actual).isEqualTo("/sdcard/pkg.clazz-testWithAVeryLongNameThatExceeds255CharactersqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnmQWERT-batch-id.mp4") } } } From 62186d9b98182928d6f47c6d4c84d50b0eb5de50 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 21:46:36 +0400 Subject: [PATCH 05/12] Fixed the comments p2 --- .../listeners/video/ScreenRecorderTestBatchListener.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 9e7862422..7ca46d30d 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -95,7 +95,7 @@ class ScreenRecorderTestBatchListener( stop() lastRemoteFile?.let { file -> lastUUID?.let { - val testDuration = testStartTimeMap[it.toString()]!!.calculateTimeDuration() + val testDuration = testStartTimeMap[it.toString()]!!.calculateTestDuration() pullLastBatchVideo(file, testDuration) removeRemoteVideo(file, testDuration) } @@ -126,7 +126,7 @@ class ScreenRecorderTestBatchListener( private suspend fun pullVideo(test: Test) { try { stop() - val testDuration = testStartTimeMap[test.toSafeTestName()]!!.calculateTimeDuration() + val testDuration = testStartTimeMap[test.toSafeTestName()]!!.calculateTestDuration() if (screenRecordingPolicy == ScreenRecordingPolicy.ON_ANY || hasFailed) { pullTestVideo(test, testDuration) } @@ -193,7 +193,7 @@ class ScreenRecorderTestBatchListener( logger.trace { "Removed file in ${millis}ms $remoteFilePath" } } - private fun Long.calculateTimeDuration() = TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) + private fun Long.calculateTestDuration() = TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) } private suspend fun AndroidDevice.verifyHealthy(): Boolean { From f482f12c7baa999673fa19009f515cd569ab0c32 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Fri, 24 May 2024 22:42:57 +0400 Subject: [PATCH 06/12] Fix startTime --- .../executor/listeners/video/ScreenRecorderTestBatchListener.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 7ca46d30d..01023f429 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -70,7 +70,7 @@ class ScreenRecorderTestBatchListener( lastUUID = this testStartTimeMap[this.toString()] = System.currentTimeMillis() } - testStartTimeMap[test.toTest().toSafeTestName()] + testStartTimeMap[test.toTest().toSafeTestName()] = System.currentTimeMillis() screenRecorder.run() } } From aac60213cd95ff81e6b04213e3d3816853512f83 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Sat, 25 May 2024 10:54:09 +0400 Subject: [PATCH 07/12] Made work with map more safety --- .../video/ScreenRecorderTestBatchListener.kt | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 01023f429..742d8095e 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -56,6 +56,7 @@ class ScreenRecorderTestBatchListener( private var lastUUID: UUID? = null private val testStartTimeMap: MutableMap = mutableMapOf() + private val defaultTimeLimit: Long = 180 override suspend fun testStarted(test: TestIdentifier) { hasFailed = false @@ -66,11 +67,12 @@ class ScreenRecorderTestBatchListener( val supervisor = SupervisorJob() supervisorJob = supervisor async(supervisor) { + val startTime = System.currentTimeMillis() with(UUID.randomUUID()) { //safety for immediately failed run lastUUID = this - testStartTimeMap[this.toString()] = System.currentTimeMillis() + testStartTimeMap[this.toString()] = startTime } - testStartTimeMap[test.toTest().toSafeTestName()] = System.currentTimeMillis() + testStartTimeMap[test.toTest().toSafeTestName()] = startTime screenRecorder.run() } } @@ -95,7 +97,7 @@ class ScreenRecorderTestBatchListener( stop() lastRemoteFile?.let { file -> lastUUID?.let { - val testDuration = testStartTimeMap[it.toString()]!!.calculateTestDuration() + val testDuration = testStartTimeMap[it.toString()].calculateTestDuration() pullLastBatchVideo(file, testDuration) removeRemoteVideo(file, testDuration) } @@ -126,7 +128,7 @@ class ScreenRecorderTestBatchListener( private suspend fun pullVideo(test: Test) { try { stop() - val testDuration = testStartTimeMap[test.toSafeTestName()]!!.calculateTestDuration() + val testDuration = testStartTimeMap[test.toSafeTestName()].calculateTestDuration() if (screenRecordingPolicy == ScreenRecordingPolicy.ON_ANY || hasFailed) { pullTestVideo(test, testDuration) } @@ -142,12 +144,12 @@ class ScreenRecorderTestBatchListener( val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) device.safePullFile(remoteFilePath, localVideoFile.toString()) localVideoFiles.add(localVideoFile) } else { - for (i in 0 .. (testDuration / 180)) { + for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId, i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) localVideoFiles.add(localVideoFile) @@ -167,11 +169,11 @@ class ScreenRecorderTestBatchListener( */ private suspend fun pullLastBatchVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) device.safePullFile(remoteFilePath, localVideoFile.toString()) } else { - for (i in 0 .. (testDuration / 180)) { + for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId, id = i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) } @@ -182,10 +184,10 @@ class ScreenRecorderTestBatchListener( private suspend fun removeRemoteVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= 180 || !videoConfiguration.increasedTimeLimitFeatureEnabled) { + if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { device.fileManager.removeRemotePath(remoteFilePath) } else { - for (i in 0 .. (testDuration / 180)) { + for (i in 0 .. (testDuration / defaultTimeLimit)) { device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i.toString())) } } @@ -193,7 +195,7 @@ class ScreenRecorderTestBatchListener( logger.trace { "Removed file in ${millis}ms $remoteFilePath" } } - private fun Long.calculateTestDuration() = TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) + private fun Long?.calculateTestDuration(): Long = this?.let { TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) - 1 } ?: defaultTimeLimit // -1 in case it takes some time to start and stop recording } private suspend fun AndroidDevice.verifyHealthy(): Boolean { From 17bc26890549143fd9a988f7cc9149efeb36e981 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Sat, 25 May 2024 12:07:29 +0400 Subject: [PATCH 08/12] Bottleneck fixed --- .../listeners/video/ScreenRecorderTestBatchListener.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 742d8095e..5b6ea55de 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -28,6 +28,7 @@ import java.io.File import java.time.Duration import java.util.UUID import java.util.concurrent.TimeUnit +import kotlin.math.max import kotlin.system.measureTimeMillis class ScreenRecorderTestBatchListener( @@ -195,7 +196,7 @@ class ScreenRecorderTestBatchListener( logger.trace { "Removed file in ${millis}ms $remoteFilePath" } } - private fun Long?.calculateTestDuration(): Long = this?.let { TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) - 1 } ?: defaultTimeLimit // -1 in case it takes some time to start and stop recording + private fun Long?.calculateTestDuration(): Long = max((this?.let { TimeUnit.SECONDS.convert(System.currentTimeMillis() - this, TimeUnit.MILLISECONDS) - 1 } ?: defaultTimeLimit), defaultTimeLimit) // -1 in case it takes some time to start and stop recording } private suspend fun AndroidDevice.verifyHealthy(): Boolean { From 5b1cbc739aad97184dd68a8e9bedcedab9168179 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Sat, 25 May 2024 13:48:21 +0400 Subject: [PATCH 09/12] Updated the docs --- .../src/test/resources/fixture/config/sample_1.yaml | 1 + .../resources/fixture/config/sample_1_no_tracking.yaml | 1 + docs/runner/android/configure.md | 10 +++++++--- .../static/demo/android/allure-results/environment.xml | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/configuration/src/test/resources/fixture/config/sample_1.yaml b/configuration/src/test/resources/fixture/config/sample_1.yaml index 25ba2471b..ea9bcc50e 100644 --- a/configuration/src/test/resources/fixture/config/sample_1.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1.yaml @@ -95,6 +95,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 + increasedTimeLimitFeatureEnabled: false screenshotConfiguration: enabled: false width: 1080 diff --git a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml index 2096217cf..3ec223efb 100644 --- a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml @@ -97,6 +97,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 + increasedTimeLimitFeatureEnabled: false screenshotConfiguration: enabled: false width: 1080 diff --git a/docs/runner/android/configure.md b/docs/runner/android/configure.md index c49846685..9d8234a41 100644 --- a/docs/runner/android/configure.md +++ b/docs/runner/android/configure.md @@ -312,7 +312,8 @@ screenshots or configure the recording parameters you can specify this as follow :::tip -Android's `screenrecorder` doesn't support videos longer than 180 seconds +Android's `screenrecorder` doesn't support videos longer than 180 seconds for apiVersion < 34. If you want to set timeLimit > 180 you can +use `increasedTimeLimitFeatureEnabled: true` ::: @@ -330,6 +331,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 + increasedTimeLimitFeatureEnabled: false screenshotConfiguration: enabled: false width: 1080 @@ -349,7 +351,8 @@ marathon { 1080, //width 1920, //height 2, //Bitrate in Mbps - 300 //Max duration in seconds + 300, //Max duration in seconds + false // Increased timeLimit feature toggle - if enabled - we can use more than 180sec videos(depends on apiVersion) ), ScreenshotConfiguration( false, //enabled @@ -373,7 +376,8 @@ marathon { 1080, //width 1920, //height 2, //Bitrate in Mbps - 300 //Max duration in seconds + 300, //Max duration in seconds + false // Increased timeLimit feature toggle - if enabled - we can use more than 180sec videos(depends on apiVersion) ), ScreenshotConfiguration( false, //enabled diff --git a/docs/static/demo/android/allure-results/environment.xml b/docs/static/demo/android/allure-results/environment.xml index 7f01b8893..39ab7d8b3 100644 --- a/docs/static/demo/android/allure-results/environment.xml +++ b/docs/static/demo/android/allure-results/environment.xml @@ -1 +1 @@ -namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 \ No newline at end of file +namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS, increasedTimeLimitFeatureEnabled=false), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 From 2dcdf08c452ecfab27fc2c99e03d031a3bcd0ca4 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Thu, 13 Jun 2024 22:16:33 +0400 Subject: [PATCH 10/12] Fixed the comments --- .../vendor/android/VideoConfiguration.kt | 2 +- .../resources/fixture/config/sample_1.yaml | 2 +- .../fixture/config/sample_1_no_tracking.yaml | 2 +- docs/runner/android/configure.md | 4 +- .../android/allure-results/environment.xml | 2 +- .../android/adam/AdamAndroidDevice.kt | 20 +++++----- .../listeners/video/ScreenRecorder.kt | 17 +++++---- .../video/ScreenRecorderTestBatchListener.kt | 30 +++++++-------- .../extension/ConfigurationExtensions.kt | 2 +- .../android/VideoConfigurationTest.kt | 2 +- .../listeners/video/ScreenRecorderTest.kt | 38 +++++++++++++++++++ 11 files changed, 81 insertions(+), 40 deletions(-) create mode 100644 vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTest.kt diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt index c0352e054..3a69417da 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt @@ -13,5 +13,5 @@ data class VideoConfiguration( @JsonProperty("bitrateMbps") val bitrateMbps: Int = 1, @JsonProperty("timeLimit") val timeLimit: Long = 180, @JsonProperty("timeLimitUnits") val timeLimitUnits: TimeUnit = TimeUnit.SECONDS, - @JsonProperty("increasedTimeLimitFeatureEnabled") val increasedTimeLimitFeatureEnabled: Boolean = false + @JsonProperty("longVideoSupport") val longVideoSupport: Boolean = false ) diff --git a/configuration/src/test/resources/fixture/config/sample_1.yaml b/configuration/src/test/resources/fixture/config/sample_1.yaml index ea9bcc50e..74562e333 100644 --- a/configuration/src/test/resources/fixture/config/sample_1.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1.yaml @@ -95,7 +95,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - increasedTimeLimitFeatureEnabled: false + longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 diff --git a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml index 3ec223efb..a7a16b52e 100644 --- a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml @@ -97,7 +97,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - increasedTimeLimitFeatureEnabled: false + longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 diff --git a/docs/runner/android/configure.md b/docs/runner/android/configure.md index 9d8234a41..f0f96d542 100644 --- a/docs/runner/android/configure.md +++ b/docs/runner/android/configure.md @@ -313,7 +313,7 @@ screenshots or configure the recording parameters you can specify this as follow :::tip Android's `screenrecorder` doesn't support videos longer than 180 seconds for apiVersion < 34. If you want to set timeLimit > 180 you can -use `increasedTimeLimitFeatureEnabled: true` +use `longVideoSupport: true` ::: @@ -331,7 +331,7 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - increasedTimeLimitFeatureEnabled: false + longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 diff --git a/docs/static/demo/android/allure-results/environment.xml b/docs/static/demo/android/allure-results/environment.xml index 39ab7d8b3..bb092e3dd 100644 --- a/docs/static/demo/android/allure-results/environment.xml +++ b/docs/static/demo/android/allure-results/environment.xml @@ -1 +1 @@ -namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS, increasedTimeLimitFeatureEnabled=false), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 +namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS, longVideoSupport=false), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt index c54b99532..244883695 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt @@ -375,26 +375,26 @@ class AdamAndroidDevice( options: VideoConfiguration ) { var secondsRemaining = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits) - if(secondsRemaining <= 180 || apiLevel >= 34 || !options.increasedTimeLimitFeatureEnabled) { - startScreenRecorder(remoteFilePath, options) - } else { - var recordsCount = 0 - while(recordsCount == 0 || secondsRemaining>=180) { - startScreenRecorder(remoteFilePath, options, recordsCount.toString()) { + if(secondsRemaining > 180 && apiLevel < 34 && options.longVideoSupport) { + var recordsCount = 0L + while(recordsCount == 0L || secondsRemaining >= 180) { + startScreenRecorder(remoteFilePath, options, recordsCount) { secondsRemaining -= 180 recordsCount++ } } + } else { + startScreenRecorder(remoteFilePath, options) } } private suspend fun startScreenRecorder( remoteFilePath: String, options: VideoConfiguration, - fileNumber: String = "", - finallyBlock: (() -> Unit)? = null + counter: Long? = null, + recordFinished: (() -> Unit)? = null ) { - val screenRecorderCommand = options.toScreenRecorderCommand(remoteFilePath.addFileNumberForVideo(fileNumber), this) + val screenRecorderCommand = options.toScreenRecorderCommand(remoteFilePath.addFileNumberForVideo(counter), this) try { withTimeoutOrNull(androidConfiguration.timeoutConfiguration.screenrecorder) { val result = client.execute(ShellCommandRequest(screenRecorderCommand), serial = adbSerial) @@ -411,7 +411,7 @@ class AdamAndroidDevice( } catch (e: Exception) { logger.error("Unable to start screenrecord", e) } finally { - finallyBlock?.invoke() + recordFinished?.invoke() } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt index 7d087e82d..3612e7c5a 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorder.kt @@ -35,13 +35,16 @@ internal class ScreenRecorder( companion object { private val logger = MarathonLogging.logger("ScreenRecorder") private const val VIDEO_SUFFIX_LENGTH = 41 // "-" + 36 chars UUID of testBatchId + ".mp4" - fun String.addFileNumberForVideo(fileNumber: String): String { - val lastIndexForReplacement = this.length - VIDEO_SUFFIX_LENGTH - var firstIndexForReplacement = lastIndexForReplacement - if (this.length + fileNumber.length > 255) { - firstIndexForReplacement -= fileNumber.length - } - return this.replaceRange(firstIndexForReplacement, lastIndexForReplacement, fileNumber) + fun String.addFileNumberForVideo(counter: Long?): String { + return counter?.let { + val lastIndexForReplacement = this.length - VIDEO_SUFFIX_LENGTH + var firstIndexForReplacement = lastIndexForReplacement + val counterLength = counter.toString().length + if (this.length + counterLength > 255) { + firstIndexForReplacement -= counterLength + } + this.replaceRange(firstIndexForReplacement, lastIndexForReplacement, counter.toString()) + } ?: this } } } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 5b6ea55de..16a70591d 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -145,16 +145,16 @@ class ScreenRecorderTestBatchListener( val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { - val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) - device.safePullFile(remoteFilePath, localVideoFile.toString()) - localVideoFiles.add(localVideoFile) - } else { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId, i.toString()) - device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) + device.safePullFile(remoteFilePath.addFileNumberForVideo(i), localVideoFile.toString()) localVideoFiles.add(localVideoFile) } + } else { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId) + device.safePullFile(remoteFilePath, localVideoFile.toString()) + localVideoFiles.add(localVideoFile) } } logger.trace { "Pulling finished in ${millis}ms $remoteFilePath " } @@ -170,14 +170,14 @@ class ScreenRecorderTestBatchListener( */ private suspend fun pullLastBatchVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { - val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) - device.safePullFile(remoteFilePath, localVideoFile.toString()) - } else { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId, id = i.toString()) - device.safePullFile(remoteFilePath.addFileNumberForVideo(i.toString()), localVideoFile.toString()) + device.safePullFile(remoteFilePath.addFileNumberForVideo(i), localVideoFile.toString()) } + } else { + val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId) + device.safePullFile(remoteFilePath, localVideoFile.toString()) } } logger.trace { "Pulling finished in ${millis}ms $remoteFilePath " } @@ -185,12 +185,12 @@ class ScreenRecorderTestBatchListener( private suspend fun removeRemoteVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel >= 34 || videoConfiguration.timeLimit <= defaultTimeLimit || !videoConfiguration.increasedTimeLimitFeatureEnabled) { - device.fileManager.removeRemotePath(remoteFilePath) - } else { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { for (i in 0 .. (testDuration / defaultTimeLimit)) { - device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i.toString())) + device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i)) } + } else { + device.fileManager.removeRemotePath(remoteFilePath) } } logger.trace { "Removed file in ${millis}ms $remoteFilePath" } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt index 8c373b10c..0dd4c8350 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt @@ -37,7 +37,7 @@ fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String, device: A if (timeLimit > 0) { sb.append("--time-limit ") var seconds = TimeUnit.SECONDS.convert(timeLimit, timeLimitUnits) - if (seconds > 180 && ((device?.apiLevel ?: 0) < 34 || !increasedTimeLimitFeatureEnabled)) { + if (seconds > 180 && ((device?.apiLevel ?: 0) < 34 || !longVideoSupport)) { seconds = 180 } sb.append(seconds) diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt index 2d26c119d..be2175203 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt @@ -65,7 +65,7 @@ class VideoConfigurationTest { device.setup() } - assertThat(VideoConfiguration(timeLimit = 200, increasedTimeLimitFeatureEnabled = featureIsEnabled).toScreenRecorderCommand("/sdcard/video.mp4", device)) + assertThat(VideoConfiguration(timeLimit = 200, longVideoSupport = featureIsEnabled).toScreenRecorderCommand("/sdcard/video.mp4", device)) .isEqualTo("screenrecord --size 720x1280 --bit-rate 1000000 --time-limit $expectedTimeLimit /sdcard/video.mp4") } diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTest.kt new file mode 100644 index 000000000..653791f90 --- /dev/null +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTest.kt @@ -0,0 +1,38 @@ +package com.malinskiy.marathon.android.executor.listeners.video + +import com.malinskiy.marathon.android.executor.listeners.video.ScreenRecorder.Companion.addFileNumberForVideo +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.UUID.randomUUID + +class ScreenRecorderTest { + + @ParameterizedTest + @MethodSource("fileNames") + fun testFileName(fileName: String, counter: Long?, expectedName: String) { + val finalName = fileName.addFileNumberForVideo(counter) + assertEquals(expectedName, finalName, "The incorrect final fileName") + } + + companion object { + const val extension = ".mp4" + val testBatchId = randomUUID() + val currentFileName = + "svyqrgDQUIUEUuOz7yDOAt7gnRjXaTQM7Ag9JY4uwp9bE8Ms7gTW8jStGADi1lprrkkqB2ivdQH3x4nv1qrIj7Q8JLxjQYAskhh4gioN9SiRf5SOHznEIapXmXFWOWJbkTizzYxggCWhRyjOTq1kI06aNIP1QV7YwzLnMcXbZDIWz6O8FteFfa9MoX065Wd8btsYTJb1LKg3ntaX23031A-$testBatchId$extension" + @JvmStatic + fun fileNames() = listOf( + Arguments.of(currentFileName, null, currentFileName), + Arguments.of(currentFileName, 9L, + "svyqrgDQUIUEUuOz7yDOAt7gnRjXaTQM7Ag9JY4uwp9bE8Ms7gTW8jStGADi1lprrkkqB2ivdQH3x4nv1qrIj7Q8JLxjQYAskhh4gioN9SiRf5SOHznEIapXmXFWOWJbkTizzYxggCWhRyjOTq1kI06aNIP1QV7YwzLnMcXbZDIWz6O8FteFfa9MoX065Wd8btsYTJb1LKg3ntaX230319-$testBatchId$extension" + ), + Arguments.of(currentFileName, 99L, + "svyqrgDQUIUEUuOz7yDOAt7gnRjXaTQM7Ag9JY4uwp9bE8Ms7gTW8jStGADi1lprrkkqB2ivdQH3x4nv1qrIj7Q8JLxjQYAskhh4gioN9SiRf5SOHznEIapXmXFWOWJbkTizzYxggCWhRyjOTq1kI06aNIP1QV7YwzLnMcXbZDIWz6O8FteFfa9MoX065Wd8btsYTJb1LKg3ntaX230399-$testBatchId$extension" + ), + Arguments.of(currentFileName, 999L, + "svyqrgDQUIUEUuOz7yDOAt7gnRjXaTQM7Ag9JY4uwp9bE8Ms7gTW8jStGADi1lprrkkqB2ivdQH3x4nv1qrIj7Q8JLxjQYAskhh4gioN9SiRf5SOHznEIapXmXFWOWJbkTizzYxggCWhRyjOTq1kI06aNIP1QV7YwzLnMcXbZDIWz6O8FteFfa9MoX065Wd8btsYTJb1LKg3ntaX230999-$testBatchId$extension" + ), + ) + } +} From 5316ba3f8ea6338a5cd46d62560ade462864eff9 Mon Sep 17 00:00:00 2001 From: SergKhram Date: Sun, 16 Jun 2024 11:40:23 +0400 Subject: [PATCH 11/12] Removed the featureToggle --- .../config/vendor/android/VideoConfiguration.kt | 3 +-- .../src/test/resources/fixture/config/sample_1.yaml | 1 - .../fixture/config/sample_1_no_tracking.yaml | 1 - docs/runner/android/configure.md | 11 ++++------- .../demo/android/allure-results/environment.xml | 2 +- .../marathon/android/adam/AdamAndroidDevice.kt | 2 +- .../video/ScreenRecorderTestBatchListener.kt | 6 +++--- .../android/extension/ConfigurationExtensions.kt | 2 +- .../marathon/android/VideoConfigurationTest.kt | 10 ++++------ 9 files changed, 15 insertions(+), 23 deletions(-) diff --git a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt index 3a69417da..bfc3f6759 100644 --- a/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt +++ b/configuration/src/main/kotlin/com/malinskiy/marathon/config/vendor/android/VideoConfiguration.kt @@ -12,6 +12,5 @@ data class VideoConfiguration( @JsonProperty("height") val height: Int = 1280, @JsonProperty("bitrateMbps") val bitrateMbps: Int = 1, @JsonProperty("timeLimit") val timeLimit: Long = 180, - @JsonProperty("timeLimitUnits") val timeLimitUnits: TimeUnit = TimeUnit.SECONDS, - @JsonProperty("longVideoSupport") val longVideoSupport: Boolean = false + @JsonProperty("timeLimitUnits") val timeLimitUnits: TimeUnit = TimeUnit.SECONDS ) diff --git a/configuration/src/test/resources/fixture/config/sample_1.yaml b/configuration/src/test/resources/fixture/config/sample_1.yaml index 74562e333..25ba2471b 100644 --- a/configuration/src/test/resources/fixture/config/sample_1.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1.yaml @@ -95,7 +95,6 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 diff --git a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml index a7a16b52e..2096217cf 100644 --- a/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml +++ b/configuration/src/test/resources/fixture/config/sample_1_no_tracking.yaml @@ -97,7 +97,6 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 diff --git a/docs/runner/android/configure.md b/docs/runner/android/configure.md index f0f96d542..0ec757bc6 100644 --- a/docs/runner/android/configure.md +++ b/docs/runner/android/configure.md @@ -312,8 +312,8 @@ screenshots or configure the recording parameters you can specify this as follow :::tip -Android's `screenrecorder` doesn't support videos longer than 180 seconds for apiVersion < 34. If you want to set timeLimit > 180 you can -use `longVideoSupport: true` +Android's `screenrecorder` doesn't support videos longer than 180 seconds for apiVersion < 34. For such devices marathon will automatically +record as many video files as needed if the test takes longer than 180 seconds. Expect several missing frames between the files though ::: @@ -331,7 +331,6 @@ vendorConfiguration: height: 1920 bitrateMbps: 2 timeLimit: 300 - longVideoSupport: false screenshotConfiguration: enabled: false width: 1080 @@ -351,8 +350,7 @@ marathon { 1080, //width 1920, //height 2, //Bitrate in Mbps - 300, //Max duration in seconds - false // Increased timeLimit feature toggle - if enabled - we can use more than 180sec videos(depends on apiVersion) + 300 //Max duration in seconds ), ScreenshotConfiguration( false, //enabled @@ -376,8 +374,7 @@ marathon { 1080, //width 1920, //height 2, //Bitrate in Mbps - 300, //Max duration in seconds - false // Increased timeLimit feature toggle - if enabled - we can use more than 180sec videos(depends on apiVersion) + 300 //Max duration in seconds ), ScreenshotConfiguration( false, //enabled diff --git a/docs/static/demo/android/allure-results/environment.xml b/docs/static/demo/android/allure-results/environment.xml index bb092e3dd..cb2cf8936 100644 --- a/docs/static/demo/android/allure-results/environment.xml +++ b/docs/static/demo/android/allure-results/environment.xml @@ -1 +1 @@ -namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS, longVideoSupport=false), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 +namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt index 244883695..ff34e4de4 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/adam/AdamAndroidDevice.kt @@ -375,7 +375,7 @@ class AdamAndroidDevice( options: VideoConfiguration ) { var secondsRemaining = TimeUnit.SECONDS.convert(options.timeLimit, options.timeLimitUnits) - if(secondsRemaining > 180 && apiLevel < 34 && options.longVideoSupport) { + if(secondsRemaining > 180 && apiLevel < 34) { var recordsCount = 0L while(recordsCount == 0L || secondsRemaining >= 180) { startScreenRecorder(remoteFilePath, options, recordsCount) { diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt index 16a70591d..7f0bc9e80 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/executor/listeners/video/ScreenRecorderTestBatchListener.kt @@ -145,7 +145,7 @@ class ScreenRecorderTestBatchListener( val localVideoFiles = mutableListOf() val remoteFilePath = device.fileManager.remoteVideoForTest(test, testBatchId) val millis = measureTimeMillis { - if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit) { for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), test, testBatchId, i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i), localVideoFile.toString()) @@ -170,7 +170,7 @@ class ScreenRecorderTestBatchListener( */ private suspend fun pullLastBatchVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit) { for (i in 0 .. (testDuration / defaultTimeLimit)) { val localVideoFile = fileManager.createFile(FileType.VIDEO, pool, device.toDeviceInfo(), testBatchId = testBatchId, id = i.toString()) device.safePullFile(remoteFilePath.addFileNumberForVideo(i), localVideoFile.toString()) @@ -185,7 +185,7 @@ class ScreenRecorderTestBatchListener( private suspend fun removeRemoteVideo(remoteFilePath: String, testDuration: Long) { val millis = measureTimeMillis { - if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit && videoConfiguration.longVideoSupport) { + if(device.apiLevel < 34 && videoConfiguration.timeLimit > defaultTimeLimit) { for (i in 0 .. (testDuration / defaultTimeLimit)) { device.fileManager.removeRemotePath(remoteFilePath.addFileNumberForVideo(i)) } diff --git a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt index 0dd4c8350..079d122e0 100644 --- a/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt +++ b/vendor/vendor-android/src/main/kotlin/com/malinskiy/marathon/android/extension/ConfigurationExtensions.kt @@ -37,7 +37,7 @@ fun VideoConfiguration.toScreenRecorderCommand(remoteFilePath: String, device: A if (timeLimit > 0) { sb.append("--time-limit ") var seconds = TimeUnit.SECONDS.convert(timeLimit, timeLimitUnits) - if (seconds > 180 && ((device?.apiLevel ?: 0) < 34 || !longVideoSupport)) { + if (seconds > 180 && (device?.apiLevel ?: 0) < 34) { seconds = 180 } sb.append(seconds) diff --git a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt index be2175203..6fd630e93 100644 --- a/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt +++ b/vendor/vendor-android/src/test/kotlin/com/malinskiy/marathon/android/VideoConfigurationTest.kt @@ -44,7 +44,7 @@ class VideoConfigurationTest { @ParameterizedTest @MethodSource("apiLevels") - fun testLongVideoDependsOnApiLevel(sdkLevel: Int, expectedTimeLimit: Int, featureIsEnabled: Boolean) { + fun testLongVideoDependsOnApiLevel(sdkLevel: Int, expectedTimeLimit: Int) { val configuration = TestConfigurationFactory.create( fileSyncConfiguration = FileSyncConfiguration( mutableSetOf( @@ -65,17 +65,15 @@ class VideoConfigurationTest { device.setup() } - assertThat(VideoConfiguration(timeLimit = 200, longVideoSupport = featureIsEnabled).toScreenRecorderCommand("/sdcard/video.mp4", device)) + assertThat(VideoConfiguration(timeLimit = 200).toScreenRecorderCommand("/sdcard/video.mp4", device)) .isEqualTo("screenrecord --size 720x1280 --bit-rate 1000000 --time-limit $expectedTimeLimit /sdcard/video.mp4") } companion object { @JvmStatic fun apiLevels() = listOf( - Arguments.of(33, 180, true), - Arguments.of(34, 200, true), - Arguments.of(33, 180, false), - Arguments.of(34, 180, false) + Arguments.of(33, 180), + Arguments.of(34, 200) ) } } From 4ab20e1e706c32a56dc1b2be8646d8b7902b8961 Mon Sep 17 00:00:00 2001 From: Anton Malinskiy Date: Wed, 15 Nov 2023 16:48:27 +0400 Subject: [PATCH 12/12] docs(demo): add android + ios demo --- docs/static/demo/android/allure-results/environment.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/demo/android/allure-results/environment.xml b/docs/static/demo/android/allure-results/environment.xml index cb2cf8936..7f01b8893 100644 --- a/docs/static/demo/android/allure-results/environment.xml +++ b/docs/static/demo/android/allure-results/environment.xml @@ -1 +1 @@ -namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 +namesample-app testsoutputDir/home/pkunzip/Development/marathon/sample/android-app/build/reports/marathonoutputConfigurationOutputConfiguration(maxPath=255)analyticsConfigurationInfluxDb2Configuration(url='*****', token='*****', organization='home', bucket='marathon', retentionPolicyConfiguration=RetentionPolicyConfiguration(everySeconds=2592000, shardGroupDurationSeconds=0))poolingcom.malinskiy.marathon.config.strategy.PoolingStrategyConfiguration$OmniPoolingStrategyConfiguration@5d37aa0fshardingcom.malinskiy.marathon.config.strategy.ShardingStrategyConfiguration$ParallelShardingStrategyConfiguration@6076c66sortingcom.malinskiy.marathon.config.strategy.SortingStrategyConfiguration$NoSortingStrategyConfiguration@485c84d7batchingFixedSizeBatchingStrategyConfiguration(size=5, durationMillis=60000, percentile=80.0, timeLimit=2023-11-15T10:02:35.235675597Z, lastMileLength=10)flakinessProbabilityBasedFlakinessStrategyConfiguration(minSuccessRate=0.99, maxCount=3, timeLimit=2023-11-15T10:02:35.234832492Z)retrycom.malinskiy.marathon.config.strategy.RetryStrategyConfiguration$NoRetryStrategyConfiguration@dd20ebcfilteringFilteringConfiguration(allowlist=[], blocklist=[])ignoreFailuresfalseisCodeCoverageEnabledtrueexecutionStrategyExecutionStrategyConfiguration(mode=ANY_SUCCESS, fast=true)includeSerialRegexes[]excludeSerialRegexes[]testBatchTimeoutMillis1800000testOutputTimeoutMillis300000debugtruescreenRecordingPolicyON_FAILUREvendorConfigurationAndroidConfiguration(androidSdk=/home/pkunzip/.cache/android-sdk, applicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/debug/app-debug.apk, testApplicationOutput=/home/pkunzip/Development/marathon/sample/android-app/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk, splitApks=null, extraApplicationsOutput=null, outputs=null, autoGrantPermission=true, instrumentationArgs={}, applicationPmClear=false, testApplicationPmClear=false, adbInitTimeoutMillis=30000, installOptions=, serialStrategy=AUTOMATIC, screenRecordConfiguration=ScreenRecordConfiguration(preferableRecorderType=null, videoConfiguration=VideoConfiguration(enabled=true, width=720, height=1280, bitrateMbps=1, timeLimit=180, timeLimitUnits=SECONDS), screenshotConfiguration=ScreenshotConfiguration(enabled=true, width=720, height=1280, delayMs=500)), waitForDevicesTimeoutMillis=30000, allureConfiguration=AllureConfiguration(enabled=true, relativeResultsDirectory=/files/allure-results, pathRoot=APP_DATA), timeoutConfiguration=TimeoutConfiguration(shell=PT20S, listFiles=PT20S, pushFile=PT1M, pushFolder=PT1M, pullFile=PT30S, uninstall=PT20S, install=PT1M, screenrecorder=PT3M20S, screencapturer=PT0.3S, socketIdleTimeout=PT30S, portForward=PT20S, boot=PT30S), fileSyncConfiguration=FileSyncConfiguration(pull=[FileSyncEntry(relativePath=files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=/files/allure-results, pathRoot=APP_DATA, aggregationMode=TEST_RUN), FileSyncEntry(relativePath=coverage, pathRoot=APP_DATA, aggregationMode=POOL)], push=[]), threadingConfiguration=ThreadingConfiguration(bootWaitingThreads=4, adbIoThreads=4), testParserConfiguration=RemoteTestParserConfiguration(instrumentationArgs={listener=com.malinskiy.adam.junit4.android.listener.TestAnnotationProducer}), testAccessConfiguration=TestAccessConfiguration(adb=true, grpc=true, console=true, consoleToken=), adbServers=[AdbEndpoint(host=127.0.0.1, port=5037)], disableWindowAnimation=true)deviceInitializationTimeoutMillis180000 \ No newline at end of file