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 junit.framework.Assert.assertEquals; 20 import static junit.framework.Assert.assertFalse; 21 import static junit.framework.Assert.assertNotNull; 22 import static junit.framework.Assert.assertTrue; 23 import static junit.framework.Assert.fail; 24 25 import android.app.UiAutomation; 26 import android.content.Context; 27 import android.graphics.Point; 28 import android.graphics.Rect; 29 import android.os.SystemClock; 30 import android.support.test.uiautomator.By; 31 import android.support.test.uiautomator.BySelector; 32 import android.support.test.uiautomator.Configurator; 33 import android.support.test.uiautomator.UiDevice; 34 import android.support.test.uiautomator.UiObject; 35 import android.support.test.uiautomator.UiObject2; 36 import android.support.test.uiautomator.UiObjectNotFoundException; 37 import android.support.test.uiautomator.UiSelector; 38 import android.support.test.uiautomator.Until; 39 import android.view.InputDevice; 40 import android.view.KeyEvent; 41 import android.view.MotionEvent; 42 import android.view.View; 43 import android.widget.ImageView; 44 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.List; 48 import java.util.regex.Pattern; 49 50 /** 51 * A test helper class that provides support for controlling directory list 52 * and making assertions against the state of it. 53 */ 54 public class DirectoryListBot extends Bots.BaseBot { 55 56 private static final int MAX_LAYOUT_LEVEL = 10; 57 58 private static final BySelector SNACK_DELETE = 59 By.text(Pattern.compile("^Deleting [0-9]+ item.+")); 60 61 private final String mDirContainerId; 62 private final String mDirListId; 63 private final String mItemRootId; 64 private final String mPreviewId; 65 66 private UiAutomation mAutomation; 67 DirectoryListBot( UiDevice device, UiAutomation automation, Context context, int timeout)68 public DirectoryListBot( 69 UiDevice device, UiAutomation automation, Context context, int timeout) { 70 super(device, context, timeout); 71 mAutomation = automation; 72 mDirContainerId = mTargetPackage + ":id/container_directory"; 73 mDirListId = mTargetPackage + ":id/dir_list"; 74 mItemRootId = mTargetPackage + ":id/item_root"; 75 mPreviewId = mTargetPackage + ":id/preview_icon"; 76 } 77 assertDocumentsCount(int count)78 public void assertDocumentsCount(int count) throws UiObjectNotFoundException { 79 UiObject docsList = findDocumentsList(); 80 assertEquals(count, docsList.getChildCount()); 81 } 82 assertDocumentsPresent(String... labels)83 public void assertDocumentsPresent(String... labels) throws UiObjectNotFoundException { 84 List<String> absent = new ArrayList<>(); 85 for (String label : labels) { 86 if (!findDocument(label).exists()) { 87 absent.add(label); 88 } 89 } 90 if (!absent.isEmpty()) { 91 fail("Expected documents " + Arrays.asList(labels) 92 + ", but missing " + absent); 93 } 94 } 95 assertDocumentsAbsent(String... labels)96 public void assertDocumentsAbsent(String... labels) throws UiObjectNotFoundException { 97 List<String> found = new ArrayList<>(); 98 for (String label : labels) { 99 if (findDocument(label).exists()) { 100 found.add(label); 101 } 102 } 103 if (!found.isEmpty()) { 104 fail("Expected documents not present" + Arrays.asList(labels) 105 + ", but present " + found); 106 } 107 } 108 assertDocumentsCountOnList(boolean exists, int count)109 public void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException { 110 UiObject docsList = findDocumentsList(); 111 assertEquals(exists, docsList.exists()); 112 if(docsList.exists()) { 113 assertEquals(count, docsList.getChildCount()); 114 } 115 } 116 assertHasMessage(String expected)117 public void assertHasMessage(String expected) throws UiObjectNotFoundException { 118 UiObject messageTextView = findHeaderMessageTextView(); 119 String msg = String.valueOf(expected); 120 assertEquals(msg, messageTextView.getText()); 121 } 122 assertHasMessage(boolean expected)123 public void assertHasMessage(boolean expected) throws UiObjectNotFoundException { 124 UiObject messageTextView = findHeaderMessageTextView(); 125 if (expected) { 126 assertTrue(messageTextView.exists()); 127 } else { 128 assertFalse(messageTextView.exists()); 129 } 130 } 131 assertHasMessageButtonText(String expected)132 public void assertHasMessageButtonText(String expected) throws UiObjectNotFoundException { 133 UiObject button = findHeaderMessageButton(); 134 String msg = String.valueOf(expected); 135 assertEquals(msg.toUpperCase(), button.getText().toUpperCase()); 136 } 137 clickMessageButton()138 public void clickMessageButton() throws UiObjectNotFoundException { 139 UiObject button = findHeaderMessageButton(); 140 button.click(); 141 } 142 143 /** 144 * Checks against placeholder text. Placeholder can be Empty page, No results page, or the 145 * "Hourglass" page (ie. something-went-wrong page). 146 */ assertPlaceholderMessageText(String message)147 public void assertPlaceholderMessageText(String message) throws UiObjectNotFoundException { 148 UiObject messageTextView = findPlaceholderMessageTextView(); 149 assertTrue(messageTextView.exists()); 150 151 String msg = String.valueOf(message); 152 assertEquals(msg, messageTextView.getText()); 153 154 } 155 findHeaderMessageTextView()156 private UiObject findHeaderMessageTextView() { 157 return findObject( 158 mDirContainerId, 159 mTargetPackage + ":id/message_textview"); 160 } 161 findHeaderMessageButton()162 private UiObject findHeaderMessageButton() { 163 return findObject( 164 mDirContainerId, 165 mTargetPackage + ":id/button_dismiss"); 166 } 167 findPlaceholderMessageTextView()168 private UiObject findPlaceholderMessageTextView() { 169 return findObject( 170 mDirContainerId, 171 mTargetPackage + ":id/message"); 172 } 173 waitForHolderMessage()174 public void waitForHolderMessage() { 175 findPlaceholderMessageTextView().waitForExists(mTimeout); 176 } 177 assertSnackbar(int id)178 public void assertSnackbar(int id) { 179 assertNotNull(getSnackbar(mContext.getString(id))); 180 } 181 openDocument(String label)182 public void openDocument(String label) throws UiObjectNotFoundException { 183 int toolType = Configurator.getInstance().getToolType(); 184 Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER); 185 UiObject doc = findDocument(label); 186 doc.click(); 187 Configurator.getInstance().setToolType(toolType); 188 } 189 selectDocument(String label)190 public void selectDocument(String label) throws UiObjectNotFoundException { 191 waitForDocument(label); 192 UiObject2 selectionHotspot = findSelectionHotspot(label); 193 selectionHotspot.click(); 194 } 195 196 /** 197 * @param label The filename of the document 198 * @param number Which nth document it is. The number corresponding to "n selected" 199 */ selectDocument(String label, int number)200 public void selectDocument(String label, int number) throws UiObjectNotFoundException { 201 selectDocument(label); 202 203 // wait until selection is fully done to avoid future click being registered as double 204 // clicking 205 assertSelection(number); 206 } 207 isDocumentSelected(String label)208 public boolean isDocumentSelected(String label) throws UiObjectNotFoundException { 209 waitForDocument(label); 210 UiObject2 selectionHotspot = findSelectionHotspot(label); 211 return selectionHotspot.getResourceName() 212 .equals(mTargetPackage + ":id/icon_check"); 213 } 214 findSelectionHotspot(String label)215 public UiObject2 findSelectionHotspot(String label) { 216 final BySelector list = By.res(mDirListId); 217 218 BySelector selector = By.hasChild(By.text(label)); 219 UiObject2 parent = mDevice.findObject(list).findObject(selector); 220 if (parent.getClassName().equals("android.widget.LinearLayout") 221 || parent.getClassName().equals("android.widget.RelativeLayout")) { 222 // For list mode and doc grid, the parent of the textView does not contain the selector 223 // icon, but the grandparent of the textView does 224 // Gotta go one more level up 225 selector = By.hasDescendant(By.text(label).depth(2)); 226 parent = mDevice.findObject(list).findObject(selector); 227 } 228 return parent.findObject(By.clazz(ImageView.class)); 229 } 230 copyFilesToClipboard(String...labels)231 public void copyFilesToClipboard(String...labels) throws UiObjectNotFoundException { 232 for (String label: labels) { 233 selectDocument(label); 234 } 235 mDevice.pressKeyCode(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON); 236 } 237 pasteFilesFromClipboard()238 public void pasteFilesFromClipboard() { 239 mDevice.pressKeyCode(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON); 240 } 241 getSnackbar(String message)242 public UiObject2 getSnackbar(String message) { 243 return mDevice.wait(Until.findObject(By.text(message)), mTimeout); 244 } 245 clickSnackbarAction()246 public void clickSnackbarAction() throws UiObjectNotFoundException { 247 UiObject snackbarAction = 248 findObject(mTargetPackage + ":id/snackbar_action"); 249 snackbarAction.click(); 250 } 251 waitForDeleteSnackbar()252 public void waitForDeleteSnackbar() { 253 mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout); 254 } 255 waitForDeleteSnackbarGone()256 public void waitForDeleteSnackbarGone() { 257 // wait a little longer for snackbar to go away, as it disappears after a timeout. 258 mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2); 259 } 260 waitForDocument(String label)261 public void waitForDocument(String label) throws UiObjectNotFoundException { 262 findDocument(label).waitForExists(mTimeout); 263 } 264 findDocument(String label)265 public UiObject findDocument(String label) throws UiObjectNotFoundException { 266 final UiSelector docList = new UiSelector().resourceId( 267 mDirContainerId).childSelector( 268 new UiSelector().resourceId(mDirListId)); 269 270 // Wait for the first list item to appear 271 new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout); 272 273 // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label)); 274 return mDevice.findObject(docList.childSelector(new UiSelector().text(label))); 275 } 276 hasDocuments(String... labels)277 public boolean hasDocuments(String... labels) throws UiObjectNotFoundException { 278 for (String label : labels) { 279 if (!findDocument(label).exists()) { 280 return false; 281 } 282 } 283 return true; 284 } 285 hasDocumentPreview(String label)286 public boolean hasDocumentPreview(String label) { 287 final BySelector list = By.res(mDirListId); 288 final UiObject2 text = mDevice.findObject(list).findObject(By.text(label)); 289 290 UiObject2 parent = text; 291 for (int i = 1; i <= MAX_LAYOUT_LEVEL; i++) { 292 parent = parent.getParent(); 293 if (mItemRootId.equals(parent.getResourceName())) { 294 break; 295 } 296 } 297 298 return parent.hasObject(By.res(mPreviewId)); 299 } 300 assertFirstDocumentHasFocus()301 public void assertFirstDocumentHasFocus() throws UiObjectNotFoundException { 302 final UiSelector docList = new UiSelector().resourceId( 303 mDirContainerId).childSelector( 304 new UiSelector().resourceId(mDirListId)); 305 306 // Wait for the first list item to appear 307 UiObject doc = new UiObject(docList.childSelector(new UiSelector())); 308 doc.waitForExists(mTimeout); 309 310 assertTrue(doc.isFocused()); 311 } 312 findDocumentsList()313 public UiObject findDocumentsList() { 314 return findObject( 315 mDirContainerId, 316 mDirListId); 317 } 318 assertHasFocus()319 public void assertHasFocus() { 320 assertHasFocus(mDirListId); 321 } 322 assertSelection(int numSelected)323 public void assertSelection(int numSelected) { 324 String assertSelectionText = numSelected + " selected"; 325 UiObject2 selectionText = mDevice.wait( 326 Until.findObject(By.text(assertSelectionText)), mTimeout); 327 assertTrue(selectionText != null); 328 } 329 assertOrder(String[] dirs, String[] files)330 public void assertOrder(String[] dirs, String[] files) throws UiObjectNotFoundException { 331 for (int i = 0; i < dirs.length - 1; ++i) { 332 assertOrder(dirs[i], dirs[i + 1]); 333 } 334 335 if (dirs.length > 0 && files.length > 0) { 336 assertOrder(dirs[dirs.length - 1], files[0]); 337 } 338 339 for (int i = 0; i < files.length - 1; ++i) { 340 assertOrder(files[i], files[i + 1]); 341 } 342 } 343 rightClickDocument(String label)344 public void rightClickDocument(String label) throws UiObjectNotFoundException { 345 Rect startCoord = findDocument(label).getBounds(); 346 rightClickDocument(new Point(startCoord.centerX(), startCoord.centerY())); 347 } 348 rightClickDocument(Point point)349 public void rightClickDocument(Point point) throws UiObjectNotFoundException { 350 //TODO: Use Espresso instead of doing the events mock ourselves 351 MotionEvent motionDown = getTestMotionEvent( 352 MotionEvent.ACTION_DOWN, 353 MotionEvent.BUTTON_SECONDARY, 354 MotionEvent.TOOL_TYPE_MOUSE, 355 InputDevice.SOURCE_MOUSE, 356 point.x, 357 point.y); 358 mAutomation.injectInputEvent(motionDown, true); 359 SystemClock.sleep(100); 360 361 MotionEvent motionUp = getTestMotionEvent( 362 MotionEvent.ACTION_UP, 363 MotionEvent.BUTTON_SECONDARY, 364 MotionEvent.TOOL_TYPE_MOUSE, 365 InputDevice.SOURCE_MOUSE, 366 point.x, 367 point.y); 368 369 mAutomation.injectInputEvent(motionUp, true); 370 } 371 getTestMotionEvent( int action, int buttonState, int toolType, int source, int x, int y)372 private MotionEvent getTestMotionEvent( 373 int action, int buttonState, int toolType, int source, int x, int y) { 374 long eventTime = SystemClock.uptimeMillis(); 375 376 MotionEvent.PointerProperties[] pp = {new MotionEvent.PointerProperties()}; 377 pp[0].clear(); 378 pp[0].id = 0; 379 pp[0].toolType = toolType; 380 381 MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()}; 382 pointerCoords[0].clear(); 383 pointerCoords[0].x = x; 384 pointerCoords[0].y = y; 385 pointerCoords[0].pressure = 0; 386 pointerCoords[0].size = 1; 387 388 return MotionEvent.obtain( 389 eventTime, 390 eventTime, 391 action, 392 1, 393 pp, 394 pointerCoords, 395 0, 396 buttonState, 397 1f, 398 1f, 399 0, 400 0, 401 source, 402 0); 403 } 404 assertOrder(String first, String second)405 private void assertOrder(String first, String second) throws UiObjectNotFoundException { 406 407 final UiObject firstObj = findDocument(first); 408 final UiObject secondObj = findDocument(second); 409 410 final int layoutDirection = mContext.getResources().getConfiguration().getLayoutDirection(); 411 final Rect firstBound = firstObj.getVisibleBounds(); 412 final Rect secondBound = secondObj.getVisibleBounds(); 413 if (layoutDirection == View.LAYOUT_DIRECTION_LTR) { 414 assertTrue( 415 "\"" + first + "\" is not located above or to the left of \"" + second 416 + "\" in LTR", 417 firstBound.bottom < secondBound.top || firstBound.right < secondBound.left); 418 } else { 419 assertTrue( 420 "\"" + first + "\" is not located above or to the right of \"" + second + 421 "\" in RTL", 422 firstBound.bottom < secondBound.top || firstBound.left > secondBound.right); 423 } 424 } 425 } 426