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