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