Skip to content

Commit ae84916

Browse files
Ensure only one xcodebuild process, cleanup unwanted retries (mobile-dev-inc#2097)
1 parent a64aaa4 commit ae84916

File tree

8 files changed

+90
-389
lines changed

8 files changed

+90
-389
lines changed

maestro-client/src/main/java/maestro/Errors.kt

-1
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,4 @@ sealed class MaestroException(override val message: String) : RuntimeException(m
6363
sealed class MaestroDriverStartupException(override val message: String): RuntimeException() {
6464
class AndroidDriverTimeoutException(message: String): MaestroDriverStartupException(message)
6565
class AndroidInstrumentationSetupFailure(message: String): MaestroDriverStartupException(message)
66-
class IOSDriverTimeoutException(message: String): MaestroDriverStartupException(message)
6766
}

maestro-client/src/main/java/maestro/drivers/IOSDriver.kt

+17-36
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import hierarchy.AXElement
2626
import ios.IOSDevice
2727
import ios.IOSDeviceErrors
2828
import maestro.*
29-
import maestro.MaestroDriverStartupException.*
3029
import maestro.UiElement.Companion.toUiElement
3130
import maestro.UiElement.Companion.toUiElementOrNull
3231
import maestro.utils.*
@@ -35,6 +34,7 @@ import okio.source
3534
import org.slf4j.LoggerFactory
3635
import util.XCRunnerCLIUtils
3736
import java.io.File
37+
import java.net.SocketTimeoutException
3838
import java.util.UUID
3939
import kotlin.collections.set
4040

@@ -51,7 +51,7 @@ class IOSDriver(
5151
}
5252

5353
override fun open() {
54-
awaitLaunch()
54+
iosDevice.open()
5555
}
5656

5757
override fun close() {
@@ -63,7 +63,7 @@ class IOSDriver(
6363
}
6464

6565
override fun deviceInfo(): DeviceInfo {
66-
return runDeviceCall { iosDevice.deviceInfo().toCommonDeviceInfo() }
66+
return runDeviceCall("deviceInfo") { iosDevice.deviceInfo().toCommonDeviceInfo() }
6767
}
6868

6969
override fun launchApp(
@@ -96,11 +96,11 @@ class IOSDriver(
9696
}
9797

9898
override fun tap(point: Point) {
99-
runDeviceCall { iosDevice.tap(point.x, point.y) }
99+
runDeviceCall("tap") { iosDevice.tap(point.x, point.y) }
100100
}
101101

102102
override fun longPress(point: Point) {
103-
runDeviceCall { iosDevice.longPress(point.x, point.y, 3000) }
103+
runDeviceCall("longPress") { iosDevice.longPress(point.x, point.y, 3000) }
104104
}
105105

106106
override fun pressKey(code: KeyCode) {
@@ -114,7 +114,7 @@ class IOSDriver(
114114
KeyCode.LOCK to "lock",
115115
)
116116

117-
runDeviceCall {
117+
runDeviceCall("pressKey") {
118118
keyCodeNameMap[code]?.let { name ->
119119
iosDevice.pressKey(name)
120120
}
@@ -126,7 +126,7 @@ class IOSDriver(
126126
}
127127

128128
override fun contentDescriptor(excludeKeyboardElements: Boolean): TreeNode {
129-
return runDeviceCall { viewHierarchy(excludeKeyboardElements) }
129+
return runDeviceCall("contentDescriptor") { viewHierarchy(excludeKeyboardElements) }
130130
}
131131

132132
private fun viewHierarchy(excludeKeyboardElements: Boolean): TreeNode {
@@ -194,7 +194,7 @@ class IOSDriver(
194194
}
195195

196196
override fun isKeyboardVisible(): Boolean {
197-
return runDeviceCall { iosDevice.isKeyboardVisible() }
197+
return runDeviceCall("isKeyboardVisible") { iosDevice.isKeyboardVisible() }
198198
}
199199

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

209-
runDeviceCall {
209+
runDeviceCall("swipe") {
210210
waitForAppToSettle(null, null)
211211
iosDevice.scroll(
212212
xStart = startPoint.x.toDouble(),
@@ -360,7 +360,7 @@ class IOSDriver(
360360
}
361361

362362
override fun takeScreenshot(out: Sink, compressed: Boolean) {
363-
runDeviceCall { iosDevice.takeScreenshot(out, compressed) }
363+
runDeviceCall("takeScreenshot") { iosDevice.takeScreenshot(out, compressed) }
364364
}
365365

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

373373
override fun inputText(text: String) {
374374
// silently fail if no XCUIElement has focus
375-
runDeviceCall { iosDevice.input(text = text) }
375+
runDeviceCall("inputText") { iosDevice.input(text = text) }
376376
}
377377

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

386386
override fun eraseText(charactersToErase: Int) {
387-
runDeviceCall { iosDevice.eraseText(charactersToErase) }
387+
runDeviceCall("eraseText") { iosDevice.eraseText(charactersToErase) }
388388
}
389389

390390
override fun setProxy(host: String, port: Int) {
@@ -421,7 +421,7 @@ class IOSDriver(
421421
}
422422

423423
override fun setPermissions(appId: String, permissions: Map<String, String>) {
424-
runDeviceCall {
424+
runDeviceCall("setPermissions") {
425425
iosDevice.setPermissions(appId, permissions)
426426
}
427427
}
@@ -456,43 +456,24 @@ class IOSDriver(
456456
}
457457

458458
private fun isScreenStatic(): Boolean {
459-
return runDeviceCall { iosDevice.isScreenStatic() }
459+
return runDeviceCall("isScreenStatic") { iosDevice.isScreenStatic() }
460460
}
461461

462-
private fun awaitLaunch() {
463-
val startTime = System.currentTimeMillis()
464-
465-
while (System.currentTimeMillis() - startTime < getStartupTimeout()) {
466-
runCatching {
467-
iosDevice.open()
468-
return
469-
}
470-
Thread.sleep(100)
471-
}
472-
473-
throw IOSDriverTimeoutException("Maestro iOS driver did not start up in time")
474-
}
475-
476-
private fun <T> runDeviceCall(call: () -> T): T {
462+
private fun <T> runDeviceCall(callName: String, call: () -> T): T {
477463
return try {
478464
call()
465+
} catch (socketTimeoutException: SocketTimeoutException) {
466+
throw MaestroException.DriverTimeout("iOS driver timed out while doing $callName call")
479467
} catch (appCrashException: IOSDeviceErrors.AppCrash) {
480468
throw MaestroException.AppCrash(appCrashException.errorMessage)
481469
}
482470
}
483471

484-
private fun getStartupTimeout(): Long = runCatching {
485-
System.getenv(MAESTRO_DRIVER_STARTUP_TIMEOUT).toLong()
486-
}.getOrDefault(SERVER_LAUNCH_TIMEOUT_MS)
487-
488472
companion object {
489473
const val NAME = "iOS Simulator"
490474

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

493-
private const val SERVER_LAUNCH_TIMEOUT_MS = 15000L
494-
private const val MAESTRO_DRIVER_STARTUP_TIMEOUT = "MAESTRO_DRIVER_STARTUP_TIMEOUT"
495-
496477
private const val ELEMENT_TYPE_CHECKBOX = 12
497478
private const val ELEMENT_TYPE_SWITCH = 40
498479
private const val ELEMENT_TYPE_TOGGLE = 41

maestro-client/src/test/java/maestro/xctestdriver/XCTestDriverClientTest.kt

-87
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,6 @@ import java.net.InetAddress
1919

2020
class XCTestDriverClientTest {
2121

22-
@Test
23-
fun `it should return correct message in case of TimeoutException with 3 retries`() {
24-
// given
25-
val mockWebServer = MockWebServer()
26-
// do not enqueue any response
27-
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
28-
val httpUrl = mockWebServer.url("/deviceInfo")
29-
30-
// when
31-
val simulator = MockXCTestInstaller.Simulator(
32-
installationRetryCount = 0,
33-
shouldInstall = false
34-
)
35-
val mockXCTestInstaller = MockXCTestInstaller(simulator)
36-
val xcTestDriverClient = XCTestDriverClient(
37-
mockXCTestInstaller,
38-
XCTestClient("localhost", 22087)
39-
)
40-
41-
// then
42-
assertThrows<XCUITestServerError.NetworkError> {
43-
xcTestDriverClient.deviceInfo(httpUrl)
44-
}
45-
mockXCTestInstaller.assertInstallationRetries(5)
46-
mockWebServer.shutdown()
47-
}
48-
4922
@Test
5023
fun `it should return the 4xx response as is without retrying`() {
5124
// given
@@ -138,66 +111,6 @@ class XCTestDriverClientTest {
138111
mockWebServer.shutdown()
139112
}
140113

141-
@Test
142-
fun `it should return correct message in case of UnknownHostException without retries`() {
143-
// given
144-
val mockWebServer = MockWebServer()
145-
mockWebServer.enqueue(
146-
MockResponse()
147-
.setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
148-
)
149-
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
150-
val httpUrl = mockWebServer.url("http://nonexistent-domain.local")
151-
152-
// when
153-
val simulator = MockXCTestInstaller.Simulator(
154-
installationRetryCount = 0,
155-
shouldInstall = false
156-
)
157-
val mockXCTestInstaller = MockXCTestInstaller(simulator)
158-
val xcTestDriverClient = XCTestDriverClient(
159-
mockXCTestInstaller,
160-
XCTestClient("localhost", 22087)
161-
)
162-
163-
// then
164-
assertThrows<XCUITestServerError.NetworkError> {
165-
xcTestDriverClient.deviceInfo(httpUrl)
166-
}
167-
mockXCTestInstaller.assertInstallationRetries(0)
168-
mockWebServer.shutdown()
169-
}
170-
171-
@Test
172-
fun `it should return correct message in case of ConnectExceptions with 3 retries`() {
173-
// given
174-
val mockWebServer = MockWebServer()
175-
mockWebServer.enqueue(
176-
MockResponse()
177-
.setSocketPolicy(SocketPolicy.DISCONNECT_DURING_REQUEST_BODY)
178-
)
179-
mockWebServer.start(InetAddress.getByName( "localhost"), 22087)
180-
val httpUrl = mockWebServer.url("/deviceInfo")
181-
mockWebServer.shutdown()
182-
183-
// when
184-
val simulator = MockXCTestInstaller.Simulator(
185-
installationRetryCount = 0,
186-
shouldInstall = false
187-
)
188-
val mockXCTestInstaller = MockXCTestInstaller(simulator)
189-
val xcTestDriverClient = XCTestDriverClient(
190-
mockXCTestInstaller,
191-
XCTestClient("localhost", 22087)
192-
)
193-
194-
// then
195-
assertThrows<XCUITestServerError.NetworkError> {
196-
xcTestDriverClient.deviceInfo(httpUrl)
197-
}
198-
mockXCTestInstaller.assertInstallationRetries(5)
199-
}
200-
201114
companion object {
202115

203116
@JvmStatic

0 commit comments

Comments
 (0)