Ce document explique comment configurer différents tests Espresso courants.
Faire correspondre une vue à côté d'une autre
Une mise en page peut contenir certaines vues qui ne sont pas uniques en soi. Pour
exemple, un bouton d'appel répété dans une table de contacts peut avoir le même
R.id
, contiennent le même texte et possèdent les mêmes propriétés que les autres appels
dans la hiérarchie des vues.
Par exemple, dans cette activité, la vue avec le texte "7"
se répète sur plusieurs
lignes:
Souvent, la vue non unique sera associée à une étiquette unique qui se trouve
qui se trouve à côté, comme son nom à côté du bouton d'appel. Dans ce cas,
vous pouvez utiliser l'outil de mise en correspondance hasSibling()
pour affiner votre sélection:
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
Java
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
Faire correspondre une vue à l'intérieur d'une barre d'action
Le ActionBarTestActivity
comporte deux barres d'action différentes: une barre normale
une barre d'action et une barre d'action contextuelle créée à partir d'un menu d'options. Les deux
Les barres d'action comportent un élément toujours visible et deux éléments qui ne sont
visible dans le menu à développer. Lorsqu'un utilisateur clique sur un élément, un TextView devient
le contenu de l'article sur lequel l'utilisateur a cliqué.
Il est facile de faire correspondre les icônes visibles des deux barres d'action, comme illustré dans l'extrait de code suivant:
Kotlin
fun testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))) }
Java
public void testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))); }
Le code est identique pour la barre d'action contextuelle:
Kotlin
fun testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))) }
Java
public void testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))); }
Cliquer sur des éléments du menu à développer est un peu plus délicat pour l'action normale car certains appareils ont un bouton de menu à développer matérielle, qui ouvre le certains éléments d'un menu d'options, et certains appareils ont un menu à développer qui ouvre un menu à développer normal. Heureusement, Espresso gère cela pour nous.
Pour la barre d'action normale:
Kotlin
fun testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext<Context>()) // Click the item. onView(withText("World")) .perform(click()) // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))) }
Java
public void testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext()); // Click the item. onView(withText("World")) .perform(click()); // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))); }
Voici à quoi cela ressemble sur les appareils dotés d'un bouton de menu à développer:
Pour la barre d'action contextuelle, c'est encore très facile:
Kotlin
fun testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu() // Click on the item. onView(withText("Key")) .perform(click()) // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))) } }
Java
public void testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu(); // Click on the item. onView(withText("Key")) .perform(click()); // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))); } }
Pour afficher le code complet de ces exemples, consultez la
Exemple ActionBarTest.java
sur GitHub
Déclarer qu'une vue ne s'affiche pas
Après avoir effectué une série d'actions, vous devrez certainement
l'état de l'UI testée. Il peut s'agir d'un cas négatif, par exemple
quelque chose ne se passe pas. Gardez à l'esprit que vous pouvez
transformer n'importe quelle vue Hamcrest
de mise en correspondance dans un ViewAssertion
à l'aide de ViewAssertions.matches()
.
Dans l'exemple ci-dessous, nous prenons l'outil de mise en correspondance isDisplayed()
et l'inversons en utilisant
l'outil de mise en correspondance not()
standard:
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import org.hamcrest.Matchers.not onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())))
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.not; onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())));
L'approche ci-dessus fonctionne si la vue fait toujours partie de la hiérarchie. Si c'est le cas
non, vous obtiendrez un NoMatchingViewException
et vous devrez utiliser
ViewAssertions.doesNotExist()
Déclarer qu'aucune vue n'est présente
Si la vue a disparu de la hiérarchie des vues, ce qui peut se produire lorsqu'une
a entraîné une transition vers une autre activité.
ViewAssertions.doesNotExist()
:
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.withId onView(withId(R.id.bottom_left)) .check(doesNotExist())
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.matcher.ViewMatchers.withId; onView(withId(R.id.bottom_left)) .check(doesNotExist());
Déclarer qu'un élément de données ne se trouve pas dans un adaptateur
Pour prouver qu'un élément de données particulier ne se trouve pas dans une AdapterView
, vous devez
les choses un peu différemment. Nous devons trouver les AdapterView
qui nous intéressent
et d'interroger les données
qu'elles détiennent. Nous n'avons pas besoin d'utiliser onData()
.
À la place, nous utilisons onView()
pour trouver AdapterView
, puis utilisons un autre
pour qu'il fonctionne
sur les données dans la vue.
Tout d'abord, l'outil de mise en correspondance:
Kotlin
private fun withAdaptedData(dataMatcher: Matcher<Any>): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("with class name: ") dataMatcher.describeTo(description) } public override fun matchesSafely(view: View) : Boolean { if (view !is AdapterView<*>) { return false } val adapter = view.adapter for (i in 0 until adapter.count) { if (dataMatcher.matches(adapter.getItem(i))) { return true } } return false } } }
Java
private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("with class name: "); dataMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { if (!(view instanceof AdapterView)) { return false; } @SuppressWarnings("rawtypes") Adapter adapter = ((AdapterView) view).getAdapter(); for (int i = 0; i < adapter.getCount(); i++) { if (dataMatcher.matches(adapter.getItem(i))) { return true; } } return false; } }; }
Ensuite, tout ce dont nous avons besoin est onView()
pour trouver AdapterView
:
Kotlin
fun testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))) } }
Java
@SuppressWarnings("unchecked") public void testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))); } }
Et nous avons une assertion qui échoue si un élément est égal à "item: 168". existe dans une vue d'adaptateur avec la liste d'ID.
Pour voir l'exemple complet, examinez la méthode testDataItemNotInAdapter()
dans la
AdapterViewTest.java
sur GitHub.
Utiliser un gestionnaire d'échecs personnalisé
Le remplacement de la valeur FailureHandler
par défaut dans Espresso par une valeur personnalisée permet d'obtenir
gestion d'erreurs supplémentaires ou différentes, comme une capture d'écran ou la transmission
ainsi que des informations de débogage supplémentaires.
L'exemple CustomFailureHandlerTest
montre comment implémenter un
gestionnaire d'échecs:
Kotlin
private class CustomFailureHandler(targetContext: Context) : FailureHandler { private val delegate: FailureHandler init { delegate = DefaultFailureHandler(targetContext) } override fun handle(error: Throwable, viewMatcher: Matcher<View>) { try { delegate.handle(error, viewMatcher) } catch (e: NoMatchingViewException) { throw MySpecialException(e) } } }
Java
private static class CustomFailureHandler implements FailureHandler { private final FailureHandler delegate; public CustomFailureHandler(Context targetContext) { delegate = new DefaultFailureHandler(targetContext); } @Override public void handle(Throwable error, Matcher<View> viewMatcher) { try { delegate.handle(error, viewMatcher); } catch (NoMatchingViewException e) { throw new MySpecialException(e); } } }
Ce gestionnaire d'échecs génère une exception MySpecialException
au lieu d'une
NoMatchingViewException
et délègue toutes les autres défaillances au
DefaultFailureHandler
Le CustomFailureHandler
peut être enregistré auprès de
Espresso dans la méthode setUp()
du test:
Kotlin
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
Java
@Override public void setUp() throws Exception { super.setUp(); getActivity(); setFailureHandler(new CustomFailureHandler( ApplicationProvider.getApplicationContext())); }
Pour en savoir plus, consultez les
FailureHandler
de commande et
Espresso.setFailureHandler()
Cibler des fenêtres autres que celles par défaut
Android prend en charge plusieurs fenêtres. Normalement, cette information est transparente pour les utilisateurs.
et le développeur de l'application, mais dans certains cas, plusieurs fenêtres sont visibles,
comme lorsqu'une fenêtre de saisie semi-automatique s'affiche sur la fenêtre principale de l'application dans
le widget Recherche. Pour simplifier les choses, Espresso utilise par défaut une heuristique pour
devinez avec quel Window
vous avez l'intention d'interagir. Cette heuristique est presque
toujours assez bon ; Toutefois, dans de rares cas, vous devrez préciser
une interaction doit cibler. Vous pouvez le faire en fournissant votre propre fenêtre racine
ou Root
:
Kotlin
onView(withText("South China Sea")) .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView())))) .perform(click())
Java
onView(withText("South China Sea")) .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))) .perform(click());
Comme c'est le cas pour
ViewMatchers
,
nous fournissons un ensemble
RootMatchers
Bien entendu, vous pouvez toujours implémenter votre propre objet Matcher
.
Examinons la fonction MultipleWindowTest. exemple sur GitHub.
Faire correspondre un en-tête ou un pied de page dans une vue sous forme de liste
Les en-têtes et les pieds de page sont ajoutés à ListViews
à l'aide des balises addHeaderView()
et
addFooterView()
. Pour s'assurer que Espresso.onData()
sait quel objet de données
pour la mise en correspondance, assurez-vous de transmettre une valeur d'objet de données prédéfinie en tant que deuxième paramètre
à addHeaderView()
et addFooterView()
. Exemple :
Kotlin
const val FOOTER = "FOOTER" ... val footerView = layoutInflater.inflate(R.layout.list_item, listView, false) footerView.findViewById<TextView>(R.id.item_content).text = "count:" footerView.findViewById<TextView>(R.id.item_size).text = data.size.toString listView.addFooterView(footerView, FOOTER, true)
Java
public static final String FOOTER = "FOOTER"; ... View footerView = layoutInflater.inflate(R.layout.list_item, listView, false); footerView.findViewById<TextView>(R.id.item_content).setText("count:"); footerView.findViewById<TextView>(R.id.item_size).setText(String.valueOf(data.size())); listView.addFooterView(footerView, FOOTER, true);
Ensuite, vous pouvez écrire une correspondance pour le pied de page:
Kotlin
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.instanceOf import org.hamcrest.Matchers.`is` fun isFooter(): Matcher<Any> { return allOf(`is`(instanceOf(String::class.java)), `is`(LongListActivity.FOOTER)) }
Java
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @SuppressWarnings("unchecked") public static Matcher<Object> isFooter() { return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER)); }
De plus, le chargement de la vue lors d'un test est simple:
Kotlin
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.sample.LongListMatchers.isFooter fun testClickFooter() { onData(isFooter()) .perform(click()) // ... }
Java
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.sample.LongListMatchers.isFooter; public void testClickFooter() { onData(isFooter()) .perform(click()); // ... }
Examinez l'exemple de code complet, disponible dans la méthode testClickFooter()
de
AdapterViewTest.java
sur GitHub.