1 /* 2 * Copyright (C) 2018 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.traceur.uitest; 18 19 import static org.junit.Assert.assertNotNull; 20 import static org.junit.Assert.assertTrue; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.os.RemoteException; 26 import android.os.SystemClock; 27 import android.platform.test.annotations.Presubmit; 28 import android.support.test.uiautomator.By; 29 import android.support.test.uiautomator.UiDevice; 30 import android.support.test.uiautomator.UiObject2; 31 import android.support.test.uiautomator.UiObjectNotFoundException; 32 import android.support.test.uiautomator.UiSelector; 33 import android.support.test.uiautomator.UiScrollable; 34 import android.support.test.uiautomator.Until; 35 36 import androidx.test.InstrumentationRegistry; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import org.junit.After; 40 import org.junit.Before; 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import java.util.List; 45 import java.util.regex.Pattern; 46 47 @RunWith(AndroidJUnit4.class) 48 public class TraceurAppTests { 49 50 private static final String TRACEUR_PACKAGE = "com.android.traceur"; 51 private static final int LAUNCH_TIMEOUT_MS = 10000; 52 private static final int UI_TIMEOUT_MS = 7500; 53 private static final int SHORT_PAUSE_MS = 1000; 54 55 private UiDevice mDevice; 56 57 @Before setUp()58 public void setUp() throws Exception { 59 mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 60 61 try { 62 if (!mDevice.isScreenOn()) { 63 mDevice.wakeUp(); 64 } 65 66 // Press Menu to skip the lock screen. 67 // In case we weren't on the lock screen, press Home to return to a clean launcher. 68 mDevice.pressMenu(); 69 mDevice.pressHome(); 70 71 mDevice.setOrientationNatural(); 72 } catch (RemoteException e) { 73 throw new RuntimeException("Failed to freeze device orientation.", e); 74 } 75 76 mDevice.waitForIdle(); 77 78 Context context = InstrumentationRegistry.getContext(); 79 Intent intent = context.getPackageManager().getLaunchIntentForPackage(TRACEUR_PACKAGE); 80 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); // Clear out any previous instances 81 context.startActivity(intent); 82 83 // Wait for the app to appear. 84 assertTrue(mDevice.wait(Until.hasObject(By.pkg(TRACEUR_PACKAGE).depth(0)), 85 LAUNCH_TIMEOUT_MS)); 86 // Default trace categories are restored in case a previous test modified them and 87 // terminated early. 88 restoreDefaultCategories(); 89 } 90 91 @After tearDown()92 public void tearDown() throws Exception { 93 mDevice.unfreezeRotation(); 94 // Finish Traceur activity. 95 mDevice.pressBack(); 96 mDevice.pressHome(); 97 } 98 99 /** 100 * Verifies that the main page contains the correct UI elements. 101 * If the main page is scrollable, the test checks that all expected elements are found while 102 * scrolling. Otherwise, it checks that the expected elements are already on the page. 103 */ 104 @Presubmit 105 @Test testElementsOnMainScreen()106 public void testElementsOnMainScreen() throws Exception { 107 UiScrollable scrollableMainScreen = new UiScrollable(new UiSelector().scrollable(true)); 108 109 if (scrollableMainScreen.exists()) { 110 scrollableMainScreen.setAsVerticalList(); 111 scrollableMainScreen.setMaxSearchSwipes(10); 112 113 boolean recordFound = scrollableMainScreen.scrollTextIntoView("Record trace"); 114 assertTrue("Record trace switch not found.", recordFound); 115 116 boolean applicationsFound = 117 scrollableMainScreen.scrollTextIntoView("Trace debuggable applications"); 118 assertTrue("Applications element not found.", applicationsFound); 119 120 boolean categoriesFound = scrollableMainScreen.scrollTextIntoView("Categories"); 121 assertTrue("Categories element not found.", categoriesFound); 122 123 boolean restoreFound = scrollableMainScreen.scrollTextIntoView("Restore default categories"); 124 assertTrue("Restore default categories element not found.", restoreFound); 125 126 boolean bufferSizeFound = scrollableMainScreen.scrollTextIntoView("Per-CPU buffer size"); 127 assertTrue("Per-CPU buffer size element not found.", bufferSizeFound); 128 129 boolean clearFound = scrollableMainScreen.scrollTextIntoView("Clear saved traces"); 130 assertTrue("Clear saved traces element not found.", clearFound); 131 132 boolean longTraceFound = scrollableMainScreen.scrollTextIntoView("Long traces"); 133 assertTrue("Long traces element not found.", longTraceFound); 134 135 boolean maxTraceSizeFound = scrollableMainScreen.scrollTextIntoView("Maximum long trace size"); 136 assertTrue("Maximum long trace size element not found.", maxTraceSizeFound); 137 138 boolean maxTraceDurationFound = 139 scrollableMainScreen.scrollTextIntoView("Maximum long trace duration"); 140 assertTrue("Maximum long trace duration element not found.", maxTraceDurationFound); 141 142 boolean quickSettingsFound = scrollableMainScreen.scrollTextIntoView("Show Quick Settings tile"); 143 assertTrue("Show Quick Settings tile switch not found.", quickSettingsFound); 144 } else { 145 assertNotNull("Record trace switch not found.", 146 mDevice.wait(Until.findObject(By.text("Record trace")), 147 UI_TIMEOUT_MS)); 148 assertNotNull("Applications element not found.", 149 mDevice.wait(Until.findObject(By.text("Trace debuggable applications")), 150 UI_TIMEOUT_MS)); 151 assertNotNull("Categories element not found.", 152 mDevice.wait(Until.findObject(By.text("Categories")), 153 UI_TIMEOUT_MS)); 154 assertNotNull("Restore default categories element not found.", 155 mDevice.wait(Until.findObject(By.text("Restore default categories")), 156 UI_TIMEOUT_MS)); 157 assertNotNull("Per-CPU buffer size element not found.", 158 mDevice.wait(Until.findObject(By.text("Per-CPU buffer size")), 159 UI_TIMEOUT_MS)); 160 assertNotNull("Clear saved traces element not found.", 161 mDevice.wait(Until.findObject(By.text("Clear saved traces")), 162 UI_TIMEOUT_MS)); 163 assertNotNull("Long traces element not found.", 164 mDevice.wait(Until.findObject(By.text("Long traces")), 165 UI_TIMEOUT_MS)); 166 assertNotNull("Maximum long trace size element not found.", 167 mDevice.wait(Until.findObject(By.text("Maximum long trace size")), 168 UI_TIMEOUT_MS)); 169 assertNotNull("Maximum long trace duration element not found.", 170 mDevice.wait(Until.findObject(By.text("Maximum long trace duration")), 171 UI_TIMEOUT_MS)); 172 assertNotNull("Show Quick Settings tile switch not found.", 173 mDevice.wait(Until.findObject(By.text("Show Quick Settings tile")), 174 UI_TIMEOUT_MS)); 175 } 176 } 177 178 /** 179 * Checks that a trace can be recorded and shared. 180 * This test records a trace by toggling 'Record trace' in the UI, taps on the share 181 * notification once the trace is saved, then (on non-AOSP) verifies that a share dialog 182 * appears. 183 */ 184 @Presubmit 185 @Test testSuccessfulTracing()186 public void testSuccessfulTracing() throws Exception { 187 UiObject2 recordTraceSwitch = mDevice.wait(Until.findObject(By.text("Record trace")), 188 UI_TIMEOUT_MS); 189 assertNotNull("Record trace switch not found.", recordTraceSwitch); 190 recordTraceSwitch.click(); 191 192 mDevice.waitForIdle(); 193 194 mDevice.wait(Until.hasObject(By.text("Trace is being recorded")), UI_TIMEOUT_MS); 195 mDevice.wait(Until.gone(By.text("Trace is being recorded")), UI_TIMEOUT_MS); 196 197 recordTraceSwitch = mDevice.wait(Until.findObject(By.text("Record trace")), UI_TIMEOUT_MS); 198 assertNotNull("Record trace switch not found.", recordTraceSwitch); 199 recordTraceSwitch.click(); 200 201 mDevice.waitForIdle(); 202 203 // Wait for the popover notification to appear and then disappear, 204 // so we can reliably click the notification in the notification shade. 205 mDevice.wait(Until.hasObject(By.text("Tap to share your trace")), UI_TIMEOUT_MS); 206 mDevice.wait(Until.gone(By.text("Tap to share your trace")), UI_TIMEOUT_MS); 207 208 mDevice.openNotification(); 209 UiObject2 shareNotification = mDevice.wait(Until.findObject( 210 By.text("Tap to share your trace")), 211 UI_TIMEOUT_MS); 212 assertNotNull("Share notification not found.", shareNotification); 213 shareNotification.click(); 214 215 mDevice.waitForIdle(); 216 217 UiObject2 shareDialog = mDevice.wait(Until.findObject( 218 By.textContains("Only share system traces with people and apps you trust.")), 219 UI_TIMEOUT_MS); 220 assertNotNull("Share dialog not found.", shareDialog); 221 222 // The buttons on dialogs sometimes have their capitalization manipulated by themes. 223 UiObject2 shareButton = mDevice.wait(Until.findObject( 224 By.text(Pattern.compile("share", Pattern.CASE_INSENSITIVE))), UI_TIMEOUT_MS); 225 assertNotNull("Share button not found.", shareButton); 226 shareButton.click(); 227 228 // The share sheet will not appear on AOSP builds, as there are no apps available to share 229 // traces with. This checks if Gmail is installed (i.e. if the build is non-AOSP) before 230 // verifying that the share sheet exists. 231 try { 232 Context context = InstrumentationRegistry.getContext(); 233 context.getPackageManager().getApplicationInfo("com.google.android.gm", 0); 234 UiObject2 shareSheet = mDevice.wait(Until.findObject( 235 By.res("android:id/profile_tabhost")), UI_TIMEOUT_MS); 236 assertNotNull("Share sheet not found.", shareSheet); 237 } catch (PackageManager.NameNotFoundException e) { 238 // Gmail is not installed, so the device is on an AOSP build. 239 } 240 } 241 242 /** 243 * Checks that trace categories are displayed after tapping on the 'Categories' button. 244 */ 245 @Presubmit 246 @Test testTraceCategoriesExist()247 public void testTraceCategoriesExist() { 248 openTraceCategories(); 249 List<UiObject2> categories = getTraceCategories(); 250 assertNotNull("List of categories not found.", categories); 251 assertTrue("No available trace categories.", categories.size() > 0); 252 } 253 254 /** 255 * Checks that the 'Categories' summary updates when trace categories are selected. 256 * This test checks that the summary for the 'Categories' button changes from 'Default' to 'N 257 * selected' when a trace category is clicked, then back to 'Default' when the same category is 258 * clicked again. 259 */ 260 @Presubmit 261 @Test testCorrectCategoriesSummary()262 public void testCorrectCategoriesSummary() { 263 UiObject2 summary = getCategoriesSummary(); 264 assertTrue("Expected 'Default' summary not found on startup.", 265 summary.getText().contains("Default")); 266 267 openTraceCategories(); 268 toggleFirstTraceCategory(); 269 270 // The summary must be reset after each toggle because the reference will be stale. 271 summary = getCategoriesSummary(); 272 assertTrue("Expected 'N selected' summary not found.", 273 summary.getText().contains("selected")); 274 275 openTraceCategories(); 276 toggleFirstTraceCategory(); 277 278 summary = getCategoriesSummary(); 279 assertTrue("Expected 'Default' summary not found after changing categories.", 280 summary.getText().contains("Default")); 281 } 282 283 /** 284 * Checks that the 'Restore default categories' button resets the trace categories summary. 285 * This test changes the set of selected trace categories from the default, then checks that the 286 * 'Categories' summary resets to 'Default' when the restore button is clicked. 287 */ 288 @Presubmit 289 @Test testRestoreDefaultCategories()290 public void testRestoreDefaultCategories() { 291 openTraceCategories(); 292 toggleFirstTraceCategory(); 293 294 UiObject2 summary = getCategoriesSummary(); 295 assertTrue("Expected 'N selected' summary not found.", 296 summary.getText().contains("selected")); 297 298 restoreDefaultCategories(); 299 300 // The summary must be reset after the toggle because the reference will be stale. 301 summary = getCategoriesSummary(); 302 assertTrue("Expected 'Default' summary not found after restoring categories.", 303 summary.getText().contains("Default")); 304 } 305 306 /** 307 * Taps on the 'Categories' button. 308 */ openTraceCategories()309 private void openTraceCategories() { 310 UiObject2 categoriesButton = mDevice.wait(Until.findObject( 311 By.text("Categories")), UI_TIMEOUT_MS); 312 assertNotNull("Categories button not found.", categoriesButton); 313 categoriesButton.click(); 314 315 mDevice.waitForIdle(); 316 } 317 318 /** 319 * Taps on the 'Restore default categories' button. 320 */ restoreDefaultCategories()321 private void restoreDefaultCategories() { 322 UiObject2 restoreButton = mDevice.wait(Until.findObject( 323 By.text("Restore default categories")), UI_TIMEOUT_MS); 324 assertNotNull("'Restore default categories' button not found.", restoreButton); 325 restoreButton.click(); 326 327 mDevice.waitForIdle(); 328 // This pause is necessary because the trace category restoration takes time to propagate to 329 // the main page. 330 SystemClock.sleep(SHORT_PAUSE_MS); 331 } 332 333 /** 334 * Returns the UiObject2 of the summary for 'Categories'. 335 * This must only be used on Traceur's main page. 336 */ getCategoriesSummary()337 private UiObject2 getCategoriesSummary() { 338 UiObject2 categoriesButton = mDevice.wait(Until.findObject( 339 By.text("Categories")), UI_TIMEOUT_MS); 340 assertNotNull("Categories button not found.", categoriesButton); 341 // The summary text is a sibling view of 'Categories' and can be found through their parent. 342 UiObject2 categoriesSummary = categoriesButton.getParent().wait(Until.findObject( 343 By.res("android:id/summary")), UI_TIMEOUT_MS); 344 assertNotNull("Categories summary not found.", categoriesSummary); 345 return categoriesSummary; 346 } 347 348 /** 349 * Returns the list of available trace categories. 350 * This must only be used after openTraceCategories() has been called. 351 */ getTraceCategories()352 private List<UiObject2> getTraceCategories() { 353 UiObject2 categoriesListView = mDevice.wait(Until.findObject( 354 By.res("android:id/select_dialog_listview")), UI_TIMEOUT_MS); 355 assertNotNull("List of categories not found.", categoriesListView); 356 return categoriesListView.getChildren(); 357 } 358 359 /** 360 * Toggles the first checkbox in the list of trace categories. 361 * This must only be used after openTraceCategories() has been called. 362 */ toggleFirstTraceCategory()363 private void toggleFirstTraceCategory() { 364 getTraceCategories().get(0).click(); 365 366 mDevice.waitForIdle(); 367 368 UiObject2 confirmButton = mDevice.wait(Until.findObject( 369 By.res("android:id/button1")), UI_TIMEOUT_MS); 370 assertNotNull("'OK' button not found under trace categories list.", confirmButton); 371 confirmButton.click(); 372 373 mDevice.waitForIdle(); 374 // This pause is necessary because the trace category selection takes time to propagate to 375 // the main page. 376 SystemClock.sleep(SHORT_PAUSE_MS); 377 } 378 379 } 380