1 // Copyright 2020 The Chromium Authors 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.base.test; 6 7 import static com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils.matchesCheckNames; 8 9 import static org.hamcrest.Matchers.anyOf; 10 import static org.hamcrest.Matchers.is; 11 12 import android.app.Activity; 13 import android.content.Intent; 14 import android.text.TextUtils; 15 16 import androidx.annotation.CallSuper; 17 import androidx.annotation.Nullable; 18 import androidx.test.espresso.contrib.AccessibilityChecks; 19 import androidx.test.runner.lifecycle.Stage; 20 21 import com.google.android.apps.common.testing.accessibility.framework.ClickableSpanViewCheck; 22 import com.google.android.apps.common.testing.accessibility.framework.DuplicateClickableBoundsViewCheck; 23 import com.google.android.apps.common.testing.accessibility.framework.EditableContentDescViewCheck; 24 import com.google.android.apps.common.testing.accessibility.framework.SpeakableTextPresentInfoCheck; 25 import com.google.android.apps.common.testing.accessibility.framework.SpeakableTextPresentViewCheck; 26 import com.google.android.apps.common.testing.accessibility.framework.TouchTargetSizeViewCheck; 27 28 import org.junit.Assert; 29 import org.junit.rules.TestRule; 30 import org.junit.runner.Description; 31 import org.junit.runners.model.Statement; 32 33 import org.chromium.base.ContextUtils; 34 import org.chromium.base.Log; 35 import org.chromium.base.test.util.ApplicationTestUtils; 36 37 /** 38 * A replacement for ActivityTestRule, designed for use in Chromium. This implementation supports 39 * launching the target activity through a launcher or redirect from another Activity. 40 * 41 * @param <T> The type of Activity this Rule will use. 42 */ 43 public class BaseActivityTestRule<T extends Activity> implements TestRule { 44 private static final String TAG = "BaseActivityTestRule"; 45 46 private final Class<T> mActivityClass; 47 private boolean mFinishActivity = true; 48 private T mActivity; 49 50 /** 51 * @param activityClass The Class of the Activity the TestRule will use. 52 */ BaseActivityTestRule(Class<T> activityClass)53 public BaseActivityTestRule(Class<T> activityClass) { 54 mActivityClass = activityClass; 55 56 // Enable accessibility checks, but suppress checks that fit into the following: 57 // 58 // TouchTargetSize checks - Many views in Chrome give false positives for the minimum 59 // target size of 48dp. 100s of tests fail, leave disabled 60 // until a complete audit can be done. 61 // 62 // ClickableSpan checks - Chrome uses ClickableSpan's throughout for in-line links, 63 // but a URLSpan is considered more accessible, except in the 64 // case of relative links. Disable until after an audit. 65 // 66 // EditableContentDesc checks - Editable TextViews (EditText's) should not have a 67 // content description and instead have a hint or label. 68 // Various Autofill tests fail because of this, leave 69 // disabled until after an audit. 70 // 71 // DuplicateClickableBounds checks - Some containers are marked clickable when they do not 72 // process click events. Two views with the same bounds 73 // should not both be clickable. Some examples in: 74 // PageInfoRowView and TabModal. 75 // 76 // SpeakableTextPresent* checks - Some views are failing this test on certain try bots, 77 // so disable this check to reduce churn for sheriffs 78 // until issue can be found. Some examples in: 79 // AccessibilitySettings, ReaderMode, and Feedv2 tests. 80 // 81 // TODO(AccessibilityChecks): Complete above audits and ideally suppress no checks. 82 try { 83 AccessibilityChecks.enable() 84 .setSuppressingResultMatcher( 85 anyOf( 86 matchesCheckNames( 87 is(TouchTargetSizeViewCheck.class.getSimpleName())), 88 matchesCheckNames( 89 is(ClickableSpanViewCheck.class.getSimpleName())), 90 matchesCheckNames( 91 is(EditableContentDescViewCheck.class.getSimpleName())), 92 matchesCheckNames( 93 is( 94 DuplicateClickableBoundsViewCheck.class 95 .getSimpleName())), 96 matchesCheckNames( 97 is( 98 SpeakableTextPresentInfoCheck.class 99 .getSimpleName())), 100 matchesCheckNames( 101 is( 102 SpeakableTextPresentViewCheck.class 103 .getSimpleName())))); 104 } catch (IllegalStateException e) { 105 // Suppress IllegalStateException for AccessibilityChecks already enabled. 106 } 107 } 108 109 @Override 110 @CallSuper apply(final Statement base, final Description desc)111 public Statement apply(final Statement base, final Description desc) { 112 return new Statement() { 113 @Override 114 public void evaluate() throws Throwable { 115 try { 116 base.evaluate(); 117 } finally { 118 if (mFinishActivity && mActivity != null) { 119 ApplicationTestUtils.finishActivity(mActivity); 120 } 121 } 122 } 123 }; 124 } 125 126 /** 127 * @param finishActivity Whether to finish the Activity between tests. This is only meaningful 128 * in the context of {@link Batch} tests. Non-batched tests will always finish Activities 129 * between tests. 130 */ 131 public void setFinishActivity(boolean finishActivity) { 132 mFinishActivity = finishActivity; 133 } 134 135 /** 136 * @return The activity under test. 137 */ 138 public T getActivity() { 139 return mActivity; 140 } 141 142 /** Set the Activity to be used by this TestRule. */ 143 public void setActivity(T activity) { 144 mActivity = activity; 145 } 146 147 protected Intent getActivityIntent() { 148 return new Intent(ContextUtils.getApplicationContext(), mActivityClass); 149 } 150 151 /** 152 * Launches the Activity under test using the provided intent. If the provided intent is null, 153 * an explicit intent targeting the Activity is created and used. 154 */ 155 public void launchActivity(@Nullable Intent startIntent) { 156 if (startIntent == null) { 157 startIntent = getActivityIntent(); 158 } else { 159 String packageName = ContextUtils.getApplicationContext().getPackageName(); 160 Assert.assertTrue( 161 TextUtils.equals(startIntent.getPackage(), packageName) 162 || (startIntent.getComponent() != null 163 && TextUtils.equals( 164 startIntent.getComponent().getPackageName(), 165 packageName))); 166 } 167 168 startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 169 170 Log.d(TAG, String.format("Launching activity %s", mActivityClass.getName())); 171 172 final Intent intent = startIntent; 173 mActivity = 174 ApplicationTestUtils.waitForActivityWithClass( 175 mActivityClass, 176 Stage.CREATED, 177 () -> ContextUtils.getApplicationContext().startActivity(intent)); 178 } 179 180 /** 181 * Recreates the Activity, blocking until finished. 182 * After calling this, getActivity() returns the new Activity. 183 */ 184 public void recreateActivity() { 185 setActivity(ApplicationTestUtils.recreateActivity(getActivity())); 186 } 187 } 188