Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically scroll when needed when clicking in tests #6092

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ public void when_scrollQuestionsList_should_questionsNotDisappear() {
.copyForm("3403.xml", asList("staff_list.csv", "staff_rights.csv"))
.startBlankForm("3403_ODK Version 1.23.3 Tester")
.clickOnText("New Farmer Registration")
.scrollToAndClickText("Insemination")
.clickOnText("Insemination")
.assertText("New Farmer Registration");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.odk.collect.android.support

import android.view.View
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Root
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.scrollTo
import org.hamcrest.Matcher
import org.odk.collect.android.support.WaitFor.tryAgainOnFail

object Interactions {

/**
* Click on the view matched by [view]. The root to use can optionally be specified with
* [root] (otherwise Espresso will use heuristics to determine the most likely root). If
* initially clicking on the view fails, this will then attempt to scroll to the view and
* retry the click.
*/
fun clickOn(view: Matcher<View>, root: Matcher<Root>? = null) {
val onView = if (root != null) {
onView(view).inRoot(root)
} else {
onView(view)
}

try {
onView.perform(click())
} catch (e: Exception) {
onView.perform(scrollTo(), click())
}
}

/**
* Like [clickOn], but an [assertion] can be made after the click. If this fails, the click
* action will be reattempted.
*
* This can be useful in cases where [clickOn] itself appears to succeed, but the test fails
* because the click never actually occurs (most likely due to some flakiness in
* [androidx.test.espresso.action.ViewActions.click]).
*/
fun clickOn(view: Matcher<View>, root: Matcher<Root>? = null, assertion: () -> Unit) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this helper is advisable over the other clickOn as it seems there's some flakiness in Espresso's ViewActions.click that are resolved with this "try again on fail" approach.

tryAgainOnFail {
clickOn(view, root)
assertion()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
package org.odk.collect.android.support.pages

import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import org.odk.collect.android.support.Interactions
import org.odk.collect.strings.R.string

class FirstLaunchPage : Page<FirstLaunchPage>() {

override fun assertOnPage(): FirstLaunchPage {
assertText(org.odk.collect.strings.R.string.configure_with_qr_code)
assertText(string.configure_with_qr_code)
return this
}

fun clickTryCollect(): MainMenuPage {
return tryAgainOnFail(MainMenuPage()) {
scrollToAndClickSubtext(org.odk.collect.strings.R.string.try_demo)
Interactions.clickOn(withSubstring(getTranslatedString(string.try_demo))) {
MainMenuPage().assertOnPage()
}

return MainMenuPage()
}

fun clickManuallyEnterProjectDetails(): ManualProjectCreatorDialogPage {
scrollToAndClickText(org.odk.collect.strings.R.string.configure_manually)
return ManualProjectCreatorDialogPage().assertOnPage()
return clickOnString(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should resolve this flake.

string.configure_manually,
ManualProjectCreatorDialogPage()
)
}

fun clickConfigureWithQrCode(): QrCodeProjectCreatorDialogPage {
scrollToAndClickText(org.odk.collect.strings.R.string.configure_with_qr_code)
return QrCodeProjectCreatorDialogPage().assertOnPage()
return clickOnString(
string.configure_with_qr_code,
QrCodeProjectCreatorDialogPage()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withHint
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withSubstring
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
Expand All @@ -50,6 +49,7 @@ import org.odk.collect.android.R
import org.odk.collect.android.application.Collect
import org.odk.collect.android.storage.StoragePathProvider
import org.odk.collect.android.support.ActivityHelpers.getLaunchIntent
import org.odk.collect.android.support.Interactions
import org.odk.collect.android.support.WaitFor.tryAgainOnFail
import org.odk.collect.android.support.WaitFor.wait250ms
import org.odk.collect.android.support.WaitFor.waitFor
Expand Down Expand Up @@ -239,8 +239,7 @@ abstract class Page<T : Page<T>> {
}

fun <D : Page<D>> clickOnString(stringID: Int, destination: D): D {
tryAgainOnFail {
clickOnString(stringID)
Interactions.clickOn(withText(getTranslatedString(stringID))) {
destination.assertOnPage()
}

Expand All @@ -253,12 +252,12 @@ abstract class Page<T : Page<T>> {
}

fun clickOnText(text: String): T {
onView(withText(text)).perform(click())
Interactions.clickOn(withText(text))
return this as T
}

fun clickOnId(id: Int): T {
onView(withId(id)).perform(click())
Interactions.clickOn(withId(id))
return this as T
}

Expand All @@ -270,26 +269,20 @@ abstract class Page<T : Page<T>> {
fun clickOKOnDialog(): T {
closeSoftKeyboard() // Make sure to avoid issues with keyboard being up
waitForDialogToSettle()
onView(withId(android.R.id.button1))
.inRoot(isDialog())
.perform(click())
Interactions.clickOn(withId(android.R.id.button1), root = isDialog())
return this as T
}

fun <D : Page<D>?> clickOKOnDialog(destination: D): D {
closeSoftKeyboard() // Make sure to avoid issues with keyboard being up
waitForDialogToSettle()
onView(withId(android.R.id.button1))
.inRoot(isDialog())
.perform(click())
Interactions.clickOn(withId(android.R.id.button1), root = isDialog())
return destination!!.assertOnPage()
}

fun clickOnTextInDialog(text: String): T {
waitForDialogToSettle()
onView(withText(text))
.inRoot(isDialog())
.perform(click())
Interactions.clickOn(withText(text), root = isDialog())
return this as T
}

Expand All @@ -315,7 +308,7 @@ abstract class Page<T : Page<T>> {
}

fun clickOnAreaWithIndex(clazz: String?, index: Int): T {
onView(withIndex(withClassName(endsWith(clazz)), index)).perform(click())
Interactions.clickOn(withIndex(withClassName(endsWith(clazz)), index))
return this as T
}

Expand Down Expand Up @@ -382,21 +375,6 @@ abstract class Page<T : Page<T>> {
return this as T
}

fun scrollToAndClickText(text: Int): T {
onView(withText(getTranslatedString(text))).perform(scrollTo(), click())
return this as T
}

fun scrollToAndClickSubtext(text: Int): T {
onView(withSubstring(getTranslatedString(text))).perform(scrollTo(), click())
return this as T
}

fun scrollToAndClickText(text: String?): T {
onView(withText(text)).perform(scrollTo(), click())
return this as T
}

fun scrollToRecyclerViewItemAndClickText(text: String?): T {
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(hasDescendant(withText(text)), scrollTo()))
onView(withId(androidx.preference.R.id.recycler_view)).perform(RecyclerViewActions.actionOnItem<RecyclerView.ViewHolder>(hasDescendant(withText(text)), click()))
Expand Down Expand Up @@ -478,7 +456,7 @@ abstract class Page<T : Page<T>> {
}

fun closeSnackbar(): T {
onView(withContentDescription(org.odk.collect.strings.R.string.close_snackbar)).perform(click())
Interactions.clickOn(withContentDescription(org.odk.collect.strings.R.string.close_snackbar))
return this as T
}

Expand All @@ -487,8 +465,7 @@ abstract class Page<T : Page<T>> {
}

fun clickOptionsIcon(expectedOptionString: String): T {
tryAgainOnFail {
onView(OVERFLOW_BUTTON_MATCHER).perform(click())
Interactions.clickOn(OVERFLOW_BUTTON_MATCHER) {
assertText(expectedOptionString)
}

Expand Down Expand Up @@ -532,7 +509,7 @@ abstract class Page<T : Page<T>> {
}

fun clickOnTextInPopup(text: Int): T {
onView(withText(text)).inRoot(isPlatformPopup()).perform(click())
Interactions.clickOn(withText(text), root = isPlatformPopup())
return this as T
}

Expand Down