diff --git a/maestro-client/src/main/java/maestro/Maestro.kt b/maestro-client/src/main/java/maestro/Maestro.kt index 1876e8365c..4f7fafe455 100644 --- a/maestro-client/src/main/java/maestro/Maestro.kt +++ b/maestro-client/src/main/java/maestro/Maestro.kt @@ -122,7 +122,8 @@ class Maestro( endPoint: Point? = null, startRelative: String? = null, endRelative: String? = null, - duration: Long + duration: Long, + waitToSettleTimeoutMs: Int? = null ) { val deviceInfo = deviceInfo() @@ -146,23 +147,23 @@ class Maestro( } } - waitForAppToSettle() + waitForAppToSettle(waitToSettleTimeoutMs = waitToSettleTimeoutMs) } - fun swipe(swipeDirection: SwipeDirection, uiElement: UiElement, durationMs: Long) { + fun swipe(swipeDirection: SwipeDirection, uiElement: UiElement, durationMs: Long, waitToSettleTimeoutMs: Int?) { LOGGER.info("Swiping ${swipeDirection.name} on element: $uiElement") driver.swipe(uiElement.bounds.center(), swipeDirection, durationMs) - waitForAppToSettle() + waitForAppToSettle(waitToSettleTimeoutMs = waitToSettleTimeoutMs) } - fun swipeFromCenter(swipeDirection: SwipeDirection, durationMs: Long) { + fun swipeFromCenter(swipeDirection: SwipeDirection, durationMs: Long, waitToSettleTimeoutMs: Int?) { val deviceInfo = deviceInfo() LOGGER.info("Swiping ${swipeDirection.name} from center") val center = Point(x = deviceInfo.widthGrid / 2, y = deviceInfo.heightGrid / 2) driver.swipe(center, swipeDirection, durationMs) - waitForAppToSettle() + waitForAppToSettle(waitToSettleTimeoutMs = waitToSettleTimeoutMs) } fun scrollVertical() { diff --git a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt index 3656ce3d50..f666d5a5bd 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -55,6 +55,7 @@ data class SwipeCommand( val startRelative: String? = null, val endRelative: String? = null, val duration: Long = DEFAULT_DURATION_IN_MILLIS, + val waitToSettleTimeoutMs: Int? = null, override val label: String? = null, override val optional: Boolean = false, ) : Command { @@ -102,6 +103,7 @@ data class ScrollUntilVisibleCommand( val scrollDuration: String = DEFAULT_SCROLL_DURATION, val visibilityPercentage: Int, val timeout: String = DEFAULT_TIMEOUT_IN_MILLIS, + val waitToSettleTimeoutMs: Int? = null, val centerElement: Boolean, override val label: String? = null, override val optional: Boolean = false, diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index 1ac2e77d6f..2199cab9f7 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -502,7 +502,7 @@ class Orchestra( } } catch (ignored: MaestroException.ElementNotFound) { } - maestro.swipeFromCenter(direction, durationMs = command.scrollDuration.toLong()) + maestro.swipeFromCenter(direction, durationMs = command.scrollDuration.toLong(), waitToSettleTimeoutMs = command.waitToSettleTimeoutMs) } while (System.currentTimeMillis() < endTime) throw MaestroException.ElementNotFound( @@ -1138,18 +1138,19 @@ class Orchestra( when { elementSelector != null && direction != null -> { val uiElement = findElement(elementSelector, optional = command.optional) - maestro.swipe(direction, uiElement.element, command.duration) + maestro.swipe(direction, uiElement.element, command.duration, waitToSettleTimeoutMs = command.waitToSettleTimeoutMs) } startRelative != null && endRelative != null -> { - maestro.swipe(startRelative = startRelative, endRelative = endRelative, duration = command.duration) + maestro.swipe(startRelative = startRelative, endRelative = endRelative, duration = command.duration, waitToSettleTimeoutMs = command.waitToSettleTimeoutMs) } - direction != null -> maestro.swipe(swipeDirection = direction, duration = command.duration) + direction != null -> maestro.swipe(swipeDirection = direction, duration = command.duration, waitToSettleTimeoutMs = command.waitToSettleTimeoutMs) start != null && end != null -> maestro.swipe( startPoint = start, endPoint = end, - duration = command.duration + duration = command.duration, + waitToSettleTimeoutMs = command.waitToSettleTimeoutMs ) else -> error("Illegal arguments for swiping") diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt index 1a55efbaf3..4d8851166a 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -499,7 +499,7 @@ data class YamlFluentCommand( private fun swipeCommand(swipe: YamlSwipe): MaestroCommand { when (swipe) { - is YamlSwipeDirection -> return MaestroCommand(SwipeCommand(direction = swipe.direction, duration = swipe.duration, label = swipe.label, optional = swipe.optional)) + is YamlSwipeDirection -> return MaestroCommand(SwipeCommand(direction = swipe.direction, duration = swipe.duration, label = swipe.label, optional = swipe.optional, waitToSettleTimeoutMs = swipe.waitToSettleTimeoutMs)) is YamlCoordinateSwipe -> { val start = swipe.start val end = swipe.end @@ -518,11 +518,11 @@ data class YamlFluentCommand( } endPoint = Point(endPoints[0], endPoints[1]) - return MaestroCommand(SwipeCommand(startPoint = startPoint, endPoint = endPoint, duration = swipe.duration, label = swipe.label, optional = swipe.optional)) + return MaestroCommand(SwipeCommand(startPoint = startPoint, endPoint = endPoint, duration = swipe.duration, label = swipe.label, optional = swipe.optional, waitToSettleTimeoutMs = swipe.waitToSettleTimeoutMs)) } is YamlRelativeCoordinateSwipe -> { return MaestroCommand( - SwipeCommand(startRelative = swipe.start, endRelative = swipe.end, duration = swipe.duration, label = swipe.label, optional = swipe.optional) + SwipeCommand(startRelative = swipe.start, endRelative = swipe.end, duration = swipe.duration, label = swipe.label, optional = swipe.optional, waitToSettleTimeoutMs = swipe.waitToSettleTimeoutMs) ) } is YamlSwipeElement -> return swipeElementCommand(swipe) @@ -543,6 +543,7 @@ data class YamlFluentCommand( duration = swipeElement.duration, label = swipeElement.label, optional = swipeElement.optional, + waitToSettleTimeoutMs = swipeElement.waitToSettleTimeoutMs ) ) } @@ -625,6 +626,7 @@ data class YamlFluentCommand( centerElement = yaml.centerElement, label = yaml.label, optional = yaml.optional, + waitToSettleTimeoutMs = yaml.waitToSettleTimeoutMs ) ) } diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt index c2e4054b00..4360c588bf 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlScrollUntilVisible.kt @@ -12,6 +12,7 @@ data class YamlScrollUntilVisible( val speed: String = ScrollUntilVisibleCommand.DEFAULT_SCROLL_DURATION, val visibilityPercentage: Int = ScrollUntilVisibleCommand.DEFAULT_ELEMENT_VISIBILITY_PERCENTAGE, val centerElement: Boolean = ScrollUntilVisibleCommand.DEFAULT_CENTER_ELEMENT, + val waitToSettleTimeoutMs: Int? = null, val label: String? = null, val optional: Boolean = false, ) diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt index b827be7778..5ffac7b5ef 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -15,13 +15,15 @@ interface YamlSwipe { val duration: Long val label: String? val optional: Boolean + val waitToSettleTimeoutMs: Int? } data class YamlSwipeDirection( - val direction: SwipeDirection, + val direction: SwipeDirection, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null, override val optional: Boolean, + override val waitToSettleTimeoutMs: Int? = null, ) : YamlSwipe data class YamlCoordinateSwipe( @@ -30,6 +32,7 @@ data class YamlCoordinateSwipe( override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null, override val optional: Boolean, + override val waitToSettleTimeoutMs: Int? = null, ) : YamlSwipe data class YamlRelativeCoordinateSwipe( @@ -38,6 +41,7 @@ data class YamlRelativeCoordinateSwipe( override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null, override val optional: Boolean, + override val waitToSettleTimeoutMs: Int? = null, ) : YamlSwipe @JsonDeserialize(`as` = YamlSwipeElement::class) @@ -48,6 +52,7 @@ data class YamlSwipeElement( override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null, override val optional: Boolean, + override val waitToSettleTimeoutMs: Int? = null, ) : YamlSwipe private const val DEFAULT_DURATION_IN_MILLIS = 400L @@ -61,13 +66,14 @@ class YamlSwipeDeserializer : JsonDeserializer() { val duration = getDuration(root) val label = getLabel(root) val optional = getOptional(root) + val waitToSettleTimeoutMs = getWaitToSettleTimeoutMs(root) when { input.contains("start") || input.contains("end") -> { check(root.get("direction") == null) { "You cannot provide direction with start/end swipe." } check(root.get("start") != null && root.get("end") != null) { "You need to provide both start and end coordinates, to swipe with coordinates" } - return resolveCoordinateSwipe(root, duration, label, optional) + return resolveCoordinateSwipe(root, duration, label, optional, waitToSettleTimeoutMs) } input.contains("direction") -> { check(root.get("start") == null && root.get("end") == null) { @@ -83,7 +89,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { } val isDirectionalSwipe = isDirectionalSwipe(input) return if (isDirectionalSwipe) { - YamlSwipeDirection(SwipeDirection.valueOf(direction.uppercase()), duration, label, optional) + YamlSwipeDirection(SwipeDirection.valueOf(direction.uppercase()), duration, label, optional, waitToSettleTimeoutMs = waitToSettleTimeoutMs) } else { mapper.convertValue(root, YamlSwipeElement::class.java) } @@ -100,7 +106,13 @@ class YamlSwipeDeserializer : JsonDeserializer() { } } - private fun resolveCoordinateSwipe(root: TreeNode, duration: Long, label: String?, optional: Boolean): YamlSwipe { + private fun resolveCoordinateSwipe( + root: TreeNode, + duration: Long, + label: String?, + optional: Boolean, + waitToSettleTimeoutMs: Int? + ): YamlSwipe { when { isRelativeSwipe(root) -> { val start = root.path("start").toString().replace("\"", "") @@ -129,6 +141,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { duration, label, optional, + waitToSettleTimeoutMs = waitToSettleTimeoutMs ) } else -> return YamlCoordinateSwipe( @@ -137,6 +150,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { duration, label, optional, + waitToSettleTimeoutMs = waitToSettleTimeoutMs ) } } @@ -161,6 +175,14 @@ class YamlSwipeDeserializer : JsonDeserializer() { } } + private fun getWaitToSettleTimeoutMs(root: TreeNode): Int? { + return if (root.path("waitToSettleTimeoutMs").isMissingNode) { + null + } else { + root.path("waitToSettleTimeoutMs").toString().replace("\"", "").toIntOrNull() + } + } + private fun getOptional(root: TreeNode): Boolean { return if (root.path("optional").isMissingNode) { false @@ -171,6 +193,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { private fun isDirectionalSwipe(input: List): Boolean { return input == listOf("direction", "duration") || input == listOf("direction") || - input == listOf("direction", "label") || input == listOf("direction", "duration", "label") + input == listOf("direction", "label") || input == listOf("direction", "duration", "label") || + (input.contains("direction") && !input.contains("from")) } } diff --git a/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt b/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt index 2e20c03f93..c6105fe2c9 100644 --- a/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt +++ b/maestro-orchestra/src/test/java/maestro/orchestra/yaml/YamlCommandReaderTest.kt @@ -2,6 +2,7 @@ package maestro.orchestra.yaml import com.google.common.truth.Truth.assertThat import maestro.KeyCode +import maestro.Point import maestro.ScrollDirection import maestro.SwipeDirection import maestro.TapRepeat @@ -603,6 +604,46 @@ internal class YamlCommandReaderTest { ) } + @Test + fun waitToSettleTimeoutMsCommands( + @YamlFile("027_waitToSettleTimeoutMs.yaml") commands: List + ) { + assertThat(commands).containsExactly( + ApplyConfigurationCommand(MaestroConfig( + appId = "com.example.app" + )), + ScrollUntilVisibleCommand( + selector = ElementSelector(idRegex = "maybe-later"), + direction = ScrollDirection.DOWN, + waitToSettleTimeoutMs = 50, + centerElement = false, + visibilityPercentage = 100 + ), + SwipeCommand( + startRelative = "90%, 50%", + endRelative = "10%, 50%", + waitToSettleTimeoutMs = 50 + ), + SwipeCommand( + direction = SwipeDirection.LEFT, + duration = 400L, + waitToSettleTimeoutMs = 50 + ), + SwipeCommand( + direction = SwipeDirection.LEFT, + duration = 400L, + elementSelector = ElementSelector(idRegex = "feeditem_identifier"), + waitToSettleTimeoutMs = 50, + ), + SwipeCommand( + startPoint = Point(x = 100, y = 200), + endPoint = Point(x = 300, y = 400), + waitToSettleTimeoutMs = 50, + duration = 400L + ) + ) + } + private fun commands(vararg commands: Command): List = commands.map(::MaestroCommand).toList() diff --git a/maestro-orchestra/src/test/resources/YamlCommandReaderTest/027_waitToSettleTimeoutMs.yaml b/maestro-orchestra/src/test/resources/YamlCommandReaderTest/027_waitToSettleTimeoutMs.yaml new file mode 100644 index 0000000000..406712e972 --- /dev/null +++ b/maestro-orchestra/src/test/resources/YamlCommandReaderTest/027_waitToSettleTimeoutMs.yaml @@ -0,0 +1,23 @@ +appId: com.example.app +--- +- scrollUntilVisible: + element: + id: "maybe-later" + waitToSettleTimeoutMs: 50 + direction: DOWN +- swipe: + start: 90%, 50% + end: 10%, 50% + waitToSettleTimeoutMs: 50 +- swipe: + direction: LEFT + waitToSettleTimeoutMs: 50 +- swipe: + from: + id: "feeditem_identifier" + direction: LEFT + waitToSettleTimeoutMs: 50 +- swipe: + start: 100, 200 + end: 300, 400 + waitToSettleTimeoutMs: 50