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 UiBot(UiDevice device, Context context, int timeout)90 public UiBot(UiDevice device, Context context, int timeout) { 91 super(device, context, timeout); 92 } 93 assertWindowTitle(String expected)94 public void assertWindowTitle(String expected) { 95 onView(TOOLBAR) 96 .check(matches(withToolbarTitle(is(expected)))); 97 } 98 assertMenuEnabled(int id, boolean enabled)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 assertInActionMode(boolean inActionMode)105 public void assertInActionMode(boolean inActionMode) { 106 assertEquals(inActionMode, waitForActionModeBarToAppear()); 107 } 108 openOverflowMenu()109 public UiObject openOverflowMenu() throws UiObjectNotFoundException { 110 UiObject obj = findMenuMoreOptions(); 111 obj.click(); 112 mDevice.waitForIdle(mTimeout); 113 return obj; 114 } 115 setDialogText(String text)116 public void setDialogText(String text) throws UiObjectNotFoundException { 117 onView(TEXT_ENTRY) 118 .perform(ViewActions.replaceText(text)); 119 } 120 assertDialogText(String expected)121 public void assertDialogText(String expected) throws UiObjectNotFoundException { 122 onView(TEXT_ENTRY) 123 .check(matches(withText(is(expected)))); 124 } 125 inFixedLayout()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 inDrawerLayout()135 public boolean inDrawerLayout() { 136 return !inFixedLayout(); 137 } 138 switchToListMode()139 public void switchToListMode() { 140 final UiObject2 listMode = menuListMode(); 141 if (listMode != null) { 142 listMode.click(); 143 } 144 } 145 clickActionItem(String label)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 switchToGridMode()154 public void switchToGridMode() { 155 final UiObject2 gridMode = menuGridMode(); 156 if (gridMode != null) { 157 gridMode.click(); 158 } 159 } 160 menuGridMode()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 menuListMode()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 clickToolbarItem(int id)171 public void clickToolbarItem(int id) { 172 onView(withId(id)).perform(click()); 173 } 174 clickNewFolder()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 clickActionbarOverflowItem(String label)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 clickToolbarOverflowItem(String label)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 waitForActionModeBarToAppear()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 findDownloadRetryDialog()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 findFileRenameDialog()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 findRenameErrorMessage()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") assertDialogOkButtonFocused()222 public void assertDialogOkButtonFocused() { 223 onView(withId(android.R.id.button1)).check(matches(hasFocus())); 224 } 225 clickDialogOkButton()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 clickDialogCancelButton()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 findMenuLabelWithName(String label)240 UiObject findMenuLabelWithName(String label) { 241 UiSelector selector = new UiSelector().text(label); 242 return mDevice.findObject(selector); 243 } 244 findMenuWithName(String label)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) { 254 break; 255 } 256 } 257 return menuItem; 258 } 259 hasMenuWithName(String label)260 boolean hasMenuWithName(String label) { 261 return findMenuWithName(label) != null; 262 } 263 findMenuMoreOptions()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 withToolbarTitle( final Matcher<CharSequence> textMatcher)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