1f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/* 2f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Copyright (C) 2014 The Android Open Source Project 3f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 4f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Licensed under the Apache License, Version 2.0 (the "License"); 5f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * you may not use this file except in compliance with the License. 6f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * You may obtain a copy of the License at 7f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 8f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * http://www.apache.org/licenses/LICENSE-2.0 9f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 10f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Unless required by applicable law or agreed to in writing, software 11f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * distributed under the License is distributed on an "AS IS" BASIS, 12f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * See the License for the specific language governing permissions and 14f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * limitations under the License. 15f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 16f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 17f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpackage com.google.android.apps.common.testing.ui.espresso.assertion; 18f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 19f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.assertThat; 20f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.android.apps.common.testing.ui.espresso.util.TreeIterables.breadthFirstViewTraversal; 21f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static com.google.common.base.Preconditions.checkNotNull; 22f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport static org.hamcrest.Matchers.is; 23f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 24f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.NoMatchingViewException; 25f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.ViewAssertion; 26f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.android.apps.common.testing.ui.espresso.util.HumanReadables; 27f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.base.Preconditions; 28f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.base.Predicate; 29f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport com.google.common.collect.Iterables; 30f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 31f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.util.Log; 32f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport android.view.View; 33f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 34f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport junit.framework.AssertionFailedError; 35f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 36f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport org.hamcrest.Matcher; 37f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport org.hamcrest.StringDescription; 38f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 39f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.ArrayList; 40f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.Iterator; 41f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevimport java.util.List; 42f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 43f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev/** 44f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * A collection of common {@link ViewAssertion}s. 45f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 46f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelevpublic final class ViewAssertions { 47f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 48f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private static final String TAG = ViewAssertions.class.getSimpleName(); 49f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 50f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 51f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev private ViewAssertions() {} 52f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 53f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev /** 54f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Returns an assert that ensures the view matcher does not find any matching view in the 55f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * hierarchy. 56f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 57f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public static ViewAssertion doesNotExist() { 58f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return new ViewAssertion() { 59f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 60f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public void check(View view, NoMatchingViewException noView) { 61f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (view != null) { 62f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev assertThat("View is present in the hierarchy: " + HumanReadables.describe(view), true, 63f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev is(false)); 64f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 65f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 66f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev }; 67f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 68f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 69f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev /** 70f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Returns a generic {@link ViewAssertion} that asserts that a view exists in the view hierarchy 71f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * and is matched by the given view matcher. 72f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 73f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public static ViewAssertion matches(final Matcher<? super View> viewMatcher) { 74f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev checkNotNull(viewMatcher); 75f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return new ViewAssertion() { 76f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 77f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public void check(View view, NoMatchingViewException noViewException) { 78f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev StringDescription description = new StringDescription(); 79f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev description.appendText("'"); 80f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev viewMatcher.describeTo(description); 81f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (noViewException != null) { 82f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev description.appendText(String.format( 83f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev "' check could not be performed because view '%s' was not found.\n", viewMatcher)); 84f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev Log.e(TAG, description.toString()); 85f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev throw noViewException; 86f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } else { 87f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // TODO(valeraz): ideally, we should append the matcher used to find the view 88f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // This can be done in DefaultFailureHandler (just like we currently to with 89f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev // PerformException) 90f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev description.appendText("' doesn't match the selected view."); 91f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev assertThat(description.toString(), view, viewMatcher); 92f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 93f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 94f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev }; 95f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 96f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 97f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 98f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev /** 99f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Returns a generic {@link ViewAssertion} that asserts that the descendant views selected by the 100f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * selector match the specified matcher. 101f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * 102f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * Example: onView(rootView).check(selectedDescendantsMatch( 103f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev * not(isAssignableFrom(TextView.class)), hasContentDescription())); 104f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev */ 105f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public static ViewAssertion selectedDescendantsMatch( 106f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev final Matcher<View> selector, final Matcher<View> matcher) { 107f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return new ViewAssertion() { 108f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @SuppressWarnings("unchecked") 109f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 110f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public void check(View view, NoMatchingViewException noViewException) { 111f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev Preconditions.checkNotNull(view); 112f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 113f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev final Predicate<View> viewPredicate = new Predicate<View>() { 114f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev @Override 115f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev public boolean apply(View input) { 116f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev return selector.matches(input); 117f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 118f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev }; 119f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 120f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev Iterator<View> selectedViewIterator = 121f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev Iterables.filter(breadthFirstViewTraversal(view), viewPredicate).iterator(); 122f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 123f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev List<View> nonMatchingViews = new ArrayList<View>(); 124f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev while (selectedViewIterator.hasNext()) { 125f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev View selectedView = selectedViewIterator.next(); 126f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 127f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (!matcher.matches(selectedView)) { 128f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev nonMatchingViews.add(selectedView); 129f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 130f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 131f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev 132f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev if (nonMatchingViews.size() > 0) { 133f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev String errorMessage = HumanReadables.getViewHierarchyErrorMessage(view, 134f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev nonMatchingViews, 135f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev String.format("At least one view did not match the required matcher: %s", matcher), 136f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev "****DOES NOT MATCH****"); 137f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev throw new AssertionFailedError(errorMessage); 138f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 139f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 140f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev }; 141f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev } 142f69eb9ac2856f470cb79f57141f711ed3ceed99dNick Korostelev} 143