2015-03-31 3 views
26

Mit GridView, die einige Bilder hat. Die Zelle des GridViews kommt aus demselben vordefinierten Layout, das die gleiche ID und Desc hat.in Espresso, wie wählt man die Ansicht, die gleiche ID zu vermeiden AmbiguousViewMatcherException

R.id.item_image == 2131493330

onView(withId(is(R.id.item_image))).perform(click()); 

Da alle Zellen im Raster gleiche ID haben, wurde es AmbiguousViewMatcherException. Wie hole ich einfach den einen oder einen von ihnen ab? Danke!

android.support.test.espresso.AmbiguousViewMatcherException: 'mit id: ist < 2131493330>' mehrere Ansichten in der Hierarchie übereinstimmt. Problemansichten sind unten mit **** **** MATCHES **** gekennzeichnet.

+ -------------> Bildansicht {id = 2131493330, res-name = item_image, desc = Bild, Sichtbarkeit = SICHTBAR, Breite = 262, Höhe = 262, Has-Fokus = false, has-focusable = false, has-window-focus = true, is-clickable = false, is-enabled = true, is-focused = false, is-focusable = false, is-layout-request = false, is -selected = false, root-is-layout-request = false, has-input-connection = false, x = 0.0, y = 0.0} **** SPIELE ****

+ ----- --------> ImageView {id = 2131493330, res-name = item_image, desc = Bild, Sichtbarkeit = VISIBLE, Breite = 262, Höhe = 262, has-focus = false, has-focusable = false, hat -window-focus = true, is-clickable = false, is-enabled = true, is-focused = false, is-focusable = false, is-layout-request = false, is-selected = false, root-is-layout -requested = false, hat-input-connection = false, x = 0.0, y = 0 .0} **** SPIELE **** |

Antwort

16

sollten Sie onData() auf GridView zu bedienen verwenden:

onData(withId(R.id.item_image)) 
     .inAdapterView(withId(R.id.grid_adapter_id)) 
     .atPosition(0) 
     .perform(click()); 

Dieser Code auf dem Bild im ersten Element klicken, wird in GridView

+0

Dank! werde es bald versuchen. Ich denke, es ist die Antwort. – lannyf

+0

Hallo, ist 'R.id.grid_adapter_id' das eigentliche 'RecyclerView' /' ListView'? Vielen Dank! –

+2

@NeonWarge, ja, ID von GridView oder kann auch ListView sein. Aber nicht Recycler View. Sie können 'onData()' damit nicht verwenden. – denys

12

ich eine ViewMatcher erstellt, die den ersten Blick passt es findet. Vielleicht ist es hilfreich für jemanden. Zum Beispiel wenn Sie kein AdapterView haben, um onData() on zu verwenden.

/** 
* Created by stost on 15.05.14. 
* Matches any view. But only on first match()-call. 
*/ 
public class FirstViewMatcher extends BaseMatcher<View> { 


    public static boolean matchedBefore = false; 

    public FirstViewMatcher() { 
     matchedBefore = false; 
    } 

    @Override 
    public boolean matches(Object o) { 
     if (matchedBefore) { 
      return false; 
     } else { 
      matchedBefore = true; 
      return true; 
     } 
    } 

    @Override 
    public void describeTo(Description description) { 
     description.appendText(" is the first view that comes along "); 
    } 

    @Factory 
    public static <T> Matcher<View> firstView() { 
     return new FirstViewMatcher(); 
    } 
} 

es wie folgt verwendet:

onView(FirstViewMatcher.firstView()).perform(click()); 
+0

Sie haben versucht, dies zu ändern, um den ersten eines bestimmten Ansichtstyps zurückzugeben. Gedanken? – wapples

+2

@wappes Hängt davon ab ... Wenn Sie einen View-Typ und alle Typen, die von ihm stammen, abgleichen wollen, können Sie etwas wie 'if (o instanceofClassToMatch.class)' in der matchs-() - Methode verwenden. Wenn Sie nur eine bestimmte Klasse abgleichen möchten, können Sie etwas wie 'if ("ClassToMatchAsString" .equals (o.getClass(). GetName())) 'oder' if ("ClassToMatchAsString" .equals (o.getClass() .getSimpleName())) '. Oder wenn Sie den Code überhaupt nicht bearbeiten wollen, sollte das auch funktionieren: 'onView (allOf (mitClassName (endsWith (" ClassToMatchAsString ")), FirstViewMatcher.firstView())). Ausführen (click());' – StefanTo

