1 /* 2 * Copyright (C) 2014 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.google.android.apps.common.testing.ui.espresso; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 21 import com.google.android.apps.common.testing.ui.espresso.util.HumanReadables; 22 import com.google.common.base.Optional; 23 import com.google.common.collect.Lists; 24 25 import android.view.View; 26 27 import org.hamcrest.Matcher; 28 29 import java.util.List; 30 31 /** 32 * Indicates that a given matcher did not match any elements in the view hierarchy. 33 * <p> 34 * Contains details about the matcher and the current view hierarchy to aid in debugging. 35 * </p> 36 * <p> 37 * Since this is usually an unrecoverable error this exception is a runtime exception. 38 * </p> 39 * <p> 40 * References to the view and failing matcher are purposefully not included in the state of this 41 * object - since it will most likely be created on the UI thread and thrown on the instrumentation 42 * thread, it would be invalid to touch the view on the instrumentation thread. Also the view 43 * hierarchy may have changed since exception creation (leading to more confusion). 44 * </p> 45 */ 46 public final class NoMatchingViewException extends RuntimeException implements EspressoException { 47 48 private Matcher<? super View> viewMatcher; 49 private View rootView; 50 private List<View> adapterViews = Lists.newArrayList(); 51 private boolean includeViewHierarchy = true; 52 private Optional<String> adapterViewWarning = Optional.<String>absent(); 53 54 private NoMatchingViewException(String description) { 55 super(description); 56 } 57 58 private NoMatchingViewException(Builder builder) { 59 super(getErrorMessage(builder)); 60 this.viewMatcher = builder.viewMatcher; 61 this.rootView = builder.rootView; 62 this.adapterViews = builder.adapterViews; 63 this.adapterViewWarning = builder.adapterViewWarning; 64 this.includeViewHierarchy = builder.includeViewHierarchy; 65 } 66 67 private static String getErrorMessage(Builder builder) { 68 String errorMessage = ""; 69 if (builder.includeViewHierarchy) { 70 String message = String.format("No views in hierarchy found matching: %s", 71 builder.viewMatcher); 72 if (builder.adapterViewWarning.isPresent()) { 73 message = message + builder.adapterViewWarning.get(); 74 } 75 errorMessage = HumanReadables.getViewHierarchyErrorMessage(builder.rootView, 76 null /* problemViews */, 77 message, 78 null /* problemViewSuffix */); 79 } else { 80 errorMessage = String.format("Could not find a view that matches %s" , builder.viewMatcher); 81 } 82 return errorMessage; 83 } 84 85 /** Builder for {@link NoMatchingViewException}. */ 86 public static class Builder { 87 88 private Matcher<? super View> viewMatcher; 89 private View rootView; 90 private List<View> adapterViews = Lists.newArrayList(); 91 private boolean includeViewHierarchy = true; 92 private Optional<String> adapterViewWarning = Optional.<String>absent(); 93 94 public Builder from(NoMatchingViewException exception) { 95 this.viewMatcher = exception.viewMatcher; 96 this.rootView = exception.rootView; 97 this.adapterViews = exception.adapterViews; 98 this.adapterViewWarning = exception.adapterViewWarning; 99 this.includeViewHierarchy = exception.includeViewHierarchy; 100 return this; 101 } 102 103 public Builder withViewMatcher(Matcher<? super View> viewMatcher) { 104 this.viewMatcher = viewMatcher; 105 return this; 106 } 107 108 public Builder withRootView(View rootView) { 109 this.rootView = rootView; 110 return this; 111 } 112 113 public Builder withAdapterViews(List<View> adapterViews) { 114 this.adapterViews = adapterViews; 115 return this; 116 } 117 118 public Builder includeViewHierarchy(boolean includeViewHierarchy) { 119 this.includeViewHierarchy = includeViewHierarchy; 120 return this; 121 } 122 123 public Builder withAdapterViewWarning(Optional<String> adapterViewWarning) { 124 this.adapterViewWarning = adapterViewWarning; 125 return this; 126 } 127 128 public NoMatchingViewException build() { 129 checkNotNull(viewMatcher); 130 checkNotNull(rootView); 131 checkNotNull(adapterViews); 132 checkNotNull(adapterViewWarning); 133 return new NoMatchingViewException(this); 134 } 135 } 136 } 137