How to check MenuItem is enabled/disabled in Espresso UI Automation Test - android

I am writing UI Automation tests in Espresso for Android & came across a scenario for which I haven't got any solution so far.
In one Fragment, I have OptionsMenu with a single item. The state of that MenuItem is set according to value from API response.
#Override
public void onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.clear();
getActivity().getMenuInflater().inflate(R.menu.menu_cancel_order, menu);
MenuItem cancelMenuItem = menu.findItem(R.id.cancel_order);
if(something) { // something can be a boolean value from server
cancelMenuItem.setEnabled(true);
} else {
cancelMenuItem.setEnabled(false);
}
}
For UI testing, I need to write test case for checking whether this MenuItem is enabled/disabled.
For clicking on the overflowmenu,
ViewInteraction actionMenuItemView = onView(
allOf(withId(R.id.action_settings), withContentDescription("Settings"), isDisplayed()));
actionMenuItemView.perform(click());
And so far what I have tried to check the Assertion is given below.
onView(allOf(withText("Cancel Order"), withId(R.id.cancel_order))).check(matches(not(isEnabled())));
But this fires NoMatchingViewException with message
NoMatchingViewException: No views in hierarchy found matching: (with
text: is "Cancel Order" and with id:
com.equinix.ecp.betatest:id/cancel_order)
So I tried changing it to
onView(allOf(withText("Cancel Order"))).check(matches(not(isEnabled())));
Somehow this matched the view but it was not a MenuItem but the TextView inside the MenuItem & since I am setting setEnabled() to MenuItem, check() Assertion won't work as expected since it is a TextView.
So my question is how to write Test for checking enabled/disabled state of MenuItem.

