(4.5.5.3)Espresso的进阶: ViewAction

来源:互联网 发布:c语言线程同步 编辑:程序博客网 时间:2024/06/07 02:48

  • ViewAction
    • 1 源码分析
      • 11 所有的动作都 实现 interface ViewAction
      • 10 ViewActions 相当于工具类提供一些公用的方法
    • 2 系统支持
      • 21 Click Press 类型
        • 211 click点击
        • 212 doubleclick双击
        • 213 longclick长按
        • 215 pressImeActionButton
        • 216 pressKeyEspressoKey key pressKeyint keyCode 物理按键
        • 217 pressMenuKey菜单键 点击
        • 214 pressBack后退键 点击
        • 218 closeSoftKeyboard关闭键盘
        • 219 openLink 链接点击
      • 22 Text类型
        • 221 typeText 获得焦点并注入文本
        • 222 typeTextIntoFocusedView在已获得焦点的View上注入文本
        • 223 clearText 文本清除
        • 224 replaceText String 文本替换
      • 23 Gestures类型
        • 231 scrollTo 滑动到指定View
        • 232 swipeXX 向XX方向滑动
    • RecyleView

Espresso编写自动化就做三件事情:找某些东西、做某些事情、检查某些东西

  • 找到 并返回 XXInteraction交互类
    • public static ViewInteraction onView(final Matcher< View > viewMatcher)
    • public static DataInteraction onData(Matcher< ? extends Object> dataMatcher)
  • 执行相关动作 ,返回 XXInteraction交互类
    • ViewInteraction perform(final ViewAction… viewActions)
  • 校验,返回 XXInteraction交互类
    • ViewInteraction check(final ViewAssertion viewAssert)

ViewAction

2.1 源码分析

2.1.1 所有的动作都 实现 interface ViewAction

  /**   *  识别所操作的对象类型   * http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/Matcher.html   */  public Matcher<View> getConstraints(); //视图操作的一个描述  public String getDescription();  /**   * 实际的一个操作,在之类我们就可以获取到操作的对象了   *   * @param uiController the controller to use to interact with the UI.   * @param view the view to act upon. never null.   */  public void perform(UiController uiController, View view);
  • 一个简单的示例:获取VTextView的文本
//获取指定view的文本内容String tv = getText(withId(R.id.textToBeChanged));public String getText(final Matcher<View> matcher) {        final String[] text = {null};        onView(matcher).perform(new ViewAction() {            //识别所操作的对象类型            @Override            public Matcher<View> getConstraints() {                return isAssignableFrom(TextView.class);            }            //视图操作的一个描述            @Override            public String getDescription() {                return "getting text from a TextView";            }            //实际的一个操作,在之类我们就可以获取到操作的对象了。            @Override            public void perform(UiController uiController, View view) {                TextView textView = (TextView)view;                text[0] = textView.getText().toString();            }        });        return text[0];    }

2.1.0 ViewActions 相当于工具类,提供一些公用的方法

