Home | History | Annotate | Download | only in bots
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.documentsui.bots;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.action.ViewActions.click;
     21 import static android.support.test.espresso.assertion.ViewAssertions.matches;
     22 import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
     23 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
     24 import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
     25 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     26 import static android.support.test.espresso.matcher.ViewMatchers.withText;
     27 import static junit.framework.Assert.assertEquals;
     28 import static junit.framework.Assert.assertNotNull;
     29 import static org.hamcrest.CoreMatchers.allOf;
     30 import static org.hamcrest.CoreMatchers.is;
     31 import static org.hamcrest.Matchers.endsWith;
     32 
     33 import android.content.Context;
     34 import android.support.test.espresso.Espresso;
     35 import android.support.test.espresso.action.ViewActions;
     36 import android.support.test.espresso.matcher.BoundedMatcher;
     37 import android.support.test.espresso.matcher.ViewMatchers;
     38 import android.support.test.uiautomator.By;
     39 import android.support.test.uiautomator.UiDevice;
     40 import android.support.test.uiautomator.UiObject;
     41 import android.support.test.uiautomator.UiObject2;
     42 import android.support.test.uiautomator.UiObjectNotFoundException;
     43 import android.support.test.uiautomator.UiSelector;
     44 import android.support.test.uiautomator.Until;
     45 import android.util.TypedValue;
     46 import android.view.View;
     47 import android.widget.Toolbar;
     48 
     49 import com.android.documentsui.R;
     50 
     51 import org.hamcrest.Description;
     52 import org.hamcrest.Matcher;
     53 
     54 import java.util.Iterator;
     55 import java.util.List;
     56 
     57 /**
     58  * A test helper class that provides support for controlling DocumentsUI activities
     59  * programmatically, and making assertions against the state of the UI.
     60  * <p>
     61  * Support for working directly with Roots and Directory view can be found in the respective bots.
     62  */
     63 public class UiBot extends Bots.BaseBot {
     64 
     65     public static final String TARGET_PKG = "com.android.documentsui";
     66 
     67     @SuppressWarnings("unchecked")
     68     private static final Matcher<View> TOOLBAR = allOf(
     69             isAssignableFrom(Toolbar.class),
     70             withId(R.id.toolbar));
     71 
     72     @SuppressWarnings("unchecked")
     73     private static final Matcher<View> ACTIONBAR = allOf(
     74             withClassName(endsWith("ActionBarContextView")));
     75 
     76     @SuppressWarnings("unchecked")
     77     private static final Matcher<View> TEXT_ENTRY = allOf(
     78             withClassName(endsWith("EditText")));
     79 
     80     @SuppressWarnings("unchecked")
     81     private static final Matcher<View> TOOLBAR_OVERFLOW = allOf(
     82             withClassName(endsWith("OverflowMenuButton")),
     83             ViewMatchers.isDescendantOfA(TOOLBAR));
     84 
     85     @SuppressWarnings("unchecked")
     86     private static final Matcher<View> ACTIONBAR_OVERFLOW = allOf(
     87             withClassName(endsWith("OverflowMenuButton")),
     88             ViewMatchers.isDescendantOfA(ACTIONBAR));
     89 
     90     public UiBot(UiDevice device, Context context, int timeout) {
     91         super(device, context, timeout);
     92     }
     93 
     94     public void assertWindowTitle(String expected) {
     95         onView(TOOLBAR)
     96                 .check(matches(withToolbarTitle(is(expected))));
     97     }
     98 
     99     public void assertMenuEnabled(int id, boolean enabled) {
    100         UiObject2 menu = findMenuWithName(mContext.getString(id));
    101         assertNotNull(menu);
    102         assertEquals(enabled, menu.isEnabled());
    103     }
    104 
    105     public void assertInActionMode(boolean inActionMode) {
    106         assertEquals(inActionMode, waitForActionModeBarToAppear());
    107     }
    108 
    109     public UiObject openOverflowMenu() throws UiObjectNotFoundException {
    110         UiObject obj = findMenuMoreOptions();
    111         obj.click();
    112         mDevice.waitForIdle(mTimeout);
    113         return obj;
    114     }
    115 
    116     public void setDialogText(String text) throws UiObjectNotFoundException {
    117         onView(TEXT_ENTRY)
    118                 .perform(ViewActions.replaceText(text));
    119     }
    120 
    121     public void assertDialogText(String expected) throws UiObjectNotFoundException {
    122         onView(TEXT_ENTRY)
    123                 .check(matches(withText(is(expected))));
    124     }
    125 
    126     public boolean inFixedLayout() {
    127         TypedValue val = new TypedValue();
    128         // We alias files_activity to either fixed or drawer layouts based
    129         // on screen dimensions. In order to determine which layout
    130         // has been selected, we check the resolved value.
    131         mContext.getResources().getValue(R.layout.files_activity, val, true);
    132         return val.resourceId == R.layout.fixed_layout;
    133     }
    134 
    135     public boolean inDrawerLayout() {
    136         return !inFixedLayout();
    137     }
    138 
    139     public void switchToListMode() {
    140         final UiObject2 listMode = menuListMode();
    141         if (listMode != null) {
    142             listMode.click();
    143         }
    144     }
    145 
    146     public void clickActionItem(String label) throws UiObjectNotFoundException {
    147         if (!waitForActionModeBarToAppear()) {
    148             throw new UiObjectNotFoundException("ActionMode bar not found");
    149         }
    150         clickActionbarOverflowItem(label);
    151         mDevice.waitForIdle();
    152     }
    153 
    154     public void switchToGridMode() {
    155         final UiObject2 gridMode = menuGridMode();
    156         if (gridMode != null) {
    157             gridMode.click();
    158         }
    159     }
    160 
    161     UiObject2 menuGridMode() {
    162         // Note that we're using By.desc rather than By.res, because of b/25285770
    163         return find(By.desc("Grid view"));
    164     }
    165 
    166     UiObject2 menuListMode() {
    167         // Note that we're using By.desc rather than By.res, because of b/25285770
    168         return find(By.desc("List view"));
    169     }
    170 
    171     public void clickToolbarItem(int id) {
    172         onView(withId(id)).perform(click());
    173     }
    174 
    175     public void clickNewFolder() {
    176         onView(ACTIONBAR_OVERFLOW).perform(click());
    177 
    178         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
    179         onView(withText("New folder")).perform(click());
    180     }
    181 
    182     public void clickActionbarOverflowItem(String label) {
    183         onView(ACTIONBAR_OVERFLOW).perform(click());
    184         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
    185         onView(withText(label)).perform(click());
    186     }
    187 
    188     public void clickToolbarOverflowItem(String label) {
    189         onView(TOOLBAR_OVERFLOW).perform(click());
    190         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
    191         onView(withText(label)).perform(click());
    192     }
    193 
    194     public boolean waitForActionModeBarToAppear() {
    195         UiObject2 bar =
    196                 mDevice.wait(Until.findObject(By.res("android:id/action_mode_bar")), mTimeout);
    197         return (bar != null);
    198     }
    199 
    200     public UiObject findDownloadRetryDialog() {
    201         UiSelector selector = new UiSelector().text("Couldn't download");
    202         UiObject title = mDevice.findObject(selector);
    203         title.waitForExists(mTimeout);
    204         return title;
    205     }
    206 
    207     public UiObject findFileRenameDialog() {
    208         UiSelector selector = new UiSelector().text("Rename");
    209         UiObject title = mDevice.findObject(selector);
    210         title.waitForExists(mTimeout);
    211         return title;
    212     }
    213 
    214     public UiObject findRenameErrorMessage() {
    215         UiSelector selector = new UiSelector().text(mContext.getString(R.string.name_conflict));
    216         UiObject title = mDevice.findObject(selector);
    217         title.waitForExists(mTimeout);
    218         return title;
    219     }
    220 
    221     @SuppressWarnings("unchecked")
    222     public void assertDialogOkButtonFocused() {
    223         onView(withId(android.R.id.button1)).check(matches(hasFocus()));
    224     }
    225 
    226     public void clickDialogOkButton() {
    227         // Espresso has flaky results when keyboard shows up, so hiding it for now
    228         // before trying to click on any dialog button
    229         Espresso.closeSoftKeyboard();
    230         onView(withId(android.R.id.button1)).perform(click());
    231     }
    232 
    233     public void clickDialogCancelButton() throws UiObjectNotFoundException {
    234         // Espresso has flaky results when keyboard shows up, so hiding it for now
    235         // before trying to click on any dialog button
    236         Espresso.closeSoftKeyboard();
    237         onView(withId(android.R.id.button2)).perform(click());
    238     }
    239 
    240     public UiObject findMenuLabelWithName(String label) {
    241         UiSelector selector = new UiSelector().text(label);
    242         return mDevice.findObject(selector);
    243     }
    244 
    245     UiObject2 findMenuWithName(String label) {
    246         List<UiObject2> menuItems = mDevice.findObjects(By.clazz("android.widget.LinearLayout"));
    247         Iterator<UiObject2> it = menuItems.iterator();
    248 
    249         UiObject2 menuItem = null;
    250         while (it.hasNext()) {
    251             menuItem = it.next();
    252             UiObject2 text = menuItem.findObject(By.text(label));
    253             if (text != null && menuItem.isClickable()) {
    254                 break;
    255             }
    256         }
    257         return menuItem;
    258     }
    259 
    260     boolean hasMenuWithName(String label) {
    261         return findMenuWithName(label) != null;
    262     }
    263 
    264     UiObject findMenuMoreOptions() {
    265         UiSelector selector = new UiSelector().className("android.widget.ImageButton")
    266                 .descriptionContains("More options");
    267         // TODO: use the system string ? android.R.string.action_menu_overflow_description
    268         return mDevice.findObject(selector);
    269     }
    270 
    271     private static Matcher<Object> withToolbarTitle(
    272             final Matcher<CharSequence> textMatcher) {
    273         return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
    274             @Override
    275             public boolean matchesSafely(Toolbar toolbar) {
    276                 return textMatcher.matches(toolbar.getTitle());
    277             }
    278 
    279             @Override
    280             public void describeTo(Description description) {
    281                 description.appendText("with toolbar title: ");
    282                 textMatcher.describeTo(description);
    283             }
    284         };
    285     }
    286 }
    287