If I have an "AppCompatTextView" element that I can access by:
onView(withId(R.id.allergies_text))
From Layout Inspector:
Is there a way I can access the text of the element in Android Studio? (to access whatever text is there... not check if some text exists in the element)
I tried to do:
val tv = onView(withId(R.id.medical_summary_text_view)) as TextView
val text = text.text.toString()
print(text)
But I get the error:
android.support.test.espresso.ViewInteraction cannot be cast to android.widget.TextView
You should create a matcher to access to that element value.
For instance, you can check if it's text has some value:
Matcher<View> hasValueEqualTo(final String content) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Has EditText/TextView the value: " + content);
}
#Override
public boolean matchesSafely(View view) {
if (!(view instanceof TextView) && !(view instanceof EditText)) {
return false;
}
if (view != null) {
String text;
if (view instanceof TextView) {
text = ((TextView) view).getText().toString();
} else {
text = ((EditText) view).getText().toString();
}
return (text.equalsIgnoreCase(content));
}
return false;
}
};
}
And call it this way:
onView(withId(R.id.medical_summary_text_view))
.check(matches(hasValueEqualTo(value)));
or you can edit this matcher to return just whether the text is empty or not:
Matcher<View> textViewHasValue() {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("The TextView/EditText has value");
}
#Override
public boolean matchesSafely(View view) {
if (!(view instanceof TextView) && !(view instanceof EditText)) {
return false;
}
if (view != null) {
String text;
if (view instanceof TextView) {
text = ((TextView) view).getText().toString();
} else {
text = ((EditText) view).getText().toString();
}
return (!TextUtils.isEmpty(text));
}
return false;
}
};
}
And call it this way:
onView(withId(R.id.medical_summary_text_view))
.check(matches(textViewHasValue()));
You can get the text of the ViewInteraction by the following function:
fun getText(matcher: ViewInteraction): String {
var text = String()
matcher.perform(object : ViewAction {
override fun getConstraints(): Matcher<View> {
return isAssignableFrom(TextView::class.java)
}
override fun getDescription(): String {
return "Text of the view"
}
override fun perform(uiController: UiController, view: View) {
val tv = view as TextView
text = tv.text.toString()
}
})
return text
}
val numberResult: ViewInteraction = onView(withId(R.id.txNumberResult))
var searchText = getText(numberResult)
I faced a similar issue and this is what ended up working for me:
if you have an activity rule
var activityRule = ActivityTestRule(MainActivity::class.java, true, false)
then you can do something like this:
activityRule.launchActivity(null)
val textView: TextView = activityRule.activity.findViewById(R.id.some_text_view)
val text = textView.text
I think this may be more along the lines of what the original poster was looking for.
When you really want to have the text and not only match it with another value or empty, I post the full final working solution in Java (Not Kotlin) based on the algoritghm of #Mesut GUNES
public class TextHelpers {
public static String getText(ViewInteraction matcher){
final String[] text = new String[1];
ViewAction va = new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isAssignableFrom(TextView.class);
}
#Override
public String getDescription(){
return "Text of the view";
}
#Override
public void perform(UiController uiController,View view) {
TextView tv = (TextView) view;
text[0] = tv.getText().toString();
}
};
matcher.perform(va);
return text[0];
}
}
So, in your test you can call it like that:
TextHelpers.getText(Espresso.onView(withId(R.id.element)));
It works for all controls extending TextView, so it does on EditText too.
Related
How to go about checking whether RecyclerView items are displayed in the correct order using Espresso? I'm trying to test it checking it by the text for the title of each element.
When I try this piece of code it works to click the element but can't go on to instead of performing a click trying to Assert the text for the element
onView(withId(R.id.rv_metrics)).perform(actionOnItemAtPosition(0, click()));
When I try to use a custom matcher instead I keep getting the error
Error performing 'load adapter data' on view 'with id: mypackage_name:id/rv_metrics'
I know now onData doesn't work for RecyclerView but before that I was trying to use a custom matcher for this task.
public static Matcher<Object> hasTitle(final String inputString) {
return new BoundedMatcher<Object, Metric>(Metric.class) {
#Override
protected boolean matchesSafely(Metric metric) {
return inputString.equals(metric.getMetric());
}
#Override
public void describeTo(org.hamcrest.Description description) {
description.appendText("with title: ");
}
};
}
I also tried something like this but it obviously doesn't work due to the type given as parameter to the actionOnItemAtPosition method but would we have something similar to it that could maybe work?
onView(withId(R.id.rv_metrics)).check(actionOnItemAtPosition(0, ViewAssertions.matches(withText("Weight"))));
What am I missing here please?
Thanks a lot.
As it's been mentioned here, RecyclerView objects work differently than AdapterView objects, so onData() cannot be used to interact with them.
In order to find a view at specific position of a RecyclerView you need to implement a custom RecyclerViewMatcher like below:
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
new Object[] { Integer.valueOf
(recyclerViewId) });
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
And then use it in your test case in this way:
#Test
void testCase() {
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(0, R.id.txt_title))
.check(matches(withText("Weight")))
.perform(click());
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(1, R.id.txt_title))
.check(matches(withText("Height")))
.perform(click());
}
If somebody is interested in the Kotlin version, here it is
fun hasItemAtPosition(position: Int, matcher: Matcher<View>) : Matcher<View> {
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("has item at position $position : ")
matcher.describeTo(description)
}
override fun matchesSafely(item: RecyclerView?): Boolean {
val viewHolder = item?.findViewHolderForAdapterPosition(position)
return matcher.matches(viewHolder?.itemView)
}
}
}
I simplified a bit Mosius answer:
public static Matcher<View> hasItemAtPosition(final Matcher<View> matcher, final int position) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
#Override
public void describeTo(Description description) {
description.appendText("has item at position " + position + ": ");
matcher.describeTo(description);
}
#Override
protected boolean matchesSafely(RecyclerView recyclerView) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
return matcher.matches(viewHolder.itemView);
}
};
}
We pass Matcher to the function so we can provide further conditions. Example usage:
onView(hasItemAtPosition(hasDescendant(withText("Item 1")), 0)).check(matches(isDisplayed()));
onView(hasItemAtPosition(hasDescendant(withText("Item 2")), 1)).check(matches(isDisplayed()));
The original problem has been solved but am posting an answer here as found the Barista library solves this problem in one single line of code.
assertDisplayedAtPosition(R.id.rv_metrics, 0, R.id.tv_title, "weight");
It's made on top of Espresso and the documentation for it can be found here
Hope this may be helpful to someone. :)
If you want to match a matcher on a position in RecyclerView, then you can try to create a custom Matcher<View>:
public static Matcher<View> hasItemAtPosition(int position, Matcher<View> matcher) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
#Override public void describeTo(Description description) {
description.appendText("has item: ");
matcher.describeTo(description);
description.appendText(" at position: " + position);
}
#Override protected boolean matchesSafely(RecyclerView view) {
RecyclerView.Adapter adapter = view.getAdapter();
int type = adapter.getItemViewType(position);
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, type);
adapter.onBindViewHolder(holder, position);
return matcher.matches(holder.itemView);
}
};
}
And you can use it for example:
onView(withId(R.id.rv_metrics)).check(matches(0, hasDescendant(withText("Weight")))))
I need to make an instrumented test using espresso or uiautomator (I'm open to other sugestions) to validate the existence of a value based on another value in a list.
The questions/anwsers I have seen, all have solutions regarding the using of an index from the list view, or using a specific tag with a number associated (which is basicaly the same thing)
My problem is that I don't know the position, but I need to check that when I encounter a specific String value, that on that row, an image is showing.
The list are populated by a Custom Adapter.
Got any ideas?
After several attemps I end up doing some custom matcher that with a few alterations should work for all kind of views.
Here's how it's called in the test:
onView(CommonMatchers.withTextAndComparableText(R.id.tvTypeName, R.id.rlParentView, R.id.tvName, is(typeNameText), is (nameText))).check(matches(isDisplayed()));
variables:
childId - view that I want to compare from,
parentId - parent of both views,
comparableChildId - view that I want to compare to
Here's the Matcher:
public static Matcher<View> withTextAndComparableText(int childId, int parentId, int comparableChildId,
final Matcher<String>
eventMatcher,
final Matcher<String> parcelIdMatcher) {
checkNotNull(eventMatcher);
return new BoundedMatcher<View, TextView>(TextView.class) {
#Override
public void describeTo(Description description) {
description.appendText("with text: ");
eventMatcher.describeTo(description);
}
#Override
public boolean matchesSafely(TextView textView) {
if (eventMatcher.matches(textView.getText().toString()) && childId == textView.getId()) {
TextView textViewToCompare = (TextView) checkParentAndComparableChildForValidation(parentId,
comparableChildId, textView, parcelIdMatcher);
return textViewToCompare != null && parcelIdMatcher.matches(textViewToCompare.getText().toString());
}
return false;
}
};
}
Then add the next private methods to first get the parent view and then the comprable child view
private static View checkParentAndComparableChildForValidation(int parentId, int comparableChildId,
View objectView,
Matcher<String> parcelIdMatcher) {
ViewParent parentView = findParentRecursively(objectView, parentId);
Object childObject = getObjectFromParent((View) parentView, comparableChildId);
if (childObject instanceof View) {
return (View) childObject;
}
return null;
}
private static Object getObjectFromParent(View viewParent, int childId) {
return viewParent.findViewById(childId);
}
private static ViewParent findParentRecursively(View view, int targetId) {
if (view.getId() == targetId) {
return (ViewParent) view;
}
View parent = (View) view.getParent();
if (parent == null) {
return null;
}
return findParentRecursively(parent, targetId);
}
And voilá!
If you want to compare with an Image or other view, there's few alterations to be done...
public static Matcher<View> withImageAndComparableText(int childId, int parentId, int comparableChildId,
final Matcher<String> parcelIdMatcher) {
return new BoundedMatcher<View, ImageView>(ImageView.class) {
#Override
public void describeTo(Description description) {
description.appendText("with id: " + childId);
}
#Override
public boolean matchesSafely(ImageView imageView) {
if (imageView.getId() == childId) {
TextView textViewToCompare = (TextView) checkParentAndComparableChildForValidation(parentId,
comparableChildId, imageView, parcelIdMatcher);
return textViewToCompare != null && parcelIdMatcher.matches(textViewToCompare.getText().toString());
}
return false;
}
};
}
and the call in the tests:
onView(CommonMatchers.withImageAndComparableText(imageId, R.id.rlParentView, R.id.name, is(parcelId)))
.check(matches(isDisplayed()));
Hope it helps
For the given RecyclerView, I can access the LinearLayout element by:
recyclerView.findViewHolderForAdapterPosition(1)
Where 1 is the 2nd element/position within RecyclerView and the ViewHolder is returned.
How can I then access the child elements of the ViewHolder LinearLayout? Or is there a way to get to the AppCompatTextView element so that I can pull the text from that element?
I have created RecyclerViewMatcher class then following code
onView(withRecyclerView(R.id.rv_fragment_recipes).atPosition(0))
.check(matches(isDisplayed()));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPositionOnView(0, R.id.tv_recipe_name))
.check(matches(isDisplayed()))
.check(matches(withText("Nutella Pie")));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPositionOnView(0, R.id.tv_servings))
.check(matches(isDisplayed()))
.check(matches(withText("Servings : 8")));
onView(withRecyclerView(R.id.rv_fragment_recipes).atPosition(0))
.perform(click());
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
new Object[] { Integer.valueOf
(recyclerViewId) });
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
for refernce you can follow this project
Use IdelingResource if recyclerView data is not static
Can we perform assertion on Edittext Value and write down our test case according to it's output. Like if we Edittext value is Equal to our value we want to perform Condition A else B.
onView(withId(viewId)).check(matches(isEditTextValueEqualTo(viewId, value)));
Matcher<View> isEditTextValueEqualTo(final int viewId, final String content) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Match Edit Text Value with View ID Value : : " + content);
}
#Override
public boolean matchesSafely(View view) {
if (view != null) {
String editTextValue = ((EditText) view.findViewById(viewId)).getText().toString();
if (editTextValue.equalsIgnoreCase(content)) {
return true;
}
}
return false;
}
};
}
This is not working using try.. Catch(Exception e)
I don't think you shoud do a findViewById inside the matcher, I see no reason to do this.
I have updated your matcher:
Matcher<View> isEditTextValueEqualTo(final String content) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(Description description) {
description.appendText("Match Edit Text Value with View ID Value : : " + content);
}
#Override
public boolean matchesSafely(View view) {
if (!(view instanceof TextView) && !(view instanceof EditText)) {
return false;
}
if (view != null) {
String text;
if (view instanceof TextView) {
text =((TextView) view).getText().toString();
} else {
text =((EditText) view).getText().toString();
}
return (text.equalsIgnoreCase(content));
}
return false;
}
};
}
And call it this way:
onView(withId(viewId)).check(matches(isEditTextValueEqualTo(value)));
When I check the value and assertion failed it throws AssertionFailedError which is not in hierarchy of Exception. It gets fixed with try... catch(AssertionFailedError e)
I am building a popup menu in android and I need to store some IDs in each menu item.
The IDs are String therefore it would be nice if I could set an array of String to the MenuItem.
The problem is that MenuItem does not have setTag method.
How else can I attach some data to it?
EDIT:
Geobits mentioned about getActionView();
Unfortunately it returns null.
However, is it save to do the following?
View view = new View(getActivity());
view.setTag(tag);
menuItem.setActionView(view);
Each MenuItem has an associated View called an ActionView. If you're using a custom ActionView, you can fetch it using MenuItem.getActionView(), and set/retrieve the tag on it.
For instance, to set a tag:
public void setMenuItemTag(MenuItem item, Object tag)
{
View actionView = item.getActionView();
actionView.setTag(tag);
}
Edit
If you're not using a custom ActionView, you can use a HashMap to store tags. Use the MenuItem as the key.
public void setMenuItemTag(MenuItem item, Object tag)
{
myMap.put(item, tag);
}
// returns null if tag has not been set(or was set to null)
public Object getMenuItemTag(MenuItem item, Object tag)
{
return myMap.get(item);
}
Hacky way to set tags when not using custom views.
The idea is pretty simple, after the Actual View is created, we try to find it by its text and icon
private void addButtonToMenu(Toolbar toolbar, Menu menu, String title, Drawable icon, String testId) {
MenuItem menuItem = menu.add(title);
if (icon != null){
menuItem.setIcon(icon);
}
setTestId(toolbar, title, icon, testId);
}
private void setTestId(Toolbar toolbar, String title, Drawable icon, String testId) {
UiUtils.runOnPreDrawOnce(toolbar, () -> {
ActionMenuView buttonsLayout = ViewUtils.findChildByClass(toolbar, ActionMenuView.class);
List<TextView> buttons = ViewUtils.findChildrenByClass(buttonsLayout, TextView.class);
for (TextView view : buttons) {
if (!TextUtils.isEmpty(title) && title.equals(view.getText())) {
view.setTag(testId);
} else if (icon != null && ArraryUtils.containes(view.getCompoundDrawables(), icon)) {
view.setTag(testId);
}
}
});
}
// Helper functions
public class UiUtils {
public static void runOnPreDrawOnce(final View view, final Runnable task) {
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
task.run();
return true;
}
});
}
}
public class ArrayUtils {
public static boolean contains(Object[] array, Object item) {
if (isNullOrEmpty(array)) return false;
for (Object o : array) {
if (o == item) return true;
}
return false;
}
private static boolean isNullOrEmpty(Object[] array) {
return array == null || array.length == 0;
}
}
public class ViewUtils {
#Nullable
public static <T> T findChildByClass(ViewGroup root, Class clazz) {
for (int i = 0; i < root.getChildCount(); i++) {
View view = root.getChildAt(i);
if (clazz.isAssignableFrom(view.getClass())) {
return (T) view;
}
if (view instanceof ViewGroup) {
view = findChildByClass((ViewGroup) view, clazz);
if (view != null && clazz.isAssignableFrom(view.getClass())) {
return (T) view;
}
}
}
return null;
}
public static <T> List<T> findChildrenByClass(ViewGroup root, Class clazz) {
List<T> ret = new ArrayList<>();
for (int i = 0; i < root.getChildCount(); i++) {
View view = root.getChildAt(i);
if (clazz.isAssignableFrom(view.getClass())) {
ret.add((T) view);
}
}
return ret;
}
}