blob: 4885b6d7baf4d94d8b94799325c9c6d922c2440d [file] [log] [blame]
Steve McKay5e1acc92016-02-19 12:57:05 -08001/*
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
17package com.android.documentsui.bots;
18
Brett Chabot2027ca02018-12-13 19:06:31 -080019import static androidx.test.espresso.Espresso.onView;
20import static androidx.test.espresso.action.ViewActions.click;
21import static androidx.test.espresso.assertion.ViewAssertions.matches;
22import static androidx.test.espresso.matcher.ViewMatchers.hasFocus;
23import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;
24import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
25import static androidx.test.espresso.matcher.ViewMatchers.withId;
26import static androidx.test.espresso.matcher.ViewMatchers.withText;
27
Steve McKay5e1acc92016-02-19 12:57:05 -080028import static junit.framework.Assert.assertEquals;
Steve McKay5e1acc92016-02-19 12:57:05 -080029import static junit.framework.Assert.assertNotNull;
Tony Huang7b3f0742019-01-25 12:02:44 +080030import static junit.framework.Assert.assertTrue;
Brett Chabot2027ca02018-12-13 19:06:31 -080031
Steve McKay502c3b12016-06-29 16:01:39 -070032import static org.hamcrest.CoreMatchers.allOf;
Ben Linb8c54e72016-06-10 12:13:27 -070033import static org.hamcrest.CoreMatchers.is;
Steve McKay502c3b12016-06-29 16:01:39 -070034import static org.hamcrest.Matchers.endsWith;
Steve McKay5e1acc92016-02-19 12:57:05 -080035
36import android.content.Context;
37import android.support.test.uiautomator.By;
38import android.support.test.uiautomator.UiDevice;
39import android.support.test.uiautomator.UiObject;
40import android.support.test.uiautomator.UiObject2;
41import android.support.test.uiautomator.UiObjectNotFoundException;
42import android.support.test.uiautomator.UiSelector;
Ben Lind1736fd2016-09-15 10:44:11 -070043import android.support.test.uiautomator.Until;
Steve McKay85ec0d62016-06-24 15:05:08 -070044import android.util.TypedValue;
Steve McKay502c3b12016-06-29 16:01:39 -070045import android.view.View;
Tony Huang8d8d92f2018-09-13 14:41:16 +080046
47import androidx.appcompat.widget.Toolbar;
Bill Lin34329192019-03-05 22:22:20 +080048import androidx.test.InstrumentationRegistry;
Brett Chabot2027ca02018-12-13 19:06:31 -080049import androidx.test.espresso.Espresso;
50import androidx.test.espresso.action.ViewActions;
51import androidx.test.espresso.matcher.BoundedMatcher;
52import androidx.test.espresso.matcher.ViewMatchers;
Steve McKay5e1acc92016-02-19 12:57:05 -080053
54import com.android.documentsui.R;
Ben Linb8c54e72016-06-10 12:13:27 -070055
Ben Lin8952ec62016-05-11 17:38:18 -070056import org.hamcrest.Description;
57import org.hamcrest.Matcher;
Steve McKay5e1acc92016-02-19 12:57:05 -080058
59import java.util.Iterator;
60import java.util.List;
61
62/**
63 * A test helper class that provides support for controlling DocumentsUI activities
64 * programmatically, and making assertions against the state of the UI.
Steve McKayaee0e622016-06-23 14:24:41 -070065 * <p>
66 * Support for working directly with Roots and Directory view can be found in the respective bots.
Steve McKay5e1acc92016-02-19 12:57:05 -080067 */
Steve McKay85ec0d62016-06-24 15:05:08 -070068public class UiBot extends Bots.BaseBot {
Steve McKayaee0e622016-06-23 14:24:41 -070069
Bill Lin34329192019-03-05 22:22:20 +080070 public static String targetPackageName;
Steve McKay5e1acc92016-02-19 12:57:05 -080071
Steve McKay502c3b12016-06-29 16:01:39 -070072 @SuppressWarnings("unchecked")
73 private static final Matcher<View> TOOLBAR = allOf(
74 isAssignableFrom(Toolbar.class),
75 withId(R.id.toolbar));
76
77 @SuppressWarnings("unchecked")
78 private static final Matcher<View> ACTIONBAR = allOf(
79 withClassName(endsWith("ActionBarContextView")));
80
81 @SuppressWarnings("unchecked")
82 private static final Matcher<View> TEXT_ENTRY = allOf(
83 withClassName(endsWith("EditText")));
84
85 @SuppressWarnings("unchecked")
86 private static final Matcher<View> TOOLBAR_OVERFLOW = allOf(
87 withClassName(endsWith("OverflowMenuButton")),
88 ViewMatchers.isDescendantOfA(TOOLBAR));
89
90 @SuppressWarnings("unchecked")
91 private static final Matcher<View> ACTIONBAR_OVERFLOW = allOf(
92 withClassName(endsWith("OverflowMenuButton")),
93 ViewMatchers.isDescendantOfA(ACTIONBAR));
94
Steve McKay5e1acc92016-02-19 12:57:05 -080095 public UiBot(UiDevice device, Context context, int timeout) {
96 super(device, context, timeout);
Bill Lin34329192019-03-05 22:22:20 +080097 targetPackageName =
98 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName();
Steve McKay5e1acc92016-02-19 12:57:05 -080099 }
100
101 public void assertWindowTitle(String expected) {
Steve McKay502c3b12016-06-29 16:01:39 -0700102 onView(TOOLBAR)
Steve McKayaee0e622016-06-23 14:24:41 -0700103 .check(matches(withToolbarTitle(is(expected))));
Steve McKay5e1acc92016-02-19 12:57:05 -0800104 }
105
Tony Huang7b3f0742019-01-25 12:02:44 +0800106 public void assertSearchBarShow() {
107 UiSelector selector = new UiSelector().text(mContext.getString(R.string.search_bar_hint));
108 UiObject searchHint = mDevice.findObject(selector);
109 assertTrue(searchHint.exists());
110 }
111
Steve McKay5e1acc92016-02-19 12:57:05 -0800112 public void assertMenuEnabled(int id, boolean enabled) {
Steve McKayaee0e622016-06-23 14:24:41 -0700113 UiObject2 menu = findMenuWithName(mContext.getString(id));
Steve McKay5e1acc92016-02-19 12:57:05 -0800114 assertNotNull(menu);
115 assertEquals(enabled, menu.isEnabled());
116 }
117
Aga Wronska7e5b9632016-02-26 11:36:07 -0800118 public void assertInActionMode(boolean inActionMode) {
Ben Lind1736fd2016-09-15 10:44:11 -0700119 assertEquals(inActionMode, waitForActionModeBarToAppear());
Aga Wronska7e5b9632016-02-26 11:36:07 -0800120 }
121
Steve McKay5e1acc92016-02-19 12:57:05 -0800122 public UiObject openOverflowMenu() throws UiObjectNotFoundException {
123 UiObject obj = findMenuMoreOptions();
124 obj.click();
125 mDevice.waitForIdle(mTimeout);
126 return obj;
127 }
128
129 public void setDialogText(String text) throws UiObjectNotFoundException {
Steve McKay502c3b12016-06-29 16:01:39 -0700130 onView(TEXT_ENTRY)
Steve McKay85ec0d62016-06-24 15:05:08 -0700131 .perform(ViewActions.replaceText(text));
Steve McKay5e1acc92016-02-19 12:57:05 -0800132 }
133
Jon Mann38f14392017-01-25 13:51:55 -0800134 public void assertDialogText(String expected) throws UiObjectNotFoundException {
135 onView(TEXT_ENTRY)
136 .check(matches(withText(is(expected))));
137 }
138
Steve McKay85ec0d62016-06-24 15:05:08 -0700139 public boolean inFixedLayout() {
140 TypedValue val = new TypedValue();
141 // We alias files_activity to either fixed or drawer layouts based
142 // on screen dimensions. In order to determine which layout
143 // has been selected, we check the resolved value.
144 mContext.getResources().getValue(R.layout.files_activity, val, true);
145 return val.resourceId == R.layout.fixed_layout;
146 }
147
148 public boolean inDrawerLayout() {
149 return !inFixedLayout();
Ben Lin4c6e4a22016-05-23 09:49:51 -0700150 }
151
Garfield, Tan28bbd8d2016-08-18 15:22:47 -0700152 public void switchToListMode() {
153 final UiObject2 listMode = menuListMode();
154 if (listMode != null) {
155 listMode.click();
156 }
157 }
158
Julian Mancini30e015a2017-06-07 15:14:49 -0700159 public void clickActionItem(String label) throws UiObjectNotFoundException {
160 if (!waitForActionModeBarToAppear()) {
161 throw new UiObjectNotFoundException("ActionMode bar not found");
162 }
163 clickActionbarOverflowItem(label);
164 mDevice.waitForIdle();
165 }
166
Garfield, Tan28bbd8d2016-08-18 15:22:47 -0700167 public void switchToGridMode() {
168 final UiObject2 gridMode = menuGridMode();
169 if (gridMode != null) {
170 gridMode.click();
Steve McKay5e1acc92016-02-19 12:57:05 -0800171 }
172 }
173
174 UiObject2 menuGridMode() {
175 // Note that we're using By.desc rather than By.res, because of b/25285770
176 return find(By.desc("Grid view"));
177 }
178
Steve McKayaee0e622016-06-23 14:24:41 -0700179 UiObject2 menuListMode() {
Steve McKay5e1acc92016-02-19 12:57:05 -0800180 // Note that we're using By.desc rather than By.res, because of b/25285770
181 return find(By.desc("List view"));
182 }
183
Steve McKay85ec0d62016-06-24 15:05:08 -0700184 public void clickToolbarItem(int id) {
185 onView(withId(id)).perform(click());
Steve McKay5e1acc92016-02-19 12:57:05 -0800186 }
187
Steve McKay85ec0d62016-06-24 15:05:08 -0700188 public void clickNewFolder() {
Steve McKay502c3b12016-06-29 16:01:39 -0700189 onView(ACTIONBAR_OVERFLOW).perform(click());
Steve McKay85ec0d62016-06-24 15:05:08 -0700190
191 // Click the item by label, since Espresso doesn't support lookup by id on overflow.
192 onView(withText("New folder")).perform(click());
Steve McKay5e1acc92016-02-19 12:57:05 -0800193 }
194
Steve McKay85ec0d62016-06-24 15:05:08 -0700195 public void clickActionbarOverflowItem(String label) {
Steve McKay502c3b12016-06-29 16:01:39 -0700196 onView(ACTIONBAR_OVERFLOW).perform(click());
Steve McKay85ec0d62016-06-24 15:05:08 -0700197 // Click the item by label, since Espresso doesn't support lookup by id on overflow.
198 onView(withText(label)).perform(click());
Steve McKay5e1acc92016-02-19 12:57:05 -0800199 }
200
Steve McKay85ec0d62016-06-24 15:05:08 -0700201 public void clickToolbarOverflowItem(String label) {
Steve McKay502c3b12016-06-29 16:01:39 -0700202 onView(TOOLBAR_OVERFLOW).perform(click());
Steve McKay85ec0d62016-06-24 15:05:08 -0700203 // Click the item by label, since Espresso doesn't support lookup by id on overflow.
204 onView(withText(label)).perform(click());
Ben Linb8c54e72016-06-10 12:13:27 -0700205 }
206
Ben Lind1736fd2016-09-15 10:44:11 -0700207 public boolean waitForActionModeBarToAppear() {
208 UiObject2 bar =
Tony Huang8d8d92f2018-09-13 14:41:16 +0800209 mDevice.wait(Until.findObject(
Tony Huanged2f31f2019-03-27 18:47:37 +0800210 By.res(mTargetPackage + ":id/action_mode_bar")), mTimeout);
Ben Lind1736fd2016-09-15 10:44:11 -0700211 return (bar != null);
Steve McKay5e1acc92016-02-19 12:57:05 -0800212 }
213
Tony Huang1bbf37a2018-09-12 15:57:55 +0800214 public void clickRename() throws UiObjectNotFoundException {
215 if (!waitForActionModeBarToAppear()) {
216 throw new UiObjectNotFoundException("ActionMode bar not found");
217 }
218 clickActionbarOverflowItem(mContext.getString(R.string.menu_rename));
219 mDevice.waitForIdle();
220 }
221
222 public void clickDelete() throws UiObjectNotFoundException {
223 if (!waitForActionModeBarToAppear()) {
224 throw new UiObjectNotFoundException("ActionMode bar not found");
225 }
226 clickToolbarItem(R.id.action_menu_delete);
227 mDevice.waitForIdle();
228 }
229
Steve McKaybeac2232016-03-11 10:11:52 -0800230 public UiObject findDownloadRetryDialog() {
231 UiSelector selector = new UiSelector().text("Couldn't download");
232 UiObject title = mDevice.findObject(selector);
233 title.waitForExists(mTimeout);
234 return title;
235 }
236
Jon Mann38f14392017-01-25 13:51:55 -0800237 public UiObject findFileRenameDialog() {
238 UiSelector selector = new UiSelector().text("Rename");
239 UiObject title = mDevice.findObject(selector);
240 title.waitForExists(mTimeout);
241 return title;
242 }
243
244 public UiObject findRenameErrorMessage() {
245 UiSelector selector = new UiSelector().text(mContext.getString(R.string.name_conflict));
246 UiObject title = mDevice.findObject(selector);
247 title.waitForExists(mTimeout);
248 return title;
249 }
250
Ben Lin6f6d5782016-09-28 14:44:32 -0700251 @SuppressWarnings("unchecked")
252 public void assertDialogOkButtonFocused() {
253 onView(withId(android.R.id.button1)).check(matches(hasFocus()));
254 }
255
Ben Lin4c6e4a22016-05-23 09:49:51 -0700256 public void clickDialogOkButton() {
257 // Espresso has flaky results when keyboard shows up, so hiding it for now
258 // before trying to click on any dialog button
Steve McKay85ec0d62016-06-24 15:05:08 -0700259 Espresso.closeSoftKeyboard();
260 onView(withId(android.R.id.button1)).perform(click());
Steve McKay5e1acc92016-02-19 12:57:05 -0800261 }
262
Ben Lin4c6e4a22016-05-23 09:49:51 -0700263 public void clickDialogCancelButton() throws UiObjectNotFoundException {
264 // Espresso has flaky results when keyboard shows up, so hiding it for now
265 // before trying to click on any dialog button
Steve McKay85ec0d62016-06-24 15:05:08 -0700266 Espresso.closeSoftKeyboard();
267 onView(withId(android.R.id.button2)).perform(click());
Steve McKay5e1acc92016-02-19 12:57:05 -0800268 }
269
Jun Ono418e5a32017-11-22 10:29:30 +0900270 public UiObject findMenuLabelWithName(String label) {
Steve McKay5e1acc92016-02-19 12:57:05 -0800271 UiSelector selector = new UiSelector().text(label);
272 return mDevice.findObject(selector);
273 }
274
275 UiObject2 findMenuWithName(String label) {
Tony Huangd02c8a22019-11-14 18:49:16 +0800276 UiObject2 list = mDevice.findObject(By.clazz("android.widget.ListView"));
277 List<UiObject2> menuItems = list.getChildren();
Steve McKay5e1acc92016-02-19 12:57:05 -0800278 Iterator<UiObject2> it = menuItems.iterator();
279
280 UiObject2 menuItem = null;
Steve McKayaee0e622016-06-23 14:24:41 -0700281 while (it.hasNext()) {
Steve McKay5e1acc92016-02-19 12:57:05 -0800282 menuItem = it.next();
283 UiObject2 text = menuItem.findObject(By.text(label));
Tony Huangd02c8a22019-11-14 18:49:16 +0800284 if (text != null) {
285 return menuItem;
Steve McKay5e1acc92016-02-19 12:57:05 -0800286 }
287 }
Tony Huangd02c8a22019-11-14 18:49:16 +0800288 return null;
Steve McKay5e1acc92016-02-19 12:57:05 -0800289 }
290
Steve McKay85ec0d62016-06-24 15:05:08 -0700291 boolean hasMenuWithName(String label) {
292 return findMenuWithName(label) != null;
293 }
294
Steve McKay5e1acc92016-02-19 12:57:05 -0800295 UiObject findMenuMoreOptions() {
Tony Huang8d8d92f2018-09-13 14:41:16 +0800296 UiSelector selector = new UiSelector().className("android.widget.ImageView")
Steve McKay5e1acc92016-02-19 12:57:05 -0800297 .descriptionContains("More options");
Steve McKayaee0e622016-06-23 14:24:41 -0700298 // TODO: use the system string ? android.R.string.action_menu_overflow_description
Steve McKay5e1acc92016-02-19 12:57:05 -0800299 return mDevice.findObject(selector);
300 }
301
Ben Lin8952ec62016-05-11 17:38:18 -0700302 private static Matcher<Object> withToolbarTitle(
Steve McKayaee0e622016-06-23 14:24:41 -0700303 final Matcher<CharSequence> textMatcher) {
304 return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
305 @Override
306 public boolean matchesSafely(Toolbar toolbar) {
307 return textMatcher.matches(toolbar.getTitle());
308 }
309
310 @Override
311 public void describeTo(Description description) {
312 description.appendText("with toolbar title: ");
313 textMatcher.describeTo(description);
314 }
315 };
Ben Lin8952ec62016-05-11 17:38:18 -0700316 }
Steve McKay5e1acc92016-02-19 12:57:05 -0800317}