/**  * A collection of common {@link ViewActions}.一组公共ViewAction的集合 */public final class ViewActions {  private ViewActions() {}  /**   * “滑动”操作的距离,相对于View控件的长度   */  private static final float EDGE_FUZZ_FACTOR = 0.083f;  /**   * A.1 存储了一系列的 校验规则,在执行相关ViewActions前,先执行校验   */  private static Set<Pair<String, ViewAssertion>> globalAssertions =      new CopyOnWriteArraySet<Pair<String, ViewAssertion>>();  /**   * A.1.1 加入一个 “动作执行前的校验”   */  public static void addGlobalAssertion(String name, ViewAssertion viewAssertion) {    checkNotNull(name);    checkNotNull(viewAssertion);    Pair<String, ViewAssertion> vaPair = new Pair<String, ViewAssertion>(name, viewAssertion);    checkArgument(!globalAssertions.contains(vaPair),        "ViewAssertion with name %s is already in the global assertions!", name);    globalAssertions.add(vaPair);  }  /**   * A.1.2 移除一个“动作执行前的校验”   */  public static void removeGlobalAssertion(ViewAssertion viewAssertion) {    boolean removed = false;    for (Pair<String, ViewAssertion> vaPair : globalAssertions) {      if (viewAssertion != null && viewAssertion.equals(vaPair.second)) {        removed = removed || globalAssertions.remove(vaPair);      }    }    checkArgument(removed, "ViewAssertion was not in global assertions!");  }  // A.1.3 清空“动作执行前的校验”  public static void clearGlobalAssertions() {    globalAssertions.clear();  }  /**   * B.1 在执行指定的ViewAction 之前,先进行相关的校验;   * 实质上就是对 “指定的ViewAction”进行了封装,加入了校验   */  public static ViewAction actionWithAssertions(final ViewAction viewAction) {    if (globalAssertions.isEmpty()) {      return viewAction;    }    return new ViewAction() {      @Override      public String getDescription() {        StringBuilder msg = new StringBuilder("Running view assertions[");        for (Pair<String, ViewAssertion> vaPair : globalAssertions) {          msg.append(vaPair.first);          msg.append(", ");        }        msg.append("] and then running: ");        msg.append(viewAction.getDescription());        return msg.toString();      }      @Override      public Matcher<View> getConstraints() {        return viewAction.getConstraints();      }      @Override      public void perform(UiController uic, View view) {        for (Pair<String, ViewAssertion> vaPair : globalAssertions) {          Log.i("ViewAssertion", "Asserting " + vaPair.first);          vaPair.second.check(view, null);        }        viewAction.perform(uic, view);      }    };  }  ...

2.2 系统支持

2.2.1 Click | Press 类型

2.2.1.1 click()点击

  /**   * 点击   * View必须显示在屏幕上   */  public static ViewAction click() {    return actionWithAssertions(        new GeneralClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER, Press.FINGER));  }  /**   * 单击   *   * If the click takes longer than the 'long press' duration (which is possible) the provided   * rollback action is invoked on the view and a click is attempted again.   *   * This is only necessary if the view being clicked on has some different behaviour for long press   * versus a normal tap.   *   * For example - if a long press on a particular view element opens a popup menu -   * ViewActions.pressBack() may be an acceptable rollback action.   *   * <br>   * View constraints:   * <ul>   * <li>must be displayed on screen</li>   * <li>any constraints of the rollbackAction</li>   * <ul>   */  public static ViewAction click(ViewAction rollbackAction) {    checkNotNull(rollbackAction);    return actionWithAssertions(        new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER, rollbackAction));  }

2.2.1.2 doubleclick()双击

  /**   * 双击   * 要求:view在界面上   */  public static ViewAction doubleClick() {    return actionWithAssertions(        new GeneralClickAction(Tap.DOUBLE, GeneralLocation.CENTER, Press.FINGER));  }

2.2.1.3 longclick()长按

  /**   * 长按   * 要求:view在界面上   */  public static ViewAction longClick() {    return actionWithAssertions(        new GeneralClickAction(Tap.LONG, GeneralLocation.CENTER, Press.FINGER));  }

2.2.1.5 pressImeActionButton()

  /**   * Returns an action that presses the current action button (next, done, search, etc) on the IME   * (Input Method Editor). The selected view will have its onEditorAction method called.   */  public static ViewAction pressImeActionButton() {    return actionWithAssertions(new EditorAction());  }

2.2.1.6 pressKey(EspressoKey key) & pressKey(int keyCode) 物理按键

2.2.1.7 pressMenuKey()菜单键 点击

2.2.1.4 pressBack()后退键 点击

  // 后退键 点击  public static ViewAction pressBack() {    return pressKey(KeyEvent.KEYCODE_BACK);  }  // 菜单键 点击  public static ViewAction pressMenuKey() {    return pressKey(KeyEvent.KEYCODE_MENU);  }  /**   * Returns an action that presses the specified key with the specified modifiers.   */  public static ViewAction pressKey(EspressoKey key) {    return actionWithAssertions(new KeyEventAction(key));  }  /**   * Returns an action that presses the key specified by the keyCode (eg. Keyevent.KEYCODE_BACK).   */  public static ViewAction pressKey(int keyCode) {    return actionWithAssertions(        new KeyEventAction(new EspressoKey.Builder().withKeyCode(keyCode).build()));  }

2.2.1.8 closeSoftKeyboard()关闭键盘

  /**   * 关闭键盘   * 如果键盘是关闭状态则不处理   */  public static ViewAction closeSoftKeyboard() {    return actionWithAssertions(new CloseKeyboardAction());  }
  public static ViewAction openLinkWithText(String linkText) {    return openLinkWithText(is(linkText));  }  public static ViewAction openLinkWithUri(String uri) {    return openLinkWithUri(is(Uri.parse(uri)));  }  public static ViewAction openLinkWithText(Matcher<String> linkTextMatcher) {    return openLink(linkTextMatcher, any(Uri.class));  }  public static ViewAction openLinkWithUri(Matcher<Uri> uriMatcher) {    return openLink(any(String.class), uriMatcher);  }  /**   * 打开连接,支持Uri 和 text格式的. 通过调用 连接的onclick方法.   * 需要:   * <li>已经在屏幕上显示   * <li>是TextView的子类   * <li>含有超链接   */  public static ViewAction openLink(Matcher<String> linkTextMatcher, Matcher<Uri> uriMatcher) {    checkNotNull(linkTextMatcher);    checkNotNull(uriMatcher);    return actionWithAssertions(new OpenLinkAction(linkTextMatcher, uriMatcher));  }}

