1 // Copyright 2020 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.android_webview.test.devui; 6 7 import static androidx.test.espresso.Espresso.onData; 8 import static androidx.test.espresso.Espresso.onView; 9 import static androidx.test.espresso.action.ViewActions.click; 10 import static androidx.test.espresso.action.ViewActions.longClick; 11 import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; 12 import static androidx.test.espresso.assertion.ViewAssertions.matches; 13 import static androidx.test.espresso.intent.Intents.assertNoUnverifiedIntents; 14 import static androidx.test.espresso.intent.Intents.intended; 15 import static androidx.test.espresso.intent.Intents.intending; 16 import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; 17 import static androidx.test.espresso.matcher.ViewMatchers.isEnabled; 18 import static androidx.test.espresso.matcher.ViewMatchers.withId; 19 import static androidx.test.espresso.matcher.ViewMatchers.withText; 20 21 import static org.hamcrest.MatcherAssert.assertThat; 22 import static org.hamcrest.Matchers.allOf; 23 import static org.hamcrest.Matchers.anything; 24 import static org.hamcrest.Matchers.is; 25 import static org.hamcrest.Matchers.not; 26 27 import static org.chromium.android_webview.test.common.crash.CrashInfoTest.createCrashInfo; 28 import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.getClipBoardTextOnUiThread; 29 import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.setClipBoardTextOnUiThread; 30 import static org.chromium.android_webview.test.devui.DeveloperUiTestUtils.withCount; 31 32 import android.app.Activity; 33 import android.app.Instrumentation.ActivityResult; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.pm.ApplicationInfo; 37 import android.content.pm.PackageManager; 38 import android.content.pm.ResolveInfo; 39 import android.content.res.Resources; 40 import android.graphics.Bitmap; 41 import android.graphics.Canvas; 42 import android.graphics.drawable.Drawable; 43 import android.support.test.InstrumentationRegistry; 44 import android.view.View; 45 import android.widget.ImageView; 46 47 import androidx.annotation.IdRes; 48 import androidx.test.espresso.DataInteraction; 49 import androidx.test.espresso.intent.Intents; 50 import androidx.test.espresso.intent.matcher.IntentMatchers; 51 import androidx.test.espresso.intent.rule.IntentsTestRule; 52 import androidx.test.filters.LargeTest; 53 import androidx.test.filters.MediumTest; 54 55 import org.hamcrest.Description; 56 import org.hamcrest.Matcher; 57 import org.hamcrest.TypeSafeMatcher; 58 import org.junit.After; 59 import org.junit.Assume; 60 import org.junit.Rule; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 64 import org.chromium.android_webview.common.PlatformServiceBridge; 65 import org.chromium.android_webview.common.crash.CrashInfo; 66 import org.chromium.android_webview.common.crash.CrashInfo.UploadState; 67 import org.chromium.android_webview.common.crash.CrashUploadUtil; 68 import org.chromium.android_webview.common.crash.CrashUploadUtil.CrashUploadDelegate; 69 import org.chromium.android_webview.common.crash.SystemWideCrashDirectories; 70 import org.chromium.android_webview.devui.CrashesListFragment; 71 import org.chromium.android_webview.devui.MainActivity; 72 import org.chromium.android_webview.devui.R; 73 import org.chromium.android_webview.devui.WebViewPackageError; 74 import org.chromium.android_webview.devui.util.CrashBugUrlFactory; 75 import org.chromium.android_webview.devui.util.WebViewPackageHelper; 76 import org.chromium.android_webview.test.AwJUnit4ClassRunner; 77 import org.chromium.base.Callback; 78 import org.chromium.base.FileUtils; 79 import org.chromium.base.test.util.Batch; 80 import org.chromium.base.test.util.CallbackHelper; 81 import org.chromium.base.test.util.Feature; 82 import org.chromium.components.minidump_uploader.CrashFileManager; 83 import org.chromium.content_public.browser.test.util.TestThreadUtils; 84 85 import java.io.File; 86 import java.io.FileWriter; 87 import java.io.IOException; 88 import java.util.Date; 89 import java.util.List; 90 import java.util.Locale; 91 import java.util.concurrent.ExecutionException; 92 import java.util.concurrent.TimeUnit; 93 94 /** 95 * UI tests for {@link CrashesListFragment}. 96 */ 97 @LargeTest 98 @RunWith(AwJUnit4ClassRunner.class) 99 @Batch(Batch.PER_CLASS) 100 public class CrashesListFragmentTest { 101 private static final String FAKE_APP_PACKAGE_NAME = "com.test.some_package"; 102 private static final String CRASH_REPORT_BUTTON_TEXT = "File bug report"; 103 private static final String CRASH_UPLOAD_BUTTON_TEXT = "Upload this crash report"; 104 105 @Rule 106 public IntentsTestRule mRule = 107 new IntentsTestRule<MainActivity>(MainActivity.class, false, false); 108 109 @After tearDown()110 public void tearDown() { 111 FileUtils.recursivelyDeleteFile(SystemWideCrashDirectories.getWebViewCrashDir(), null); 112 FileUtils.recursivelyDeleteFile(SystemWideCrashDirectories.getWebViewCrashLogDir(), null); 113 // Activity is launched, i.e the test is not skipped. 114 if (mRule.getActivity() != null) { 115 // Tests are responsible for verifying every Intent they trigger. 116 assertNoUnverifiedIntents(); 117 } 118 } 119 launchCrashesFragment()120 private void launchCrashesFragment() { 121 Intent intent = new Intent(); 122 intent.putExtra(MainActivity.FRAGMENT_ID_INTENT_EXTRA, MainActivity.FRAGMENT_ID_CRASHES); 123 mRule.launchActivity(intent); 124 onView(withId(R.id.fragment_crashes_list)).check(matches(isDisplayed())); 125 126 // Stub all external intents, to avoid launching other apps (ex. system browser), has to be 127 // done after launching the activity. 128 intending(not(IntentMatchers.isInternal())) 129 .respondWith(new ActivityResult(Activity.RESULT_OK, null)); 130 } 131 createMinidumpFile(CrashInfo crashInfo)132 private static File createMinidumpFile(CrashInfo crashInfo) throws IOException { 133 CrashFileManager crashFileManager = 134 new CrashFileManager(SystemWideCrashDirectories.getOrCreateWebViewCrashDir()); 135 File dir = crashFileManager.getCrashDirectory(); 136 dir.mkdirs(); 137 String suffix; 138 switch (crashInfo.uploadState) { 139 case UPLOADED: 140 suffix = ".up"; 141 break; 142 case SKIPPED: 143 suffix = ".skipped"; 144 break; 145 case PENDING_USER_REQUESTED: 146 suffix = ".forced"; 147 break; 148 default: 149 suffix = ".dmp"; 150 } 151 return File.createTempFile( 152 "test_minidump", "-" + crashInfo.localId + suffix + ".try0", dir); 153 } 154 appendUploadedEntryToLog(CrashInfo crashInfo)155 private static File appendUploadedEntryToLog(CrashInfo crashInfo) throws IOException { 156 CrashFileManager crashFileManager = 157 new CrashFileManager(SystemWideCrashDirectories.getOrCreateWebViewCrashDir()); 158 File logFile = crashFileManager.getCrashUploadLogFile(); 159 logFile.getParentFile().mkdirs(); 160 FileWriter writer = new FileWriter(logFile, /* append= */ true); 161 StringBuilder sb = new StringBuilder(); 162 sb.append(TimeUnit.MILLISECONDS.toSeconds(crashInfo.uploadTime)); 163 sb.append(","); 164 sb.append(crashInfo.uploadId); 165 sb.append(","); 166 sb.append(crashInfo.localId); 167 sb.append('\n'); 168 try { 169 writer.write(sb.toString()); 170 } finally { 171 writer.close(); 172 } 173 174 return logFile; 175 } 176 writeJsonLogFile(CrashInfo crashInfo)177 private static File writeJsonLogFile(CrashInfo crashInfo) throws IOException { 178 File dir = SystemWideCrashDirectories.getOrCreateWebViewCrashLogDir(); 179 File jsonFile = File.createTempFile("test_minidump-", crashInfo.localId + ".json", dir); 180 FileWriter writer = new FileWriter(jsonFile); 181 writer.write(crashInfo.serializeToJson()); 182 writer.close(); 183 return jsonFile; 184 } 185 getCrashListLoadedListener()186 private CallbackHelper getCrashListLoadedListener() throws ExecutionException { 187 return TestThreadUtils.runOnUiThreadBlocking(() -> { 188 final CallbackHelper helper = new CallbackHelper(); 189 CrashesListFragment.setCrashInfoLoadedListenerForTesting( 190 () -> { helper.notifyCalled(); }); 191 return helper; 192 }); 193 } 194 195 /** 196 * Matches that a {@link ImageView} has the given {@link Drawable}. 197 */ withDrawable(Drawable expectedDrawable)198 private static Matcher<View> withDrawable(Drawable expectedDrawable) { 199 return new TypeSafeMatcher<View>() { 200 @Override 201 public boolean matchesSafely(View view) { 202 if (!(view instanceof ImageView)) { 203 return false; 204 } 205 return drawableEquals(((ImageView) view).getDrawable(), expectedDrawable); 206 } 207 208 @Override 209 public void describeTo(Description description) { 210 description.appendText("with Drawable"); 211 } 212 }; 213 } 214 215 /** 216 * Matches that a {@link ImageView} has the given {@link Drawable}. 217 * 218 * @param expectedId the id resource for the given drawable 219 */ 220 private static Matcher<View> withDrawable(@IdRes int expectedId) { 221 return new TypeSafeMatcher<View>() { 222 private Resources mResources = 223 InstrumentationRegistry.getTargetContext().getResources(); 224 225 @Override 226 public boolean matchesSafely(View view) { 227 Drawable expectedDrawable = mResources.getDrawable(expectedId); 228 return withDrawable(expectedDrawable).matches(view); 229 } 230 231 @Override 232 public void describeTo(Description description) { 233 try { 234 description.appendText("with Drawable Id: ") 235 .appendText(mResources.getResourceName(expectedId)); 236 } catch (Resources.NotFoundException e) { 237 description.appendText("with Drawable Id (resource name not found): ") 238 .appendText(Integer.toString(expectedId)); 239 } 240 } 241 }; 242 } 243 244 private static boolean drawableEquals(Drawable actualDrawable, Drawable expectedDrawable) { 245 if (actualDrawable == null || expectedDrawable == null) { 246 return false; 247 } 248 Bitmap actualBitmap = getBitmap(actualDrawable); 249 Bitmap expectedBitmap = getBitmap(expectedDrawable); 250 return actualBitmap.sameAs(expectedBitmap); 251 } 252 253 // Convert a drawable to a Bitmap for comparison. 254 private static Bitmap getBitmap(Drawable drawable) { 255 Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 256 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 257 Canvas canvas = new Canvas(bitmap); 258 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 259 drawable.draw(canvas); 260 return bitmap; 261 } 262 263 /** 264 * Check that the given crash item header shows the correct package name, capture date and icon 265 * for the given {@code crashInfo}. 266 * 267 * @param {@link DataInteraction} represents the crash item header. 268 * @param {@link CrashInfo} to match. 269 * @return the same {@code headerDataInteraction} passed for the convenience of chaining. 270 */ 271 private static DataInteraction checkUnknownPackageCrashItemHeader( 272 DataInteraction headerDataInteraction, CrashInfo crashInfo) { 273 return checkPackageCrashItemHeader(headerDataInteraction, crashInfo, FAKE_APP_PACKAGE_NAME); 274 } 275 276 /** 277 * Check that the given crash item header shows the "unknown app" package name, capture date and 278 * icon for the given {@code crashInfo}. 279 * 280 * @param {@link DataInteraction} represents the crash item header. 281 * @param {@link CrashInfo} to match. 282 * @return the same {@code headerDataInteraction} passed for the convenience of chaining. 283 */ 284 private static DataInteraction checkMissingPackageInfoCrashItemHeader( 285 DataInteraction headerDataInteraction, CrashInfo crashInfo) { 286 return checkPackageCrashItemHeader(headerDataInteraction, crashInfo, "unknown app"); 287 } 288 289 /** 290 * Check that the given crash item header shows the given package name, capture date and 291 * icon for the given {@code crashInfo}. 292 * 293 * @param {@link DataInteraction} represents the crash item header. 294 * @param {@link CrashInfo} to match. 295 * @param packageName to match. 296 * @return the same {@code headerDataInteraction} passed for the convenience of chaining. 297 */ 298 private static DataInteraction checkPackageCrashItemHeader( 299 DataInteraction headerDataInteraction, CrashInfo crashInfo, String packageName) { 300 String captureDate = new Date(crashInfo.captureTime).toString(); 301 headerDataInteraction.onChildView(withId(android.R.id.text1)) 302 .check(matches(withText(packageName))); 303 headerDataInteraction.onChildView(withId(android.R.id.text2)) 304 .check(matches(withText(captureDate))); 305 // There should not be an app with FAKE_APP_PACKAGE_NAME so system default icon should be 306 // shown. 307 headerDataInteraction.onChildView(withId(R.id.crash_package_icon)) 308 .check(matches(withDrawable(android.R.drawable.sym_def_app_icon))); 309 310 return headerDataInteraction; 311 } 312 313 /** 314 * Perform click on hide crash button by checking the required conditions for the button. 315 * 316 * @param {@link DataInteraction} represents the crash item body. 317 */ 318 private static void clickHideCrashButton(DataInteraction bodyDataInteraction) { 319 bodyDataInteraction.onChildView(withId(R.id.crash_hide_button)) 320 .check(matches(isDisplayed())) 321 .check(matches(isEnabled())) 322 .check(matches(withDrawable(R.drawable.ic_delete))) 323 .perform(click()); 324 } 325 326 /** 327 * Check that the given crash item body shows the correct uploadState, uploadId and uploadDate. 328 * 329 * @param {@link DataInteraction} represents the crash item body. 330 * @param {@link CrashInfo} to match its upload status. 331 * @return the same {@code headerDataInteraction} passed for the convenience of chaining. 332 */ 333 private static DataInteraction checkCrashItemUploadStatus( 334 DataInteraction bodyDataInteraction, CrashInfo crashInfo) { 335 DataInteraction uploadStatusDataInteraction = 336 bodyDataInteraction.onChildView(withId(R.id.upload_status)); 337 String uploadState = CrashesListFragment.uploadStateString(crashInfo.uploadState); 338 uploadStatusDataInteraction.onChildView(withId(android.R.id.text1)) 339 .check(matches(withText(uploadState))); 340 String uploadInfo = crashInfo.uploadState == UploadState.UPLOADED 341 ? new Date(crashInfo.uploadTime).toString() + "\nID: " + crashInfo.uploadId 342 : ""; 343 uploadStatusDataInteraction.onChildView(withId(android.R.id.text2)) 344 .check(matches(withText(uploadInfo))); 345 346 return bodyDataInteraction; 347 } 348 349 private static class TestPlatformServiceBridge extends PlatformServiceBridge { 350 private boolean mCanUseGms; 351 private boolean mUserConsent; 352 353 TestPlatformServiceBridge(boolean canUseGms, boolean userConsent) { 354 mCanUseGms = canUseGms; 355 mUserConsent = userConsent; 356 } 357 358 @Override 359 public boolean canUseGms() { 360 return mCanUseGms; 361 } 362 363 @Override 364 public void queryMetricsSetting(Callback<Boolean> callback) { 365 callback.onResult(mUserConsent); 366 } 367 } 368 369 @Test 370 @Feature({"AndroidWebView"}) 371 public void testShowingSingleCrashReport_uploaded() throws Throwable { 372 final long systemTime = System.currentTimeMillis(); 373 CrashInfo crashInfo = createCrashInfo("123456", systemTime, "0abcde123456", 374 systemTime + 1000, FAKE_APP_PACKAGE_NAME, UploadState.UPLOADED); 375 376 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 377 assertThat("upload log file should exist", appendUploadedEntryToLog(crashInfo).exists()); 378 379 CallbackHelper helper = getCrashListLoadedListener(); 380 int crashListLoadInitCount = helper.getCallCount(); 381 launchCrashesFragment(); 382 helper.waitForCallback(crashListLoadInitCount, 1); 383 384 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 385 386 // Check crash item header 387 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 388 .perform(click()); // click to expand it 389 // The body is considered item#2 in the list view after expansion 390 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 391 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 392 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 393 394 bodyDataInteraction.onChildView(withId(R.id.crash_report_button)) 395 .check(matches(isDisplayed())) 396 .check(matches(isEnabled())) 397 .check(matches(withText(CRASH_REPORT_BUTTON_TEXT))); 398 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 399 .check(matches(not(isDisplayed()))); 400 bodyDataInteraction.onChildView(withId(R.id.crash_hide_button)) 401 .check(matches(isDisplayed())) 402 .check(matches(isEnabled())) 403 .check(matches(withDrawable(R.drawable.ic_delete))); 404 } 405 406 @Test 407 @Feature({"AndroidWebView"}) 408 public void testOpenBugReportCrash() throws Throwable { 409 final long systemTime = System.currentTimeMillis(); 410 CrashInfo crashInfo = createCrashInfo("123456", systemTime, "0abcde123456", 411 systemTime + 1000, FAKE_APP_PACKAGE_NAME, UploadState.UPLOADED); 412 413 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 414 assertThat("upload log file should exist", appendUploadedEntryToLog(crashInfo).exists()); 415 416 CallbackHelper helper = getCrashListLoadedListener(); 417 int crashListLoadInitCount = helper.getCallCount(); 418 launchCrashesFragment(); 419 helper.waitForCallback(crashListLoadInitCount, 1); 420 421 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 422 // Click the header to expand the list item. 423 onData(anything()).atPosition(0).perform(click()); 424 // The body is considered item#2 in the list view after expansion. 425 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 426 onData(anything()) 427 .atPosition(1) 428 .onChildView(withId(R.id.crash_report_button)) 429 .perform(click()); 430 onView(withText(CrashesListFragment.CRASH_BUG_DIALOG_MESSAGE)) 431 .check(matches(isDisplayed())); 432 // button2 is the AlertDialog negative button id. 433 onView(withId(android.R.id.button2)).check(matches(withText("Dismiss"))).perform(click()); 434 onView(withText(CrashesListFragment.CRASH_BUG_DIALOG_MESSAGE)).check(doesNotExist()); 435 // Verify that no intents are sent out. 436 Intents.times(0); 437 438 onData(anything()) 439 .atPosition(1) 440 .onChildView(withId(R.id.crash_report_button)) 441 .perform(click()); 442 // button1 is the AlertDialog positive button id. 443 onView(withId(android.R.id.button1)) 444 .check(matches(withText("Provide more info"))) 445 .perform(click()); 446 onView(withText(CrashesListFragment.CRASH_BUG_DIALOG_MESSAGE)).check(doesNotExist()); 447 Intent expectedIntent = new CrashBugUrlFactory(crashInfo).getReportIntent(); 448 // TODO(hazems): use IntentMatchers.filterEquals() after pulling the new version of 449 // espresso-intents 450 intended(allOf(IntentMatchers.hasAction(expectedIntent.getAction()), 451 IntentMatchers.hasData(expectedIntent.getData()))); 452 } 453 454 @Test 455 @LargeTest 456 @Feature({"AndroidWebView"}) 457 public void testShowingSingleCrashReport_pending() throws Throwable { 458 final long systemTime = System.currentTimeMillis(); 459 CrashInfo crashInfo = createCrashInfo( 460 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.PENDING); 461 462 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 463 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 464 465 CallbackHelper helper = getCrashListLoadedListener(); 466 int crashListLoadInitCount = helper.getCallCount(); 467 launchCrashesFragment(); 468 helper.waitForCallback(crashListLoadInitCount, 1); 469 470 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 471 472 // Check crash item header 473 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 474 .perform(click()); // click to expand it 475 // The body is considered item#2 in the list view after expansion 476 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 477 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 478 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 479 480 bodyDataInteraction.onChildView(withId(R.id.crash_report_button)) 481 .check(matches(isDisplayed())) 482 .check(matches(withText(CRASH_REPORT_BUTTON_TEXT))) 483 .check(matches(not(isEnabled()))); 484 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 485 .check(matches(isDisplayed())) 486 .check(matches(withText(CRASH_UPLOAD_BUTTON_TEXT))) 487 .check(matches(isEnabled())); 488 bodyDataInteraction.onChildView(withId(R.id.crash_hide_button)) 489 .check(matches(isDisplayed())) 490 .check(matches(isEnabled())) 491 .check(matches(withDrawable(R.drawable.ic_delete))); 492 } 493 494 @Test 495 @Feature({"AndroidWebView"}) 496 public void testShowingSingleCrashReport_pendingUserRequest() throws Throwable { 497 final long systemTime = System.currentTimeMillis(); 498 CrashInfo crashInfo = createCrashInfo("123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, 499 UploadState.PENDING_USER_REQUESTED); 500 501 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 502 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 503 504 CallbackHelper helper = getCrashListLoadedListener(); 505 int crashListLoadInitCount = helper.getCallCount(); 506 launchCrashesFragment(); 507 helper.waitForCallback(crashListLoadInitCount, 1); 508 509 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 510 511 // Check crash item header 512 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 513 .perform(click()); // click to expand it 514 // The body is considered item#2 in the list view after expansion 515 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 516 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 517 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 518 519 bodyDataInteraction.onChildView(withId(R.id.crash_report_button)) 520 .check(matches(isDisplayed())) 521 .check(matches(withText(CRASH_REPORT_BUTTON_TEXT))) 522 .check(matches(not(isEnabled()))) 523 .perform(click()); 524 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 525 .check(matches(not(isDisplayed()))); 526 bodyDataInteraction.onChildView(withId(R.id.crash_hide_button)) 527 .check(matches(isDisplayed())) 528 .check(matches(isEnabled())) 529 .check(matches(withDrawable(R.drawable.ic_delete))); 530 } 531 532 @Test 533 @Feature({"AndroidWebView"}) 534 public void testShowingSingleCrashReport_skipped() throws Throwable { 535 final long systemTime = System.currentTimeMillis(); 536 CrashInfo crashInfo = createCrashInfo( 537 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.SKIPPED); 538 539 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 540 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 541 542 CallbackHelper helper = getCrashListLoadedListener(); 543 int crashListLoadInitCount = helper.getCallCount(); 544 launchCrashesFragment(); 545 helper.waitForCallback(crashListLoadInitCount, 1); 546 547 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 548 549 // Check crash item header 550 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 551 .perform(click()); // click to expand it 552 // The body is considered item#2 in the list view after expansion 553 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 554 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 555 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 556 557 bodyDataInteraction.onChildView(withId(R.id.crash_report_button)) 558 .check(matches(isDisplayed())) 559 .check(matches(withText(CRASH_REPORT_BUTTON_TEXT))) 560 .check(matches(not(isEnabled()))); 561 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 562 .check(matches(isDisplayed())) 563 .check(matches(withText(CRASH_UPLOAD_BUTTON_TEXT))) 564 .check(matches(isEnabled())); 565 bodyDataInteraction.onChildView(withId(R.id.crash_hide_button)) 566 .check(matches(isDisplayed())) 567 .check(matches(isEnabled())) 568 .check(matches(withDrawable(R.drawable.ic_delete))); 569 } 570 571 @Test 572 @Feature({"AndroidWebView"}) 573 public void testForceUploadSkippedCrashReport_noWifi() throws Throwable { 574 final long systemTime = System.currentTimeMillis(); 575 CrashInfo crashInfo = createCrashInfo( 576 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.SKIPPED); 577 578 File minidumpFile = createMinidumpFile(crashInfo); 579 assertThat("temp minidump file should exist", minidumpFile.exists()); 580 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 581 582 CrashUploadUtil.setCrashUploadDelegateForTesting(new CrashUploadDelegate() { 583 @Override 584 public void scheduleNewJob(Context context) {} 585 586 @Override 587 public boolean isNetworkUnmetered(Context context) { 588 return false; 589 } 590 }); 591 592 CallbackHelper helper = getCrashListLoadedListener(); 593 int crashListLoadInitCount = helper.getCallCount(); 594 launchCrashesFragment(); 595 helper.waitForCallback(crashListLoadInitCount, 1); 596 597 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 598 599 // click on the crash item header to expand 600 onData(anything()).atPosition(0).perform(click()); 601 // The body is considered item#2 in the list view after expansion 602 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 603 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 604 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 605 606 // Firstly test clicking the upload button, and dismissing the dialog 607 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)).perform(click()); 608 onView(withText(CrashesListFragment.NO_WIFI_DIALOG_MESSAGE)).check(matches(isDisplayed())); 609 // button2 is the AlertDialog negative button id. 610 onView(withId(android.R.id.button2)).check(matches(withText("Cancel"))).perform(click()); 611 // Check no changes in the view after dismissing the dialog 612 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 613 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 614 .check(matches(isDisplayed())); 615 616 // Secondly test clicking the upload button, and proceeding with upload. 617 crashListLoadInitCount = helper.getCallCount(); 618 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)).perform(click()); 619 onView(withText(CrashesListFragment.NO_WIFI_DIALOG_MESSAGE)).check(matches(isDisplayed())); 620 // button1 is the AlertDialog positive button id. 621 onView(withId(android.R.id.button1)).check(matches(withText("Upload"))).perform(click()); 622 helper.waitForCallback(crashListLoadInitCount, 1); 623 // upload button is now hidden 624 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 625 .check(matches(not(isDisplayed()))); 626 crashInfo.uploadState = UploadState.PENDING_USER_REQUESTED; 627 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 628 629 // Check that minidump file suffix is changed to ".forced" 630 File renamedMinidumpFile = 631 new File(minidumpFile.getAbsolutePath().replace("skipped", "forced")); 632 assertThat("skipped minidump file shouldn't exist", not(minidumpFile.exists())); 633 assertThat("renamed forced minidump file should exist", renamedMinidumpFile.exists()); 634 } 635 636 @Test 637 @LargeTest 638 @Feature({"AndroidWebView"}) 639 public void testForceUploadSkippedCrashReport_withWifi() throws Throwable { 640 final long systemTime = System.currentTimeMillis(); 641 CrashInfo crashInfo = createCrashInfo( 642 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.SKIPPED); 643 644 File minidumpFile = createMinidumpFile(crashInfo); 645 assertThat("temp minidump file should exist", minidumpFile.exists()); 646 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 647 648 CrashUploadUtil.setCrashUploadDelegateForTesting(new CrashUploadDelegate() { 649 @Override 650 public void scheduleNewJob(Context context) {} 651 652 @Override 653 public boolean isNetworkUnmetered(Context context) { 654 return true; 655 } 656 }); 657 658 CallbackHelper helper = getCrashListLoadedListener(); 659 int crashListLoadInitCount = helper.getCallCount(); 660 launchCrashesFragment(); 661 helper.waitForCallback(crashListLoadInitCount, 1); 662 663 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 664 665 // click on the crash item header to expand 666 onData(anything()).atPosition(0).perform(click()); 667 // The body is considered item#2 in the list view after expansion 668 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 669 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 670 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 671 672 crashListLoadInitCount = helper.getCallCount(); 673 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)).perform(click()); 674 helper.waitForCallback(crashListLoadInitCount, 1); 675 // upload button is now hidden 676 bodyDataInteraction.onChildView(withId(R.id.crash_upload_button)) 677 .check(matches(not(isDisplayed()))); 678 crashInfo.uploadState = UploadState.PENDING_USER_REQUESTED; 679 checkCrashItemUploadStatus(bodyDataInteraction, crashInfo); 680 681 // Check that minidump file suffix is changed to ".forced" 682 File renamedMinidumpFile = 683 new File(minidumpFile.getAbsolutePath().replace("skipped", "forced")); 684 assertThat("skipped minidump file shouldn't exist", not(minidumpFile.exists())); 685 assertThat("renamed forced minidump file should exist", renamedMinidumpFile.exists()); 686 } 687 688 @Test 689 @LargeTest 690 @Feature({"AndroidWebView"}) 691 // Test when a crash has a known package name that can be found using PackageManager 692 public void testInstalledPackageInfo() throws Throwable { 693 Context context = InstrumentationRegistry.getTargetContext(); 694 PackageManager packageManager = context.getPackageManager(); 695 // Use the system settings package as a fake app where a crash happened because it's more 696 // likely to be available on every device. If it's not found, skip the test. 697 final String appPackageName = "com.android.settings"; 698 ApplicationInfo appInfo; 699 try { 700 appInfo = packageManager.getApplicationInfo(appPackageName, 0); 701 } catch (PackageManager.NameNotFoundException e) { 702 appInfo = null; 703 } 704 Assume.assumeNotNull( 705 "This test assumes \"com.android.settings\" package is available", appInfo); 706 707 final long systemTime = System.currentTimeMillis(); 708 CrashInfo crashInfo = createCrashInfo( 709 "123456", systemTime, null, -1, appPackageName, UploadState.PENDING); 710 711 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 712 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 713 714 CallbackHelper helper = getCrashListLoadedListener(); 715 int crashListLoadInitCount = helper.getCallCount(); 716 launchCrashesFragment(); 717 helper.waitForCallback(crashListLoadInitCount, 1); 718 719 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 720 721 DataInteraction headerDataInteraction = onData(anything()).atPosition(0); 722 headerDataInteraction.onChildView(withId(android.R.id.text1)) 723 .check(matches(withText(appPackageName))); 724 headerDataInteraction.onChildView(withId(R.id.crash_package_icon)) 725 .check(matches(withDrawable(packageManager.getApplicationIcon(appInfo)))); 726 } 727 728 @Test 729 @Feature({"AndroidWebView"}) 730 // Test when app package name field is missing in the crash info. 731 public void testMissingPackageInfo() throws Throwable { 732 final long systemTime = System.currentTimeMillis(); 733 CrashInfo crashInfo = 734 createCrashInfo("123456", systemTime, null, -1, null, UploadState.PENDING); 735 736 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 737 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 738 739 CallbackHelper helper = getCrashListLoadedListener(); 740 int crashListLoadInitCount = helper.getCallCount(); 741 launchCrashesFragment(); 742 helper.waitForCallback(crashListLoadInitCount, 1); 743 744 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 745 746 checkMissingPackageInfoCrashItemHeader(onData(anything()).atPosition(0), crashInfo); 747 } 748 749 @Test 750 @Feature({"AndroidWebView"}) 751 // Test when crash is missing json, but has upload log file and minidump. 752 public void testShowingSingleCrashReport_uploaded_missingJson() throws Throwable { 753 CrashInfo crashInfo = createCrashInfo("123456", -1, null, 1000, null, UploadState.UPLOADED); 754 755 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 756 assertThat("upload log file should exist", appendUploadedEntryToLog(crashInfo).exists()); 757 758 CallbackHelper helper = getCrashListLoadedListener(); 759 int crashListLoadInitCount = helper.getCallCount(); 760 launchCrashesFragment(); 761 helper.waitForCallback(crashListLoadInitCount, 1); 762 763 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 764 765 checkMissingPackageInfoCrashItemHeader(onData(anything()).atPosition(0), crashInfo); 766 } 767 768 @Test 769 @Feature({"AndroidWebView"}) 770 // Test when crash is missing json, but has upload log file and minidump. 771 public void testShowingSingleCrashReport_pending_missingJson() throws Throwable { 772 CrashInfo crashInfo = createCrashInfo("123456", -1, null, 1000, null, UploadState.PENDING); 773 774 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 775 776 CallbackHelper helper = getCrashListLoadedListener(); 777 int crashListLoadInitCount = helper.getCallCount(); 778 launchCrashesFragment(); 779 helper.waitForCallback(crashListLoadInitCount, 1); 780 781 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 782 783 checkMissingPackageInfoCrashItemHeader(onData(anything()).atPosition(0), crashInfo); 784 } 785 786 @Test 787 @Feature({"AndroidWebView"}) 788 public void testMaxNumberOfCrashes() throws Throwable { 789 final long systemTime = System.currentTimeMillis(); 790 final int crashReportsNum = CrashesListFragment.MAX_CRASHES_NUMBER + 10; 791 CrashInfo[] crashInfo = new CrashInfo[crashReportsNum]; 792 for (int i = 0; i < crashReportsNum; ++i) { 793 // Set capture time with an arbitrary chosen 2 second difference to make sure crashes 794 // are shown in descending order with most recent crash first. 795 crashInfo[i] = createCrashInfo("abcd" + Integer.toString(i), systemTime + i * 2000, 796 null, -1, FAKE_APP_PACKAGE_NAME, UploadState.PENDING); 797 798 assertThat( 799 "temp minidump file should exist", createMinidumpFile(crashInfo[i]).exists()); 800 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo[i]).exists()); 801 } 802 803 CallbackHelper helper = getCrashListLoadedListener(); 804 int crashListLoadInitCount = helper.getCallCount(); 805 launchCrashesFragment(); 806 helper.waitForCallback(crashListLoadInitCount, 1); 807 808 onView(withId(R.id.crashes_list)) 809 .check(matches(withCount(CrashesListFragment.MAX_CRASHES_NUMBER))); 810 // Check that only the most recent MAX_CRASHES_NUMBER crashes are shown. 811 for (int i = 0; i < CrashesListFragment.MAX_CRASHES_NUMBER; ++i) { 812 // Crashes should be shown with the most recent first, i.e the reverse of the order 813 // they are initialized at. 814 checkUnknownPackageCrashItemHeader( 815 onData(anything()).atPosition(i), crashInfo[crashReportsNum - i - 1]); 816 } 817 } 818 819 @Test 820 @Feature({"AndroidWebView"}) 821 public void testHideCrashButton_uploaded() throws Throwable { 822 final long systemTime = System.currentTimeMillis(); 823 CrashInfo crashInfo = createCrashInfo("123456", systemTime, "0abcde123456", 824 systemTime + 1000, FAKE_APP_PACKAGE_NAME, UploadState.UPLOADED); 825 826 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 827 assertThat("upload log file should exist", appendUploadedEntryToLog(crashInfo).exists()); 828 829 CallbackHelper helper = getCrashListLoadedListener(); 830 int crashListLoadInitCount = helper.getCallCount(); 831 launchCrashesFragment(); 832 helper.waitForCallback(crashListLoadInitCount, 1); 833 834 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 835 836 // Check crash item header 837 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 838 .perform(click()); // click to expand it 839 // The body is considered item#2 in the list view after expansion 840 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 841 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 842 843 crashListLoadInitCount = helper.getCallCount(); 844 clickHideCrashButton(bodyDataInteraction); 845 helper.waitForCallback(crashListLoadInitCount, 1); 846 847 onView(withId(R.id.crashes_list)).check(matches(withCount(0))); 848 } 849 850 @Test 851 @Feature({"AndroidWebView"}) 852 public void testHideCrashButton_pending() throws Throwable { 853 final long systemTime = System.currentTimeMillis(); 854 CrashInfo crashInfo = createCrashInfo( 855 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.PENDING); 856 857 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 858 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 859 860 CallbackHelper helper = getCrashListLoadedListener(); 861 int crashListLoadInitCount = helper.getCallCount(); 862 launchCrashesFragment(); 863 helper.waitForCallback(crashListLoadInitCount, 1); 864 865 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 866 867 // Check crash item header 868 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 869 .perform(click()); // click to expand it 870 // The body is considered item#2 in the list view after expansion 871 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 872 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 873 874 crashListLoadInitCount = helper.getCallCount(); 875 clickHideCrashButton(bodyDataInteraction); 876 helper.waitForCallback(crashListLoadInitCount, 1); 877 878 onView(withId(R.id.crashes_list)).check(matches(withCount(0))); 879 } 880 881 @Test 882 @Feature({"AndroidWebView"}) 883 public void testHideCrashButton_uploaded_missingJson() throws Throwable { 884 CrashInfo crashInfo = createCrashInfo("123456", -1, null, 1000, null, UploadState.UPLOADED); 885 886 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 887 assertThat("upload log file should exist", appendUploadedEntryToLog(crashInfo).exists()); 888 889 CallbackHelper helper = getCrashListLoadedListener(); 890 int crashListLoadInitCount = helper.getCallCount(); 891 launchCrashesFragment(); 892 helper.waitForCallback(crashListLoadInitCount, 1); 893 894 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 895 896 // Check crash item header 897 checkMissingPackageInfoCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 898 .perform(click()); // click to expand it 899 // The body is considered item#2 in the list view after expansion 900 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 901 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 902 903 crashListLoadInitCount = helper.getCallCount(); 904 clickHideCrashButton(bodyDataInteraction); 905 helper.waitForCallback(crashListLoadInitCount, 1); 906 907 onView(withId(R.id.crashes_list)).check(matches(withCount(0))); 908 } 909 910 @Test 911 @Feature({"AndroidWebView"}) 912 public void testHideCrashButton_pending_missingJson() throws Throwable { 913 CrashInfo crashInfo = createCrashInfo("123456", -1, null, -1, null, UploadState.PENDING); 914 915 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 916 917 CallbackHelper helper = getCrashListLoadedListener(); 918 int crashListLoadInitCount = helper.getCallCount(); 919 launchCrashesFragment(); 920 helper.waitForCallback(crashListLoadInitCount, 1); 921 922 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 923 924 // Check crash item header 925 checkMissingPackageInfoCrashItemHeader(onData(anything()).atPosition(0), crashInfo) 926 .perform(click()); // click to expand it 927 // The body is considered item#2 in the list view after expansion 928 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 929 DataInteraction bodyDataInteraction = onData(anything()).atPosition(1); 930 931 crashListLoadInitCount = helper.getCallCount(); 932 clickHideCrashButton(bodyDataInteraction); 933 helper.waitForCallback(crashListLoadInitCount, 1); 934 935 onView(withId(R.id.crashes_list)).check(matches(withCount(0))); 936 } 937 938 @Test 939 @LargeTest 940 @Feature({"AndroidWebView"}) 941 public void testRefreshMenuOption() throws Throwable { 942 CallbackHelper helper = getCrashListLoadedListener(); 943 int crashListLoadInitCount = helper.getCallCount(); 944 launchCrashesFragment(); 945 helper.waitForCallback(crashListLoadInitCount, 1); 946 947 onView(withId(R.id.crashes_list)).check(matches(withCount(0))); 948 949 final long systemTime = System.currentTimeMillis(); 950 CrashInfo crashInfo = createCrashInfo( 951 "123456", systemTime, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.PENDING); 952 953 assertThat("temp minidump file should exist", createMinidumpFile(crashInfo).exists()); 954 assertThat("temp json log file should exist", writeJsonLogFile(crashInfo).exists()); 955 956 crashListLoadInitCount = helper.getCallCount(); 957 onView(withText("Refresh")).check(matches(isDisplayed())); 958 onView(withText("Refresh")).perform(click()); 959 helper.waitForCallback(crashListLoadInitCount, 1); 960 961 onView(withId(R.id.crashes_list)).check(matches(withCount(1))); 962 checkUnknownPackageCrashItemHeader(onData(anything()).atPosition(0), crashInfo); 963 } 964 965 @Test 966 @LargeTest 967 @Feature({"AndroidWebView"}) 968 public void testLongPressCopy() throws Throwable { 969 Context context = InstrumentationRegistry.getTargetContext(); 970 final long systemTime = System.currentTimeMillis(); 971 CrashInfo uploadedCrashInfo = createCrashInfo("123456", systemTime - 1000, "0abcde123456", 972 systemTime, FAKE_APP_PACKAGE_NAME, UploadState.UPLOADED); 973 CrashInfo pendingCrashInfo = createCrashInfo( 974 "78910", systemTime - 2000, null, -1, FAKE_APP_PACKAGE_NAME, UploadState.PENDING); 975 976 assertThat("temp json log file for uploaded crash should exist", 977 writeJsonLogFile(uploadedCrashInfo).exists()); 978 assertThat("upload log file should exist", 979 appendUploadedEntryToLog(uploadedCrashInfo).exists()); 980 981 assertThat("temp minidump file for pending crash should exist", 982 createMinidumpFile(pendingCrashInfo).exists()); 983 assertThat("temp json log file for pending crash should exist", 984 writeJsonLogFile(pendingCrashInfo).exists()); 985 986 CallbackHelper helper = getCrashListLoadedListener(); 987 int crashListLoadInitCount = helper.getCallCount(); 988 launchCrashesFragment(); 989 helper.waitForCallback(crashListLoadInitCount, 1); 990 991 onView(withId(R.id.crashes_list)).check(matches(withCount(2))); 992 993 // click on the first crash item header to expand 994 onData(anything()).atPosition(0).perform(click()); 995 // long click on the crash item body to copy 996 onData(anything()).atPosition(1).perform(longClick()); 997 String expectedUploadInfo = new Date(uploadedCrashInfo.uploadTime).toString() 998 + "\nID: " + uploadedCrashInfo.uploadId; 999 assertThat(getClipBoardTextOnUiThread(context), is(expectedUploadInfo)); 1000 1001 // click on the first crash item header to collapse 1002 onData(anything()).atPosition(0).perform(click()); 1003 // click on the second crash item header to expand 1004 onData(anything()).atPosition(1).perform(click()); 1005 // Clear clipboard content 1006 setClipBoardTextOnUiThread(context, "", ""); 1007 // Crash body is now the second item in the list view, long click on the crash item body to 1008 // copy. 1009 onData(anything()).atPosition(2).perform(longClick()); 1010 // This a pending upload, nothing should be copied 1011 assertThat(getClipBoardTextOnUiThread(context), is("")); 1012 } 1013 1014 @Test 1015 @MediumTest 1016 @Feature({"AndroidWebView"}) 1017 public void testConsentErrorMessage_notShown_differentWebViewPackageIsShown() throws Throwable { 1018 Context context = InstrumentationRegistry.getTargetContext(); 1019 // Inject a dummy PackageInfo as the current WebView package to make sure it will always be 1020 // different from the test's app package. 1021 WebViewPackageHelper.setCurrentWebViewPackageForTesting( 1022 HomeFragmentTest.FAKE_WEBVIEW_PACKAGE); 1023 PlatformServiceBridge.injectInstance( 1024 new TestPlatformServiceBridge(/*canUseGms=*/true, /*userConsent=*/false)); 1025 launchCrashesFragment(); 1026 1027 String expectedErrorMessage = String.format(Locale.US, 1028 WebViewPackageError.DIFFERENT_WEBVIEW_PROVIDER_ERROR_MESSAGE, 1029 WebViewPackageHelper.loadLabel(context)); 1030 onView(withId(R.id.main_error_view)).check(matches(isDisplayed())); 1031 onView(withId(R.id.error_text)).check(matches(withText(expectedErrorMessage))); 1032 } 1033 1034 @Test 1035 @MediumTest 1036 @Feature({"AndroidWebView"}) 1037 public void testConsentErrorMessage_notShown_userConsented() throws Throwable { 1038 Context context = InstrumentationRegistry.getTargetContext(); 1039 // Inject test app package as the current WebView package. 1040 WebViewPackageHelper.setCurrentWebViewPackageForTesting( 1041 WebViewPackageHelper.getContextPackageInfo(context)); 1042 PlatformServiceBridge.injectInstance( 1043 new TestPlatformServiceBridge(/*canUseGms=*/true, /*userConsent=*/true)); 1044 launchCrashesFragment(); 1045 1046 onView(withId(R.id.main_error_view)).check(matches(not(isDisplayed()))); 1047 } 1048 1049 @Test 1050 @MediumTest 1051 @Feature({"AndroidWebView"}) 1052 public void testConsentErrorMessage_shown_canUseGms() throws Throwable { 1053 Context context = InstrumentationRegistry.getTargetContext(); 1054 1055 Intent settingsIntent = 1056 new Intent(CrashesListFragment.USAGE_AND_DIAGONSTICS_ACTIVITY_INTENT_ACTION); 1057 List<ResolveInfo> intentResolveInfo = 1058 context.getPackageManager().queryIntentActivities(settingsIntent, 0); 1059 Assume.assumeTrue( 1060 "This test assumes \"usage& diagonstics\" settings can be found on the device", 1061 intentResolveInfo.size() > 0); 1062 1063 // Inject test app package as the current WebView package. 1064 WebViewPackageHelper.setCurrentWebViewPackageForTesting( 1065 WebViewPackageHelper.getContextPackageInfo(context)); 1066 PlatformServiceBridge.injectInstance( 1067 new TestPlatformServiceBridge(/*canUseGms=*/true, /*userConsent=*/false)); 1068 launchCrashesFragment(); 1069 1070 onView(withId(R.id.main_error_view)).check(matches(isDisplayed())); 1071 onView(withId(R.id.error_text)) 1072 .check(matches( 1073 withText(CrashesListFragment.CRASH_COLLECTION_DISABLED_ERROR_MESSAGE))); 1074 onView(withId(R.id.action_button)) 1075 .check(matches(withText("Open Settings"))) 1076 .perform(click()); 1077 intended(IntentMatchers.hasAction( 1078 CrashesListFragment.USAGE_AND_DIAGONSTICS_ACTIVITY_INTENT_ACTION)); 1079 } 1080 1081 @Test 1082 @MediumTest 1083 @Feature({"AndroidWebView"}) 1084 public void testConsentErrorMessage_shown_onlyInCrashFragment() throws Throwable { 1085 Context context = InstrumentationRegistry.getTargetContext(); 1086 // Inject test app package as the current WebView package. 1087 WebViewPackageHelper.setCurrentWebViewPackageForTesting( 1088 WebViewPackageHelper.getContextPackageInfo(context)); 1089 PlatformServiceBridge.injectInstance( 1090 new TestPlatformServiceBridge(/*canUseGms=*/true, /*userConsent=*/false)); 1091 launchCrashesFragment(); 1092 1093 onView(withId(R.id.main_error_view)).check(matches(isDisplayed())); 1094 onView(withId(R.id.error_text)) 1095 .check(matches( 1096 withText(CrashesListFragment.CRASH_COLLECTION_DISABLED_ERROR_MESSAGE))); 1097 1098 // CrashesListFragment -> FlagsFragment (Not shown) 1099 onView(withId(R.id.navigation_flags_ui)).perform(click()); 1100 onView(withId(R.id.main_error_view)).check(matches(not(isDisplayed()))); 1101 // FlagsFragment -> HomeFragment (Not shown) 1102 onView(withId(R.id.navigation_home)).perform(click()); 1103 onView(withId(R.id.main_error_view)).check(matches(not(isDisplayed()))); 1104 // HomeFragment -> CrashesListFragment (shown again) 1105 onView(withId(R.id.navigation_crash_ui)).perform(click()); 1106 onView(withId(R.id.main_error_view)).check(matches(isDisplayed())); 1107 onView(withId(R.id.error_text)) 1108 .check(matches( 1109 withText(CrashesListFragment.CRASH_COLLECTION_DISABLED_ERROR_MESSAGE))); 1110 } 1111 1112 @Test 1113 @MediumTest 1114 @Feature({"AndroidWebView"}) 1115 public void testConsentErrorMessage_shown_cannotUseGms() throws Throwable { 1116 Context context = InstrumentationRegistry.getTargetContext(); 1117 // Inject test app package as the current WebView package. 1118 WebViewPackageHelper.setCurrentWebViewPackageForTesting( 1119 WebViewPackageHelper.getContextPackageInfo(context)); 1120 PlatformServiceBridge.injectInstance( 1121 new TestPlatformServiceBridge(/*canUseGms=*/false, /*userConsent=*/false)); 1122 launchCrashesFragment(); 1123 1124 onView(withId(R.id.main_error_view)).check(matches(isDisplayed())); 1125 onView(withId(R.id.error_text)) 1126 .check(matches(withText(CrashesListFragment.NO_GMS_ERROR_MESSAGE))); 1127 onView(withId(R.id.action_button)).check(matches(not(isDisplayed()))); 1128 } 1129 } 1130