| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.documentsui.bots; |
| |
| import static androidx.test.espresso.Espresso.onView; |
| import static androidx.test.espresso.action.ViewActions.click; |
| import static androidx.test.espresso.assertion.ViewAssertions.matches; |
| import static androidx.test.espresso.matcher.ViewMatchers.hasFocus; |
| import static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom; |
| import static androidx.test.espresso.matcher.ViewMatchers.withClassName; |
| import static androidx.test.espresso.matcher.ViewMatchers.withId; |
| import static androidx.test.espresso.matcher.ViewMatchers.withText; |
| |
| import static junit.framework.Assert.assertEquals; |
| import static junit.framework.Assert.assertNotNull; |
| import static junit.framework.Assert.assertTrue; |
| |
| import static org.hamcrest.CoreMatchers.allOf; |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.Matchers.endsWith; |
| |
| import android.content.Context; |
| import android.support.test.uiautomator.By; |
| import android.support.test.uiautomator.UiDevice; |
| import android.support.test.uiautomator.UiObject; |
| import android.support.test.uiautomator.UiObject2; |
| import android.support.test.uiautomator.UiObjectNotFoundException; |
| import android.support.test.uiautomator.UiSelector; |
| import android.support.test.uiautomator.Until; |
| import android.util.TypedValue; |
| import android.view.View; |
| |
| import androidx.appcompat.widget.Toolbar; |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.espresso.Espresso; |
| import androidx.test.espresso.action.ViewActions; |
| import androidx.test.espresso.matcher.BoundedMatcher; |
| import androidx.test.espresso.matcher.ViewMatchers; |
| |
| import com.android.documentsui.R; |
| |
| import org.hamcrest.Description; |
| import org.hamcrest.Matcher; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| |
| /** |
| * A test helper class that provides support for controlling DocumentsUI activities |
| * programmatically, and making assertions against the state of the UI. |
| * <p> |
| * Support for working directly with Roots and Directory view can be found in the respective bots. |
| */ |
| public class UiBot extends Bots.BaseBot { |
| |
| public static String targetPackageName; |
| |
| @SuppressWarnings("unchecked") |
| private static final Matcher<View> TOOLBAR = allOf( |
| isAssignableFrom(Toolbar.class), |
| withId(R.id.toolbar)); |
| |
| @SuppressWarnings("unchecked") |
| private static final Matcher<View> ACTIONBAR = allOf( |
| withClassName(endsWith("ActionBarContextView"))); |
| |
| @SuppressWarnings("unchecked") |
| private static final Matcher<View> TEXT_ENTRY = allOf( |
| withClassName(endsWith("EditText"))); |
| |
| @SuppressWarnings("unchecked") |
| private static final Matcher<View> TOOLBAR_OVERFLOW = allOf( |
| withClassName(endsWith("OverflowMenuButton")), |
| ViewMatchers.isDescendantOfA(TOOLBAR)); |
| |
| @SuppressWarnings("unchecked") |
| private static final Matcher<View> ACTIONBAR_OVERFLOW = allOf( |
| withClassName(endsWith("OverflowMenuButton")), |
| ViewMatchers.isDescendantOfA(ACTIONBAR)); |
| |
| public UiBot(UiDevice device, Context context, int timeout) { |
| super(device, context, timeout); |
| targetPackageName = |
| InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageName(); |
| } |
| |
| public void assertWindowTitle(String expected) { |
| onView(TOOLBAR) |
| .check(matches(withToolbarTitle(is(expected)))); |
| } |
| |
| public void assertSearchBarShow() { |
| UiSelector selector = new UiSelector().text(mContext.getString(R.string.search_bar_hint)); |
| UiObject searchHint = mDevice.findObject(selector); |
| assertTrue(searchHint.exists()); |
| } |
| |
| public void assertMenuEnabled(int id, boolean enabled) { |
| UiObject2 menu = findMenuWithName(mContext.getString(id)); |
| assertNotNull(menu); |
| assertEquals(enabled, menu.isEnabled()); |
| } |
| |
| public void assertInActionMode(boolean inActionMode) { |
| assertEquals(inActionMode, waitForActionModeBarToAppear()); |
| } |
| |
| public UiObject openOverflowMenu() throws UiObjectNotFoundException { |
| UiObject obj = findMenuMoreOptions(); |
| obj.click(); |
| mDevice.waitForIdle(mTimeout); |
| return obj; |
| } |
| |
| public void setDialogText(String text) throws UiObjectNotFoundException { |
| onView(TEXT_ENTRY) |
| .perform(ViewActions.replaceText(text)); |
| } |
| |
| public void assertDialogText(String expected) throws UiObjectNotFoundException { |
| onView(TEXT_ENTRY) |
| .check(matches(withText(is(expected)))); |
| } |
| |
| public boolean inFixedLayout() { |
| TypedValue val = new TypedValue(); |
| // We alias files_activity to either fixed or drawer layouts based |
| // on screen dimensions. In order to determine which layout |
| // has been selected, we check the resolved value. |
| mContext.getResources().getValue(R.layout.files_activity, val, true); |
| return val.resourceId == R.layout.fixed_layout; |
| } |
| |
| public boolean inDrawerLayout() { |
| return !inFixedLayout(); |
| } |
| |
| public void switchToListMode() { |
| final UiObject2 listMode = menuListMode(); |
| if (listMode != null) { |
| listMode.click(); |
| } |
| } |
| |
| public void clickActionItem(String label) throws UiObjectNotFoundException { |
| if (!waitForActionModeBarToAppear()) { |
| throw new UiObjectNotFoundException("ActionMode bar not found"); |
| } |
| clickActionbarOverflowItem(label); |
| mDevice.waitForIdle(); |
| } |
| |
| public void switchToGridMode() { |
| final UiObject2 gridMode = menuGridMode(); |
| if (gridMode != null) { |
| gridMode.click(); |
| } |
| } |
| |
| UiObject2 menuGridMode() { |
| // Note that we're using By.desc rather than By.res, because of b/25285770 |
| return find(By.desc("Grid view")); |
| } |
| |
| UiObject2 menuListMode() { |
| // Note that we're using By.desc rather than By.res, because of b/25285770 |
| return find(By.desc("List view")); |
| } |
| |
| public void clickToolbarItem(int id) { |
| onView(withId(id)).perform(click()); |
| } |
| |
| public void clickNewFolder() { |
| onView(ACTIONBAR_OVERFLOW).perform(click()); |
| |
| // Click the item by label, since Espresso doesn't support lookup by id on overflow. |
| onView(withText("New folder")).perform(click()); |
| } |
| |
| public void clickActionbarOverflowItem(String label) { |
| onView(ACTIONBAR_OVERFLOW).perform(click()); |
| // Click the item by label, since Espresso doesn't support lookup by id on overflow. |
| onView(withText(label)).perform(click()); |
| } |
| |
| public void clickToolbarOverflowItem(String label) { |
| onView(TOOLBAR_OVERFLOW).perform(click()); |
| // Click the item by label, since Espresso doesn't support lookup by id on overflow. |
| onView(withText(label)).perform(click()); |
| } |
| |
| public boolean waitForActionModeBarToAppear() { |
| UiObject2 bar = |
| mDevice.wait(Until.findObject( |
| By.res(mTargetPackage + ":id/action_mode_bar")), mTimeout); |
| return (bar != null); |
| } |
| |
| public void clickRename() throws UiObjectNotFoundException { |
| if (!waitForActionModeBarToAppear()) { |
| throw new UiObjectNotFoundException("ActionMode bar not found"); |
| } |
| clickActionbarOverflowItem(mContext.getString(R.string.menu_rename)); |
| mDevice.waitForIdle(); |
| } |
| |
| public void clickDelete() throws UiObjectNotFoundException { |
| if (!waitForActionModeBarToAppear()) { |
| throw new UiObjectNotFoundException("ActionMode bar not found"); |
| } |
| clickToolbarItem(R.id.action_menu_delete); |
| mDevice.waitForIdle(); |
| } |
| |
| public UiObject findDownloadRetryDialog() { |
| UiSelector selector = new UiSelector().text("Couldn't download"); |
| UiObject title = mDevice.findObject(selector); |
| title.waitForExists(mTimeout); |
| return title; |
| } |
| |
| public UiObject findFileRenameDialog() { |
| UiSelector selector = new UiSelector().text("Rename"); |
| UiObject title = mDevice.findObject(selector); |
| title.waitForExists(mTimeout); |
| return title; |
| } |
| |
| public UiObject findRenameErrorMessage() { |
| UiSelector selector = new UiSelector().text(mContext.getString(R.string.name_conflict)); |
| UiObject title = mDevice.findObject(selector); |
| title.waitForExists(mTimeout); |
| return title; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void assertDialogOkButtonFocused() { |
| onView(withId(android.R.id.button1)).check(matches(hasFocus())); |
| } |
| |
| public void clickDialogOkButton() { |
| // Espresso has flaky results when keyboard shows up, so hiding it for now |
| // before trying to click on any dialog button |
| Espresso.closeSoftKeyboard(); |
| onView(withId(android.R.id.button1)).perform(click()); |
| } |
| |
| public void clickDialogCancelButton() throws UiObjectNotFoundException { |
| // Espresso has flaky results when keyboard shows up, so hiding it for now |
| // before trying to click on any dialog button |
| Espresso.closeSoftKeyboard(); |
| onView(withId(android.R.id.button2)).perform(click()); |
| } |
| |
| public UiObject findMenuLabelWithName(String label) { |
| UiSelector selector = new UiSelector().text(label); |
| return mDevice.findObject(selector); |
| } |
| |
| UiObject2 findMenuWithName(String label) { |
| List<UiObject2> menuItems = mDevice.findObjects(By.clazz("android.widget.LinearLayout")); |
| Iterator<UiObject2> it = menuItems.iterator(); |
| |
| UiObject2 menuItem = null; |
| while (it.hasNext()) { |
| menuItem = it.next(); |
| UiObject2 text = menuItem.findObject(By.text(label)); |
| if (text != null && menuItem.isClickable()) { |
| break; |
| } |
| } |
| return menuItem; |
| } |
| |
| boolean hasMenuWithName(String label) { |
| return findMenuWithName(label) != null; |
| } |
| |
| UiObject findMenuMoreOptions() { |
| UiSelector selector = new UiSelector().className("android.widget.ImageView") |
| .descriptionContains("More options"); |
| // TODO: use the system string ? android.R.string.action_menu_overflow_description |
| return mDevice.findObject(selector); |
| } |
| |
| private static Matcher<Object> withToolbarTitle( |
| final Matcher<CharSequence> textMatcher) { |
| return new BoundedMatcher<Object, Toolbar>(Toolbar.class) { |
| @Override |
| public boolean matchesSafely(Toolbar toolbar) { |
| return textMatcher.matches(toolbar.getTitle()); |
| } |
| |
| @Override |
| public void describeTo(Description description) { |
| description.appendText("with toolbar title: "); |
| textMatcher.describeTo(description); |
| } |
| }; |
| } |
| } |