diff --git a/.github/workflows/commcare-android-pr-workflow.yml b/.github/workflows/commcare-android-pr-workflow.yml index 0ff53f28ca..bea0e4d11f 100644 --- a/.github/workflows/commcare-android-pr-workflow.yml +++ b/.github/workflows/commcare-android-pr-workflow.yml @@ -224,5 +224,5 @@ jobs: BROWSERSTACK_PASSWORD: ${{ secrets.BROWSERSTACK_PASSWORD }} RELEASE_APP_LOCATION: commcare-release-apk/app-commcare-release.apk TEST_APP_LOCATION: commcare-release-androidTest-apk/app-commcare-release-androidTest.apk - BROWSERSTACK_DEVICES: "Samsung Galaxy S10-9.0" + BROWSERSTACK_DEVICES: "Samsung Galaxy S20-10.0" PR_NUMBER: ${{ github.event.number }} diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/CaseClaimTest.java b/app/instrumentation-tests/src/org/commcare/androidTests/CaseClaimTest.java index 230494f01b..1c49864bf6 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/CaseClaimTest.java +++ b/app/instrumentation-tests/src/org/commcare/androidTests/CaseClaimTest.java @@ -20,9 +20,11 @@ import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.commcare.utils.InstrumentationUtility.waitForView; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; @@ -126,6 +128,8 @@ public void testCaseClaimByDifferentUser() { .perform(click()); // Close the new case of cordelia with this user. + onView(withId(R.id.screen_entity_select_list)) + .perform(waitForView(withText(name), 10000, true)); onView(withText(name)) .perform(click()); onView(withText("Continue")) diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/DialogTests.kt b/app/instrumentation-tests/src/org/commcare/androidTests/DialogTests.kt index 78f92ce3d5..a993804c34 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/DialogTests.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/DialogTests.kt @@ -19,8 +19,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @LargeTest @BrowserstackTests -class DialogTests: BaseTest() { - +class DialogTests : BaseTest() { companion object { const val CCZ_NAME = "integration_test_app.ccz" const val APP_NAME = "Integration Tests" @@ -36,26 +35,26 @@ class DialogTests: BaseTest() { fun testDialogCreation() { InstrumentationUtility.openModule("Errors") onView(withText("Error on open")) - .perform(click()) + .perform(click()) checkDialogExistence_withRotation("Error Occurred") onView(withText("Error on repeat creation")) - .perform(click()) + .perform(click()) onView(withId(R.id.nav_btn_next)) - .perform(click()) + .perform(click()) onView(withId(R.id.choice_dialog_panel_2)).check(matches(withText("Add a new Error on add?"))) InstrumentationUtility.rotateLeft() - //TODO Expect dialog to not persist due to a activity lifecycle bug in our dialog framework. + // TODO Expect dialog to not persist due to a activity lifecycle bug in our dialog framework. withText(R.id.choice_dialog_panel_2).doesNotExist() InstrumentationUtility.rotatePortrait() onView(withId(R.id.nav_btn_next)) - .perform(click()) + .perform(click()) onView(withId(R.id.choice_dialog_panel_2)) - .perform(click()) + .perform(click()) checkDialogExistence_withRotation("Error Occurred") @@ -69,8 +68,7 @@ class DialogTests: BaseTest() { InstrumentationUtility.rotateLeft() withText(text).isDisplayed() InstrumentationUtility.rotatePortrait() - onView(withText("OK")) - .perform(click()) + onView(withText(R.string.ok)) + .perform(click()) } - } diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentLimitTest.kt b/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentLimitTest.kt index 1f5dfb927c..708c26ab3b 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentLimitTest.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentLimitTest.kt @@ -30,8 +30,10 @@ import org.junit.runner.RunWith @LargeTest @BrowserstackTests class FormAttachmentLimitTest : BaseTest() { - private val CCZ_NAME = "media_capture.ccz" - private val APP_NAME = "Media Capture Test" + companion object { + const val CCZ_NAME = "media_capture.ccz" + const val APP_NAME = "Media Capture Test" + } @Before fun setup() { diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentUploadTest.java b/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentUploadTest.java index 36e76be597..24a3d5bd22 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentUploadTest.java +++ b/app/instrumentation-tests/src/org/commcare/androidTests/FormAttachmentUploadTest.java @@ -19,6 +19,7 @@ import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; import static junit.framework.Assert.assertEquals; @@ -57,6 +58,8 @@ public void testAttachmentUpload() { onView(withText("Gather Signature")) .perform(click()); + onView(isRoot()) + .perform(InstrumentationUtility.waitForView(instanceOf(DrawView.class), 5000, true)); onView(instanceOf(DrawView.class)) .perform(new GeneralSwipeAction(Swipe.SLOW, GeneralLocation.CENTER_LEFT, GeneralLocation.TOP_RIGHT, Press.FINGER)); onView(instanceOf(DrawView.class)) diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/LoginTest.kt b/app/instrumentation-tests/src/org/commcare/androidTests/LoginTest.kt index e7ffc5b0ea..909dbd4e57 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/LoginTest.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/LoginTest.kt @@ -101,6 +101,7 @@ class LoginTest: BaseTest() { InstrumentationUtility.logout() // login offline with bad password + InstrumentationUtility.changeWifi(false) InstrumentationUtility.login("user_with_no_data", "badpass") onView(withText("Either the password you entered was incorrect, or CommCare couldn't reach the server")) .check(matches(isDisplayed())) diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/ManualQuarantineTest.kt b/app/instrumentation-tests/src/org/commcare/androidTests/ManualQuarantineTest.kt index 030a96ce3b..0754d2a4d7 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/ManualQuarantineTest.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/ManualQuarantineTest.kt @@ -10,7 +10,10 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.SdkSuppress import org.commcare.dalvik.R -import org.commcare.utils.* +import org.commcare.utils.CustomMatchers +import org.commcare.utils.InstrumentationUtility +import org.commcare.utils.doesNotExist +import org.commcare.utils.isDisplayed import org.hamcrest.Matchers.allOf import org.junit.After import org.junit.Before @@ -23,8 +26,7 @@ import org.junit.runners.MethodSorters @LargeTest @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.Q) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class ManualQuarantineTest: BaseTest() { - +class ManualQuarantineTest : BaseTest() { companion object { const val CCZ_NAME = "ccqa.ccz" const val APP_NAME = "Basic Tests" @@ -49,7 +51,7 @@ class ManualQuarantineTest: BaseTest() { InstrumentationUtility.gotoHome() InstrumentationUtility.selectOptionItem(withText("Advanced")) onView(withText("Enable Manual Form Quarantine")) - .perform(click()) + .perform(click()) InstrumentationUtility.gotoHome() } @@ -57,82 +59,86 @@ class ManualQuarantineTest: BaseTest() { fun test_A_Quarantine() { InstrumentationUtility.openModule(MODULE_DISPLAY_FORM) onView(withId(R.id.nav_btn_finish)) - .perform(click()) + .perform(click()) InstrumentationUtility.openModule(MODULE_DISPLAY_FORM) onView(withId(R.id.nav_btn_finish)) - .perform(click()) + .perform(click()) // Go to saved forms and quarantine them InstrumentationUtility.selectOptionItem(withText("Saved Forms")) // Quarantine first form. - onView(CustomMatchers.find( + onView( + CustomMatchers.find( allOf(withText("Display Form")), - 1 - )).perform(longClick()) + 1, + ), + ).perform(longClick()) // Unsent forms should not be delete-able withText("Delete Record").doesNotExist() onView(withText("Scan Record Integrity")) - .perform(click()) + .perform(click()) onView(withText("QUARANTINE FORM")) - .perform(click()) + .perform(click()) // After quarantining one form we can't quarantine another before re-enabling it from setting. onView(withText("Display Form")) - .perform(longClick()) + .perform(longClick()) onView(withText("Scan Record Integrity")) - .perform(click()) + .perform(click()) withText("QUARANTINE FORM").doesNotExist() - onView(withText("OK")) - .perform(click()) + onView(withText(R.string.ok)) + .perform(click()) enableFormQuarantine() // Quarantine second form InstrumentationUtility.selectOptionItem(withText("Saved Forms")) onView(withText("Display Form")) - .perform(longClick()) + .perform(longClick()) withText("Delete Record").doesNotExist() onView(withText("Scan Record Integrity")) - .perform(click()) + .perform(click()) onView(withText("QUARANTINE FORM")) - .perform(click()) + .perform(click()) } @Test fun test_B_FormSubmission_withQuarantineForm() { InstrumentationUtility.selectOptionItem(withText("Saved Forms")) onView(withId(R.id.entity_select_filter_dropdown)) - .perform(click()) + .perform(click()) onView(withText("Filter: Quarantined Forms")) - .perform(click()) + .perform(click()) // Send 1 form back to unsent queue - onView(CustomMatchers.find( + onView( + CustomMatchers.find( allOf(withText("Display Form")), - 1 - )).perform(longClick()) + 1, + ), + ).perform(longClick()) onView(withText("Add Record Back to Unsent Queue")) - .perform(click()) + .perform(click()) // Confirm there is 1 form left in quarantine, and that you can delete it onView(withText("Display Form")) - .perform(longClick()) + .perform(longClick()) onView(withText("Delete Record")) - .perform(click()) + .perform(click()) withText("Display Form").doesNotExist() // Confirm 1 form is now in unsent onView(withId(R.id.entity_select_filter_dropdown)) - .perform(click()) + .perform(click()) onView(withText("Filter By: Only Unsent Forms")) - .perform(click()) + .perform(click()) withText("Display Form").isDisplayed() InstrumentationUtility.changeWifi(true) InstrumentationUtility.gotoHome() onView(withText("Sync with Server")) - .perform(click()) + .perform(click()) withText("Sync Successful! Your information is up to date.").isDisplayed() } } diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/MenuTests.kt b/app/instrumentation-tests/src/org/commcare/androidTests/MenuTests.kt index e20ff91735..1dc7be7fb0 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/MenuTests.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/MenuTests.kt @@ -8,11 +8,17 @@ import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.filters.SdkSuppress -import org.commcare.activities.* +import org.commcare.activities.CommCareVerificationActivity +import org.commcare.activities.CommCareWiFiDirectActivity +import org.commcare.activities.FormRecordListActivity +import org.commcare.activities.RecoveryActivity +import org.commcare.activities.UpdateActivity import org.commcare.annotations.BrowserstackTests import org.commcare.dalvik.R import org.commcare.utils.CustomMatchers @@ -21,36 +27,42 @@ import org.hamcrest.Matchers.startsWith import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import kotlin.jvm.java @RunWith(AndroidJUnit4::class) @LargeTest -class MenuTests: BaseTest() { - +class MenuTests : BaseTest() { companion object { const val CCZ_NAME = "settings_sheet_tests.ccz" const val APP_NAME = "App for Settings Sheet" - val homeMenuItems = arrayOf("Update App", + val homeMenuItems = + arrayOf( + "Update App", "Saved Forms", "Change Language", "About CommCare", "Advanced", - "Settings") + "Settings", + ) - val advancedOptions = arrayOf("Wi-Fi Direct", + val advancedOptions = + arrayOf( + "Wi-Fi Direct", "Manage SD", "Report Problem", "Force Log Submission", "Validate Media", "Connection Test", "Recovery Mode", - "Clear User Data") + "Clear User Data", + ) } @Before fun setup() { installApp(APP_NAME, CCZ_NAME) - InstrumentationUtility.login("settings.test","123") + InstrumentationUtility.login("settings.test", "123") } @Test @@ -62,63 +74,72 @@ class MenuTests: BaseTest() { checkStringExists(homeMenuItems) InstrumentationUtility.rotatePortrait() + // wait for the menu to settle after the rotation flash + InstrumentationUtility.waitForView(withText(homeMenuItems[0])) + onView(withText(homeMenuItems[0])) - .perform(click()) + .perform(click()) Intents.intended(IntentMatchers.hasComponent(UpdateActivity::class.java.name)) onView(withText("App is up to date")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) InstrumentationUtility.gotoHome() InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[1])) - .perform(click()) + .perform(click()) Intents.intended(IntentMatchers.hasComponent(FormRecordListActivity::class.java.name)) onView(withText(startsWith("Filter By:"))) - .perform(click()) - checkStringExists(arrayOf( + .perform(click()) + checkStringExists( + arrayOf( "Filter By: All Completed Forms", "Filter By: Only Submitted Forms", "Filter By: Only Unsent Forms", "Only Incomplete Forms", - "Filter: Quarantined Forms")) + "Filter: Quarantined Forms", + ), + ) InstrumentationUtility.gotoHome() InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[2])) - .perform(click()) + .perform(click()) // Confirm we see 2 choices onView(withId(R.id.choices_list_view)) - .check(matches(CustomMatchers.matchListSize(2))) + .check(matches(CustomMatchers.matchListSize(2))) InstrumentationUtility.gotoHome() InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[3])) - .perform(click()) + .perform(click()) onView(withText("About CommCare")) - .check(matches(isDisplayed())) - onView(withText("OK")) - .perform(click()) + .check(matches(isDisplayed())) + onView(withText(R.string.ok)) + .perform(click()) InstrumentationUtility.gotoHome() InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[4])) - .perform(click()) + .perform(click()) onView(withText("CommCare > Advanced")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) checkStringExists(advancedOptions) InstrumentationUtility.gotoHome() InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[5])) - .perform(click()) + .perform(click()) onView(withText("CommCare > Settings")) - .check(matches(isDisplayed())) - checkStringExists(arrayOf( + .check(matches(isDisplayed())) + checkStringExists( + arrayOf( "Auto Update Frequency", "Set Print Template", "Grid Menus Enabled", "Fuzzy Search Matches", - "Opt Out of Analytics")) + "Opt Out of Analytics", + ), + ) InstrumentationUtility.gotoHome() } @@ -127,39 +148,39 @@ class MenuTests: BaseTest() { fun testAdvancedActions() { openAdvancedOption(0) onView(withText("Do you want to send, receive, or submit forms?")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withId(R.id.negative_button)) - .perform(click()) + .perform(click()) Intents.intended(IntentMatchers.hasComponent(CommCareWiFiDirectActivity::class.java.name)) InstrumentationUtility.gotoHome() openAdvancedOption(1) val formDumpConfirmation = "Do not use this feature unless you have been trained to do so. Do you wish to proceed?" onView(withText(formDumpConfirmation)) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withId(R.id.negative_button)) - .perform(click()) + .perform(click()) onView(withText("CommCare > Advanced")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withText(advancedOptions[1])) - .perform(click()) + .perform(click()) onView(withText(formDumpConfirmation)) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withId(R.id.positive_button)) - .perform(click()) + .perform(click()) onView(withText(startsWith("Dump Forms"))) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) onView(withText(startsWith("Submit Forms"))) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) Espresso.pressBack() onView(withText(formDumpConfirmation)) - .check(doesNotExist()) + .check(doesNotExist()) InstrumentationUtility.gotoHome() openAdvancedOption(4) Intents.intended(IntentMatchers.hasComponent(CommCareVerificationActivity::class.java.name)) onView(withId(R.id.home_gridview_buttons)) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) openAdvancedOption(6) Intents.intended(IntentMatchers.hasComponent(RecoveryActivity::class.java.name)) @@ -167,9 +188,9 @@ class MenuTests: BaseTest() { openAdvancedOption(7) onView(withId(R.id.positive_button)) - .perform(click()) + .perform(click()) onView(withText("Welcome back! Please log in.")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) } @SdkSuppress(maxSdkVersion = Build.VERSION_CODES.Q) @@ -177,14 +198,14 @@ class MenuTests: BaseTest() { fun runConnectionTest() { openAdvancedOption(5) onView(withId(R.id.run_connection_test)) - .perform(click()) + .perform(click()) onView(withText("No problems were detected.")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) InstrumentationUtility.changeWifi(false) onView(withId(R.id.run_connection_test)) - .perform(click()) + .perform(click()) onView(withText("You are not connected the Internet. Please run this test again after connecting to Wi-Fi or mobile data.")) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) InstrumentationUtility.changeWifi(true) InstrumentationUtility.logout() } @@ -192,15 +213,15 @@ class MenuTests: BaseTest() { private fun openAdvancedOption(index: Int) { InstrumentationUtility.openOptionsMenu() onView(withText(homeMenuItems[4])) - .perform(click()) + .perform(click()) onView(withText(advancedOptions[index])) - .perform(click()) + .perform(click()) } private fun checkStringExists(arr: Array) { arr.forEach { item -> onView(withText(item)) - .check(matches(isDisplayed())) + .check(matches(isDisplayed())) } } } diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/SessionExpirationTests.kt b/app/instrumentation-tests/src/org/commcare/androidTests/SessionExpirationTests.kt index 8c72c0302e..fdd740f985 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/SessionExpirationTests.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/SessionExpirationTests.kt @@ -1,42 +1,26 @@ package org.commcare.androidTests -import android.widget.FrameLayout -import androidx.test.espresso.Espresso.* -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.By import androidx.test.uiautomator.UiDevice -import androidx.test.uiautomator.UiObject -import org.commcare.dalvik.R import androidx.test.uiautomator.Until -import androidx.test.uiautomator.Until.findObject -import junit.framework.Assert.assertNotNull +import org.commcare.AppUtils import org.commcare.annotations.BrowserstackTests import org.commcare.dalvik.test.BuildConfig import org.commcare.utils.InstrumentationUtility -import org.commcare.utils.isPresent -import org.hamcrest.Matchers.* - -import org.junit.Assert import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import java.text.DateFormat -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter -import java.util.* @RunWith(AndroidJUnit4::class) @LargeTest @BrowserstackTests -class SessionExpirationTests: BaseTest() { +class SessionExpirationTests : BaseTest() { companion object { const val CCZ_NAME = "session_expiration_test.ccz" const val APP_NAME = "Session Expiration Test" @@ -49,20 +33,19 @@ class SessionExpirationTests: BaseTest() { } @Test - fun testRestoreUser(){ + fun testRestoreUser() { val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) uiDevice.openNotification() - if (BuildConfig.DEBUG) { - uiDevice.wait(Until.hasObject(By.textStartsWith("Logged Into Commcare")),5000) + if (BuildConfig.DEBUG || AppUtils.getInstalledAppRecords().size <= 1) { + uiDevice.wait(Until.hasObject(By.textStartsWith("Logged Into Commcare")), 5000) } else { - uiDevice.wait(Until.hasObject(By.textStartsWith("Logged Into "+ APP_NAME)),5000) + uiDevice.wait(Until.hasObject(By.textStartsWith("Logged Into $APP_NAME")), 5000) } uiDevice.findObject(By.textStartsWith("Session Expires:")).click() - uiDevice.wait(Until.hasObject(By.textStartsWith("Welcome")),45000) + uiDevice.wait(Until.hasObject(By.textStartsWith("Welcome")), 45000) - //after the user is logged out, verifies the login expired notification + // after the user is logged out, verifies the login expired notification uiDevice.openNotification() uiDevice.wait(Until.hasObject(By.textEndsWith("Login Expire")), 1000) } - } diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/SignatureTests.kt b/app/instrumentation-tests/src/org/commcare/androidTests/SignatureTests.kt index 5a68d2f0b1..314e2f4e74 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/SignatureTests.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/SignatureTests.kt @@ -15,6 +15,7 @@ import org.commcare.annotations.BrowserstackTests import org.commcare.utils.InstrumentationUtility import org.commcare.views.DrawView import org.commcare.dalvik.R +import org.commcare.utils.InstrumentationUtility.sleep import org.commcare.utils.doesNotExist import org.commcare.utils.isDisplayed import org.hamcrest.Matchers @@ -127,6 +128,7 @@ class SignatureTests: BaseTest() { Espresso.pressBack() withText(R.string.keep_changes).doesNotExist() Espresso.pressBack() + sleep(1) } /** diff --git a/app/instrumentation-tests/src/org/commcare/androidTests/XFormErrorTests.kt b/app/instrumentation-tests/src/org/commcare/androidTests/XFormErrorTests.kt index 7c32b61d4e..8fc02ef725 100644 --- a/app/instrumentation-tests/src/org/commcare/androidTests/XFormErrorTests.kt +++ b/app/instrumentation-tests/src/org/commcare/androidTests/XFormErrorTests.kt @@ -1,40 +1,24 @@ package org.commcare.androidTests - -import android.view.KeyCharacterMap -import android.view.KeyEvent -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.action.ViewActions.* -import androidx.test.espresso.contrib.RecyclerViewActions -import androidx.test.espresso.matcher.ViewMatchers -import androidx.test.espresso.matcher.ViewMatchers.* +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.matcher.ViewMatchers.withSubstring +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import junit.framework.Assert.assertTrue import org.commcare.annotations.BrowserstackTests import org.commcare.dalvik.R -import org.commcare.utils.CustomMatchers import org.commcare.utils.InstrumentationUtility import org.commcare.utils.isPresent -import org.hamcrest.Matcher -import org.hamcrest.Matchers -import org.hamcrest.Matchers.* -import org.hamcrest.TypeSafeMatcher import org.junit.Before import org.junit.Test -import org.junit.runner.Description import org.junit.runner.RunWith -import java.util.* - @RunWith(AndroidJUnit4::class) @LargeTest @BrowserstackTests -class XFormErrorTests: BaseTest() { +class XFormErrorTests : BaseTest() { companion object { const val CCZ_NAME = "other.ccz" const val APP_NAME = "Other Test" @@ -47,33 +31,36 @@ class XFormErrorTests: BaseTest() { } @Test - fun testXformErrors(){ - InstrumentationUtility.openForm(1,0) + fun testXformErrors() { + InstrumentationUtility.openForm(1, 0) testImmediateFail() testDelayedFail() testRelevancyFail() testRepeatError() } - fun testImmediateFail(){ + fun testImmediateFail() { assertTrue(onView(withSubstring("Error in calculation for /data/immediate_fail")).isPresent()) onView(withText("OK")).perform(click()) assertTrue(onView(withText("XForm Error Tests")).isPresent()) } - fun testDelayedFail(){ + + fun testDelayedFail() { onView(withText("Delayed Fail")).perform(click()) onView(withText("Item 1")).perform(click()) assertTrue(onView(withSubstring("Error in calculation for /data/calculated_value")).isPresent()) onView(withText("OK")).perform(click()) assertTrue(onView(withText("XForm Error Tests")).isPresent()) } - fun testRelevancyFail(){ + + fun testRelevancyFail() { onView(withText("Relevancy Fail")).perform(click()) assertTrue(onView(withSubstring("Error in calculation for /data/bad_relevancy")).isPresent()) onView(withText("OK")).perform(click()) assertTrue(onView(withText("XForm Error Tests")).isPresent()) } - fun testRepeatError(){ + + fun testRepeatError() { onView(withText("Repeat Error")).perform(click()) assertTrue(onView(withSubstring("Error in calculation for /data/hidden_value")).isPresent()) onView(withText("OK")).perform(click()) diff --git a/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt b/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt index 67f9078239..8c2d6f0ada 100644 --- a/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt +++ b/app/instrumentation-tests/src/org/commcare/utils/InstrumentationUtility.kt @@ -169,6 +169,7 @@ object InstrumentationUtility { // Click on About CommCare 4 times to become developer. for (i in 0..3) { openOptionsMenu() + onView(isRoot()).perform(waitForView(withText("About CommCare"))) onView(withText("About CommCare")) .perform(click()) onView(withText("OK")) @@ -332,14 +333,25 @@ object InstrumentationUtility { */ @JvmStatic fun changeWifi(enable: Boolean) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - val context = InstrumentationRegistry.getInstrumentation().targetContext - val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager - wifiManager.isWifiEnabled = enable - sleep(10) // Sleep 5 seconds so that wifi is set up. - } else { - throw IllegalAccessException("changeWifi should only be called in pre-android Q devices") + val uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + uiDevice.executeShellCommand(if (enable) "svc wifi enable" else "svc wifi disable") + waitForWifiState(enable) + } + + private fun waitForWifiState( + expectedEnabled: Boolean, + timeoutMs: Long = 10_000, + ) { + val context = InstrumentationRegistry.getInstrumentation().targetContext + val wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager + val deadline = System.currentTimeMillis() + timeoutMs + while (System.currentTimeMillis() < deadline) { + if (wifiManager.isWifiEnabled == expectedEnabled) return + sleep(2) } + throw IllegalStateException( + "changeWifi did not reach state=${expectedEnabled.let { if (it) "enabled" else "disabled" }} within ${timeoutMs}ms", + ) } /** @@ -575,6 +587,7 @@ object InstrumentationUtility { * A utility to wait until a certain view appears * usage: onView(isRoot()).perform(waitForView(withText(""))) */ + @JvmStatic fun waitForView( viewMatcher: Matcher, timeout: Long = 10000, @@ -586,7 +599,8 @@ object InstrumentationUtility { override fun getDescription(): String { val matcherDescription = StringDescription() viewMatcher.describeTo(matcherDescription) - return "wait for a specific view <$matcherDescription> to be ${if (waitForDisplayed) "displayed" else "not displayed during $timeout millis."}" + return "wait for a specific view <$matcherDescription> " + + "to be ${if (waitForDisplayed) "displayed" else "not displayed during $timeout millis."}" } override fun perform( diff --git a/app/res/layout/entity_select_layout.xml b/app/res/layout/entity_select_layout.xml index 307cb99d0f..ac1b753dec 100644 --- a/app/res/layout/entity_select_layout.xml +++ b/app/res/layout/entity_select_layout.xml @@ -34,6 +34,7 @@ android:background="@null" android:padding="@dimen/entity_item_image_margins" app:srcCompat="@drawable/close_cross_icon" + android:contentDescription="@string/clear_search_button_description" android:layout_alignParentEnd="true" /> diff --git a/app/res/values/strings.xml b/app/res/values/strings.xml index 5fa590773e..2599c57cce 100644 --- a/app/res/values/strings.xml +++ b/app/res/values/strings.xml @@ -707,4 +707,5 @@ This form has reached the maximum of %s attachments. To add a new one, remove an existing attachment first. Can\'t leave page while saving form + Clear query