From 23b94724c331d6ee14f55ad451ed1c250a88ef42 Mon Sep 17 00:00:00 2001 From: Danil Klimenko Date: Tue, 29 Oct 2024 20:51:50 +0200 Subject: [PATCH 1/6] Added new flow to the swipe command. You can swipe from element id to the point using relative | absolute coordinates. --- .../src/main/java/maestro/Maestro.kt | 24 ++++++++ .../main/java/maestro/orchestra/Orchestra.kt | 16 +++++- .../orchestra/yaml/YamlFluentCommand.kt | 13 +++++ .../java/maestro/orchestra/yaml/YamlSwipe.kt | 56 +++++++++++++++++-- 4 files changed, 102 insertions(+), 7 deletions(-) diff --git a/maestro-client/src/main/java/maestro/Maestro.kt b/maestro-client/src/main/java/maestro/Maestro.kt index 1876e8365c..23449350dd 100644 --- a/maestro-client/src/main/java/maestro/Maestro.kt +++ b/maestro-client/src/main/java/maestro/Maestro.kt @@ -165,6 +165,30 @@ class Maestro( waitForAppToSettle() } + fun swipeFromElementToPoint( + uiElement: UiElement, + endPoint: Point? = null, + endRelative: String? = null, + duration: Long = 300L + ) { + val deviceInfo = deviceInfo() + + when { + endPoint != null -> driver.swipe(uiElement.bounds.center(), endPoint, duration) + endRelative != null -> { + val endPoints = endRelative.replace("%", "") + .split(",").map { it.trim().toInt() } + val endX = deviceInfo.widthGrid * endPoints[0] / 100 + val endY = deviceInfo.heightGrid * endPoints[1] / 100 + val end = Point(endX, endY) + + driver.swipe(uiElement.bounds.center(), end, duration) + } + } + + waitForAppToSettle () + } + fun scrollVertical() { LOGGER.info("Scrolling vertically") diff --git a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt index e81119cc60..304c5a4c9a 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/Orchestra.kt @@ -1148,7 +1148,21 @@ class Orchestra( endPoint = end, duration = command.duration ) - + elementSelector != null && (end != null || endRelative != null) -> { + val uiElement = findElement(elementSelector, optional = command.optional) + if (end != null) + maestro.swipeFromElementToPoint( + uiElement = uiElement.element, + endPoint = end, + duration = command.duration + ) + else + maestro.swipeFromElementToPoint( + uiElement = uiElement.element, + endRelative = endRelative, + duration = command.duration + ) + } else -> error("Illegal arguments for swiping") } return true 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..f91227febc 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -526,6 +526,7 @@ data class YamlFluentCommand( ) } is YamlSwipeElement -> return swipeElementCommand(swipe) + is YamlRelativeCoordinateSwipeElement -> return swipeRelativeCoordinatesSwipeElementCommand(swipe) else -> { throw IllegalStateException( "Provide swipe direction UP, DOWN, RIGHT OR LEFT or by giving explicit " + @@ -547,6 +548,18 @@ data class YamlFluentCommand( ) } + private fun swipeRelativeCoordinatesSwipeElementCommand(swipeElement: YamlRelativeCoordinateSwipeElement): MaestroCommand { + return MaestroCommand( + swipeCommand = SwipeCommand( + elementSelector = toElementSelector(swipeElement.from), + endRelative = swipeElement.end, + duration = swipeElement.duration, + label = swipeElement.label, + optional = swipeElement.optional, + ) + ) + } + private fun toElementSelector(selectorUnion: YamlElementSelectorUnion): ElementSelector { return if (selectorUnion is StringElementSelector) { ElementSelector( 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..0863be1cef 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -18,7 +18,7 @@ interface YamlSwipe { } 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, @@ -50,6 +50,14 @@ data class YamlSwipeElement( override val optional: Boolean, ) : YamlSwipe +data class YamlRelativeCoordinateSwipeElement( + val from: YamlElementSelectorUnion, + val end: String, + override val duration: Long = DEFAULT_DURATION_IN_MILLIS, + override val label: String? = null, + override val optional: Boolean, +) : YamlSwipe + private const val DEFAULT_DURATION_IN_MILLIS = 400L class YamlSwipeDeserializer : JsonDeserializer() { @@ -62,7 +70,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { val label = getLabel(root) val optional = getOptional(root) when { - input.contains("start") || input.contains("end") -> { + 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" @@ -88,18 +96,54 @@ class YamlSwipeDeserializer : JsonDeserializer() { mapper.convertValue(root, YamlSwipeElement::class.java) } } + input.contains("from") && input.contains("end") -> { + // Handling YamlRelativeCoordinateSwipeElement + return resolveRelativeCoordinateSwipeElement(root, duration, label, optional, mapper) + } else -> { throw IllegalArgumentException( "Swipe command takes either: \n" + - "\t1. direction: Direction based swipe with: \"RIGHT\", \"LEFT\", \"UP\", or \"DOWN\" or \n" + - "\t2. start and end: Coordinates based swipe with: \"start\" and \"end\" coordinates \n" + - "\t3. direction and element to swipe directionally on element\n" + - "It seems you provided invalid input with: $input" + "\t1. direction: Direction based swipe with: \"RIGHT\", \"LEFT\", \"UP\", or \"DOWN\" or \n" + + "\t2. start and end: Coordinates based swipe with: \"start\" and \"end\" coordinates \n" + + "\t3. direction and element to swipe directionally on element\n" + + "\t4. element to swipe and end to swipe from element to coordinates\n" + + + "It seems you provided invalid input with: $input" ) } } } + private fun resolveRelativeCoordinateSwipeElement( + root: TreeNode, + duration: Long, + label: String?, + optional: Boolean, + mapper: ObjectMapper + ): YamlRelativeCoordinateSwipeElement { + val from = mapper.convertValue(root.path("from"), YamlElementSelectorUnion::class.java) + val end = root.path("end").toString().replace("\"", "") + + val isRelative = end.contains("%") + + if (isRelative) { + val endPoints = end + .replace("%", "") + .split(",") + .map { it.trim().toInt() } + check(endPoints[0] in 0..100 && endPoints[1] in 0..100) { + "Invalid end point: $end should be between 0 to 100 when using relative coordinates." + } + } else { + val endPoints = end + .split(",") + .map { it.trim().toInt() } + check(endPoints.size == 2) { "Invalid format for absolute coordinates: $end" } + } + + return YamlRelativeCoordinateSwipeElement(from, end, duration, label, optional) + } + private fun resolveCoordinateSwipe(root: TreeNode, duration: Long, label: String?, optional: Boolean): YamlSwipe { when { isRelativeSwipe(root) -> { From b84b14247156802ec7792838e078b46234765a78 Mon Sep 17 00:00:00 2001 From: Danil Klimenko Date: Tue, 29 Oct 2024 21:18:14 +0200 Subject: [PATCH 2/6] replaced end: with to: --- .../orchestra/yaml/YamlFluentCommand.kt | 2 +- .../java/maestro/orchestra/yaml/YamlSwipe.kt | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) 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 f91227febc..ec6833affc 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlFluentCommand.kt @@ -552,7 +552,7 @@ data class YamlFluentCommand( return MaestroCommand( swipeCommand = SwipeCommand( elementSelector = toElementSelector(swipeElement.from), - endRelative = swipeElement.end, + endRelative = swipeElement.to, duration = swipeElement.duration, label = swipeElement.label, optional = swipeElement.optional, 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 0863be1cef..20804da4d7 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -52,7 +52,7 @@ data class YamlSwipeElement( data class YamlRelativeCoordinateSwipeElement( val from: YamlElementSelectorUnion, - val end: String, + val to: String, override val duration: Long = DEFAULT_DURATION_IN_MILLIS, override val label: String? = null, override val optional: Boolean, @@ -96,8 +96,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { mapper.convertValue(root, YamlSwipeElement::class.java) } } - input.contains("from") && input.contains("end") -> { - // Handling YamlRelativeCoordinateSwipeElement + input.contains("from") && input.contains("to") -> { return resolveRelativeCoordinateSwipeElement(root, duration, label, optional, mapper) } else -> { @@ -122,26 +121,26 @@ class YamlSwipeDeserializer : JsonDeserializer() { mapper: ObjectMapper ): YamlRelativeCoordinateSwipeElement { val from = mapper.convertValue(root.path("from"), YamlElementSelectorUnion::class.java) - val end = root.path("end").toString().replace("\"", "") + val to = root.path("to").toString().replace("\"", "") - val isRelative = end.contains("%") + val isRelative = to.contains("%") if (isRelative) { - val endPoints = end + val endPoints = to .replace("%", "") .split(",") .map { it.trim().toInt() } check(endPoints[0] in 0..100 && endPoints[1] in 0..100) { - "Invalid end point: $end should be between 0 to 100 when using relative coordinates." + "Invalid end point: $to should be between 0 to 100 when using relative coordinates." } } else { - val endPoints = end + val endPoints = to .split(",") .map { it.trim().toInt() } - check(endPoints.size == 2) { "Invalid format for absolute coordinates: $end" } + check(endPoints.size == 2) { "Invalid format for absolute coordinates: $to" } } - return YamlRelativeCoordinateSwipeElement(from, end, duration, label, optional) + return YamlRelativeCoordinateSwipeElement(from, to, duration, label, optional) } private fun resolveCoordinateSwipe(root: TreeNode, duration: Long, label: String?, optional: Boolean): YamlSwipe { From 2d8d0df2dd9feeaa73d00913bf4cc0c75f0fde88 Mon Sep 17 00:00:00 2001 From: Danil Klimenko Date: Tue, 29 Oct 2024 21:26:43 +0200 Subject: [PATCH 3/6] Fix test desc --- .../src/main/java/maestro/orchestra/Commands.kt | 3 +++ 1 file changed, 3 insertions(+) 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..511d63bab1 100644 --- a/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt +++ b/maestro-orchestra-models/src/main/java/maestro/orchestra/Commands.kt @@ -76,6 +76,9 @@ data class SwipeCommand( startRelative != null && endRelative != null -> { "Swipe from ($startRelative) to ($endRelative) in $duration ms" } + elementSelector != null && (endPoint != null || endRelative != null) -> { + "Swiping from ${elementSelector.description()} to ${endPoint ?: endRelative} coordinates" + } else -> "Invalid input to swipe command" } } From 3fa3a9edc578c09104dd26d9f2dc31b1d951ce64 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Wed, 30 Oct 2024 19:41:36 -0500 Subject: [PATCH 4/6] quick fix for swipe IllegalArgumentException --- .../src/main/java/maestro/orchestra/yaml/YamlSwipe.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 20804da4d7..a71362c562 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -105,7 +105,7 @@ class YamlSwipeDeserializer : JsonDeserializer() { "\t1. direction: Direction based swipe with: \"RIGHT\", \"LEFT\", \"UP\", or \"DOWN\" or \n" + "\t2. start and end: Coordinates based swipe with: \"start\" and \"end\" coordinates \n" + "\t3. direction and element to swipe directionally on element\n" + - "\t4. element to swipe and end to swipe from element to coordinates\n" + + "\t4. from and direction/to: more precise swipe from one point to another\n" + "It seems you provided invalid input with: $input" ) From de715ef9770d730472e29beb6716b6987b7295e5 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Sat, 7 Jun 2025 21:09:34 -0500 Subject: [PATCH 5/6] quick fix for YamlRelativeCoordinateSwipeElement --- .../src/main/java/maestro/orchestra/yaml/YamlSwipe.kt | 1 + 1 file changed, 1 insertion(+) 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 240fb83a7b..7a9b9609cf 100644 --- a/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt +++ b/maestro-orchestra/src/main/java/maestro/orchestra/yaml/YamlSwipe.kt @@ -61,6 +61,7 @@ data class YamlRelativeCoordinateSwipeElement( 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 From a49b8668fb2e5524234c0a24de852d4b2c273011 Mon Sep 17 00:00:00 2001 From: Nathan Pierce Date: Mon, 16 Jun 2025 23:01:34 -0500 Subject: [PATCH 6/6] pull/2117 swipe from -> to tests --- e2e/workspaces/demo_app/swipe_from_to.yaml | 58 ++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 e2e/workspaces/demo_app/swipe_from_to.yaml diff --git a/e2e/workspaces/demo_app/swipe_from_to.yaml b/e2e/workspaces/demo_app/swipe_from_to.yaml new file mode 100644 index 0000000000..a8800a3a67 --- /dev/null +++ b/e2e/workspaces/demo_app/swipe_from_to.yaml @@ -0,0 +1,58 @@ +appId: com.example.example +tags: + - passing +--- +- launchApp: + clearState: true +- tapOn: + text: Swipe Test (from_to) +- assertVisible: + text: "Selected: Item 1" +- swipe: + from: + id: "scrollWheel" + to: 50%, 47% # x, y +- assertVisible: + text: "Selected: Item 2" +- swipe: + from: + id: "scrollWheel" + to: 50%, 47% # x, y +- assertVisible: + text: "Selected: Item 3" +- swipe: + from: + id: "scrollWheel" + to: 50%, 47% # x, y +- assertVisible: + text: "Selected: Item 4" +- swipe: + from: + id: "scrollWheel" + to: 50%, 47% # x, y +- assertVisible: + text: "Selected: Item 5" +- swipe: + from: + id: "scrollWheel" + to: 50%, 58% # x, y +- assertVisible: + text: "Selected: Item 4" +- swipe: + from: + id: "scrollWheel" + to: 50%, 58% # x, y +- assertVisible: + text: "Selected: Item 3" +- swipe: + from: + id: "scrollWheel" + to: 50%, 58% # x, y +- assertVisible: + text: "Selected: Item 2" +- swipe: + from: + id: "scrollWheel" + to: 50%, 58% # x, y +- assertVisible: + text: "Selected: Item 1" \ No newline at end of file