• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.android.documentsui.bots;
18 
19 import static androidx.test.espresso.Espresso.onView;
20 import static androidx.test.espresso.action.ViewActions.click;
21 import static androidx.test.espresso.assertion.ViewAssertions.matches;
22 import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
23 import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
24 import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
25 import static androidx.test.espresso.matcher.ViewMatchers.withId;
26 import static androidx.test.espresso.matcher.ViewMatchers.withText;
27 
28 import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
29 
30 import static junit.framework.Assert.assertEquals;
31 import static junit.framework.Assert.assertNotNull;
32 import static junit.framework.Assert.assertNull;
33 import static junit.framework.Assert.assertTrue;
34 
35 import static org.hamcrest.CoreMatchers.allOf;
36 import static org.hamcrest.CoreMatchers.is;
37 import static org.hamcrest.Matchers.endsWith;
38 
39 import android.content.Context;
40 import android.util.TypedValue;
41 import android.view.View;
42 
43 import androidx.appcompat.widget.Toolbar;
44 import androidx.test.InstrumentationRegistry;
45 import androidx.test.espresso.Espresso;
46 import androidx.test.espresso.action.ViewActions;
47 import androidx.test.espresso.matcher.BoundedMatcher;
48 import androidx.test.espresso.matcher.ViewMatchers;
49 import androidx.test.uiautomator.By;
50 import androidx.test.uiautomator.UiDevice;
51 import androidx.test.uiautomator.UiObject;
52 import androidx.test.uiautomator.UiObject2;
53 import androidx.test.uiautomator.UiObjectNotFoundException;
54 import androidx.test.uiautomator.UiSelector;
55 import androidx.test.uiautomator.Until;
56 
57 import com.android.documentsui.R;
58 
59 import org.hamcrest.Description;
60 import org.hamcrest.Matcher;
61 
62 import java.util.Iterator;
63 import java.util.List;
64 
65 /**
66  * A test helper class that provides support for controlling DocumentsUI activities
67  * programmatically, and making assertions against the state of the UI.
68  * <p>
69  * Support for working directly with Roots and Directory view can be found in the respective bots.
70  */
71 public class UiBot extends Bots.BaseBot {
72 
73     @SuppressWarnings("unchecked")
74     private static final Matcher<View> TOOLBAR = allOf(
75             isAssignableFrom(Toolbar.class),
76             withId(R.id.toolbar));
77     @SuppressWarnings("unchecked")
78     private static final Matcher<View> ACTIONBAR = allOf(
79             withClassName(endsWith("ActionBarContextView")));
80     @SuppressWarnings("unchecked")
81     private static final Matcher<View> TEXT_ENTRY = allOf(
82             withClassName(endsWith("EditText")));
83     @SuppressWarnings("unchecked")
84     private static final Matcher<View> TOOLBAR_OVERFLOW = allOf(
85             withClassName(endsWith("OverflowMenuButton")),
86             ViewMatchers.isDescendantOfA(TOOLBAR));
87     @SuppressWarnings("unchecked")
88     private static final Matcher<View> ACTIONBAR_OVERFLOW = allOf(
89             withClassName(endsWith("OverflowMenuButton")),
90             ViewMatchers.isDescendantOfA(ACTIONBAR));
91 
92     public static String targetPackageName;
93 
UiBot(UiDevice device, Context context, int timeout)94     public UiBot(UiDevice device, Context context, int timeout) {
95         super(device, context, timeout);
96         targetPackageName =
97                 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
98     }
99 
withToolbarTitle(final Matcher<CharSequence> textMatcher)100     private static Matcher<Object> withToolbarTitle(final Matcher<CharSequence> textMatcher) {
101         return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
102             @Override
103             public boolean matchesSafely(Toolbar toolbar) {
104                 return textMatcher.matches(toolbar.getTitle());
105             }
106 
107             @Override
108             public void describeTo(Description description) {
109                 description.appendText("with toolbar title: ");
110                 textMatcher.describeTo(description);
111             }
112         };
113     }
114 
115     public void assertWindowTitle(String expected) {
116         onView(TOOLBAR)
117                 .check(matches(withToolbarTitle(is(expected))));
118     }
119 
120     public void assertSearchBarShow() {
121         UiSelector selector = new UiSelector().text(mContext.getString(R.string.search_bar_hint));
122         UiObject searchHint = mDevice.findObject(selector);
123         assertTrue(searchHint.exists());
124     }
125 
126     public void assertMenuEnabled(int id, boolean enabled) {
127         UiObject2 menu = findMenuWithName(mContext.getString(id));
128         if (enabled) {
129             assertNotNull(menu);
130             assertEquals(enabled, menu.isEnabled());
131         } else {
132             assertNull(menu);
133         }
134     }
135 
136     public void assertInActionMode(boolean inActionMode) {
137         assertEquals(inActionMode, waitForActionModeBarToAppear());
138     }
139 
140     public UiObject openOverflowMenu() throws UiObjectNotFoundException {
141         UiObject obj = findMenuMoreOptions();
142         obj.click();
143         mDevice.waitForIdle(mTimeout);
144         return obj;
145     }
146 
147     public void setDialogText(String text) throws UiObjectNotFoundException {
148         onView(TEXT_ENTRY)
149                 .perform(ViewActions.replaceText(text));
150     }
151 
152     public void assertDialogText(String expected) throws UiObjectNotFoundException {
153         onView(TEXT_ENTRY)
154                 .check(matches(withText(is(expected))));
155     }
156 
157     public boolean inFixedLayout() {
158         TypedValue val = new TypedValue();
159         // We alias files_activity to either fixed or drawer layouts based
160         // on screen dimensions. In order to determine which layout
161         // has been selected, we check the resolved value.
162         mContext.getResources().getValue(R.layout.files_activity, val, true);
163         return val.resourceId == R.layout.fixed_layout;
164     }
165 
166     public boolean inDrawerLayout() {
167         return !inFixedLayout();
168     }
169 
170     public void switchToListMode() {
171         final UiObject2 listMode = menuListMode();
172         if (listMode != null) {
173             listMode.click();
174         }
175     }
176 
177     public void clickActionItem(String label) throws UiObjectNotFoundException {
178         if (!waitForActionModeBarToAppear()) {
179             throw new UiObjectNotFoundException("ActionMode bar not found");
180         }
181         clickActionbarOverflowItem(label);
182         mDevice.waitForIdle();
183     }
184 
185     public void switchToGridMode() {
186         final UiObject2 gridMode = menuGridMode();
187         if (gridMode != null) {
188             gridMode.click();
189         }
190     }
191 
192     UiObject2 menuGridMode() {
193         // Note that we're using By.desc rather than By.res, because of b/25285770
194         return find(By.desc("Grid view"));
195     }
196 
197     UiObject2 menuListMode() {
198         // Note that we're using By.desc rather than By.res, because of b/25285770
199         return find(By.desc("List view"));
200     }
201 
202     public void clickToolbarItem(int id) {
203         onView(withId(id)).perform(click());
204     }
205 
206     public void clickNewFolder() {
207         onView(ACTIONBAR_OVERFLOW).perform(click());
208 
209         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
210         onView(withText("New folder")).perform(click());
211     }
212 
213     public void clickActionbarOverflowItem(String label) {
214         if (isUseMaterial3FlagEnabled()) {
215             onView(TOOLBAR_OVERFLOW).perform(click());
216         } else {
217             onView(ACTIONBAR_OVERFLOW).perform(click());
218         }
219         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
220         onView(withText(label)).perform(click());
221     }
222 
223     public void clickToolbarOverflowItem(String label) {
224         onView(TOOLBAR_OVERFLOW).perform(click());
225         // Click the item by label, since Espresso doesn't support lookup by id on overflow.
226         onView(withText(label)).perform(click());
227     }
228 
229     public void clickSaveButton() {
230         onView(withId(android.R.id.button1)).perform(click());
231     }
232 
233     public boolean waitForActionModeBarToAppear() {
234         String actionModeId = isUseMaterial3FlagEnabled() ? "toolbar" : "action_mode_bar";
235         UiObject2 bar =
236                 mDevice.wait(
237                         Until.findObject(By.res(mTargetPackage + ":id/" + actionModeId)), mTimeout);
238         return (bar != null);
239     }
240 
241     public void clickRename() throws UiObjectNotFoundException {
242         if (!waitForActionModeBarToAppear()) {
243             throw new UiObjectNotFoundException("ActionMode bar not found");
244         }
245         clickActionbarOverflowItem(mContext.getString(R.string.menu_rename));
246         mDevice.waitForIdle();
247     }
248 
249     public void clickDelete() throws UiObjectNotFoundException {
250         if (!waitForActionModeBarToAppear()) {
251             throw new UiObjectNotFoundException("ActionMode bar not found");
252         }
253         clickToolbarItem(R.id.action_menu_delete);
254         mDevice.waitForIdle();
255     }
256 
257     public UiObject findDownloadRetryDialog() {
258         UiSelector selector = new UiSelector().text("Couldn't download");
259         UiObject title = mDevice.findObject(selector);
260         title.waitForExists(mTimeout);
261         return title;
262     }
263 
264     public UiObject findFileRenameDialog() {
265         UiSelector selector = new UiSelector().text("Rename");
266         UiObject title = mDevice.findObject(selector);
267         title.waitForExists(mTimeout);
268         return title;
269     }
270 
271     public UiObject findRenameErrorMessage() {
272         UiSelector selector = new UiSelector().text(mContext.getString(R.string.name_conflict));
273         UiObject title = mDevice.findObject(selector);
274         title.waitForExists(mTimeout);
275         return title;
276     }
277 
278     @SuppressWarnings("unchecked")
279     public void assertDialogOkButtonFocused() {
280         onView(withId(android.R.id.button1)).check(matches(hasFocus()));
281     }
282 
283     public void clickDialogOkButton() {
284         // Espresso has flaky results when keyboard shows up, so hiding it for now
285         // before trying to click on any dialog button
286         Espresso.closeSoftKeyboard();
287         UiObject2 okButton = mDevice.findObject(By.res("android:id/button1"));
288         okButton.click();
289     }
290 
291     public void clickDialogCancelButton() throws UiObjectNotFoundException {
292         // Espresso has flaky results when keyboard shows up, so hiding it for now
293         // before trying to click on any dialog button
294         Espresso.closeSoftKeyboard();
295         UiObject2 okButton = mDevice.findObject(By.res("android:id/button2"));
296         okButton.click();
297     }
298 
299     public UiObject findMenuLabelWithName(String label) {
300         UiSelector selector = new UiSelector().text(label);
301         return mDevice.findObject(selector);
302     }
303 
304     UiObject2 findMenuWithName(String label) {
305         UiObject2 list = mDevice.findObject(By.clazz("android.widget.ListView"));
306         List<UiObject2> menuItems = list.getChildren();
307         Iterator<UiObject2> it = menuItems.iterator();
308 
309         UiObject2 menuItem = null;
310         while (it.hasNext()) {
311             menuItem = it.next();
312             UiObject2 text = menuItem.findObject(By.text(label));
313             if (text != null) {
314                 return menuItem;
315             }
316         }
317         return null;
318     }
319 
320     boolean hasMenuWithName(String label) {
321         return findMenuWithName(label) != null;
322     }
323 
324     UiObject findMenuMoreOptions() {
325         UiSelector selector = new UiSelector().className("android.widget.ImageView")
326                 .descriptionContains("More options");
327         // TODO: use the system string ? android.R.string.action_menu_overflow_description
328         return mDevice.findObject(selector);
329     }
330 }
331