it would be a good idea to make use of uiautomatorviewer, to put a breakpoint in at the point your test fails, and then inspect your app's layout for clues
it sounds to me that you have two views. One with the id of R.id.cancel_order and another with text "Cancel Order" which probably has another id (or could/should).
So together they return NoMatchingView, because they aren't the same view.
They could be sibling views, or possibly one is a descendent of another. This is where uiautomatorviewer is very handy for figuring out what's happening on screen
as long as you've installed "Android SDK Platform-Tools" and "Android SDK Tools"
from Terminal:
cd /Users/<user name>/Library/Android/sdk/tools/bin
./uiautomatorviewer
(it's also helpful to save this as a script and just use an alias shortcut for convenience)
as for your matcher, i would try :
onView(allOf(
withId(R.id.cancel_order),
hasSibling(withText("Cancel Order"))
)).check(matches(not(isEnabled())));
or change hasSibling(_) to hasDescendent(_) or isDescendentOfA(_), depending on their relationship (which you can find out by using uiautomatorviewer)

I would suggest you use the IDs of the menu items to perform your checks.
I tried it with this menu:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="at.hellobank.hellomarkets.symbols.DetailActivity">
<item
android:id="#+id/action_1"
android:icon="#android:drawable/arrow_down_float"
android:title="Menu1"
app:showAsAction="always" />
<item
android:id="#+id/action_2"
android:enabled="false"
android:icon="#android:drawable/arrow_down_float"
android:title="Menu2"
app:showAsAction="always" />
</menu>
So one menu item is enabled one is disabled. My test to check this looks like this and is working as expected:
#Test
public void testMenuItemsStatus() throws Exception {
onView(withId(R.id.action_1)).check(matches(isEnabled()));
onView(withId(R.id.action_2)).check(matches(not(isEnabled())));
}
Generally using IDs in tests is better imho because you are more independent of typos and general language. withText("Cancel Order") probably will not work if you test the app localized in another language.

Related

Switch button animation doesn't work in Drawer Navigation View

After spending a lot of time and not finding a suitable solution, I decided to turn to this service for help.
I have an example of the simplest application that implements Drawer. This example contains only menu items without any implementations. In the menu item Share, I added a Switch and a listener to it:
main_nav.xml
<item
android:id="#+id/nav_share"
android:icon="#drawable/ic_menu_share"
app:actionViewClass="androidx.appcompat.widget.SwitchCompat"
android:title="#string/menu_share" />
MainActivity.kt
val menuItem = navigation_view.menu.findItem(R.id.nav_share)
val switch_id = menuItem.actionView as SwitchCompat
switch_id.isChecked = true
switch_id.setOnClickListener {
// TODO: empty scope
}
The switch works smoothly and with animation as shown below:
But now I need to turn off the Home menu item if the Switch is off:
switch_id.setOnClickListener {
navigation_view.menu.findItem(R.id.nav_home).isEnabled = switch_id.isChecked
}
After adding this line, my Switch animation breaks!
I spent two days solving this issue and in the end to no avail.
My question: WTF with android menu in 2021? Why does accessing a menu item break the animation and how can I fix it? I would be grateful for any help !!
I created an issue with this question in Android Tracker and got an answer:
Either way, this is likely to be a non-trivial fix for an issue that
is not a regression and not impacting correctness of the UI. At this
time, we have no plans to address the issue.
And then a workaround was suggested:
Something along the lines of adding a setOnCheckedChangeListener that
posts a delayed Runnable to effect the change. I'd recommend asking on
StackOverflow if you need a detailed example.
E.g.
How to delay "runOnUiThread" in android?
I added a postDelay(200) and the Switch animation works fine:
switch.setOnCheckedChangeListener { _, isChecked ->
Handler(Looper.getMainLooper()).postDelayed({
navigationView.menu.findItem(R.id.nav_home).isEnabled = isChecked
}, 200)
}

Testing Android PreferenceFragment with Espresso

I am trying to write tests for the PreferenceFragments fragment in Settings.
However, I've been getting this error:
android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: is assignable from class: class android.widget.AdapterView
The code for the Test is the following:
#RunWith(AndroidJUnit4.class)
#SmallTest
public class SettingsFragmentTest {
#Rule
public ActivityTestRule<SettingsActivity> mActivityRule = new ActivityTestRule<>(
SettingsActivity.class);
#Test
public void preferredLocationShouldBeVisibleOnDisplay(){
mActivityRule.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
SettingsFragment settingsFragment = startSettingsFragment();
}
});
// This check passes correctly
onView(withId(R.id.weather_settings_fragment))
.check(matches(isCompletelyDisplayed()));
// This check gives me the NoMatchingViewException
onData(allOf(is(instanceOf(Preference.class)),
withKey("location")))
.check(matches(isCompletelyDisplayed()));
}
private SettingsFragment startSettingsFragment(){
SettingsActivity activity = mActivityRule.getActivity();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
SettingsFragment settingsFragment = new SettingsFragment();
transaction.replace(R.id.weather_settings_fragment, settingsFragment, "settingsFragment");
transaction.commit();
return settingsFragment;
}
}
The settings_activity layout looks as follows:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.example.android.sunshine.SettingsFragment"
android:id="#+id/weather_settings_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And the Preferences Screen layout is the following:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditTextPreference
android:defaultValue="#string/pref_location_default"
android:inputType="text"
android:key="#string/pref_location_key"
android:singleLine="true"
android:title="#string/pref_location_label" />
<ListPreference
android:defaultValue="#string/pref_units_metric"
android:entries="#array/pref_units_options"
android:entryValues="#array/pref_units_values"
android:key="#string/pref_units_key"
android:title="#string/pref_units_label" />
<CheckBoxPreference
android:defaultValue="#bool/show_notifications_by_default"
android:key="#string/pref_enable_notifications_key"
android:summaryOff="#string/pref_enable_notifications_false"
android:summaryOn="#string/pref_enable_notifications_true"
android:title="#string/pref_enable_notifications_label" />
</PreferenceScreen>
I haven't been able to find any examples or information online on how to test PreferenceFragments. Most of the information related to testing Activities.
PreferenceMatchers seems to work only with the preference classes from the Android framework, but not with support preference library (com.android.support:preference-v14). Since the latter uses a RecylerView internally, I was able to get hold of the preference items by using RecyclerViewActions from espresso-contrib:
onView(withId(R.id.list))
.perform(RecyclerViewActions.actionOnItem(hasDescendant(withText(R.string.pref_manage_categories_title)),
click()));
For PreferenceFragment:
PreferenceFragment uses a ListView internally, so you can use onData() with allOf() and withTitle():
onData(allOf(is(
instanceOf(Preference.class)),
withTitle(R.string.my_pref_string)))
.perform(click());
Or use onData() with allOf() and withKey():
onData(allOf(is(instanceOf(Preference.class)), withKey("myPrefKey")))
.onChildView(withText(R.string.my_pref_string))
.perform(click());
Or use onData() with anything(), but this can be unreliable and cause timeouts:
// atPosition() is required - Not sure why
onData(anything())
.atPosition(3)
.onChildView(withText(R.string.my_pref_string))
.perform(click());
Asserting or checking for a preference item is done in a similar way:
onData(allOf(is(
instanceOf(Preference.class)),
withTitle(R.string.my_pref_string)))
.check(matches(isDisplayed()));
For PreferenceFragmentCompat:
PreferenceFragmentCompat uses a RecyclerView internally, so you must use the espresso-contrib library, as mentioned in the answer by mtotschnig on 3 Septemebr 2018. If you're using the androidx.preference:preference:1.x.x library with unit tests written in Kotlin, it can be tricky to know what to use. You can start with something like this:
onView(withId(androidx.preference.R.id.recycler_view))
.perform(actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.my_pref_title)), click()))
Note: To quickly add the imports for these methods, put the blinking cursor on the unresolved method, then do Android Studio ➔ Help ➔ Find Action ➔ search for "show context action" or "show intention action" ➔ click on the result option ➔ A popup window will appear ➔ click on "Import static method ...". You can also assign a keyboard shortcut to "Show Context Actions". More info here. Another way is to enable "Add unambiguous imports on the fly" in the Settings.
After navigating to your settings activity, you can use Espresso withText() with the preference android:title String
#Rule
public ActivityTestRule<SettingsActivity> mActivityRule = new ActivityTestRule<>(
SettingsActivity.class);
onView(withText(mActivityRule.getActivity().getResources().getString(R.string.my_pref_title))
.perform(click());
Side note: if it is a ListPreference, then when clicking on it as mentioned above, then you can test selecting an item from the list the same way, but use the list entry text in withText() as no titles here.
onView(withText(mActivityRule.getActivity().getResources().getString(R.string.list_entry_sring))
.perform(click());
I normally test Fragments through their activity with Espresso. Just test the UI exposed by your Fragment as if it were in the Activity that you're starting with the ActivityTestRule. If the Fragment isn't present on initial launch of the Activity, navigate in your test the same way a user would in order to start the Fragment transaction. If you find yourself needing to test business logic that requires you to stand up Fragments in some sort of test harness, that's usually a good indicator that it should be pulled out into a separate class that can be (ideally) unit tested devoid of any Android dependencies.
You can always try Record Espresso test
https://developer.android.com/studio/test/espresso-test-recorder.html
Here is the tips on how to work with lists
https://developer.android.com/training/testing/espresso/lists.html
This is PreferenceMatcher that expose API like this:
onData(PreferenceMatchers.withKey(mContext.getString(R.string.key_settings)))
.perform(click());
About PreferenceFragmentCompat, in addition to #Mr-IDE answer,
checking for a preference item with scrollTo<RecyclerView.ViewHolder>
onView(withId(androidx.preference.R.id.recycler_view))
.perform(
scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.my_pref_title))
)
)
#Test
public void clickListPreference() throws Exception{
// Check if it is displayed
Context appContext = InstrumentationRegistry.getTargetContext();
onData(allOf(
is(instanceOf(Preference.class)),
withKey(appContext.getResources().getString(R.string.pref_units_key))))
.check(matches(isDisplayed()));
// Check if click is working
onData(allOf(
is(instanceOf(Preference.class)),
withKey(appContext.getResources().getString(R.string.pref_units_key))))
.onChildView(withText(appContext.getResources()
.getString(R.string.pref_units_label))).perform(click());
}
Hope this will help you..

