diff --git a/maestro-client/src/main/java/maestro/Errors.kt b/maestro-client/src/main/java/maestro/Errors.kt index 5a9b766979..8b65606a14 100644 --- a/maestro-client/src/main/java/maestro/Errors.kt +++ b/maestro-client/src/main/java/maestro/Errors.kt @@ -25,7 +25,9 @@ sealed class MaestroException(override val message: String) : RuntimeException(m class UnableToClearState(message: String) : MaestroException(message) - class UnableToProcessCommand(message: String, val command: String): MaestroException(message) + class UnableToPullState(message: String) : MaestroException(message) + + class UnableToPushState(message: String) : MaestroException(message) class AppCrash(message: String): MaestroException(message) diff --git a/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt b/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt index bc9068709e..672a2c9cdf 100644 --- a/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt +++ b/maestro-client/src/main/java/maestro/drivers/IOSDriver.kt @@ -22,7 +22,6 @@ package maestro.drivers import com.github.michaelbull.result.expect import com.github.michaelbull.result.getOrThrow import com.github.michaelbull.result.onSuccess -import com.github.michaelbull.result.runCatching import hierarchy.AXElement import ios.IOSDevice import ios.IOSDeviceErrors @@ -30,7 +29,6 @@ import maestro.* import maestro.UiElement.Companion.toUiElement import maestro.UiElement.Companion.toUiElementOrNull import maestro.utils.* -import maestro.utils.network.XCUITestServerError import okio.Sink import okio.source import org.slf4j.LoggerFactory @@ -531,10 +529,8 @@ class IOSDriver( } catch (socketTimeoutException: SocketTimeoutException) { LOGGER.error("Got socket timeout processing $callName command", socketTimeoutException) throw socketTimeoutException - } catch (badRequest: XCUITestServerError.BadRequest) { - LOGGER.error("Bad request for $callName, reason: ${badRequest.errorResponse}") - throw MaestroException.UnableToProcessCommand(command = callName, message = badRequest.error.errorMessage) } catch (appCrashException: IOSDeviceErrors.AppCrash) { + LOGGER.error("Detected app crash during $callName command", appCrashException) throw MaestroException.AppCrash(appCrashException.errorMessage) } } diff --git a/maestro-client/src/test/java/maestro/xctestdriver/XCTestDriverClientTest.kt b/maestro-client/src/test/java/maestro/xctestdriver/XCTestDriverClientTest.kt index e761b18d6d..41d9505f54 100644 --- a/maestro-client/src/test/java/maestro/xctestdriver/XCTestDriverClientTest.kt +++ b/maestro-client/src/test/java/maestro/xctestdriver/XCTestDriverClientTest.kt @@ -6,7 +6,7 @@ import maestro.ios.MockXCTestInstaller import maestro.utils.network.XCUITestServerError import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer -import maestro.utils.network.Error +import okhttp3.mockwebserver.SocketPolicy import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.junit.jupiter.params.ParameterizedTest @@ -14,6 +14,7 @@ import org.junit.jupiter.params.provider.MethodSource import xcuitest.XCTestClient import xcuitest.XCTestDriverClient import xcuitest.api.DeviceInfo +import xcuitest.api.Error import java.net.InetAddress class XCTestDriverClientTest { diff --git a/maestro-ios-driver/src/main/kotlin/util/XCRunnerCLIUtils.kt b/maestro-ios-driver/src/main/kotlin/util/XCRunnerCLIUtils.kt index 986d968c79..b0c1577da8 100644 --- a/maestro-ios-driver/src/main/kotlin/util/XCRunnerCLIUtils.kt +++ b/maestro-ios-driver/src/main/kotlin/util/XCRunnerCLIUtils.kt @@ -44,7 +44,7 @@ object XCRunnerCLIUtils { val mapper = jacksonObjectMapper() val appsMap = mapper.readValue(json, Map::class.java) as Map - return appsMap.keys + runningApps(deviceId).keys + return appsMap.keys } fun setProxy(host: String, port: Int) { diff --git a/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt b/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt index c819b29b60..0be37a3eb5 100644 --- a/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt +++ b/maestro-ios-driver/src/main/kotlin/xcuitest/XCTestDriverClient.kt @@ -3,7 +3,6 @@ package xcuitest import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import hierarchy.ViewHierarchy import maestro.utils.HttpClient -import maestro.utils.network.Error import maestro.utils.network.XCUITestServerError import okhttp3.* import okhttp3.MediaType.Companion.toMediaType @@ -255,7 +254,7 @@ class XCTestDriverClient( logger.error("Request for $pathString failed with bad request ${code}, body: $responseBodyAsString") throw XCUITestServerError.BadRequest( "Request for $pathString failed with bad request ${code}, body: $responseBodyAsString", - error + responseBodyAsString ) } error.errorMessage.contains("Lost connection to the application.*".toRegex()) -> { diff --git a/maestro-utils/src/main/kotlin/network/Error.kt b/maestro-ios-driver/src/main/kotlin/xcuitest/api/Error.kt similarity index 85% rename from maestro-utils/src/main/kotlin/network/Error.kt rename to maestro-ios-driver/src/main/kotlin/xcuitest/api/Error.kt index e128617383..9c13c85db9 100644 --- a/maestro-utils/src/main/kotlin/network/Error.kt +++ b/maestro-ios-driver/src/main/kotlin/xcuitest/api/Error.kt @@ -1,4 +1,4 @@ -package maestro.utils.network +package xcuitest.api import com.fasterxml.jackson.annotation.JsonProperty diff --git a/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip b/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip index 67b21b8d37..a7e1ed2a58 100644 Binary files a/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip and b/maestro-ios-driver/src/main/resources/maestro-driver-ios.zip differ diff --git a/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip b/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip index 3fdba3de90..653be4e4a4 100644 Binary files a/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip and b/maestro-ios-driver/src/main/resources/maestro-driver-iosUITests-Runner.zip differ diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/EraseTextHandler.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/EraseTextHandler.swift index 945804ac07..eed26cadfc 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/EraseTextHandler.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/EraseTextHandler.swift @@ -19,9 +19,8 @@ struct EraseTextHandler: HTTPHandler { do { let start = Date() - if let errorResponse = await waitUntilKeyboardIsPresented(appIds: requestBody.appIds) { - return errorResponse - } + let appId = RunningApp.getForegroundAppId(requestBody.appIds) + await waitUntilKeyboardIsPresented(appId: appId) let deleteText = String(repeating: XCUIKeyboardKey.delete.rawValue, count: requestBody.charactersToErase) @@ -36,20 +35,11 @@ struct EraseTextHandler: HTTPHandler { } } - private func waitUntilKeyboardIsPresented(appIds: [String]) async -> HTTPResponse? { - let foregroundAppIds = RunningApp.getForegroundAppIds(appIds) - logger.info("Foreground apps \(foregroundAppIds)") - - let isKeyboardPresented: Bool = (try? await TimeoutHelper.repeatUntil(timeout: 1, delta: 0.2) { - return foregroundAppIds.contains { appId in - XCUIApplication(bundleIdentifier: appId).keyboards.firstMatch.exists - } - }) ?? false + private func waitUntilKeyboardIsPresented(appId: String?) async { + try? await TimeoutHelper.repeatUntil(timeout: 1, delta: 0.2) { + guard let appId = appId else { return true } - // Return an error response if the keyboard is not presented - if !isKeyboardPresented { - return AppError(type: .timeout, message: "Keyboard not presented within 1 second timeout for erase command").httpResponse + return XCUIApplication(bundleIdentifier: appId).keyboards.firstMatch.exists } - return nil } } diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/InputTextRouteHandler.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/InputTextRouteHandler.swift index 343cba0fbb..d3d80e987b 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/InputTextRouteHandler.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Handlers/InputTextRouteHandler.swift @@ -17,9 +17,8 @@ struct InputTextRouteHandler : HTTPHandler { do { let start = Date() - if let errorResponse = await waitUntilKeyboardIsPresented(appIds: requestBody.appIds) { - return errorResponse - } + let appId = RunningApp.getForegroundAppId(requestBody.appIds) + await waitUntilKeyboardIsPresented(appId: appId) try await TextInputHelper.inputText(requestBody.text) @@ -31,20 +30,11 @@ struct InputTextRouteHandler : HTTPHandler { } } - private func waitUntilKeyboardIsPresented(appIds: [String]) async -> HTTPResponse? { - let foregroundAppIds = RunningApp.getForegroundAppIds(appIds) - logger.info("Foreground apps \(foregroundAppIds)") - - let isKeyboardPresented: Bool = (try? await TimeoutHelper.repeatUntil(timeout: 1, delta: 0.2) { - return foregroundAppIds.contains { appId in - XCUIApplication(bundleIdentifier: appId).keyboards.firstMatch.exists - } - }) ?? false + private func waitUntilKeyboardIsPresented(appId: String?) async { + try? await TimeoutHelper.repeatUntil(timeout: 1, delta: 0.2) { + guard let appId = appId else { return true } - // Return an error response if the keyboard is not presented - if !isKeyboardPresented { - return AppError(type: .timeout, message: "Keyboard not presented within 1 second timeout for input command").httpResponse + return XCUIApplication(bundleIdentifier: appId).keyboards.firstMatch.exists } - return nil } } diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/AppError.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/AppError.swift index f5f54300bc..70f3d05ffd 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/AppError.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/AppError.swift @@ -5,7 +5,6 @@ import FlyingFox enum AppErrorType: String, Codable { case `internal` case precondition - case timeout } struct AppError: Error, Codable { @@ -16,7 +15,6 @@ struct AppError: Error, Codable { switch type { case .internal: return .internalServerError case .precondition: return .badRequest - case .timeout: return .badRequest } } diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/TimeoutHelper.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/TimeoutHelper.swift index c1431c31f0..078f17eaef 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/TimeoutHelper.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/Helpers/TimeoutHelper.swift @@ -22,25 +22,4 @@ struct TimeoutHelper { } } } - - static func repeatUntil(timeout: TimeInterval, delta: TimeInterval, block: () -> Bool) async throws -> Bool { - guard delta >= 0 else { - throw NSError(domain: "Invalid value", code: 1, userInfo: [NSLocalizedDescriptionKey: "Delta cannot be negative"]) - } - - let timeout = Date().addingTimeInterval(timeout) - - while Date() < timeout { - do { - try await Task.sleep(nanoseconds: UInt64(1_000_000_000 * delta)) - } catch { - throw NSError(domain: "Failed to sleep task", code: 2, userInfo: [NSLocalizedDescriptionKey: "Task could not be put to sleep"]) - } - - if (block()) { - return true - } - } - return false - } } diff --git a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTest/RunningApp.swift b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTest/RunningApp.swift index f42396f4de..75d068dbf0 100644 --- a/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTest/RunningApp.swift +++ b/maestro-ios-xctest-runner/maestro-driver-iosUITests/Routes/XCTest/RunningApp.swift @@ -24,14 +24,5 @@ struct RunningApp { } ?? RunningApp.springboardBundleId } - static func getForegroundAppIds(_ appIds: [String]) -> [String] { - // springboard is always on foreground - let allAppIds = appIds + ["com.apple.springboard"] - - - return allAppIds.filter { appId in - let app = XCUIApplication(bundleIdentifier: appId) - return app.state == .runningForeground - } - } + } diff --git a/maestro-utils/build.gradle.kts b/maestro-utils/build.gradle.kts index 5a43c908db..c9b828813e 100644 --- a/maestro-utils/build.gradle.kts +++ b/maestro-utils/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { api(libs.square.okio) implementation(libs.square.okhttp) implementation(libs.micrometer.core) - api(libs.jackson.module.kotlin) implementation(libs.micrometer.observation) testImplementation(libs.mockk) diff --git a/maestro-utils/src/main/kotlin/network/Errors.kt b/maestro-utils/src/main/kotlin/network/Errors.kt index 746a63f982..ced967b0de 100644 --- a/maestro-utils/src/main/kotlin/network/Errors.kt +++ b/maestro-utils/src/main/kotlin/network/Errors.kt @@ -12,5 +12,5 @@ sealed class XCUITestServerError: Throwable() { data class UnknownFailure(val errorResponse: String) : XCUITestServerError() data class NetworkError(val errorResponse: String): XCUITestServerError() data class AppCrash(val errorResponse: String): XCUITestServerError() - data class BadRequest(val errorResponse: String, val error: Error): XCUITestServerError() + data class BadRequest(val errorResponse: String, val clientMessage: String): XCUITestServerError() } \ No newline at end of file diff --git a/maestro-utils/src/test/kotlin/network/ErrorsTest.kt b/maestro-utils/src/test/kotlin/network/ErrorsTest.kt index 99d41c1dbd..db7b67a3ff 100644 --- a/maestro-utils/src/test/kotlin/network/ErrorsTest.kt +++ b/maestro-utils/src/test/kotlin/network/ErrorsTest.kt @@ -70,10 +70,9 @@ class ErrorsTest { fun `XCUITestServerError BadRequest should have correct messages`() { val errorMessage = "Bad request" val clientMessage = "Client error" - val error = Error(errorMessage = clientMessage, errorCode = "bad-request-precondition") assertThrows(errorMessage) { - throw XCUITestServerError.BadRequest(errorMessage, error) + throw XCUITestServerError.BadRequest(errorMessage, clientMessage) } } } \ No newline at end of file