13

nicht vollständig im Zusammenhang mit Rasteransicht Situation aber können Sie hamcrest allOf Matcher verwenden, um mehrere Bedingungen zu kombinieren:

import static org.hamcrest.CoreMatchers.allOf; 

onView(allOf(withId(R.id.login_password), 
      withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE))) 
     .check(matches(isCompletelyDisplayed())) 
     .check(matches(withHint(R.string.password_placeholder))); 
48

Ich war erstaunt, dass ich konnte keine Lösung finden, indem er einfach einen Index zusammen mit einem Matcher (dh mit Text, mit Id) zur Verfügung stellte. Die akzeptierte Antwort löst nur das Problem, wenn Sie mit onData und ListViews arbeiten.

Wenn Sie mehr als eine Ansicht auf dem Bildschirm mit dem gleichen RESID/text/contentDesc, können Sie wählen, welche Sie wollen, ohne mithilfe dieses benutzerdefinierten Matcher einen AmbiguousViewMatcherException verursacht:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) { 
    return new TypeSafeMatcher<View>() { 
     int currentIndex = 0; 

     @Override 
     public void describeTo(Description description) { 
      description.appendText("with index: "); 
      description.appendValue(index); 
      matcher.describeTo(description); 
     } 

     @Override 
     public boolean matchesSafely(View view) { 
      return matcher.matches(view) && currentIndex++ == index; 
     } 
    }; 
} 

Zum Beispiel:

onView(withIndex(withId(R.id.my_view), 2)).perform(click()); 

führt eine Klickaktion für die dritte Instanz von R.id.my_view aus.

+0

funktioniert gut. Vielen Dank. – rpattabi

+0

Dies ist eine großartige Lösung, nach der ich gesucht habe, da meine Ansicht keine Adapteransicht ist. Wir verwenden die Ansicht "Sticky List", die eine Liste ohne Listenansicht implementiert. –

+0

brilliant. Ich habe diese Lösung auch in Versionen wie first() und second() eingepackt. – Ivan

2

Hüllen:

onView(withId(R.id.songListView)).perform(RealmRecyclerViewActions.scrollTo(Matchers.first(Matchers.withTextLabeled("Love Song")))); 
onView(Matchers.first(withText("Love Song"))).perform(click()); 

in meinem Matchers.class

public static Matcher<View> first(Matcher<View> expected){ 

    return new TypeSafeMatcher<View>() { 
     private boolean first = false; 

     @Override 
     protected boolean matchesSafely(View item) { 

      if(expected.matches(item) && !first){ 
       return first = true; 
      } 

      return false; 
     } 

     @Override 
     public void describeTo(Description description) { 
      description.appendText("Matcher.first(" + expected.toString() + ")"); 
     } 
    }; 
} 
7

Versuchte @FrostRocket Antwort als suchte die meisten versprechend aber benötigt, um einige Anpassungen hinzuzufügen:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) { 
    return new TypeSafeMatcher<View>() { 
     int currentIndex; 
     int viewObjHash; 

     @SuppressLint("DefaultLocale") @Override 
     public void describeTo(Description description) { 
      description.appendText(String.format("with index: %d ", index)); 
      matcher.describeTo(description); 
     } 

     @Override 
     public boolean matchesSafely(View view) { 
      if (matcher.matches(view) && currentIndex++ == index) { 
       viewObjHash = view.hashCode(); 
      } 
      return view.hashCode() == viewObjHash; 
     } 
    }; 
} 
+0

Hallo, warum musstest du den Hash Code Check hinzufügen? Diese Version funktioniert für mich, aber ich war nicht sicher, warum es das tun muss. Vielen Dank! – rfodge

+0

Hallo, ich muss sagen ich erinnere mich nicht genau. Ich denke, es hatte etwas damit zu tun, dass die Ansicht im Test mutiert ist, aber jetzt nicht sicher ist. – fada21

+0

Ich denke, der Grund ist, dass der Matcher mehrmals auf der gleichen Komponente aufgerufen werden könnte. In diesem Fall sollte auch die Bezugnahme auf die Ansicht (anstelle des Hash-Codes) funktionieren. –

Verwandte Themen