diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/AutoScrollFallbackProviderImpl.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/AutoScrollFallbackProviderImpl.kt new file mode 100644 index 000000000..bec86b7c3 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/AutoScrollFallbackProviderImpl.kt @@ -0,0 +1,56 @@ +package com.kaspersky.kaspresso.autoscroll + +import androidx.test.espresso.ViewInteraction +import com.kaspersky.kaspresso.internal.extensions.other.isAllowed +import com.kaspersky.kaspresso.logger.UiTestLogger +import com.kaspersky.kaspresso.params.AutoScrollParams +import com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.FallbackAutoScrollToAction + +class AutoScrollFallbackProviderImpl( + private val params: AutoScrollParams, + private val logger: UiTestLogger +) : AutoScrollProvider { + + /** + * Invokes the given [action] and calls [scroll] if it fails. Helps in cases when autoscroll has already failed because of + * the scrollable container having paddings that autoscroll cannot successfully solve + * + * @param interaction the instance of [ViewInteraction] interface to perform actions and assertions. + * @param action the actual action on the interacted view. + * + * @throws Throwable if the exception caught while invoking [action] is not allowed via [params]. + * @return the result of [action] invocation. + */ + @Throws(Throwable::class) + override fun withAutoScroll(interaction: ViewInteraction, action: () -> T): T { + return try { + action.invoke() + } catch (error: Throwable) { + if (error.isAllowed(params.allowedExceptions)) { + return scroll(interaction, action, error) + } + throw error + } + } + + /** + * Performs the autoscrolling functionality. Performs scroll and re-invokes the given [action]. + * + * @param interaction the instance of [ViewInteraction] interface to perform actions and assertions. + * @param action the actual action on the interacted view. + * @param cachedError the error to be thrown if autoscroll would not help. + * + * @throws cachedError if autoscroll action did not help. + * @return the result of [action] invocation. + */ + @Throws(Throwable::class) + override fun scroll(interaction: ViewInteraction, action: () -> T, cachedError: Throwable): T { + return try { + interaction.perform(FallbackAutoScrollToAction()) + logger.i("View fallback autoScroll successfully performed.") + action.invoke() + } catch (error: Throwable) { + throw cachedError + } + } +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/ObjectAutoScrollProviderImpl.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/ObjectAutoScrollProviderImpl.kt index 6d4487aca..d7b1542a4 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/ObjectAutoScrollProviderImpl.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/autoscroll/ObjectAutoScrollProviderImpl.kt @@ -1,5 +1,7 @@ package com.kaspersky.kaspresso.autoscroll +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.UiObjectNotFoundException import androidx.test.uiautomator.UiScrollable import androidx.test.uiautomator.UiSelector import com.kaspersky.components.kautomator.intercept.interaction.UiObjectInteraction @@ -37,7 +39,8 @@ class ObjectAutoScrollProviderImpl( } /** - * Performs the autoscrolling functionality. Performs scroll and re-invokes the given [action]. + * Performs the autoscrolling functionality. Performs scroll and re-invokes the given [action] for every + * single scrollable on the screen, starting from the top and going through the nested scrollables. * * @param interaction the instance of [UiObjectInteraction] interface to perform actions and assertions. * @param action the actual action on the interacted view. @@ -47,36 +50,65 @@ class ObjectAutoScrollProviderImpl( * @return the result of [action] invocation. */ override fun scroll(interaction: UiObjectInteraction, action: () -> T, cachedError: Throwable): T { - /** - * Looks for a scrollable content - */ - val scrollable = UiScrollable(UiSelector().scrollable(true)) + try { + var index = 0 + do { + /** + * Looks for the next scrollable content. scrollable = null if no more found + */ + val uiSelector = UiSelector().scrollable(true).instance(index) + val scrollable = UiScrollable(uiSelector).apply { setScrollingOrientation() } - /** - * Scrolls to the bottom and looks for the given view. Invokes the action if the view was found. - */ - do { - if (interaction.uiObject2 != null) { - logger.i("UiObject autoscroll to the bottom successfully performed.") - return action.invoke() - } else { - interaction.reFindUiObject() - } - } while (scrollable.scrollForward()) + /** + * Scrolls to the bottom and looks for the given view. Invokes the action if the view was found. + */ + var successMessage = "UiObject autoscroll to the forwards successfully performed." + var uiObject = interaction.findUiObjectWhile(successMessage) { scrollable.scrollForward() } + if (uiObject != null) { + return action.invoke() + } + + /** + * Scrolls to the beginning and looks for the given view. Invokes the action if the view was found. + */ + successMessage = "UiObject autoscroll to backwards successfully performed." + uiObject = interaction.findUiObjectWhile(successMessage) { scrollable.scrollBackward() } + if (uiObject != null) { + return action.invoke() + } + + index += 1 + + } while (true) + + } catch (e: UiObjectNotFoundException) { + // thrown if scrollable == null -> no more scrollables found + } - /** - * Scrolls to the beginning and looks for the given view. Invokes the action if the view was found. - */ + logger.i("UiObject autoscroll did not help. Throwing exception.") + throw cachedError + } + + private fun UiObjectInteraction.findUiObjectWhile( + successMessage: String, + action: () -> Boolean, + ): UiObject2? { do { - if (interaction.uiObject2 != null) { - logger.i("UiObject autoscroll to the beginning successfully performed.") - return action.invoke() + if (uiObject2 != null) { + logger.i(successMessage) + return uiObject2 } else { - interaction.reFindUiObject() + reFindUiObject() } - } while (scrollable.scrollBackward()) + } while (action()) + return null + } - logger.i("UiObject autoscroll did not help. Throwing exception.") - throw cachedError + private fun UiScrollable.setScrollingOrientation() { + if (className.equals("android.widget.HorizontalScrollView")) { + setAsHorizontalList() + } else { + setAsVerticalList() + } } } diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/AutoScrollViewFallbackInterceptor.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/AutoScrollViewFallbackInterceptor.kt new file mode 100644 index 000000000..9c5de01c2 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/AutoScrollViewFallbackInterceptor.kt @@ -0,0 +1,23 @@ +package com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll + +import androidx.test.espresso.ViewInteraction +import com.kaspersky.kaspresso.autoscroll.AutoScrollFallbackProviderImpl +import com.kaspersky.kaspresso.autoscroll.AutoScrollProvider +import com.kaspersky.kaspresso.interceptors.behavior.ViewBehaviorInterceptor +import com.kaspersky.kaspresso.logger.UiTestLogger +import com.kaspersky.kaspresso.params.AutoScrollParams + +class AutoScrollViewFallbackInterceptor( + params: AutoScrollParams, + logger: UiTestLogger +) : ViewBehaviorInterceptor, + AutoScrollProvider by AutoScrollFallbackProviderImpl(params, logger) { + + /** + * Wraps the given [action] invocation with the fallback autoscrolling on failure. + * + * @param interaction the intercepted [ViewInteraction]. + * @param action the action to invoke. + */ + override fun intercept(interaction: ViewInteraction, action: () -> T): T = withAutoScroll(interaction, action) +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/FallbackAutoScrollToAction.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/FallbackAutoScrollToAction.kt new file mode 100644 index 000000000..e6f70ec03 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/interceptors/behavior/impl/autoscroll/FallbackAutoScrollToAction.kt @@ -0,0 +1,101 @@ +package com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll + +import android.util.Log +import android.view.View +import android.view.ViewParent +import android.widget.HorizontalScrollView +import android.widget.ListView +import android.widget.ScrollView +import androidx.core.widget.NestedScrollView +import androidx.test.espresso.PerformException +import androidx.test.espresso.UiController +import androidx.test.espresso.ViewAction +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.util.HumanReadables +import org.hamcrest.Matcher +import org.hamcrest.Matchers + +class FallbackAutoScrollToAction : ViewAction { + + override fun getConstraints(): Matcher { + return Matchers.allOf( + ViewMatchers.withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE), + ViewMatchers.isDescendantOfA( + Matchers.anyOf( + ViewMatchers.isAssignableFrom(NestedScrollView::class.java), + ViewMatchers.isAssignableFrom(ScrollView::class.java), + ViewMatchers.isAssignableFrom(HorizontalScrollView::class.java), + ViewMatchers.isAssignableFrom(ListView::class.java) + ) + ) + ) + } + + private fun View?.isScrollable(): Boolean = + (this is ScrollView || this is NestedScrollView || this is HorizontalScrollView || this is ListView) + + private tailrec fun View?.findFirstParentScrollableView(lastView: View): View? { + return if (this == lastView) { + if (this.isScrollable()) this else null + } else { + if (this.isScrollable()) { + this + } else { + this?.parent?.findFirstView()?.findFirstParentScrollableView(lastView) + } + } + } + + private tailrec fun ViewParent?.findFirstView(): View? { + return if (this is View) { + this + } else { + this?.parent.findFirstView() + } + } + + override fun perform(uiController: UiController, view: View) { + if (ViewMatchers.isDisplayingAtLeast(FULLY_VISIBLE_PERCENTAGE).matches(view)) { + Log.i(TAG, "View is already displayed. Returning.") + return + } + + val scrollView = view.findFirstParentScrollableView(view.rootView) + + when (scrollView is HorizontalScrollView) { + true -> scrollView.scrollTo(view.right + scrollView.paddingEnd, 0) + false -> scrollView?.scrollTo(0, view.bottom + scrollView.paddingBottom) + } + uiController.loopMainThreadUntilIdle() + if (!ViewMatchers.isDisplayingAtLeast(FULLY_VISIBLE_PERCENTAGE).matches(view)) { + + // Try scroll in the opposite direction before failing: leftwards or upwards + when (scrollView is HorizontalScrollView) { + true -> scrollView.scrollTo(view.left - scrollView.paddingStart, 0) + false -> scrollView?.scrollTo(0, view.top - scrollView.paddingTop) + } + uiController.loopMainThreadUntilIdle() + + if (!ViewMatchers.isDisplayingAtLeast(FULLY_VISIBLE_PERCENTAGE).matches(view)) { + throw PerformException.Builder() + .withActionDescription(this.description) + .withViewDescription(HumanReadables.describe(view)) + .withCause( + java.lang.RuntimeException( + "Fallback scrolling to view was attempted, but the view is not displayed" + ) + ) + .build() + } + } + } + + override fun getDescription(): String { + return "fallback scroll to" + } + + companion object { + private val TAG = FallbackAutoScrollToAction::class.java.simpleName + private const val FULLY_VISIBLE_PERCENTAGE = 100 + } +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt index 8e0b5be13..f9d07b23d 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt @@ -70,6 +70,7 @@ import com.kaspersky.kaspresso.interceptors.behavior.DataBehaviorInterceptor import com.kaspersky.kaspresso.interceptors.behavior.ViewBehaviorInterceptor import com.kaspersky.kaspresso.interceptors.behavior.WebBehaviorInterceptor import com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollViewBehaviorInterceptor +import com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollViewFallbackInterceptor import com.kaspersky.kaspresso.interceptors.behavior.impl.autoscroll.AutoScrollWebBehaviorInterceptor import com.kaspersky.kaspresso.interceptors.behavior.impl.failure.FailureLoggingDataBehaviorInterceptor import com.kaspersky.kaspresso.interceptors.behavior.impl.failure.FailureLoggingViewBehaviorInterceptor @@ -319,6 +320,24 @@ data class Kaspresso( } } + /** + * In some cases, scrolling to the view works, but the action cannot be performed due to padding in + * the scrollable view, namely + * - ScrollView or NestedScrollView + * - ListView + * - HorizontalScrollView + * + * This is a very special case. If any of the screens in your tests contains one of the aforementioned + * Views and they have padding, use this in your [Kaspresso.Builder] to avoid scrolling problems + */ + fun withAutoScrollFallback(): Builder = + this.apply { + viewBehaviorInterceptors = viewBehaviorInterceptors.apply { + val autoScrollIndex = indexOfFirst { it is AutoScrollViewBehaviorInterceptor } + add(autoScrollIndex + 1, AutoScrollViewFallbackInterceptor(autoScrollParams, libLogger)) + } + } + /** * Holds an implementation of [KautomatorWaitForIdleSettings] for external developer's usage in tests. * If it was not specified, the default implementation is used. diff --git a/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/common/actions/UiScrollableActions.kt b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/common/actions/UiScrollableActions.kt index dcb9be7ca..189c5160b 100644 --- a/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/common/actions/UiScrollableActions.kt +++ b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/common/actions/UiScrollableActions.kt @@ -8,10 +8,12 @@ import com.kaspersky.components.kautomator.intercept.operation.UiOperationType interface UiScrollableActions : UiBaseActions { + val uiScrollableTransformation: UiScrollable.() -> UiScrollable + fun scrollToStart() { view.perform(UiScrollableActionType.SCROLL_TO_START) { val scrollable = UiScrollable(UiSelector().resourceId(resourceName)) - scrollable.setAsVerticalList() + scrollable.uiScrollableTransformation() scrollable.flingToBeginning(Int.MAX_VALUE) } } @@ -19,7 +21,7 @@ interface UiScrollableActions : UiBaseActions { fun scrollToEnd() { view.perform(UiScrollableActionType.SCROLL_TO_END) { val scrollable = UiScrollable(UiSelector().resourceId(resourceName)) - scrollable.setAsVerticalList() + scrollable.uiScrollableTransformation() scrollable.flingToEnd(Int.MAX_VALUE) } } @@ -27,6 +29,7 @@ interface UiScrollableActions : UiBaseActions { fun scrollToView(to: UiBaseView) { view.perform(UiScrollableActionType.SCROLL_TO_VIEW) { val scrollable = UiScrollable(UiSelector().resourceId(resourceName)) + scrollable.uiScrollableTransformation() do { if (findObject(to.view.interaction.selector.bySelector) != null) return@perform diff --git a/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiHorizontalScrollView.kt b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiHorizontalScrollView.kt new file mode 100644 index 000000000..a70a1fca9 --- /dev/null +++ b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiHorizontalScrollView.kt @@ -0,0 +1,18 @@ +@file:Suppress("unused") + +package com.kaspersky.components.kautomator.component.scroll + +import androidx.test.uiautomator.UiScrollable +import com.kaspersky.components.kautomator.component.common.actions.UiScrollableActions +import com.kaspersky.components.kautomator.component.common.actions.UiSwipeableActions +import com.kaspersky.components.kautomator.component.common.builders.UiViewBuilder +import com.kaspersky.components.kautomator.component.common.builders.UiViewSelector +import com.kaspersky.components.kautomator.component.common.views.UiBaseView + +class UiHorizontalScrollView : UiBaseView, UiSwipeableActions, UiScrollableActions { + constructor(selector: UiViewSelector) : super(selector) + constructor(builder: UiViewBuilder.() -> Unit) : super(builder) + + override val uiScrollableTransformation: UiScrollable.() -> UiScrollable + get() = { setAsHorizontalList() } +} diff --git a/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiScrollView.kt b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiScrollView.kt index e82c66967..7f6e8e195 100644 --- a/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiScrollView.kt +++ b/kautomator/src/main/kotlin/com/kaspersky/components/kautomator/component/scroll/UiScrollView.kt @@ -1,6 +1,8 @@ @file:Suppress("unused") + package com.kaspersky.components.kautomator.component.scroll +import androidx.test.uiautomator.UiScrollable import com.kaspersky.components.kautomator.component.common.actions.UiScrollableActions import com.kaspersky.components.kautomator.component.common.actions.UiSwipeableActions import com.kaspersky.components.kautomator.component.common.builders.UiViewBuilder @@ -10,4 +12,7 @@ import com.kaspersky.components.kautomator.component.common.views.UiBaseView class UiScrollView : UiBaseView, UiSwipeableActions, UiScrollableActions { constructor(selector: UiViewSelector) : super(selector) constructor(builder: UiViewBuilder.() -> Unit) : super(builder) + + override val uiScrollableTransformation: UiScrollable.() -> UiScrollable + get() = { setAsVerticalList() } } diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/KScrollViewWithPaddingScreen.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/KScrollViewWithPaddingScreen.kt new file mode 100644 index 000000000..27566c749 --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/KScrollViewWithPaddingScreen.kt @@ -0,0 +1,18 @@ +package com.kaspersky.kaspressample.screen + +import com.kaspersky.kaspressample.R +import com.kaspersky.kaspressample.scrollresolver.ScrollResolverActivity +import com.kaspersky.kaspresso.screens.KScreen +import io.github.kakaocup.kakao.text.KButton + +object KScrollViewWithPaddingScreen : KScreen() { + + override val layoutId: Int? = R.layout.activity_scrollview_with_padding + override val viewClass: Class<*>? = ScrollResolverActivity::class.java + + val hbutton1 = KButton { withId(R.id.hvText1) } + val hbutton7 = KButton { withId(R.id.hvText7) } + val button1 = KButton { withId(R.id.tvText1) } + val button18 = KButton { withId(R.id.tvText18) } + val button20 = KButton { withId(R.id.tvText20) } +} diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/MainScreen.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/MainScreen.kt index 8f9eb30b0..e9b803f6a 100644 --- a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/MainScreen.kt +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/MainScreen.kt @@ -11,6 +11,8 @@ object MainScreen : KScreen() { override val layoutId: Int? = R.layout.activity_main override val viewClass: Class<*>? = MainActivity::class.java + val scrollResolverButton = KButton { withId(R.id.activity_main_auto_scroll_fallback_button) } + val simpleButton = KButton { withId(R.id.activity_main_simple_sample_button) } val webViewButton = KButton { withId(R.id.activity_main_webview_sample_button) } diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/UiScrollViewWithPaddingScreen.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/UiScrollViewWithPaddingScreen.kt new file mode 100644 index 000000000..87cdad063 --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/screen/UiScrollViewWithPaddingScreen.kt @@ -0,0 +1,21 @@ +package com.kaspersky.kaspressample.screen + +import com.kaspersky.components.kautomator.component.scroll.UiHorizontalScrollView +import com.kaspersky.components.kautomator.component.scroll.UiScrollView +import com.kaspersky.components.kautomator.component.text.UiButton +import com.kaspersky.components.kautomator.screen.UiScreen + +object UiScrollViewWithPaddingScreen : UiScreen() { + + override val packageName: String = "com.kaspersky.kaspressample" + + val hScrollView = UiHorizontalScrollView { withId(this@UiScrollViewWithPaddingScreen.packageName, "hscroll_view") } + + val scrollView = UiScrollView { withId(this@UiScrollViewWithPaddingScreen.packageName, "scroll_view") } + + val hbutton1 = UiButton { withId(this@UiScrollViewWithPaddingScreen.packageName, "hvText1") } + val hbutton7 = UiButton { withId(this@UiScrollViewWithPaddingScreen.packageName, "hvText7") } + val button1 = UiButton { withId(this@UiScrollViewWithPaddingScreen.packageName, "tvText1") } + val button20 = UiButton { withId(this@UiScrollViewWithPaddingScreen.packageName, "tvText20") } + val button21 = UiButton { withId(this@UiScrollViewWithPaddingScreen.packageName, "tvText21") } +} diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingFailingTest.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingFailingTest.kt new file mode 100644 index 000000000..8ee420782 --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingFailingTest.kt @@ -0,0 +1,79 @@ +package com.kaspersky.kaspressample.scrollresolver_tests.autoscrollfallback_kaspresso_tests + +import androidx.test.espresso.PerformException +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspressample.MainActivity +import com.kaspersky.kaspressample.screen.MainScreen +import com.kaspersky.kaspressample.screen.KScrollViewWithPaddingScreen +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import org.junit.Rule +import org.junit.Test + +class ScrollViewWithPaddingFailingTest : TestCase() { + + @get:Rule + val activityRule = activityScenarioRule() + + @Test(expected = PerformException::class) + fun click_button_in_the_middle() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click button_18 in ScrollView, middle item") { + KScrollViewWithPaddingScreen { + button18 { + click() + } + } + } + } + + @Test(expected = PerformException::class) + fun click_last_button() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click button_20 in ScrollView, last item") { + KScrollViewWithPaddingScreen { + button20 { + click() + } + } + } + } + + @Test(expected = PerformException::class) + fun click_button_in_the_end_of_HorizontalScrollView() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click hbutton_7 in HorizontalScrollView, last item") { + KScrollViewWithPaddingScreen { + hbutton7 { + click() + } + } + } + } +} diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingPassingTest.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingPassingTest.kt new file mode 100644 index 000000000..682c66bc0 --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/autoscrollfallback_kaspresso_tests/ScrollViewWithPaddingPassingTest.kt @@ -0,0 +1,96 @@ +package com.kaspersky.kaspressample.scrollresolver_tests.autoscrollfallback_kaspresso_tests + +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspressample.MainActivity +import com.kaspersky.kaspressample.screen.MainScreen +import com.kaspersky.kaspressample.screen.KScrollViewWithPaddingScreen +import com.kaspersky.kaspresso.kaspresso.Kaspresso +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import org.junit.Rule +import org.junit.Test + +class ScrollViewWithPaddingPassingTest : TestCase( + kaspressoBuilder = Kaspresso.Builder.simple().withAutoScrollFallback() +) { + + @get:Rule + val activityRule = activityScenarioRule() + + @Test + fun click_button_in_the_middle_of_ScrollView() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click button_18 in ScrollView, middle item") { + KScrollViewWithPaddingScreen { + button18 { + click() + } + } + } + } + + @Test + fun click_last_button_in_ScrollView_and_then_first_one() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click button_20 in ScrollView, last item") { + KScrollViewWithPaddingScreen { + button20 { + click() + } + } + } + + step("Click button_1 in ScrollView, first item from last one (upwards)") { + KScrollViewWithPaddingScreen { + button1 { + click() + } + } + } + } + + @Test + fun click_last_button_in_HorizontalScrollView_and_then_first_one() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click hbutton_7 in HorizontalScrollView, last item") { + KScrollViewWithPaddingScreen { + hbutton7 { + click() + } + } + } + step("Click hbutton_1 in HorizontalScrollView, first item from last one (leftwards)") { + KScrollViewWithPaddingScreen { + hbutton1 { + click() + } + } + } + } +} diff --git a/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/mutiplescrollable_kautomator_tests/MultipleScrollablesPassingTest.kt b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/mutiplescrollable_kautomator_tests/MultipleScrollablesPassingTest.kt new file mode 100644 index 000000000..39b03e82f --- /dev/null +++ b/samples/kaspresso-sample/src/androidTest/kotlin/com/kaspersky/kaspressample/scrollresolver_tests/mutiplescrollable_kautomator_tests/MultipleScrollablesPassingTest.kt @@ -0,0 +1,114 @@ +package com.kaspersky.kaspressample.scrollresolver_tests.mutiplescrollable_kautomator_tests + +import androidx.test.ext.junit.rules.activityScenarioRule +import com.kaspersky.kaspressample.MainActivity +import com.kaspersky.kaspressample.screen.MainScreen +import com.kaspersky.kaspressample.screen.UiScrollViewWithPaddingScreen +import com.kaspersky.kaspresso.testcases.api.testcase.TestCase +import org.junit.Rule +import org.junit.Test + +class MultipleScrollablesPassingTest : TestCase() { + + @get:Rule + val activityRule = activityScenarioRule() + + @Test + fun click_last_button_in_HorizontalScrollView_and_then_first_one_with_autoscroll() = + run { + step("Open Scroll Resolver Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click hbutton_7 in HorizontalScrollView, last item") { + UiScrollViewWithPaddingScreen { + hbutton7 { + click() + } + } + } + + step("Click hbutton_1 in HorizontalScrollView, first item from last one (leftwards)") { + UiScrollViewWithPaddingScreen { + hbutton1 { + click() + } + } + } + } + + /** + * This tests proves that it is faster to make the Scrollable scroll to the view we want to e.g. click, + * specially if nested + */ + @Test + fun click_last_button_in_HorizontalScrollView_and_then_first_one_without_autoscroll() = + run { + step("Open Scroll Resolver Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click hbutton_7 in HorizontalScrollView, last item") { + UiScrollViewWithPaddingScreen { + scrollView.scrollToView(hScrollView) + hScrollView.scrollToView(hbutton7) + hbutton7 { + click() + } + } + } + + step("Click hbutton_1 in HorizontalScrollView, first item from last one (leftwards)") { + UiScrollViewWithPaddingScreen { + scrollView.scrollToView(hScrollView) + hScrollView.scrollToView(hbutton1) + hbutton1 { + click() + } + } + } + } + + @Test + fun click_last_button_in_ScrollView_and_then_first_one_with_autoscroll() = + run { + step("Open Auto Scroll Fallback Screen") { + MainScreen { + scrollResolverButton { + isVisible() + click() + } + } + } + + step("Click button_20 in ScrollView, last item") { + UiScrollViewWithPaddingScreen { + button20 { + click() + } + button1 { + click() + } + } + } + + step("Click button_1 in ScrollView, first item from last one (upwards)") { + UiScrollViewWithPaddingScreen { + button1 { + click() + } + } + } + } + +} diff --git a/samples/kaspresso-sample/src/main/AndroidManifest.xml b/samples/kaspresso-sample/src/main/AndroidManifest.xml index 6d7f90a56..b1e77c63b 100644 --- a/samples/kaspresso-sample/src/main/AndroidManifest.xml +++ b/samples/kaspresso-sample/src/main/AndroidManifest.xml @@ -34,6 +34,10 @@ + + diff --git a/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/MainActivity.kt b/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/MainActivity.kt index d75df031c..5596c979c 100644 --- a/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/MainActivity.kt +++ b/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/MainActivity.kt @@ -3,6 +3,7 @@ package com.kaspersky.kaspressample import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.kaspersky.kaspressample.scrollresolver.ScrollResolverActivity import com.kaspersky.kaspressample.compose.ComplexComposeSampleActivity import com.kaspersky.kaspressample.continuously.ContinuouslySampleActivity import com.kaspersky.kaspressample.flaky.CommonFlakyActivity @@ -20,6 +21,12 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + activity_main_auto_scroll_fallback_button.setOnClickListener { + startActivity( + Intent(this, ScrollResolverActivity::class.java) + ) + } + activity_main_simple_sample_button.setOnClickListener { startActivity( Intent(this, SimpleActivity::class.java) diff --git a/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/scrollresolver/ScrollResolverActivity.kt b/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/scrollresolver/ScrollResolverActivity.kt new file mode 100644 index 000000000..2b572b17a --- /dev/null +++ b/samples/kaspresso-sample/src/main/kotlin/com/kaspersky/kaspressample/scrollresolver/ScrollResolverActivity.kt @@ -0,0 +1,13 @@ +package com.kaspersky.kaspressample.scrollresolver + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.kaspersky.kaspressample.R + +class ScrollResolverActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_scrollview_with_padding) + } +} diff --git a/samples/kaspresso-sample/src/main/res/layout/activity_main.xml b/samples/kaspresso-sample/src/main/res/layout/activity_main.xml index c167a6288..dcd8d354e 100644 --- a/samples/kaspresso-sample/src/main/res/layout/activity_main.xml +++ b/samples/kaspresso-sample/src/main/res/layout/activity_main.xml @@ -18,10 +18,17 @@ android:textSize="18pt" />