Home | History | Annotate | Download | only in espresso
      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