From 4e88f87657ee5cfec1b4bec547ec4ba3d0c53774 Mon Sep 17 00:00:00 2001 From: Amanjeet Singh Date: Sat, 16 Nov 2024 16:22:59 +0530 Subject: [PATCH] fix: retry for connection and not installation --- maestro-client/build.gradle.kts | 2 + .../kotlin/xcuitest/XCTestDriverClient.kt | 8 -- .../installer/LocalXCTestInstaller.kt | 86 +++++++++---------- maestro-ios/build.gradle.kts | 1 + 4 files changed, 46 insertions(+), 51 deletions(-) diff --git a/maestro-client/build.gradle.kts b/maestro-client/build.gradle.kts index fd765d8eb2..983443bbd6 100644 --- a/maestro-client/build.gradle.kts +++ b/maestro-client/build.gradle.kts @@ -77,6 +77,8 @@ dependencies { api(libs.jackson.module.kotlin) api(libs.jackson.dataformat.yaml) api(libs.jackson.dataformat.xml) + api(libs.kotlin.result) + api(libs.kotlin.retry) api(libs.apk.parser) implementation(project(":maestro-ios")) diff --git a/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt b/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt index 9f0a7cf3ed..924dba004f 100644 --- a/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt +++ b/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt @@ -57,8 +57,6 @@ class XCTestDriverClient( this.addInterceptor(it) } } - .addRetryInterceptor() - .addRetryAndShutdownInterceptor() .build() class XCTestDriverUnreachable(message: String) : IOException(message) @@ -272,12 +270,6 @@ class XCTestDriverClient( responseBodyAsString ) } - code == NetworkErrorHandler.NO_RETRY_RESPONSE_CODE -> { - logger.error("Request for $pathString failed, because of XCUITest server got crashed/exit, body: $responseBodyAsString") - throw XCUITestServerError.NetworkError( - "Request for $pathString failed, because of XCUITest server got crashed/exit, body: $responseBodyAsString" - ) - } error.errorMessage.contains("Lost connection to the application.*".toRegex()) -> { logger.error("Request for $pathString failed, because of app crash, body: $responseBodyAsString") throw XCUITestServerError.AppCrash( diff --git a/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt b/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt index 83d8709cfc..1f9ec73842 100644 --- a/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt +++ b/maestro-ios-driver/src/main/kotlin/xcuitest/installer/LocalXCTestInstaller.kt @@ -7,12 +7,13 @@ import com.github.michaelbull.retry.policy.retryIf import maestro.utils.MaestroTimer import okhttp3.HttpUrl import okhttp3.OkHttpClient +import kotlinx.coroutines.runBlocking import okhttp3.Request import okio.buffer import com.github.michaelbull.retry.retry +import kotlinx.coroutines.delay import okio.sink import okio.source -import kotlinx.coroutines.runBlocking import org.apache.commons.io.FileUtils import org.rauschig.jarchivelib.ArchiverFactory import org.slf4j.LoggerFactory @@ -54,6 +55,8 @@ class LocalXCTestInstaller( return false } + if (!isChannelAlive()) return false + fun killXCTestRunnerProcess() { logger.trace("Will attempt to stop all alive XCTest Runner processes before uninstalling") @@ -77,7 +80,6 @@ class LocalXCTestInstaller( killXCTestRunnerProcess() logger.trace("Uninstalling XCTest Runner from device $deviceId") - XCRunnerCLIUtils.uninstall(UI_TEST_RUNNER_APP_BUNDLE_ID, deviceId) return true } @@ -97,21 +99,21 @@ class LocalXCTestInstaller( throw IllegalStateException("XCTest was not started manually") } - uninstall() val result = runBlocking { - retry(limitAttempts(5) + retryException + binaryExponentialBackoff(base = 50L, max = 5000L)) { - logger.info("[Start] Install XCUITest runner on $deviceId") - startXCTestRunner() - logger.info("[Done] Install XCUITest runner on $deviceId") - - val isDriverAlive = isChannelAlive() - - if (!isDriverAlive) { - FileUtils.cleanDirectory(File(tempDir)) - uninstall() - logger.info("Retrying installation of driver") - throw RetryDriverInstallationAction("iOS driver not ready") + logger.info("[Start] Install XCUITest runner on $deviceId") + startXCTestRunner() + logger.info("[Done] Install XCUITest runner on $deviceId") + + retry( + limitAttempts(3) + retryException + binaryExponentialBackoff( + base = 1000L, + max = 120000L + ) + ) { + if (!isChannelAlive()) { + logger.info("Waiting for driver to become ready") + throw RetryDriverInstallationAction("iOS driver not ready after starting XCTest runner") } return@retry XCTestClient(host, defaultPort) @@ -121,10 +123,8 @@ class LocalXCTestInstaller( return result } - override fun isChannelAlive(): Boolean { - val appAlive = XCRunnerCLIUtils.isAppAlive(UI_TEST_RUNNER_APP_BUNDLE_ID, deviceId) - return appAlive && xcTestDriverStatusCheck() + return xcTestDriverStatusCheck() } private fun ensureOpen(): Boolean { @@ -151,16 +151,17 @@ class LocalXCTestInstaller( xctestAPIBuilder("status") .build() } - val request by lazy { Request.Builder() - .get() - .url(url) - .build() + val request by lazy { + Request.Builder() + .get() + .url(url) + .build() } val okHttpClient by lazy { OkHttpClient.Builder() - .connectTimeout(40, TimeUnit.SECONDS) - .readTimeout(100, TimeUnit.SECONDS) + .connectTimeout(2, TimeUnit.SECONDS) + .readTimeout(2, TimeUnit.SECONDS) .build() } val checkSuccessful = try { @@ -177,10 +178,10 @@ class LocalXCTestInstaller( } private fun startXCTestRunner() { - val processOutput = ProcessBuilder(listOf("xcrun", "simctl", "spawn", deviceId, "launchctl", "list")) - .start() - .inputStream.source().buffer().readUtf8() - .trim() + if (isChannelAlive()) { + logger.info("UI Test runner already running, returning") + return + } logger.info("[Start] Writing xctest run file") val tempDir = File(tempDir).apply { mkdir() } @@ -188,20 +189,13 @@ class LocalXCTestInstaller( writeFileToDestination(XCTEST_RUN_PATH, xctestRunFile) logger.info("[Done] Writing xctest run file") - if (processOutput.contains(UI_TEST_RUNNER_APP_BUNDLE_ID)) { - logger.info("UI Test runner already running, stopping it") - uninstall() - } else { - logger.info("Not able to find ui test runner app running, going to install now") - - logger.info("[Start] Writing maestro-driver-iosUITests-Runner app") - extractZipToApp("maestro-driver-iosUITests-Runner", UI_TEST_RUNNER_PATH) - logger.info("[Done] Writing maestro-driver-iosUITests-Runner app") + logger.info("[Start] Writing maestro-driver-iosUITests-Runner app") + extractZipToApp("maestro-driver-iosUITests-Runner", UI_TEST_RUNNER_PATH) + logger.info("[Done] Writing maestro-driver-iosUITests-Runner app") - logger.info("[Start] Writing maestro-driver-ios app") - extractZipToApp("maestro-driver-ios", UI_TEST_HOST_PATH) - logger.info("[Done] Writing maestro-driver-ios app") - } + logger.info("[Start] Writing maestro-driver-ios app") + extractZipToApp("maestro-driver-ios", UI_TEST_HOST_PATH) + logger.info("[Done] Writing maestro-driver-ios app") logger.info("[Start] Running XcUITest with `xcodebuild test-without-building`") xcTestProcess = XCRunnerCLIUtils.runXcTestWithoutBuild( @@ -210,6 +204,9 @@ class LocalXCTestInstaller( port = defaultPort, enableXCTestOutputFileLogging = enableXCTestOutputFileLogging, ) + runBlocking { + delay(2000) + } logger.info("[Done] Running XcUITest with `xcodebuild test-without-building`") } @@ -221,6 +218,7 @@ class LocalXCTestInstaller( logger.info("[Start] Cleaning up the ui test runner files") FileUtils.cleanDirectory(File(tempDir)) uninstall() + XCRunnerCLIUtils.uninstall(UI_TEST_RUNNER_APP_BUNDLE_ID, deviceId) logger.info("[Done] Cleaning up the ui test runner files") } @@ -246,8 +244,10 @@ class LocalXCTestInstaller( private const val UI_TEST_RUNNER_PATH = "/maestro-driver-iosUITests-Runner.zip" private const val XCTEST_RUN_PATH = "/maestro-driver-ios-config.xctestrun" private const val UI_TEST_HOST_PATH = "/maestro-driver-ios.zip" - private const val UI_TEST_RUNNER_APP_BUNDLE_ID = "dev.mobile.maestro-driver-iosUITests.xctrunner" + private const val UI_TEST_RUNNER_APP_BUNDLE_ID = + "dev.mobile.maestro-driver-iosUITests.xctrunner" } - class RetryDriverInstallationAction(message: String, cause: Throwable? = null): Exception(message, cause) + class RetryDriverInstallationAction(message: String, cause: Throwable? = null) : + Exception(message, cause) } diff --git a/maestro-ios/build.gradle.kts b/maestro-ios/build.gradle.kts index 1933198038..6da81a01df 100644 --- a/maestro-ios/build.gradle.kts +++ b/maestro-ios/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(project(":maestro-ios-driver")) implementation(libs.kotlin.result) + implementation(libs.kotlin.retry) implementation(libs.slf4j) implementation(libs.square.okio) api(libs.google.gson)