Skip to content

Commit

Permalink
Ensure only one xcodebuild process, cleanup unwanted retries (#2097)
Browse files Browse the repository at this point in the history
  • Loading branch information
amanjeetsingh150 authored Nov 26, 2024
1 parent a64aaa4 commit ae84916
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 389 deletions.
1 change: 0 additions & 1 deletion maestro-client/src/main/java/maestro/Errors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,4 @@ sealed class MaestroException(override val message: String) : RuntimeException(m
sealed class MaestroDriverStartupException(override val message: String): RuntimeException() {
class AndroidDriverTimeoutException(message: String): MaestroDriverStartupException(message)
class AndroidInstrumentationSetupFailure(message: String): MaestroDriverStartupException(message)
class IOSDriverTimeoutException(message: String): MaestroDriverStartupException(message)
}
53 changes: 17 additions & 36 deletions maestro-client/src/main/java/maestro/drivers/IOSDriver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import hierarchy.AXElement
import ios.IOSDevice
import ios.IOSDeviceErrors
import maestro.*
import maestro.MaestroDriverStartupException.*
import maestro.UiElement.Companion.toUiElement
import maestro.UiElement.Companion.toUiElementOrNull
import maestro.utils.*
Expand All @@ -35,6 +34,7 @@ import okio.source
import org.slf4j.LoggerFactory
import util.XCRunnerCLIUtils
import java.io.File
import java.net.SocketTimeoutException
import java.util.UUID
import kotlin.collections.set

Expand All @@ -51,7 +51,7 @@ class IOSDriver(
}

override fun open() {
awaitLaunch()
iosDevice.open()
}

override fun close() {
Expand All @@ -63,7 +63,7 @@ class IOSDriver(
}

override fun deviceInfo(): DeviceInfo {
return runDeviceCall { iosDevice.deviceInfo().toCommonDeviceInfo() }
return runDeviceCall("deviceInfo") { iosDevice.deviceInfo().toCommonDeviceInfo() }
}

override fun launchApp(
Expand Down Expand Up @@ -96,11 +96,11 @@ class IOSDriver(
}

override fun tap(point: Point) {
runDeviceCall { iosDevice.tap(point.x, point.y) }
runDeviceCall("tap") { iosDevice.tap(point.x, point.y) }
}

override fun longPress(point: Point) {
runDeviceCall { iosDevice.longPress(point.x, point.y, 3000) }
runDeviceCall("longPress") { iosDevice.longPress(point.x, point.y, 3000) }
}

override fun pressKey(code: KeyCode) {
Expand All @@ -114,7 +114,7 @@ class IOSDriver(
KeyCode.LOCK to "lock",
)

runDeviceCall {
runDeviceCall("pressKey") {
keyCodeNameMap[code]?.let { name ->
iosDevice.pressKey(name)
}
Expand All @@ -126,7 +126,7 @@ class IOSDriver(
}

override fun contentDescriptor(excludeKeyboardElements: Boolean): TreeNode {
return runDeviceCall { viewHierarchy(excludeKeyboardElements) }
return runDeviceCall("contentDescriptor") { viewHierarchy(excludeKeyboardElements) }
}

private fun viewHierarchy(excludeKeyboardElements: Boolean): TreeNode {
Expand Down Expand Up @@ -194,7 +194,7 @@ class IOSDriver(
}

override fun isKeyboardVisible(): Boolean {
return runDeviceCall { iosDevice.isKeyboardVisible() }
return runDeviceCall("isKeyboardVisible") { iosDevice.isKeyboardVisible() }
}

override fun swipe(
Expand All @@ -206,7 +206,7 @@ class IOSDriver(
val startPoint = start.coerceIn(maxWidth = deviceInfo.widthGrid, maxHeight = deviceInfo.heightGrid)
val endPoint = end.coerceIn(maxWidth = deviceInfo.widthGrid, maxHeight = deviceInfo.heightGrid)

runDeviceCall {
runDeviceCall("swipe") {
waitForAppToSettle(null, null)
iosDevice.scroll(
xStart = startPoint.x.toDouble(),
Expand Down Expand Up @@ -360,7 +360,7 @@ class IOSDriver(
}

override fun takeScreenshot(out: Sink, compressed: Boolean) {
runDeviceCall { iosDevice.takeScreenshot(out, compressed) }
runDeviceCall("takeScreenshot") { iosDevice.takeScreenshot(out, compressed) }
}

override fun startScreenRecording(out: Sink): ScreenRecording {
Expand All @@ -372,7 +372,7 @@ class IOSDriver(

override fun inputText(text: String) {
// silently fail if no XCUIElement has focus
runDeviceCall { iosDevice.input(text = text) }
runDeviceCall("inputText") { iosDevice.input(text = text) }
}

override fun openLink(link: String, appId: String?, autoVerify: Boolean, browser: Boolean) {
Expand All @@ -384,7 +384,7 @@ class IOSDriver(
}

override fun eraseText(charactersToErase: Int) {
runDeviceCall { iosDevice.eraseText(charactersToErase) }
runDeviceCall("eraseText") { iosDevice.eraseText(charactersToErase) }
}

override fun setProxy(host: String, port: Int) {
Expand Down Expand Up @@ -421,7 +421,7 @@ class IOSDriver(
}

override fun setPermissions(appId: String, permissions: Map<String, String>) {
runDeviceCall {
runDeviceCall("setPermissions") {
iosDevice.setPermissions(appId, permissions)
}
}
Expand Down Expand Up @@ -456,43 +456,24 @@ class IOSDriver(
}

private fun isScreenStatic(): Boolean {
return runDeviceCall { iosDevice.isScreenStatic() }
return runDeviceCall("isScreenStatic") { iosDevice.isScreenStatic() }
}

private fun awaitLaunch() {
val startTime = System.currentTimeMillis()

while (System.currentTimeMillis() - startTime < getStartupTimeout()) {
runCatching {
iosDevice.open()
return
}
Thread.sleep(100)
}

throw IOSDriverTimeoutException("Maestro iOS driver did not start up in time")
}

private fun <T> runDeviceCall(call: () -> T): T {
private fun <T> runDeviceCall(callName: String, call: () -> T): T {
return try {
call()
} catch (socketTimeoutException: SocketTimeoutException) {
throw MaestroException.DriverTimeout("iOS driver timed out while doing $callName call")
} catch (appCrashException: IOSDeviceErrors.AppCrash) {
throw MaestroException.AppCrash(appCrashException.errorMessage)
}
}

private fun getStartupTimeout(): Long = runCatching {
System.getenv(MAESTRO_DRIVER_STARTUP_TIMEOUT).toLong()
}.getOrDefault(SERVER_LAUNCH_TIMEOUT_MS)

companion object {
const val NAME = "iOS Simulator"

private val LOGGER = LoggerFactory.getLogger(IOSDevice::class.java)

private const val SERVER_LAUNCH_TIMEOUT_MS = 15000L
private const val MAESTRO_DRIVER_STARTUP_TIMEOUT = "MAESTRO_DRIVER_STARTUP_TIMEOUT"

private const val ELEMENT_TYPE_CHECKBOX = 12
private const val ELEMENT_TYPE_SWITCH = 40
private const val ELEMENT_TYPE_TOGGLE = 41
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,6 @@ import java.net.InetAddress

class XCTestDriverClientTest {

@Test
fun `it should return correct message in case of TimeoutException with 3 retries`() {
// given
val mockWebServer = MockWebServer()
// do not enqueue any response
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
val httpUrl = mockWebServer.url("/deviceInfo")

// when
val simulator = MockXCTestInstaller.Simulator(
installationRetryCount = 0,
shouldInstall = false
)
val mockXCTestInstaller = MockXCTestInstaller(simulator)
val xcTestDriverClient = XCTestDriverClient(
mockXCTestInstaller,
XCTestClient("localhost", 22087)
)

// then
assertThrows<XCUITestServerError.NetworkError> {
xcTestDriverClient.deviceInfo(httpUrl)
}
mockXCTestInstaller.assertInstallationRetries(5)
mockWebServer.shutdown()
}

@Test
fun `it should return the 4xx response as is without retrying`() {
// given
Expand Down Expand Up @@ -138,66 +111,6 @@ class XCTestDriverClientTest {
mockWebServer.shutdown()
}

@Test
fun `it should return correct message in case of UnknownHostException without retries`() {
// given
val mockWebServer = MockWebServer()
mockWebServer.enqueue(
MockResponse()
.setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
)
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
val httpUrl = mockWebServer.url("http://nonexistent-domain.local")

// when
val simulator = MockXCTestInstaller.Simulator(
installationRetryCount = 0,
shouldInstall = false
)
val mockXCTestInstaller = MockXCTestInstaller(simulator)
val xcTestDriverClient = XCTestDriverClient(
mockXCTestInstaller,
XCTestClient("localhost", 22087)
)

// then
assertThrows<XCUITestServerError.NetworkError> {
xcTestDriverClient.deviceInfo(httpUrl)
}
mockXCTestInstaller.assertInstallationRetries(0)
mockWebServer.shutdown()
}

@Test
fun `it should return correct message in case of ConnectExceptions with 3 retries`() {
// given
val mockWebServer = MockWebServer()
mockWebServer.enqueue(
MockResponse()
.setSocketPolicy(SocketPolicy.DISCONNECT_DURING_REQUEST_BODY)
)
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
val httpUrl = mockWebServer.url("/deviceInfo")
mockWebServer.shutdown()

// when
val simulator = MockXCTestInstaller.Simulator(
installationRetryCount = 0,
shouldInstall = false
)
val mockXCTestInstaller = MockXCTestInstaller(simulator)
val xcTestDriverClient = XCTestDriverClient(
mockXCTestInstaller,
XCTestClient("localhost", 22087)
)

// then
assertThrows<XCUITestServerError.NetworkError> {
xcTestDriverClient.deviceInfo(httpUrl)
}
mockXCTestInstaller.assertInstallationRetries(5)
}

companion object {

@JvmStatic
Expand Down
Loading

0 comments on commit ae84916

Please sign in to comment.