Espresso AutoCompleteTextView click

So I recently started messing around with Espresso in one of my existing Android projects.
Everything went pretty decently, until I came to find AutoCompleteTextView in my program. I don't seem to understand how to properly click the first thing in the autocomplete list. I'm actually not even sure which to use, onView() or onData() in this instance.
By some reasons which I don't know, AStupidNoob's solution doesn't work. So I found another one:
onView(withText("Spinner Item"))
.inRoot(RootMatchers.isPlatformPopup())
.perform(click());
The AutoCompleteTextView itself
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<AutoCompleteTextView
android:id="#+id/product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:completionThreshold="1"
android:hint="#string/product"
android:singleLine="true"
android:textSize="16sp" />
</com.google.android.material.textfield.TextInputLayout>
I think I found a bit of a cleaner method than the accepted answer!
onData(equalTo("ITEM")).inRoot(RootMatchers.isPlatformPopup()).perform(click());
The breakdown:
onData(x) This will find the view rendering the data object matching x in the drop down. The data is provided by the Adaptor given to the AutoCompleteTextView, so it can be an object of any type that Adaptor provides, it probably won't be a View. You'll want to use the standard hamcrest core matchers for this (equalTo, instanceOf, etc...) rather than (withText, withId, etc...). It might be a pain to try and find what object this is and how to match it, but there isn't a neater way: with a lot of items in your adapter some of the views won't even be in the hierarchy yet, so onView can't work! onData will make sure to load the views that match your data. Checkout here (this what onData returns) and here (this loads the matching data)
inRoot(RootMatchers.isPlatformPopup()) So it turns out the dropdown menu is on another window than the default window your activity runs in. So we have to specify that we want to search that window. The accepted answer uses RootMatchers.withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView()))) which seems to match any window that is not the default one.
Anyways HTH someone else.
For anyone still running into this issue, despite trying out the accepted solution above, I managed to get it working with help from a github issue I uncovered. For reference, I am using Robolectric 4.6, which I believe may be the reason why I am requiring a different solution from non-instrumented tests.
The solution I came up with (to verify an item is appearing in an AutoCompleteTextView popup is:
fun testAutoCompleteTextViewEntry() {
onView(withId(R.id.editText_search))
.perform(typeTextIntoFocusedView("x"), showDropDown())
onView(withText("xyz"))
.inRoot(RootMatchers.isPlatformPopup())
.check(matches(isDisplayed()))
}
// Somewhere else in your code
fun showDropDown(): ViewAction =
object : ViewAction {
override fun getDescription(): String = "Shows the dropdown menu of an AutoCompleteTextView"
override fun getConstraints(): Matcher<View> = allOf(
isEnabled(), isAssignableFrom(AutoCompleteTextView::class.java)
)
override fun perform(uiController: UiController, view: View) {
val autoCompleteTextView = view as AutoCompleteTextView
autoCompleteTextView.showDropDown()
uiController.loopMainThreadUntilIdle()
}
}
So i finally figured it out, thanks to this previous question:
Testing autocomplete textview using espresso tool
Ill just post my version of it for people who might use it in future.
onData(instanceOf("Whatever your arrayadapter contains".class)).inRoot(RootMatchers.withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())))).perform(ViewActions.click());
Following on from the answer from AStupidNoob on 28 July 2017...
To click on a specific row number of the drop-down list, you can use this:
onData(anything())
.atPosition(2)
.inRoot(RootMatchers.isPlatformPopup())
.perform(click());
To click on a specific item in a specific row number of the drop-down list, you can use this:
onData(anything())
.atPosition(2)
.inRoot(RootMatchers.isPlatformPopup())
.onChildView(withId(R.id.button_on_layout))
.perform(click());
Since the place predictions are inside a recyclerview, after typing the name of the placement, if you want to click on the first option in the predicted list, you can go with one of the RecyclerViewActions methods (actionOnItemAtPosition).
The key is to find out the id of the autocompleted places. It is set up by Google Place SDK but not yourself so might not be that straightforward to find out.
This works for my project, see if it can help:
onView(ViewMatchers.withId(com.google.android.libraries.places.R.id.places_autocomplete_list)).perform(RecyclerViewActions.actionOnItemAtPosition(0,click()));

