Ich versuche, einen instrumentierten Test in einem meiner Bildschirme, die eine Recycler-Ansicht hat, zu implementieren. Ich habe anscheinend keine Probleme, wenn ich scrollTo()
benutze, aber wenn ich actionOnItem()
mit dem gleichen View Matcher benutze, bekomme ich eine Laufzeitausnahme.Espresso RecyclerViewActions.actionOnItem Fehler
Jede Hilfe wäre willkommen. Vielen Dank!
Testcode
@Test
public void nextButton_disabledWhilePhoneNumberInvalid() {
onView(withId(R.id.countrycodepicker_enterphonenumber_ccp)).perform(click());
// This is working
// onView(withId(R.id.recycler_countryDialog))
// .perform(scrollTo(hasDescendant(first(withText("Philippines (PH)")))));
// This is also working
// onView(withId(R.id.recycler_countryDialog))
// .perform(actionOnItemAtPosition(1, click()));
// This is not working and is causing a PerformException
onView(withId(R.id.recycler_countryDialog))
.perform(actionOnItem(hasDescendant(first(withText("Philippines (PH)"))), click()));
}
private <T> Matcher<T> first(final Matcher<T> matcher) {
return new BaseMatcher<T>() {
boolean isFirst = true;
@Override
public boolean matches(final Object item) {
if (isFirst && matcher.matches(item)) {
isFirst = false;
return true;
}
return false;
}
@Override
public void describeTo(final Description description) {
description.appendText("should return first matching item");
}
};
}
Runtime Error
android.support.test.espresso.PerformException: Error performing
'performing ViewAction: single click on item matching: holder with view: has descendant: should return first matching item' on view 'with id: com.example:id/recycler_countryDialog'.
at android.support.test.espresso.PerformException$Builder.build(PerformException.java:83)
at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:80)
at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:56)
at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:184)
at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:115)
at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87)
at com.example.EnterPhoneNumberScreenTest.nextButton_disabledWhilePhoneNumberInvalid(EnterPhoneNumberScreenTest.java:53)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at android.support.test.internal.statement.UiThreadStatement.evaluate(UiThreadStatement.java:55)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:270)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1853)
Caused by: java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
at java.util.ArrayList.get(ArrayList.java:308)
at android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemViewAction.perform(RecyclerViewActions.java:232)
at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:144)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
RecyclerView Adapter-Code (pulled from the third party library I'm using)
class CountryCodeAdapter extends
RecyclerView.Adapter<CountryCodeAdapter.CountryCodeViewHolder> {
List<Country> filteredCountries = null, masterCountries = null;
TextView textView_noResult;
CountryCodePicker codePicker;
LayoutInflater inflater;
EditText editText_search;
Dialog dialog;
Context context;
CountryCodeAdapter(Context context, List<Country> countries, CountryCodePicker codePicker, final EditText editText_search, TextView textView_noResult, Dialog dialog) {
this.context = context;
this.masterCountries = countries;
this.codePicker = codePicker;
this.dialog = dialog;
this.textView_noResult = textView_noResult;
this.editText_search = editText_search;
this.inflater = LayoutInflater.from(context);
this.filteredCountries = getFilteredCountries("");
setSearchBar();
}
private void setSearchBar() {
if (codePicker.isSelectionDialogShowSearch()) {
setTextWatcher();
} else {
editText_search.setVisibility(View.GONE);
}
}
/**
* add textChangeListener, to apply new query each time editText get text changed.
*/
private void setTextWatcher() {
if (this.editText_search != null) {
this.editText_search.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
applyQuery(s.toString());
}
});
if(codePicker.isKeyboardAutoPopOnSearch()) {
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
}
}
/**
* Filter country list for given keyWord/query.
* Lists all countries that contains @param query in country's name, name code or phone code.
*
* @param query : text to match against country name, name code or phone code
*/
private void applyQuery(String query) {
textView_noResult.setVisibility(View.GONE);
query = query.toLowerCase();
//if query started from "+" ignore it
if (query.length() > 0 && query.charAt(0) == '+') {
query=query.substring(1);
}
filteredCountries= getFilteredCountries(query);
if (filteredCountries.size() == 0) {
textView_noResult.setVisibility(View.VISIBLE);
}
notifyDataSetChanged();
}
private List<Country> getFilteredCountries(String query) {
List<Country> tempCountryList = new ArrayList<Country>();
if(codePicker.preferredCountries!=null && codePicker.preferredCountries.size()>0) {
for (Country country : codePicker.preferredCountries) {
if (country.isEligibleForQuery(query)) {
tempCountryList.add(country);
}
}
if (tempCountryList.size() > 0) { //means at least one preferred country is added.
Country divider = null;
tempCountryList.add(divider);
}
}
for (Country country : masterCountries) {
if (country.isEligibleForQuery(query)) {
tempCountryList.add(country);
}
}
return tempCountryList;
}
@Override
public CountryCodeViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View rootView = inflater.inflate(R.layout.layout_recycler_country_tile, viewGroup, false);
CountryCodeViewHolder viewHolder = new CountryCodeViewHolder(rootView);
return viewHolder;
}
@Override
public void onBindViewHolder(CountryCodeViewHolder countryCodeViewHolder, final int i) {
countryCodeViewHolder.setCountry(filteredCountries.get(i));
countryCodeViewHolder.getMainView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
codePicker.setSelectedCountry(filteredCountries.get(i));
if (view != null && filteredCountries.get(i)!=null) {
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
dialog.dismiss();
}
}
});
}
@Override
public int getItemCount() {
return filteredCountries.size();
}
class CountryCodeViewHolder extends RecyclerView.ViewHolder {
RelativeLayout relativeLayout_main;
TextView textView_name, textView_code;
ImageView imageViewFlag;
LinearLayout linearFlagHolder;
View divider;
public CountryCodeViewHolder(View itemView) {
super(itemView);
relativeLayout_main = (RelativeLayout) itemView;
textView_name = (TextView) relativeLayout_main.findViewById(R.id.textView_countryName);
textView_code = (TextView) relativeLayout_main.findViewById(R.id.textView_code);
imageViewFlag = (ImageView) relativeLayout_main.findViewById(R.id.image_flag);
linearFlagHolder = (LinearLayout) relativeLayout_main.findViewById(R.id.linear_flag_holder);
divider = relativeLayout_main.findViewById(R.id.preferenceDivider);
}
public void setCountry(Country country) {
if(country!=null) {
divider.setVisibility(View.GONE);
textView_name.setVisibility(View.VISIBLE);
textView_code.setVisibility(View.VISIBLE);
linearFlagHolder.setVisibility(View.VISIBLE);
textView_name.setText(country.getName() + " (" + country.getNameCode().toUpperCase() + ")");
textView_code.setText("+" + country.getPhoneCode());
imageViewFlag.setImageResource(country.getFlagID());
}else{
divider.setVisibility(View.VISIBLE);
textView_name.setVisibility(View.GONE);
textView_code.setVisibility(View.GONE);
linearFlagHolder.setVisibility(View.GONE);
}
}
public RelativeLayout getMainView() {
return relativeLayout_main;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true">
<LinearLayout
android:id="@+id/linear_flag_holder"
android:layout_width="40dp"
android:layout_height="50dp"
android:layout_marginLeft="@dimen/google_1x"
android:layout_marginRight="@dimen/google_1x"
android:gravity="center">
<ImageView
android:id="@+id/image_flag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:src="@drawable/flag_india" />
</LinearLayout>
<TextView
android:id="@+id/textView_countryName"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/linear_flag_holder"
android:layout_toLeftOf="@+id/textView_code"
android:layout_toRightOf="@+id/linear_flag_holder"
android:layout_toStartOf="@+id/textView_code"
android:gravity="center_vertical"
android:text="India (IN)"
android:textColor="@android:color/secondary_text_light" />
<TextView
android:id="@+id/textView_code"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:gravity="center_vertical"
android:paddingEnd="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingStart="10dp"
android:text="+91"
android:textColor="@android:color/secondary_text_light"
android:textDirection="ltr" />
<View
android:id="@+id/preferenceDivider"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_alignParentTop="true"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:background="#898989"/>
</RelativeLayout>
Update 2
Wenn ich allof anstelle des benutzerdefinierten ersten Matcher verwenden, werde ich einen Fehler aufgrund eines doppelten Untersicht, da der Adapter die vorherigen wird die Wiederverwendung von Artikel als „Teiler“
android.support.test.espresso.PerformException: Error performing 'performing ViewAction: single click on item matching: holder with view: has descendant: (with text: is "Philippines (PH)")' on view 'with id: com.sample:id/recycler_countryDialog'.
at android.support.test.espresso.PerformException$Builder.build(PerformException.java:83)
at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:80)
at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:56)
at android.support.test.espresso.ViewInteraction.runSynchronouslyOnUiThread(ViewInteraction.java:184)
at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:115)
at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:87)
at com.sample.EnterPhoneNumberScreenTest.nextButton(EnterPhoneNumberScreenTest.java:59)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at android.support.test.internal.statement.UiThreadStatement.evaluate(UiThreadStatement.java:55)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:270)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1853)
Caused by: android.support.test.espresso.PerformException: Error performing 'scroll RecyclerView to: holder with view: has descendant: (with text: is "Philippines (PH)")' on view 'RecyclerView{id=2131689704, res-name=recycler_countryDialog, visibility=VISIBLE, width=714, height=1354, has-focus=false, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=true, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=21.0, y=269.0, child-count=12}'.
at android.support.test.espresso.PerformException$Builder.build(PerformException.java:83)
at android.support.test.espresso.contrib.RecyclerViewActions$ScrollToViewAction.perform(RecyclerViewActions.java:373)
at android.support.test.espresso.contrib.RecyclerViewActions$ActionOnItemViewAction.perform(RecyclerViewActions.java:226)
at android.support.test.espresso.ViewInteraction$1.run(ViewInteraction.java:144)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
Caused by: java.lang.RuntimeException: Found more than one sub-view matching holder with view: has descendant: (with text: is "Philippines (PH)")
*** Matched ViewHolder item at position: 1 ***
View Hierarchy:
+>RelativeLayout{id=-1, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, child-count=4}
|
+->LinearLayout{id=2131689698, res-name=linear_flag_holder, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, child-count=1}
|
+-->AppCompatImageView{id=2131689699, res-name=image_flag, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0}
|
+->AppCompatTextView{id=2131689706, res-name=textView_countryName, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, text=Philippines (PH), input-type=0, ime-target=false, has-links=false}
|
+->AppCompatTextView{id=2131689707, res-name=textView_code, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, text=+63, input-type=0, ime-target=false, has-links=false}
|
+->View{id=2131689708, res-name=preferenceDivider, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0}
|
*** Matched ViewHolder item at position: 2 ***
View Hierarchy:
+>RelativeLayout{id=-1, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=true, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, child-count=4}
|
+->LinearLayout{id=2131689698, res-name=linear_flag_holder, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, child-count=1}
|
+-->AppCompatImageView{id=2131689699, res-name=image_flag, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0}
|
+->AppCompatTextView{id=2131689706, res-name=textView_countryName, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, text=Philippines (PH), input-type=0, ime-target=false, has-links=false}
|
+->AppCompatTextView{id=2131689707, res-name=textView_code, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0, text=+63, input-type=0, ime-target=false, has-links=false}
|
+->View{id=2131689708, res-name=preferenceDivider, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=false, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, root-is-layout-requested=true, has-input-connection=false, x=0.0, y=0.0}
|
at android.support.test.espresso.contrib.RecyclerViewActions$ScrollToViewAction.perform(RecyclerViewActions.java:368)
... 12 more
Können Sie die Layouts, die Sie in Ihren ViewHolders und beliebigen zusätzlichen Code, den ViewHolder verwenden Programmatisch? – jdonmoyer
@jdonmoyer Ich habe das Layout sowie den Adaptercode hinzugefügt. Das involvierte Element ist tatsächlich eine Third Party Library; Ich habe den Code aus dem Projekt GitHub gezogen. –