2.2.2 Text类型

2.2.2.1 typeText() 获得焦点并注入文本

2.2.2.2 typeTextIntoFocusedView()在已获得焦点的View上注入文本

/**   * 先点击以选中View,然后一个一个的键入指定的文本字符,使用了ENTER key event.    * 注意:这个行为将先执行一个tap动作以获取焦点,如果之前View里就有文本,焦点可能在任意位置   * View 条件:   * <li>已经显示在屏幕上   * <li>支持输入   * <ul>   */  public static ViewAction typeText(String stringToBeTyped) {    return actionWithAssertions(new TypeTextAction(stringToBeTyped));  }  /**   * 在 已获得焦点的 view中一个一个键入输入的文本   * 注意:是已获得焦点的View,有可能不是当前指定的View   * View 需要满足   * <li>已经在屏幕上   * <li>支持输入    * <li>已经获得了焦点   * <ul>   */  public static ViewAction typeTextIntoFocusedView(String stringToBeTyped) {    return actionWithAssertions(new TypeTextAction(stringToBeTyped, false /* tapToFocus */));  }/** * Enables typing text on views. */public final class TypeTextAction implements ViewAction {  private static final String TAG = TypeTextAction.class.getSimpleName();  private final String stringToBeTyped;  private final boolean tapToFocus;  /**   * Constructs {@link TypeTextAction} with given string. If the string is empty it results in no-op   * (nothing is typed). By default this action sends a tap event to the center of the view to    * attain focus before typing.   *   * @param stringToBeTyped String To be typed by {@link TypeTextAction}   */  public TypeTextAction(String stringToBeTyped) {    this(stringToBeTyped, true);  }  /**   * Constructs {@link TypeTextAction} with given string. If the string is empty it results in no-op   * (nothing is typed).   *   * @param stringToBeTyped String To be typed by {@link TypeTextAction}   * @param tapToFocus indicates whether a tap should be sent to the underlying view before typing.   */  public TypeTextAction(String stringToBeTyped, boolean tapToFocus) {    checkNotNull(stringToBeTyped);    this.stringToBeTyped = stringToBeTyped;    this.tapToFocus = tapToFocus;  }  @SuppressWarnings("unchecked")  @Override  public Matcher<View> getConstraints() {    Matcher<View> matchers = allOf(isDisplayed());    if (!tapToFocus) {      matchers = allOf(matchers, hasFocus());    }    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {       return allOf(matchers, supportsInputMethods());    } else {       // SearchView does not support input methods itself (rather it delegates to an internal text       // view for input).       return allOf(matchers, anyOf(supportsInputMethods(), isAssignableFrom(SearchView.class)));    }  }  @Override  public void perform(UiController uiController, View view) {    // No-op if string is empty.    if (stringToBeTyped.length() == 0) {      Log.w(TAG, "Supplied string is empty resulting in no-op (nothing is typed).");      return;    }    if (tapToFocus) {      // Perform a click.      new GeneralClickAction(Tap.SINGLE, GeneralLocation.CENTER, Press.FINGER)          .perform(uiController, view);      uiController.loopMainThreadUntilIdle();    }    try {      if (!uiController.injectString(stringToBeTyped)) {        Log.e(TAG, "Failed to type text: " + stringToBeTyped);        throw new PerformException.Builder()          .withActionDescription(this.getDescription())          .withViewDescription(HumanReadables.describe(view))          .withCause(new RuntimeException("Failed to type text: " + stringToBeTyped))          .build();      }    } catch (InjectEventSecurityException e) {      Log.e(TAG, "Failed to type text: " + stringToBeTyped);      throw new PerformException.Builder()        .withActionDescription(this.getDescription())        .withViewDescription(HumanReadables.describe(view))        .withCause(e)        .build();    }  }  @Override  public String getDescription() {    return String.format("type text(%s)", stringToBeTyped);  }}

2.2.2.3 clearText() 文本清除

2.2.2.4 replaceText( String ) 文本替换

  /**   * 清除文本   * View必须满足:   * <li>已经显示在屏幕上   * <li>是EditText及其子类   */  public static ViewAction clearText() {    return actionWithAssertions(new ReplaceTextAction(""));  }  /**   * 一次性的将View的文本更新为指定文本   * View必须满足:   * <li>已经显示在屏幕上   * <li>是EditText及其子类   */  public static ViewAction replaceText(@Nonnull String stringToBeSet) {    return actionWithAssertions(new ReplaceTextAction(stringToBeSet));  }/** * 通过view.setText()修改文本 */public final class ReplaceTextAction implements ViewAction {  private final String stringToBeSet;  public ReplaceTextAction(String value) {    checkNotNull(value);    this.stringToBeSet = value;  }  @SuppressWarnings("unchecked")  @Override  public Matcher<View> getConstraints() {    return allOf(isDisplayed(), isAssignableFrom(EditText.class));  }  @Override  public void perform(UiController uiController, View view) {    ((EditText) view).setText(stringToBeSet);  }  @Override  public String getDescription() {    return "replace text";  }}

2.2.3 Gestures类型

2.2.3.1 scrollTo() 滑动到指定View

  /**   * 滑动到指定View   * 需要:   * <li>是 在 ScrollView 的内部;注意listview等不符合要求   * <li>visibility == View.VISIBLE    */  public static ViewAction scrollTo() {    return actionWithAssertions(new ScrollToAction());  }

2.2.3.2 swipeXX() 向XX方向滑动

 /**   * 从垂直方向的中间,从右向左滑动.   * 不见得是从边缘滑动,而是有一定的偏差   * <br>   * 需要   * <li>必须已经显示在屏幕上   */  public static ViewAction swipeLeft() {    return actionWithAssertions(new GeneralSwipeAction(Swipe.FAST,        GeneralLocation.translate(GeneralLocation.CENTER_RIGHT, -EDGE_FUZZ_FACTOR, 0),        GeneralLocation.CENTER_LEFT, Press.FINGER));  }  /**   * 从垂直方向的中间,从左向右滑动.   * 不见得是从边缘滑动,而是有一定的偏差   * 需要   * <li>必须已经显示在屏幕上   */  public static ViewAction swipeRight() {    return actionWithAssertions(new GeneralSwipeAction(Swipe.FAST,        GeneralLocation.translate(GeneralLocation.CENTER_LEFT, EDGE_FUZZ_FACTOR, 0),        GeneralLocation.CENTER_RIGHT, Press.FINGER));  }  /**   * 从水平方向的中间,从上向下滑动.   * 不见得是从边缘滑动,而是有一定的偏差   * 需要   * <li>必须已经显示在屏幕上   */  public static ViewAction swipeDown() {    return actionWithAssertions(new GeneralSwipeAction(Swipe.FAST,        GeneralLocation.translate(GeneralLocation.TOP_CENTER, 0, EDGE_FUZZ_FACTOR),        GeneralLocation.BOTTOM_CENTER, Press.FINGER));  }  /**   * 从水平方向的中间,从下向上滑动.   * 不见得是从边缘滑动,而是有一定的偏差   * 需要   * <li>必须已经显示在屏幕上   */  public static ViewAction swipeUp() {    return actionWithAssertions(new GeneralSwipeAction(Swipe.FAST,        GeneralLocation.translate(GeneralLocation.BOTTOM_CENTER, 0, -EDGE_FUZZ_FACTOR),        GeneralLocation.TOP_CENTER, Press.FINGER));  }

RecyleView

RecyclerViews 和 AdapterViews 的工作原理不同,因此onData()不适用于RecyleView

如果想要与RecyleView交互,请引入“espresso-contrib”,里边包含一系列的Actions可以用于滚动和点击

  • scrollTo - Scrolls to the matched View.
  • scrollToHolder - Scrolls to the matched View Holder.
  • scrollToPosition - Scrolls to a specific position.
  • actionOnHolderItem - Performs a View Action on a matched View Holder.
  • actionOnItem - Performs a View Action on a matched View.
  • actionOnItemAtPosition - Performs a ViewAction on a view at a specific position.

示例:

    public static class RecyclerViewActions{        /**         *  根据 子view中包含的文本----滚到 且 单击         *   onView(withId(R.id.recycler_main)).perform(clickWithChildDescendant("销售机会"));         * @param childHasText  子view中包含的文本         * @return         */        public static ViewAction clickWithChildDescendant(String childHasText){          return   actionOnItem(hasDescendant(withText(childHasText)), click());        }        /**         *  根据 子view中包含的文本----滚到 且 单击         *  recyclerView.perform(actionOnItemAtPosition(10, click()));         * @param childPos  子view中包含的文本         * @return         */        public static ViewAction clickWithChildPos(int childPos){            return   actionOnItemAtPosition(childPos, click());        }    }

再者:

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(4,click()));onView(withId(R.id.recyclerView))                .perform(RecyclerViewActions.actionOnItem(                        hasDescendant(withText("Effective Java ")), click()));
0 0
原创粉丝点击