master/detail template with action menu - right way to show both

Beginner here, targetting sdk v14 and v17 for my learning...no need for older support.
I am using the master/detail template and trying to get an action menu (for SEARCH) to show up both in phone and tablet view. Actually I can get it to work, but I have to duplicate up my code in both ItemDetailActivity.java and ItemListActivity.java
These are the methods that I have to have in both for SEARCH to work:
public boolean onQueryTextChange(String newText) {
public boolean onQueryTextSubmit (String query) {
public boolean onClose () {
I only want to search the "detail", not the "list".
So my question: is there a way to associate the action bar with only the list fragment? That way I can keep the search functions in 1 file.
Thanks!
I'll go ahead and answer what I (think) I know as I don't want to leave this question open.
From tracing in the debugger, it looks to me like the phone activity and the tablet activity are separate and if you want to hook up an actionmenu, you have to hook it up to both separately.

Actionbar navigation

My android application targets the latest platform. I am new to the platform, and read bit conflicting information on actionbar. The way I was using it for navigation was.
menu.xml
<menu>
<item android:id="#+id/action_sort_size"
android:icon="#android:drawable/ic_menu_sort_by_size"
android:title="#string/action_barabc"
android:onClick="abc" />
<item android:id="#+id/action_sort_alpha"
....
In my activity
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void abc(MenuItem item) {
//...
}
this works, but the back/up navigation is not working correctly. could be unrelated, still like to confirm.
But, I also see implementation like here
where it switches on item.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menuitem1:
Toast.makeText(this, "Menu Item 1 selected", Toast.LENGTH_SHORT)
.show();
break;
case R.id.menuitem2:
....
}
Which is the better approach?
The better approach, in my opinion, is the switch approach. There aren't many reasons why, but I'll list them:
The code is centralized. You don't have x amount of methods that basically do the same thing. It keeps your code more readable; it is "cleaner". You also get a default statement using the switch, this can help if you mess up and forget to make a case specifically for an element in the layout.
If you really wanted to have a centralized method using xml, you would have onClick reference the same method and check the ids of the View parameter. Which is essentially the same asonOptionsItemSelected.
It is a part of the API. The Android engineers would not have made it a part of the API if they didn't want the developer to use it. Yes the XML is techinally API, but XML should be used more for layouts and visuals, not for logic.
Everyone uses it. All the tutorials I have seen and everyone's code uses this method. It is now more of a convention.
It's largely personal, but if it looks like it's a convention, and everyone uses it, I'd adhere to it. Especially if you're working as part of a team. Different coding styles for such arbritrary things should be avoided.
And concerning your back/up navigation, it shouldn't make a difference which way you do it, since you have to implement the same code to get that navigation type.

Categories

Resources