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.collect.ImmutableSet;
     23 import com.google.common.collect.Lists;
     24 
     25 import android.view.View;
     26 
     27 import org.hamcrest.Matcher;
     28 
     29 /**
     30  * An exception which indicates that a Matcher<View> matched multiple views in the hierarchy when
     31  * only one view was expected. It should be called only from the main thread.
     32  * <p>
     33  * Contains details about the matcher and the current view hierarchy to aid in debugging.
     34  * </p>
     35  * <p>
     36  * Since this is usually an unrecoverable error this exception is a runtime exception.
     37  * </p>
     38  * <p>
     39  * References to the view and failing matcher are purposefully not included in the state of this
     40  * object - since it will most likely be created on the UI thread and thrown on the instrumentation
     41  * thread, it would be invalid to touch the view on the instrumentation thread. Also the view
     42  * hierarchy may have changed since exception creation (leading to more confusion).
     43  * </p>
     44  */
     45 public final class AmbiguousViewMatcherException extends RuntimeException
     46     implements EspressoException {
     47 
     48   private Matcher<? super View> viewMatcher;
     49   private View rootView;
     50   private View view1;
     51   private View view2;
     52   private View[] others;
     53 
     54   private AmbiguousViewMatcherException(String description) {
     55     super(description);
     56   }
     57 
     58   private AmbiguousViewMatcherException(Builder builder) {
     59     super(getErrorMessage(builder));
     60     this.viewMatcher = builder.viewMatcher;
     61     this.rootView = builder.rootView;
     62     this.view1 = builder.view1;
     63     this.view2 = builder.view2;
     64     this.others = builder.others;
     65   }
     66 
     67   private static String getErrorMessage(Builder builder) {
     68     String errorMessage = "";
     69     if (builder.includeViewHierarchy) {
     70       ImmutableSet<View> ambiguousViews =
     71         ImmutableSet.<View>builder().add(builder.view1, builder.view2).add(builder.others).build();
     72       errorMessage = HumanReadables.getViewHierarchyErrorMessage(builder.rootView,
     73           Lists.newArrayList(ambiguousViews),
     74           String.format("'%s' matches multiple views in the hierarchy.", builder.viewMatcher),
     75           "****MATCHES****");
     76     } else {
     77       errorMessage = String.format("Multiple Ambiguous Views found for matcher %s",
     78           builder.viewMatcher);
     79     }
     80     return errorMessage;
     81   }
     82 
     83   /** Builder for {@link AmbiguousViewMatcherException}. */
     84   public static class Builder {
     85     private Matcher<? super View> viewMatcher;
     86     private View rootView;
     87     private View view1;
     88     private View view2;
     89     private View[] others;
     90     private boolean includeViewHierarchy = true;
     91 
     92     public Builder from(AmbiguousViewMatcherException exception) {
     93       this.viewMatcher = exception.viewMatcher;
     94       this.rootView = exception.rootView;
     95       this.view1 = exception.view1;
     96       this.view2 = exception.view2;
     97       this.others = exception.others;
     98       return this;
     99     }
    100 
    101     public Builder withViewMatcher(Matcher<? super View> viewMatcher) {
    102       this.viewMatcher = viewMatcher;
    103       return this;
    104     }
    105 
    106     public Builder withRootView(View rootView) {
    107       this.rootView = rootView;
    108       return this;
    109     }
    110 
    111     public Builder withView1(View view1) {
    112       this.view1 = view1;
    113       return this;
    114     }
    115 
    116     public Builder withView2(View view2) {
    117       this.view2 = view2;
    118       return this;
    119     }
    120 
    121     public Builder withOtherAmbiguousViews(View... others) {
    122       this.others = others;
    123       return this;
    124     }
    125 
    126     public Builder includeViewHierarchy(boolean includeViewHierarchy) {
    127       this.includeViewHierarchy = includeViewHierarchy;
    128       return this;
    129     }
    130 
    131     public AmbiguousViewMatcherException build() {
    132       checkNotNull(viewMatcher);
    133       checkNotNull(rootView);
    134       checkNotNull(view1);
    135       checkNotNull(view2);
    136       checkNotNull(others);
    137       return new AmbiguousViewMatcherException(this);
    138     }
    139   }
    140 }
    141