diff --git a/CHANGELOG.md b/CHANGELOG.md index 836cb54..59a67c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ## [Unreleased] +# [1.1.6] - 2023-12-08 + +Changed: +- Input text has now a retry mechanism +- Tap has now a retry mechanism. It will get a new hierarchy and try to tap again if the tap was not successful. + # [1.1.5] - 2023-10-09 Added: @@ -153,7 +159,8 @@ New: Initial release. -[unreleased]: https://github.com/getyourguide/UiTestGlaze/compare/1.1.5...HEAD +[unreleased]: https://github.com/getyourguide/UiTestGlaze/compare/1.1.6...HEAD +[1.1.5]: https://github.com/getyourguide/UiTestGlaze/releases/tag/1.1.6 [1.1.5]: https://github.com/getyourguide/UiTestGlaze/releases/tag/1.1.5 [1.1.4]: https://github.com/getyourguide/UiTestGlaze/releases/tag/1.1.4 [1.1.3]: https://github.com/getyourguide/UiTestGlaze/releases/tag/1.1.3 diff --git a/uiTestGlaze/build.gradle b/uiTestGlaze/build.gradle index ab4d488..7c67abd 100644 --- a/uiTestGlaze/build.gradle +++ b/uiTestGlaze/build.gradle @@ -6,7 +6,7 @@ plugins { ext { PUBLISH_GROUP_ID = 'io.github.getyourguide' - PUBLISH_VERSION = '1.1.5' + PUBLISH_VERSION = '1.1.6' PUBLISH_ARTIFACT_ID = 'uitestglaze' } diff --git a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/InputTextHelper.kt b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/InputTextHelper.kt index 29ba28b..bb80631 100644 --- a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/InputTextHelper.kt +++ b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/InputTextHelper.kt @@ -5,8 +5,11 @@ import androidx.test.uiautomator.UiSelector import kotlin.time.Duration internal class InputTextHelper( + private val config: UiTestGlaze.Config, private val getHierarchyHelper: GetHierarchyHelper, private val findUiElementHelper: FindUiElementHelper, + private val hierarchySettleHelper: HierarchySettleHelper, + private val printHierarchyHelper: PrintHierarchyHelper ) { fun inputText( @@ -14,65 +17,85 @@ internal class InputTextHelper( uiElementIdentifier: UiElementIdentifier, device: UiDevice, inputShouldBeRecognizedTimeout: Duration, + numberOfRetries: Int, ) { - val hierarchy = getHierarchyHelper.getHierarchy(device) - val foundUiElement = - findUiElementHelper.getUiElement( - uiElementIdentifier, - hierarchy, - false, - device, - ) - ?: throw IllegalStateException("Can not find UiElement to enter text") - - when (uiElementIdentifier) { - is UiElementIdentifier.PositionInHierarchy -> - enterText( - uiElementIdentifier.inputIndicatorText, - foundUiElement.text, - foundUiElement.resourceId, - device, - text, - ) - - is UiElementIdentifier.ChildFrom -> - enterText( - uiElementIdentifier.inputIndicatorText, - foundUiElement.text, - foundUiElement.resourceId, + var currentTry = 0 + while (numberOfRetries >= currentTry) { + val hierarchy = getHierarchyHelper.getHierarchy(device) + val foundUiElement = + findUiElementHelper.getUiElement( + uiElementIdentifier, + hierarchy, + false, device, - text, ) + ?: throw IllegalStateException("Can not find UiElement to enter text") - is UiElementIdentifier.Id, - is UiElementIdentifier.TestTag, - -> { - device.findObject( - UiSelector().resourceId(foundUiElement.resourceId) - .instance(uiElementIdentifier.index), - ).text = text + if (foundUiElement.text == text) { + return } - is UiElementIdentifier.Text, - is UiElementIdentifier.TextResource, - is UiElementIdentifier.TextRegex, - -> { - device.findObject( - UiSelector().text(foundUiElement.text).instance(uiElementIdentifier.index), - ).text = text + when (uiElementIdentifier) { + is UiElementIdentifier.PositionInHierarchy -> + enterText( + uiElementIdentifier.inputIndicatorText, + foundUiElement.text, + foundUiElement.resourceId, + device, + text, + ) + + is UiElementIdentifier.ChildFrom -> + enterText( + uiElementIdentifier.inputIndicatorText, + foundUiElement.text, + foundUiElement.resourceId, + device, + text, + ) + + is UiElementIdentifier.Id, + is UiElementIdentifier.TestTag, + -> { + device.findObject( + UiSelector().resourceId(foundUiElement.resourceId) + .instance(uiElementIdentifier.index), + ).text = text + } + + is UiElementIdentifier.Text, + is UiElementIdentifier.TextResource, + is UiElementIdentifier.TextRegex, + -> { + device.findObject( + UiSelector().text(foundUiElement.text).instance(uiElementIdentifier.index), + ).text = text + } } - } - val startTime = System.currentTimeMillis() - var hierarchyChanged = false - do { - val hierarchyAfterEnteringText = getHierarchyHelper.getHierarchy(device) - if (hierarchy != hierarchyAfterEnteringText) { - hierarchyChanged = true - break + val startTime = System.currentTimeMillis() + var hierarchyChanged = false + do { + val hierarchyAfterEnteringText = hierarchySettleHelper.waitTillHierarchySettles( + config.loadingResourceIds, + device, + config.waitTillLoadingViewsGoneTimeout, + config.waitTillHierarchySettlesTimeout, + ) + printHierarchyHelper.print(hierarchyAfterEnteringText, "After entering text ") + if (hierarchy != hierarchyAfterEnteringText) { + hierarchyChanged = true + break + } + } while ((System.currentTimeMillis() - startTime) < inputShouldBeRecognizedTimeout.inWholeMilliseconds) + + if (!hierarchyChanged) { + if (currentTry == numberOfRetries) { + throw IllegalStateException("Timeout hit while waiting for text to appear") + } + currentTry++ + } else { + return } - } while ((System.currentTimeMillis() - startTime) < inputShouldBeRecognizedTimeout.inWholeMilliseconds) - if (!hierarchyChanged) { - throw IllegalStateException("Timeout hit while waiting for hierarchy to settle") } } diff --git a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/TapHelper.kt b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/TapHelper.kt index ad00f8a..eece34d 100644 --- a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/TapHelper.kt +++ b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/TapHelper.kt @@ -6,7 +6,6 @@ internal class TapHelper( private val config: UiTestGlaze.Config, private val findUiElementHelper: FindUiElementHelper, private val hierarchySettleHelper: HierarchySettleHelper, - private val getHierarchyHelper: GetHierarchyHelper, ) { fun tap( @@ -16,23 +15,47 @@ internal class TapHelper( longPress: Boolean, offsetX: Int, offsetY: Int, - hierarchy: TreeNode, device: UiDevice, ) { - val foundUiElement = - findUiElementHelper.getUiElement( - uiElementIdentifier, - hierarchy, - optional, - device, - ) ?: return - tapOnTreeNode(foundUiElement, optional, retryCount, longPress, offsetX, offsetY, device) + var currentRetry = 0 + var hierarchy: TreeNode? + var hierarchyAfterTap: TreeNode? + do { + hierarchy = + hierarchySettleHelper.waitTillHierarchySettles( + config.loadingResourceIds, + device, + config.waitTillLoadingViewsGoneTimeout, + config.waitTillHierarchySettlesTimeout, + ) + + val foundUiElement = + findUiElementHelper.getUiElement( + uiElementIdentifier, + hierarchy, + optional, + device, + ) ?: return + + tapOnTreeNode(foundUiElement, longPress, offsetX, offsetY, device) + + hierarchyAfterTap = + hierarchySettleHelper.waitTillHierarchySettles( + emptyList(), + device, + config.waitTillLoadingViewsGoneTimeout, + config.waitTillHierarchySettlesTimeout, + ) + currentRetry++ + } while (hierarchy == hierarchyAfterTap && currentRetry <= retryCount) + + if (hierarchy == hierarchyAfterTap && !optional) { + throw IllegalStateException("Couldn't tap element $uiElementIdentifier") + } } private fun tapOnTreeNode( uiElement: UiElement, - optional: Boolean, - retryCount: Int, longPress: Boolean, offsetX: Int, offsetY: Int, @@ -41,52 +64,16 @@ internal class TapHelper( tap( uiElement.x + (uiElement.width) / 2 + offsetX, uiElement.y + (uiElement.height) / 2 + offsetY, - optional, - retryCount, longPress, device, ) } - fun tap( - x: Int, - y: Int, - optional: Boolean, - retryCount: Int, - longPress: Boolean, - device: UiDevice, - ) { - var currentTry = 1 - while (currentTry <= retryCount) { - if (tap(x, y, longPress, device)) { - return - } else { - Thread.sleep(200) - currentTry++ - } - } - if (!optional) { - throw IllegalStateException("Couldn't tap element at position: x:$x y:$y") - } - } - - private fun tap(x: Int, y: Int, longPress: Boolean, device: UiDevice): Boolean { - val hierarchyBeforeTap = getHierarchyHelper.getHierarchy(device) - + fun tap(x: Int, y: Int, longPress: Boolean, device: UiDevice) { if (longPress) { device.swipe(x, y, x, y, 200) } else { device.click(x, y) } - - val hierarchyAfterTap = - hierarchySettleHelper.waitTillHierarchySettles( - emptyList(), - device, - config.waitTillLoadingViewsGoneTimeout, - config.waitTillHierarchySettlesTimeout, - ) - - return hierarchyAfterTap != hierarchyBeforeTap } } diff --git a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/UiTestGlaze.kt b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/UiTestGlaze.kt index 3c6acd1..7ee5ada 100644 --- a/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/UiTestGlaze.kt +++ b/uiTestGlaze/src/main/java/com/getyourguide/uitestglazesample/UiTestGlaze.kt @@ -66,6 +66,7 @@ data class UiTestGlaze( } private val logger = Logger(config.logger) + private val printHierarchyHelper = PrintHierarchyHelper(logger) private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) private val getHierarchyHelper = GetHierarchyHelper(logger) private val findUiElementHelper = FindUiElementHelper(logger, getHierarchyHelper) @@ -73,12 +74,11 @@ data class UiTestGlaze( private val hierarchySettleHelper = HierarchySettleHelper(getHierarchyHelper, findUiElementHelper, logger) private val inputTextHelper = - InputTextHelper(getHierarchyHelper, findUiElementHelper) - private val printHierarchyHelper = PrintHierarchyHelper(logger) + InputTextHelper(config, getHierarchyHelper, findUiElementHelper, hierarchySettleHelper, printHierarchyHelper) private val scrollHelper = ScrollHelper(findUiElementHelper, getHierarchyHelper, hierarchySettleHelper) private val tapHelper = - TapHelper(config, findUiElementHelper, hierarchySettleHelper, getHierarchyHelper) + TapHelper(config, findUiElementHelper, hierarchySettleHelper) /** * Tap on an element and expecting the UI to change. @@ -96,39 +96,30 @@ data class UiTestGlaze( offsetX: Int = 0, offsetY: Int = 0, ) { - val hierarchy = - hierarchySettleHelper.waitTillHierarchySettles( - config.loadingResourceIds, - device, - config.waitTillLoadingViewsGoneTimeout, - config.waitTillHierarchySettlesTimeout, - ) - tapHelper.tap(uiElementIdentifier, optional, retryCount, longPress, offsetX, offsetY, hierarchy, device) + tapHelper.tap( + uiElementIdentifier, + optional, + retryCount, + longPress, + offsetX, + offsetY, + device + ) } /** - * Tap on a defined position and expecting the UI to change. + * Tap on a defined position. * * @param xPosition X position to tap. * @param yPosition Y position to tap. - * @param optional If true, UiTestGlaze will not throw an exception if the element is not found. - * @param retryCount Number of times to retry if the Ui does not change after tapping. * @param longPress If true, UiTestGlaze will long press on the element. */ fun tap( xPosition: Int, yPosition: Int, - optional: Boolean = false, - retryCount: Int = 3, longPress: Boolean = false, ) { - hierarchySettleHelper.waitTillHierarchySettles( - config.loadingResourceIds, - device, - config.waitTillLoadingViewsGoneTimeout, - config.waitTillHierarchySettlesTimeout, - ) - tapHelper.tap(xPosition, yPosition, optional, retryCount, longPress, device) + tapHelper.tap(xPosition, yPosition, longPress, device) } /** @@ -215,11 +206,13 @@ data class UiTestGlaze( * @param text Text to input. * @param uiElementIdentifier Identifier of the element to input text. * @param inputShouldBeRecognizedTimeout Timeout to wait till the input is recognized. + * @param numberOfRetries Number of times to retry if the input is not recognized. */ fun inputText( text: String, uiElementIdentifier: UiElementIdentifier, - inputShouldBeRecognizedTimeout: Duration = 3.seconds, + inputShouldBeRecognizedTimeout: Duration = 5.seconds, + numberOfRetries: Int = 3 ) { hierarchySettleHelper.waitTillHierarchySettles( config.loadingResourceIds, @@ -232,6 +225,7 @@ data class UiTestGlaze( uiElementIdentifier = uiElementIdentifier, device = device, inputShouldBeRecognizedTimeout = inputShouldBeRecognizedTimeout, + numberOfRetries = numberOfRetries ) }