Espresso RecyclerViewActions.actionOnItem 错误

Kar*_*lin 7 android unit-testing android-espresso

我正在尝试在具有回收器视图的屏幕之一中实施仪器测试。当我使用 时,我似乎没有问题scrollTo(),但是当我开始使用actionOnItem()相同的视图匹配器时,我收到运行时异常。

任何帮助,将不胜感激。谢谢!

测试代码

@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");
        }
    };
}
Run Code Online (Sandbox Code Playgroud)

运行时错误

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)
Run Code Online (Sandbox Code Playgroud)

RecyclerView适配器代码(从我正在使用的第三方库中提取)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

查看支架布局

<?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>
Run Code Online (Sandbox Code Playgroud)

更新2

如果我使用 allOf 而不是自定义的第一个 Matcher,我会因重复的子视图而收到错误,因为适配器正在重复使用前一个项目作为“分隔符”

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(ZygoteIn

Mic*_*sky 6

actionOnItem实际上返回一个可以调用的对象atPosition()。例如,要获取位置 1 处的第二项(即不是位置 0 处的第一项),请添加.atPosition(1)如下:

onView(withId(R.id.recycler_countryDialog))
        .perform(actionOnItem<ViewHolder>(hasDescendant(withText("Philippines (PH)")), click()).atPosition(1));
Run Code Online (Sandbox Code Playgroud)


jdo*_*yer 0

我不太确定你想用第一个()匹配器来完成什么,但我的第一个猜测是这就是导致问题的原因。

RecyclerViewActions.actionOnItem() 实际上对 ViewHolder 的 itemView 进行操作(R.layout.layout_recycler_country_tile 的根视图)。假设只有一个带有文本“Philippines (PH)”的项目,删除“第一个”匹配器应该可以工作,因为 actionOnItem 已经在查看特定的 ViewHolder 行。如果您确实有多行具有相同的文本,我建议使用 allOf() 来更好地定位您感兴趣的行。