From 2e8c262068ccf34de556967679083d72626c7876 Mon Sep 17 00:00:00 2001 From: Jinhui Wang Date: Mon, 13 Jun 2016 13:51:11 -0700 Subject: Import UiAutomatorLib This lib contains commonly used UiAutomator related functions, such as ui element finding and ui navigation. Change-Id: I080c48b679873e181d160564e225840e363a0e40 --- .../com/android/afwtest/uiautomator/Constants.java | 225 ++++++++++++++ .../afwtest/uiautomator/pages/LandingPage.java | 65 ++++ .../afwtest/uiautomator/pages/PageSkipper.java | 231 +++++++++++++++ .../android/afwtest/uiautomator/pages/UiPage.java | 152 ++++++++++ .../uiautomator/pages/gms/AddAccountPage.java | 103 +++++++ .../uiautomator/pages/gms/EnterPasswordPage.java | 90 ++++++ .../pages/gms/GoogleAppsDevicePolicyPage.java | 113 +++++++ .../uiautomator/pages/gms/GoogleServicesPage.java | 95 ++++++ .../pages/managedprovisioning/BasePage.java | 117 ++++++++ .../pages/managedprovisioning/ErrorPage.java | 74 +++++ .../managedprovisioning/NfcProvisioningPage.java | 61 ++++ .../managedprovisioning/SetupYourDevicePage.java | 89 ++++++ .../managedprovisioning/SetupYourProfilePage.java | 86 ++++++ .../pages/packageinstaller/DeviceAccessPage.java | 59 ++++ .../uiautomator/pages/testdpc/SetupFinishPage.java | 112 +++++++ .../pages/testdpc/SetupFinishedAddAccountPage.java | 81 +++++ .../pages/testdpc/SetupManagementDoPage.java | 61 ++++ .../pages/testdpc/SetupManagementPoPage.java | 61 ++++ .../uiautomator/provisioning/AutomationDriver.java | 268 +++++++++++++++++ .../afwtest/uiautomator/test/AfwTestUiWatcher.java | 254 ++++++++++++++++ .../uiautomator/utils/BySelectorHelper.java | 71 +++++ .../afwtest/uiautomator/utils/TextField.java | 158 ++++++++++ .../afwtest/uiautomator/utils/WidgetUtils.java | 329 +++++++++++++++++++++ 23 files changed, 2955 insertions(+) create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java create mode 100644 libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java (limited to 'libs/UiAutomatorLib/src/com/android') diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java new file mode 100644 index 0000000..51e3101 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/Constants.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.widget.Button; +import android.widget.EditText; +import android.widget.RadioButton; + +import java.util.regex.Pattern; + +/** + * Constants for this lib and test packages. + */ +public final class Constants { + + /** + * Android package name. + */ + public static final String ANDROID_PKG_NAME = "android"; + + /** + * GMSCore Pakcage name. + */ + public static final String GMS_PKG_NAME = "com.google.android.gms"; + + /** + * Managed provisioning package name. + */ + public static final String MANAGED_PROVISIONING_PKG_NAME = "com.android.managedprovisioning"; + + /** + * Android package installer package name. + */ + public static final String PACKAGE_INSTALLER_PKG_NAME = "com.android.packageinstaller"; + + /** + * TestDpc package name. + */ + public static final String TESTDPC_PKG_NAME = "com.afwsamples.testdpc"; + + /** + * Default file name for provisioning performance stats. + */ + public static final String PROVISIONING_STATS_FILE = "Provisioning-Stats.csv"; + + /** + * Representation name of TestDpc work profile creation time. + */ + public static final String STAT_TESTDPC_WORK_PROFILE_CREATION_TIME = + "testDPC Work Profile creation"; + + /** + * Representation name of Managed Provisioning work profile creation name. + */ + public static final String STAT_MANAGED_PROVISIONING_WORK_PROFILE_CREATION_TIME = + "MP Work Profile creation"; + + /** + * Regular expression string to match resource id of GMSCore NEXT button. + */ + public static final String GMS_NEXT_BTN_RESOURCE_ID_REGEX = + GMS_PKG_NAME + ":id/(auth_setup_wizard_navbar_next|suw_navbar_next)"; + + /** + * {@link BySelector} for {@link Button} in GMS core with description "ACCEPT". + */ + public static final BySelector GMS_ACCEPT_BUTTON_SELECTOR = By.pkg(GMS_PKG_NAME).clazz( + Button.class.getName()).desc("ACCEPT"); + + /** + * {@link BySelector} for {@link Button} in GMS core with description "NEXT". + */ + public static final BySelector GMS_NEXT_BUTTON_SELECTOR = By.pkg(GMS_PKG_NAME).clazz( + Button.class.getName()).desc("NEXT"); + + /** + * Resource Id {@link BySelector} for GmsCore NEXT button. + */ + public static final BySelector GMS_NEXT_BUTTON_RES_SELECTOR = + By.res(Pattern.compile(GMS_NEXT_BTN_RESOURCE_ID_REGEX)) + .enabled(true) + .clickable(true); + + /** + * {@link BySelector} for {@link EditText} in GMS core. + */ + public static final BySelector GMS_TEXT_FIELD_SELECTOR = By.pkg(GMS_PKG_NAME).clazz( + EditText.class.getName()); + + /** + * {@link BySelector} for {@link CheckBox} in GMS core with resource-id "agree_backup". + */ + public static final BySelector GMS_CHECK_BOX_SELECTOR = By.res(GMS_PKG_NAME, "agree_backup"); + + /** + * @{@link BySelector} for downloading MDMs in GmsCore. + */ + public static final BySelector GMS_DOWNLOADING_DIALOG_SELECTOR = + By.text(Pattern.compile("Downloading.*")) + .res(ANDROID_PKG_NAME, "message") + .pkg(GMS_PKG_NAME); + + /** + * @{@link BySelector} unique to the add account page of GmsCore. + */ + public static final BySelector GMS_ADD_ACCOUNT_PAGE_SELECTOR = + By.pkg(GMS_PKG_NAME).desc("Add your account"); + + /** + * {@link BySelector} for {@link Button} in ManagedProvisioning with with + * resource-id suw_navbar_next. + */ + public static final BySelector MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR = By.res( + MANAGED_PROVISIONING_PKG_NAME, "suw_navbar_next"); + + /** + * {@link BySelector} in ManagedProvisioning. + */ + public static final BySelector MANAGED_PROVISIONING_PKG_SELECTOR = By.pkg( + MANAGED_PROVISIONING_PKG_NAME); + + + /** + * {@link BySelector} for "Learn more" button on some Managed Provisioning dialog. + */ + public static final BySelector MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR = + By.res(MANAGED_PROVISIONING_PKG_NAME, "learn_more_link") + .enabled(true) + .clickable(true); + + /** + * {@link BySelector} for {@link Button} in ManagedProvisioning with resource-id + * positive_button. + */ + public static final BySelector MANAGED_PROVISIONING_OK_BUTTON_SELECTOR = By.res( + MANAGED_PROVISIONING_PKG_NAME, "positive_button"); + + /** + * {@link BySelector} for {@link Button} in Package Installer with resource-id ok_button. + */ + public static final BySelector PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR = By.res( + PACKAGE_INSTALLER_PKG_NAME, "ok_button"); + + /** + * {@link BySelector} for {@link RadioButton} with resource-id setup_device_owner on + * TestDpc Setup Management page. + */ + public static final BySelector TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR = + By.res(TESTDPC_PKG_NAME, "setup_device_owner") + .clazz(RadioButton.class.getName()) + .clickable(true) + .checkable(true); + + /** + * {@link BySelector} for {@link RadioButton} with resource-id setup_managed_profile on + * TestDpc Setup Management page. + */ + public static final BySelector TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR = + By.res(TESTDPC_PKG_NAME, "setup_managed_profile") + .clazz(RadioButton.class.getName()) + .clickable(true) + .checkable(true); + + /** + * {@link BySelector} for {@link RadioButton} with resource-id add_account on + * TestDpc Setup finished page. + */ + public static final BySelector TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR = + By.res(TESTDPC_PKG_NAME, "add_account") + .clazz(RadioButton.class.getName()) + .clickable(true) + .checkable(true); + + /** + * {@link BySelector} for the 'NEXT' navigate button on TestDpc pages. + */ + public static final BySelector TESTDPC_NEXT_BUTTON_SELECTOR = + By.res(TESTDPC_PKG_NAME, "suw_navbar_next") + .clazz(Button.class.getName()) + .clickable(true); + + /** + * {@link BySelector} for Managed Provisioning error message. + */ + public static final BySelector MANAGED_PROVISIONING_ERROR_MSG_SELECTOR = + By.pkg(MANAGED_PROVISIONING_PKG_NAME).res(ANDROID_PKG_NAME, "message"); + + /** + * {@link BySelector} for Android platform alert message. + */ + public static final BySelector ANDROID_ALERT_MSG_SELECTOR = By.res(ANDROID_PKG_NAME, "message"); + + /** + * {@link BySelector} for Android platform button. + */ + public static final BySelector ANDROID_DIALOG_BTN_SELECTOR = By.clazz(Button.class.getName()); + + /** + * {@link BySelector} for {@link Button} in TestDpc with resource-id finish_setup. + */ + public static final BySelector TESTDPC_FINISH_SKIP_BUTTON_SELECTOR = + By.res(TESTDPC_PKG_NAME, "btn_add_account_skip"); + + /** + * Private constructor to prevent instantiation. + */ + private Constants() { + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java new file mode 100644 index 0000000..4b99dc6 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/LandingPage.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; + +/** + * A {@link UiPage} that won't navigate any further. + * + *

Such page is usually used as the last page of an automation flow.

+ */ +public final class LandingPage extends UiPage { + + /** + * Unique element that this page should wait for. + */ + private final BySelector mUniqueElementSelector; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + * @param uniqueElementSelector {@link BySelector} of the ui element to wait for + */ + public LandingPage(UiDevice uiDevice, + TestConfig config, + BySelector uniqueElementSelector) { + super(uiDevice, config); + mUniqueElementSelector = uniqueElementSelector; + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return mUniqueElementSelector; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + // Do nothing. + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java new file mode 100644 index 0000000..de15059 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/PageSkipper.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages; + +import static android.support.test.uiautomator.Direction.UP; +import static com.android.afwtest.common.Constants.ACTION_CHECK; +import static com.android.afwtest.common.Constants.ACTION_CLICK; +import static com.android.afwtest.common.Constants.ACTION_SCROLL; +import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick; +import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeFling; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.text.TextUtils; +import android.util.Log; + +import com.android.afwtest.common.Timer; +import com.android.afwtest.common.test.OemWidget; +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.utils.BySelectorHelper; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * A help class to skip pages until the expected page appears. + */ +public final class PageSkipper extends UiPage { + + private static final String TAG = "afwtest.PageSkipper"; + + /** + * Thread sleep time. + */ + private static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(3); + + /** + * Words to match when finding available "NEXT" navigation buttons. + * + * The order of the list is preferring skip over continue or next. + */ + private static final String[] NEXT_WORDS = { + "[sS]kip", "SKIP", + "[fF]inish", "FINISH", + "[dD]one", "DONE", + "[nN]ext", "NEXT", + "[cC]ontinue", "CONTINUE", + "[pP]roceed", "PROCEED", + "[yY][eE][sS]", "[oO][kK]"}; + + /** + * {@link Pattern} to match for NEXT buttons. + */ + private static final Pattern NEXT_BTN_PATTERN = + Pattern.compile(TextUtils.join("|", NEXT_WORDS)); + + /** + * Buttons with text matching {@link #NEXT_BTN_PATTERN}. + */ + private static final BySelector NAVIGATION_BTN_TEXT_SELECTOR = + By.enabled(true) + .checkable(false) + .clickable(true) + .text(NEXT_BTN_PATTERN); + + /** + * Buttons with content description matching {@link #NEXT_BTN_PATTERN}. + */ + private static final BySelector NAVIGATION_BTN_DESC_SELECTOR = + By.enabled(true) + .checkable(false) + .clickable(true) + .desc(NEXT_BTN_PATTERN); + + /** + * List of package names whose buttons should not be clicked. + */ + private Set mPackageNameBlacklist = null; + + /** + * Stop skipping when any object with this {@link BySelector} is found. + */ + private final BySelector mSkipEndSelector; + + /** + * Creates a new page skipper. + * + * @param uiDevice {@link UiDevice} object to access UIAutomator API + * @param skipEndSelector stop skipping pages if this any object matching this + * {@link BySelector} is found + * @param testConfig {@link testConfig} to get test configurations + */ + public PageSkipper(UiDevice uiDevice, BySelector skipEndSelector, TestConfig testConfig) { + super(uiDevice, testConfig); + + mSkipEndSelector = skipEndSelector; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean waitForLoading() throws Exception { + // Do nothing. + return true; + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + // No unique element. + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + long timeout = TimeUnit.MINUTES.toMillis(1) * getTestConfig().getTimeoutSize(); + Timer timer = new Timer(timeout); + List oemWidgets = getTestConfig().getOemWidgets(); + timer.start(); + while (!timer.isTimeUp() && !getUiDevice().hasObject(mSkipEndSelector)) { + + if (!clickAvailableNextButton()) { + iterateOemWidgets(oemWidgets); + } + + // Assert on fatal app crash + assertOnFatalAppCrash(); + + SystemClock.sleep(THREAD_SLEEP_TIME_MS); + } + + if (timer.isTimeUp()) { + Log.e(TAG, "Timeout"); + } + } + + /** + * Finds buttons with text or content description that match pattern {@link NEXT_BTN_PATTERN} + * and click on them. + * + * @return {@code true} if any button found, {@code false} otherwise + */ + private boolean clickAvailableNextButton() { + return clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_TEXT_SELECTOR)) + || clickAnyButton(getUiDevice().findObjects(NAVIGATION_BTN_DESC_SELECTOR)); + } + + /** + * Clicks any button from given list. + * + * @param buttons list of buttons to click + * @return {@code true} if there is any button clicked successfully, {@code false} otherwise + */ + private boolean clickAnyButton(List navigationBtns) { + for (UiObject2 obj : navigationBtns) { + // Skip widget with package name in mPackageNameBlacklist. + String pkgName = WidgetUtils.getPackageName(obj); + if (mPackageNameBlacklist != null && mPackageNameBlacklist.contains(pkgName)) { + continue; + } + + if (safeClick(obj)) { + // Returns immediately after the first 'Next' button is found and clicked; because + // after clicking the current view hierarchy will change; the found navigation + // buttons will be stale. + return true; + } + } + + return false; + } + + /** + * Iterates OEM widgets and act on found ones if necessary. + * + * @param oemWidgets {@link List} of {@link OemWidget} to operate + */ + private void iterateOemWidgets(List oemWidgets) { + + for (OemWidget widget : oemWidgets) { + BySelector selector = BySelectorHelper.getSelector(widget); + + if (getUiDevice().hasObject(selector)) { + Log.d(TAG, "Found OEM widget: " + widget.toString()); + + if (widget.getAction().equals(ACTION_CHECK)) { + safeClick(getUiDevice().findObject(selector)); + } else if (widget.getAction().equals(ACTION_SCROLL)){ + safeFling(getUiDevice().findObject(selector), UP); + } else if (widget.getAction().equals(ACTION_CLICK)) { + safeClick(getUiDevice().findObject(selector)); + } + } + } + } + + /** + * Sets list of package names which should not be skipped. + * + * @param blacklist list of package names + */ + public void setPackageNameBlacklist(Set blacklist) { + mPackageNameBlacklist = blacklist; + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java new file mode 100644 index 0000000..11f4c69 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/UiPage.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages; + +import static com.android.afwtest.uiautomator.Constants.PROVISIONING_STATS_FILE; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import com.android.afwtest.common.Timer; +import com.android.afwtest.common.test.StatsLogger; +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.test.AfwTestUiWatcher; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + + +/** + * Abstract class that represents the screen view of the device. + */ +public abstract class UiPage { + + private static final String TAG = "afwtest.UiPage"; + + /** + * Default waiting for a {@link UiPage}. + */ + private static final long DEFAULT_LOAD_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1); + + /** + * Reference to {@link UiDevice} object. + */ + private final UiDevice mUiDevice; + + /** + * Test configuration. + */ + private final TestConfig mTestConfig; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public UiPage(UiDevice uiDevice, TestConfig config) { + mUiDevice = uiDevice; + mTestConfig = config; + } + + /** + * Gets {@link UiDevice}. + * + * @return {@link UiDevice} object + */ + protected UiDevice getUiDevice() { + return mUiDevice; + } + + /** + * Gets test configuration. + * + * @return {@link TestConfig} object + */ + protected TestConfig getTestConfig() { + return mTestConfig; + } + + /** + * Gets the page loading timeout. + * + * @return page loading timeout in millisecond + */ + protected long getLoadingTimeoutInMs() throws IOException { + return DEFAULT_LOAD_TIMEOUT_MS * mTestConfig.getTimeoutSize(); + } + + /** + * Waits for this page to load. + * + * @return {@code true} if this page is loaded successful, {@code false} otherwise + */ + public boolean waitForLoading() throws Exception { + Timer timer = new Timer(getLoadingTimeoutInMs()); + timer.start(); + do { + if (WidgetUtils.safeWait(getUiDevice(), uniqueElement()) != null) { + return true; + } + assertOnFatalAppCrash(); + } while (!timer.isTimeUp()); + + Log.e(TAG, String.format("UiPage.waitForLoading timeout(%sms).", getLoadingTimeoutInMs())); + + return false; + } + + /** + * Asserts if fatal app crash is found. + */ + protected void assertOnFatalAppCrash() { + String appCrashMsg = AfwTestUiWatcher.getFatalAppCrashMsg(); + if (appCrashMsg != null) { + throw new RuntimeException(appCrashMsg); + } + } + + /** + * Gets provisioning stats logger. + * + * @return {@link StatsLogger} object + */ + protected StatsLogger getProvisioningStatsLogger() throws IOException { + return StatsLogger.getInstance(PROVISIONING_STATS_FILE); + } + + /** + * Gets unique element of this page described by {@link BySelector}. + * + *

The returned element is used to identify this page, such as to determine if this page + * is visible. For example, {@link waitForLoading} function is using the returned element + * of this function to determine if this page has successfully loaded. + *

+ * + * @return unique element of this page described by {@link BySelector} + */ + public abstract BySelector uniqueElement(); + + /** + * Navigates this page properly to next page. + * + * @throws Exception if navigation failed + */ + public abstract void navigate() throws Exception; +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java new file mode 100644 index 0000000..5440cb6 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/AddAccountPage.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms; + +import static com.android.afwtest.uiautomator.Constants.GMS_ADD_ACCOUNT_PAGE_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.TextField; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * GMS Core add account page. + */ +public final class AddAccountPage extends UiPage { + + /** + * Default waiting time for widget, in milliseconds. + */ + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20); + + /** + * Username of the account to add. + */ + private final String mUsername; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public AddAccountPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + mUsername = config.getWorkAccountUsername(); + } + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + * @param customAccountKey property key of the account in {@link TestConfig} + */ + public AddAccountPage(UiDevice uiDevice, TestConfig config, String customAccountKey) { + super(uiDevice, config); + mUsername = config.getProperty(customAccountKey); + } + + /** + * {@inheritDoc} + */ + @Override + protected long getLoadingTimeoutInMs() throws IOException { + // "Checking..." and "Loading" may take longer time than normal pages + return 3 * super.getLoadingTimeoutInMs(); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return GMS_ADD_ACCOUNT_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + TextField.enterTextAndActivateNavigationBtn( + getUiDevice(), + GMS_TEXT_FIELD_SELECTOR, + mUsername, + GMS_NEXT_BUTTON_SELECTOR); + + WidgetUtils.safeWaitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS); + WidgetUtils.waitToBeGone(getUiDevice(), GMS_ADD_ACCOUNT_PAGE_SELECTOR); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java new file mode 100644 index 0000000..0e17929 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/EnterPasswordPage.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms; + +import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME; +import static com.android.afwtest.uiautomator.Constants.GMS_TEXT_FIELD_SELECTOR; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.TextField; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +/** + * GMS Core auth enter password page. + */ +public final class EnterPasswordPage extends UiPage { + + /** + * {@link BySelector} unique to this page. + */ + private static final BySelector ENTER_PASSWORD_PAGE_SELECTOR = + By.pkg(GMS_PKG_NAME).desc("Forgot password?"); + + /** + * Password of the account to add. + */ + private final String mPassword; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public EnterPasswordPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + mPassword = config.getWorkAccountPassword(); + } + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + * @param customPasswordKey property key of the password in {@link TestConfig} + */ + public EnterPasswordPage(UiDevice uiDevice, TestConfig config, String customPasswordKey) { + super(uiDevice, config); + mPassword = config.getProperty(customPasswordKey); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return ENTER_PASSWORD_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + TextField.enterTextAndActivateNavigationBtn(getUiDevice(), + GMS_TEXT_FIELD_SELECTOR, + mPassword, + GMS_NEXT_BUTTON_SELECTOR); + WidgetUtils.waitAndClick(getUiDevice(), GMS_NEXT_BUTTON_SELECTOR); + } +} \ No newline at end of file diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java new file mode 100644 index 0000000..9b14ace --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleAppsDevicePolicyPage.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms; + +import static com.android.afwtest.uiautomator.Constants.GMS_DOWNLOADING_DIALOG_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_RES_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.Timer; +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.concurrent.TimeUnit; + +/** + * GMS Google Apps device policy page. + */ +public final class GoogleAppsDevicePolicyPage extends UiPage { + + /** + * Timeout for starting downloading mdm. + */ + private static final long START_DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5); + + /** + * Timeout for downloading the dmd. + */ + private static final long DOWNLOAD_MDM_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1); + + /** + * Number of attempts to start downloading MDM. + * + *

+ * It's found on some devices it takes some time for the MDM app icon to load; + * and before the icon is loaded, clicking the Next button doesn't work. + * Add retry logic to keep clicking until the downloading starts. + *

+ */ + private static final int CLICK_NEXT_BTN_ATTEMPTS = 5; + + /** + * {@link BySelector} for the MDM app icon. + */ + private static final BySelector MDM_ICON_SELECTOR = + By.res(GMS_PKG_NAME, "auth_device_management_download_app_icon"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public GoogleAppsDevicePolicyPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return MDM_ICON_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + // Clicks the Next button until downloading starts. + int attempts = CLICK_NEXT_BTN_ATTEMPTS; + while (getUiDevice().hasObject(GMS_NEXT_BUTTON_RES_SELECTOR) && attempts > 0) { + + --attempts; + + // Clicks the next button to start downloading mdm + WidgetUtils.safeClick(getUiDevice().findObject(GMS_NEXT_BUTTON_RES_SELECTOR)); + + // Wait for downloading mdm to appear + if (WidgetUtils.safeWait(getUiDevice(), + GMS_DOWNLOADING_DIALOG_SELECTOR, START_DOWNLOAD_MDM_TIMEOUT_MS) != null) { + break; + } + } + + // Waits until the download finishes + Timer timer = new Timer(DOWNLOAD_MDM_TIMEOUT_MS); + timer.start(); + while (!timer.isTimeUp() && getUiDevice().hasObject(GMS_DOWNLOADING_DIALOG_SELECTOR)) { + SystemClock.sleep(TimeUnit.SECONDS.toMillis(2)); + } + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java new file mode 100644 index 0000000..4a832b3 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/gms/GoogleServicesPage.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.gms; + +import static com.android.afwtest.uiautomator.Constants.GMS_CHECK_BOX_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_NEXT_BUTTON_RES_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.GMS_PKG_NAME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiScrollable; +import android.support.test.uiautomator.UiSelector; +import android.widget.ScrollView; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * GMS Core Google Services page. + */ +public final class GoogleServicesPage extends UiPage { + + /** + * Default UI waiting time, in milliseconds. + */ + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20); + + /** + * {@link BySelector} unique to this page. + */ + private static final BySelector GOOGLE_SERVICES_PAGE_SELECTOR = + By.pkg(GMS_PKG_NAME).text("Google services"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public GoogleServicesPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + protected long getLoadingTimeoutInMs() throws IOException { + // Accessing Google server takes long time + return 3 * super.getLoadingTimeoutInMs(); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return GOOGLE_SERVICES_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + + WidgetUtils.waitAndClick(getUiDevice(), GMS_CHECK_BOX_SELECTOR, DEFAULT_TIMEOUT_MS); + + // Scroll down + UiScrollable page = new UiScrollable( + new UiSelector().className(ScrollView.class.getName())); + page.flingToEnd(5); + + WidgetUtils.waitAndClick(getUiDevice(), GMS_NEXT_BUTTON_RES_SELECTOR, DEFAULT_TIMEOUT_MS); + } +} \ No newline at end of file diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java new file mode 100644 index 0000000..971a9f8 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/BasePage.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_ERROR_MSG_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_OK_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_SELECTOR; + +import android.os.SystemClock; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.Timer; +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * Base class for all Managed Provisioning pages to keep common functionality in one place. + */ +public abstract class BasePage extends UiPage { + + /** + * Thread sleep time, in milliseconds. + */ + protected static final long THREAD_SLEEP_TIME_MS = TimeUnit.SECONDS.toMillis(1); + + /** + * Default provisioning timeout in milliseconds. + */ + private static final long DEFAULT_PROVISIONING_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(1); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public BasePage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * Gets provisioning error message. + * + * @return Error message if provisioning is showing error dialog, {@code null} otherwise + */ + protected String getProvisioningError() { + // Get any managed provisioning error dialog + if (getUiDevice().hasObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR)) { + return getUiDevice().findObject(MANAGED_PROVISIONING_ERROR_MSG_SELECTOR).getText(); + } + + return null; + } + + /** + * Gets the provisioning timeout in millisecond. + * + * @return provisioning timeout in millisecond + */ + protected long getProvisioningTimeoutInMs() throws IOException { + return DEFAULT_PROVISIONING_TIMEOUT_MS * getTestConfig().getTimeoutSize(); + } + + /** + * Waits for the provisioning to finish. + * + * @param timeout Waiting timeout in milliseconds + * @return {@code true} if the provisioning is finished, {@code false} otherwise + */ + protected boolean waitForProvisioningToFinish() throws Exception { + Timer timer = new Timer(getProvisioningTimeoutInMs()); + timer.start(); + while (!timer.isTimeUp()) { + if (WidgetUtils.safeWait(getUiDevice(), MANAGED_PROVISIONING_PKG_SELECTOR) == null) { + assertOnFatalAppCrash(); + return true; + } + + // Dismiss the dialog with: "Your administrator has the ability to monitor...". + if (getUiDevice().hasObject(MANAGED_PROVISIONING_LEARN_MORE_LINK_SELECTOR)) { + getUiDevice().findObject(MANAGED_PROVISIONING_OK_BUTTON_SELECTOR).click(); + SystemClock.sleep(THREAD_SLEEP_TIME_MS); + continue; + } + + String errorMsg = getProvisioningError(); + if (errorMsg != null) { + throw new RuntimeException("Provisioning error: " + errorMsg); + } + + assertOnFatalAppCrash(); + + SystemClock.sleep(THREAD_SLEEP_TIME_MS); + } + + return false; + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java new file mode 100644 index 0000000..fa734d1 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/ErrorPage.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * A {@link UiPage} representing error dialog popped up from Managed Provisioning. + */ +public class ErrorPage extends BasePage { + + /** + * Default UI waiting time, in milliseconds. + */ + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10); + + private static final BySelector ERROR_PAGE_SELECTOR = + By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Oops!"); + + private static final BySelector OK_BUTTON_SELECTOR = + By.pkg(MANAGED_PROVISIONING_PKG_NAME) + .text(Pattern.compile("[Oo][Kk]")) + .clickable(true); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public ErrorPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return ERROR_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), OK_BUTTON_SELECTOR, DEFAULT_TIMEOUT_MS); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java new file mode 100644 index 0000000..b2c1e4f --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/NfcProvisioningPage.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_SELECTOR; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; + +/** + * A {@link UiPage} to manage NFC provisioning process. + */ +public final class NfcProvisioningPage extends BasePage { + + private static final String TAG = "afwtest.NfcProvisioningPage"; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public NfcProvisioningPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return MANAGED_PROVISIONING_PKG_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + if (!waitForProvisioningToFinish()) { + throw new RuntimeException("NFC Provisioning timeout"); + } + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java new file mode 100644 index 0000000..5357d2d --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourDevicePage.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.concurrent.TimeUnit; + +/** + * Managed Provisioning setup your device page. + */ +public class SetupYourDevicePage extends BasePage { + + /** + * Default UI waiting time, in milliseconds. + */ + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3); + + /** + * {@link BySelector} unique to this page. + */ + private static final BySelector SET_UP_YOUR_DEVICE_PAGE_SELECTOR = + By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Set up your device"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupYourDevicePage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return SET_UP_YOUR_DEVICE_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR, + DEFAULT_TIMEOUT_MS); + + onProvisioningStarted(); + + // Wait for the provisioning to finish. + if (!waitForProvisioningToFinish()) { + throw new RuntimeException("DO Provisioning timeout"); + } + } + + /** + * Handles provisioning started event. + */ + protected void onProvisioningStarted() throws Exception { + // Default to be TestDpc provisioning + getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java new file mode 100644 index 0000000..2fa5568 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/managedprovisioning/SetupYourProfilePage.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.managedprovisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.concurrent.TimeUnit; + +/** + * Managed Provisioning setup your profile page. + */ +public class SetupYourProfilePage extends BasePage { + + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(20); + + /** + * {@link BySelector} unique to this page. + */ + private static final BySelector SET_UP_YOUR_PROFILE_PAGE_SELECTOR = + By.pkg(MANAGED_PROVISIONING_PKG_NAME).text("Set up your profile"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupYourProfilePage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return SET_UP_YOUR_PROFILE_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), MANAGED_PROVISIONING_SETUP_BUTTON_SELECTOR, + DEFAULT_TIMEOUT_MS); + + onProvisioningStarted(); + + // Wait for the provisioning to finish. + if (!waitForProvisioningToFinish()) { + throw new RuntimeException("PO Provisioning timeout"); + } + } + + /** + * Handles provisioning started event. + */ + protected void onProvisioningStarted() throws Exception { + // Default to be TestDpc provisioning + getProvisioningStatsLogger().startTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java new file mode 100644 index 0000000..3c1f930 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/packageinstaller/DeviceAccessPage.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.packageinstaller; + +import static com.android.afwtest.uiautomator.Constants.PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +/** + * Package Installer device access page. + */ +public final class DeviceAccessPage extends UiPage { + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public DeviceAccessPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), PACKAGE_INSTALLER_INSTALL_BUTTON_SELECTOR); + } +} + diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java new file mode 100644 index 0000000..66c3998 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishPage.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc; + +import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +/** + * Setup finished page, which should be launched after DO or PO provisioning is successful. + */ +public final class SetupFinishPage extends UiPage { + + /** + * Flag indicating if account was migrated. + */ + private final boolean mIsAccountMigrated; + + /** + * {@link BySelector} for TestDpc SuW layout title "Finish setup". + */ + private static final BySelector TESTDPC_FINISH_SETUP_PAGE_TITLE_SELECTOR = + By.res(TESTDPC_PKG_NAME, "suw_layout_title") + .text("Finish setup"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + * @param isAccountMigrated whether account was migrated + */ + public SetupFinishPage(UiDevice uiDevice, TestConfig config, boolean isAccountMigrated) { + super(uiDevice, config); + mIsAccountMigrated = isAccountMigrated; + } + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupFinishPage(UiDevice uiDevice, TestConfig config) { + this(uiDevice, config, true); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return TESTDPC_FINISH_SETUP_PAGE_TITLE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + if (mIsAccountMigrated) { + // Find message that account migration was successful. + BySelector succeedResult = + By.pkg(TESTDPC_PKG_NAME).text("Added account that is now managed:"); + + // Find managed work account name to verify required account is migrated. + BySelector managedAccount = + By.res(TESTDPC_PKG_NAME, "managed_account_name") + .text(getTestConfig().getWorkAccountUsername().toLowerCase()); + + if (WidgetUtils.safeWait(getUiDevice(), succeedResult) == null || + WidgetUtils.safeWait(getUiDevice(), managedAccount) == null) { + throw new RuntimeException(String.format( + "Provisioning failed: %s is not setup to be managed by TestDpc!", + getTestConfig().getWorkAccountUsername())); + } + + // Save Time metric + getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME); + getProvisioningStatsLogger().writeStatsToFile(); + + } else { + BySelector succeedResult = By.pkg(TESTDPC_PKG_NAME) + .text("To manage the new managed profile, visit the badged version of this"); + WidgetUtils.safeWait(getUiDevice(), succeedResult); + } + + WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java new file mode 100644 index 0000000..ef30914 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupFinishedAddAccountPage.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc; + +import static com.android.afwtest.uiautomator.Constants.STAT_TESTDPC_WORK_PROFILE_CREATION_TIME; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +import java.util.concurrent.TimeUnit; + +/** + * Setup finished Add Account page in TestDpc. + */ +public final class SetupFinishedAddAccountPage extends UiPage { + + /** + * {@link BySelector} unique to this page. + */ + private static final BySelector TESTDPC_SETUP_FINISHED_PAGE_SELECTOR = + By.res(TESTDPC_PKG_NAME, "suw_layout_title") + .text("Setup finished"); + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupFinishedAddAccountPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return TESTDPC_SETUP_FINISHED_PAGE_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + getProvisioningStatsLogger().stopTime(STAT_TESTDPC_WORK_PROFILE_CREATION_TIME); + getProvisioningStatsLogger().writeStatsToFile(); + + if (WidgetUtils.safeWait(getUiDevice(), TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR, + TimeUnit.MINUTES.toMillis(1)) == null + || WidgetUtils.safeWait(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR, + TimeUnit.MINUTES.toMillis(1)) == null) { + assertOnFatalAppCrash(); + } + WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_ADD_ACCOUNT_RADIO_BUTTON_SELECTOR); + WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java new file mode 100644 index 0000000..64a5b80 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementDoPage.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc; + +import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +/** + * TestDpc Setup Management page during DO provisioning. + */ +public final class SetupManagementDoPage extends UiPage { + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupManagementDoPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), + TESTDPC_SETUP_DEVICE_OWNER_RADIO_BUTTON_SELECTOR); + WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR); + } +} \ No newline at end of file diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java new file mode 100644 index 0000000..04a82e5 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/pages/testdpc/SetupManagementPoPage.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.pages.testdpc; + +import static com.android.afwtest.uiautomator.Constants.TESTDPC_NEXT_BUTTON_SELECTOR; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.utils.WidgetUtils; + +/** + * TestDpc Setup Management page during PO provisioning. + */ +public final class SetupManagementPoPage extends UiPage { + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + * @param config {@link TestConfig} object holding test configurations + */ + public SetupManagementPoPage(UiDevice uiDevice, TestConfig config) { + super(uiDevice, config); + } + + /** + * {@inheritDoc} + */ + @Override + public BySelector uniqueElement() { + return TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR; + } + + /** + * {@inheritDoc} + */ + @Override + public void navigate() throws Exception { + WidgetUtils.waitAndClick(getUiDevice(), + TESTDPC_SETUP_MANAGED_PROFILE_RADIO_BUTTON_SELECTOR); + WidgetUtils.waitAndClick(getUiDevice(), TESTDPC_NEXT_BUTTON_SELECTOR); + } +} \ No newline at end of file diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java new file mode 100644 index 0000000..8a45c4c --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/provisioning/AutomationDriver.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.provisioning; + +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME; +import static com.android.afwtest.uiautomator.Constants.TESTDPC_PKG_NAME; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import com.android.afwtest.common.test.TestConfig; +import com.android.afwtest.uiautomator.pages.LandingPage; +import com.android.afwtest.uiautomator.pages.PageSkipper; +import com.android.afwtest.uiautomator.pages.UiPage; +import com.android.afwtest.uiautomator.pages.gms.AddAccountPage; +import com.android.afwtest.uiautomator.pages.gms.EnterPasswordPage; +import com.android.afwtest.uiautomator.pages.gms.GoogleAppsDevicePolicyPage; +import com.android.afwtest.uiautomator.pages.gms.GoogleServicesPage; +import com.android.afwtest.uiautomator.pages.managedprovisioning.ErrorPage; +import com.android.afwtest.uiautomator.pages.managedprovisioning.NfcProvisioningPage; +import com.android.afwtest.uiautomator.pages.managedprovisioning.SetupYourDevicePage; +import com.android.afwtest.uiautomator.pages.managedprovisioning.SetupYourProfilePage; +import com.android.afwtest.uiautomator.pages.packageinstaller.DeviceAccessPage; +import com.android.afwtest.uiautomator.pages.testdpc.SetupFinishPage; +import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementDoPage; +import com.android.afwtest.uiautomator.pages.testdpc.SetupManagementPoPage; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * A help class that automates provisioning flows. + */ +public class AutomationDriver { + + private static final String TAG = "afwtest.AutomationDriver"; + + /** + * {@link UiDevice} object. + */ + private final UiDevice mUiDevice; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + */ + public AutomationDriver(UiDevice uiDevice) { + mUiDevice = uiDevice; + } + + /** + * Gets {@link UiDevice} instance. + * + * @return {@link UiDevice} instances + */ + protected UiDevice getUiDevice() { + return mUiDevice; + } + + /** + * Runs the NFC provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return {@code true} if provisioning completes, {@code false} otherwise + */ + public boolean runNfcProvisioning(TestConfig testConfig) throws Exception { + return navigate(getNfcProvisioningPages(testConfig)); + } + + /** + * Runs the setup wizard device owner provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return {@code true} if provisioning completes, {@code false} otherwise + */ + public boolean runSuwDoProvisioning(TestConfig testConfig) throws Exception { + return navigate(getSuwDoProvisioningPages(testConfig)); + } + + /** + * Runs the setup wizard profile owner provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return {@code true} if provisioning completes, {@code false} otherwise + */ + public boolean runSuwPoProvisioning(TestConfig testConfig) throws Exception { + return navigate(getSuwPoProvisioningPages(testConfig)); + } + + /** + * Runs the non setup wizard profile owner provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return {@code true} if provisioning completes, {@code false} otherwise + */ + public boolean runNonSuwPoProvisioning(TestConfig testConfig) throws Exception { + return navigate(getNonSuwPoProvisioningPages(testConfig)); + } + + /** + * Runs the non setup wizard profile owner provisioning flow when it is not allowed. + * + * @param testConfig test configurations required for provisioning automation + * @return {@code true} if provisioning completes, {@code false} otherwise + */ + public boolean runNonSuwPoProvisioningDisallowed(TestConfig testConfig) throws Exception { + return navigate(getNonSuwPoProvisioningDisallowedPages(testConfig)); + } + + /** + * Navigates a list of {@link UiPage} one by one. + * + * @param pages list of {@link UiPage} to navigate + * + * @return {@code true} if all pages are navigated successfully, {@code false} otherwise + */ + protected boolean navigate(List pages) throws Exception { + for (UiPage page : pages) { + if (!page.waitForLoading()) { + throw new RuntimeException("Failed to load page: " + page.getClass().getName()); + } + + Log.d(TAG, String.format("Navigating: %s", page.getClass().getName())); + page.navigate(); + Log.d(TAG, String.format("Navigating: %s done", page.getClass().getName())); + } + + return true; + } + + /** + * Gets the list of {@link UiPage} for NFC provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return list of {@link UiPage} representing the setup wizard DO provisioning flow + */ + private List getNfcProvisioningPages(TestConfig testConfig) { + List pages = new LinkedList<>(); + pages.add(new NfcProvisioningPage(mUiDevice, testConfig)); + + // Land on the mdm page. + UiPage mdmPage = + new LandingPage(mUiDevice, testConfig, By.pkg(testConfig.getDeviceAdminPkgName())); + PageSkipper pageSkipper = new PageSkipper(mUiDevice, mdmPage.uniqueElement(), testConfig); + + pages.add(pageSkipper); + pages.add(mdmPage); + + // Setup page skipper. + Set packageNameBlacklist = new HashSet(); + packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME); + packageNameBlacklist.add(testConfig.getDeviceAdminPkgName()); + pageSkipper.setPackageNameBlacklist(packageNameBlacklist); + + return pages; + } + + /** + * Gets the list of {@link UiPage} for setup wizard DO provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return list of {@link UiPage} representing the setup wizard DO provisioning flow + */ + private List getSuwDoProvisioningPages(TestConfig testConfig) { + List pages = new LinkedList<>(); + pages.add(new AddAccountPage(mUiDevice, testConfig)); + pages.add(new EnterPasswordPage(mUiDevice, testConfig)); + pages.add(new GoogleServicesPage(mUiDevice, testConfig)); + pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig)); + pages.add(new DeviceAccessPage(mUiDevice, testConfig)); + pages.add(new SetupManagementDoPage(mUiDevice, testConfig)); + pages.add(new SetupYourDevicePage(mUiDevice, testConfig)); + + UiPage landingPage = new SetupFinishPage(mUiDevice, testConfig); + PageSkipper pageSkipper = + new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig); + pages.add(pageSkipper); + pages.add(landingPage); + + // Setup page skipper. + Set packageNameBlacklist = new HashSet(); + packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME); + packageNameBlacklist.add(TESTDPC_PKG_NAME); + pageSkipper.setPackageNameBlacklist(packageNameBlacklist); + + return pages; + } + + /** + * Gets the list of {@link UiPage} for setup wizard PO provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return list of {@link UiPage} representing the setup wizard PO provisioning flow + */ + private List getSuwPoProvisioningPages(TestConfig testConfig) { + List pages = new LinkedList<>(); + pages.add(new AddAccountPage(mUiDevice, testConfig)); + pages.add(new EnterPasswordPage(mUiDevice, testConfig)); + pages.add(new GoogleServicesPage(mUiDevice, testConfig)); + pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig)); + pages.add(new DeviceAccessPage(mUiDevice, testConfig)); + pages.add(new SetupManagementPoPage(mUiDevice, testConfig)); + pages.add(new SetupYourProfilePage(mUiDevice, testConfig)); + + UiPage landingPage = new SetupFinishPage(mUiDevice, testConfig); + PageSkipper pageSkipper = + new PageSkipper(mUiDevice, landingPage.uniqueElement(), testConfig); + pages.add(pageSkipper); + pages.add(landingPage); + + // Setup page skipper. + Set packageNameBlacklist = new HashSet(); + packageNameBlacklist.add(MANAGED_PROVISIONING_PKG_NAME); + packageNameBlacklist.add(TESTDPC_PKG_NAME); + pageSkipper.setPackageNameBlacklist(packageNameBlacklist); + + return pages; + } + + /** + * Gets the list of {@link UiPage} for non setup wizard PO provisioning flow. + * + * @param testConfig test configurations required for provisioning automation + * @return list of {@link UiPage} representing the non setup wizard PO provisioning flow + */ + private List getNonSuwPoProvisioningPages(TestConfig testConfig) { + List pages = new LinkedList<>(); + pages.add(new AddAccountPage(mUiDevice, testConfig)); + pages.add(new EnterPasswordPage(mUiDevice, testConfig)); + pages.add(new GoogleServicesPage(mUiDevice, testConfig)); + pages.add(new GoogleAppsDevicePolicyPage(mUiDevice, testConfig)); + pages.add(new DeviceAccessPage(mUiDevice, testConfig)); + pages.add(new SetupManagementPoPage(mUiDevice, testConfig)); + pages.add(new SetupYourProfilePage(mUiDevice, testConfig)); + pages.add(new SetupFinishPage(mUiDevice, testConfig)); + return pages; + } + + /** + * Gets the list of {@link UiPage} for testing disallowed provisioning test. + * + * @param testConfig test configurations required for provisioning automation + * @return list of {@link UiPage} for testing disallowed provisioning test. + */ + private List getNonSuwPoProvisioningDisallowedPages(TestConfig testConfig) { + List pages = new LinkedList<>(); + pages.add(new ErrorPage(mUiDevice, testConfig)); + return pages; + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java new file mode 100644 index 0000000..3d9ba2f --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/test/AfwTestUiWatcher.java @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.test; + +import static com.android.afwtest.common.Constants.KEY_MUTE_APP_CRASH_DIALOGS; +import static com.android.afwtest.uiautomator.Constants.ANDROID_PKG_NAME; +import static com.android.afwtest.uiautomator.Constants.MANAGED_PROVISIONING_PKG_NAME; +import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClick; +import static com.android.afwtest.uiautomator.utils.WidgetUtils.safeClickAny; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.UiWatcher; +import android.text.TextUtils; +import android.util.Log; +import android.widget.Button; + +import com.android.afwtest.common.test.TestConfig; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +/** + * {@link UiWatcher} to handle common unexpected scenarios. + * + *

+ * This is a singleton class. There can be only one + *

+ */ +public final class AfwTestUiWatcher implements UiWatcher { + + private static final String TAG = "afwtest.AfwTestUiWatcher"; + + /** + * Unique name of this ui watcher. + */ + private static final String UI_WATCHER_NAME = "afw-test-uiwatcher"; + + /** + * Regex string for app crashed message. + */ + private static final String APP_STOPPED_MSG_REGEX = "Unfortunately,.*has stopped.*"; + + /** + * {@link Pattern} to match app crashed message. + */ + private static final Pattern APP_STOPPED_MSG_PATTERN = Pattern.compile(APP_STOPPED_MSG_REGEX); + + /** + * {@link BySelector} for app crashed message. + */ + private static final BySelector APP_STOPPED_MSG_SELECTOR = + By.res(ANDROID_PKG_NAME, "message") + .text(APP_STOPPED_MSG_PATTERN); + + /** + * {@link BySelector} for "OK" button on app crash dialog. + */ + private static final BySelector APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR = + By.clazz(Button.class.getName()) + .text("OK"); + + /** + * Words to match for "Accept" button's text or description. + */ + private static final String[] ACCEPT_WORDS = { + "[aA]ccept", "ACCEPT", + "[aA]gree", "AGREE", + "[aA]llow", "ALLOW"}; + + /** + * {@link Pattern} to match any "Accept" word in {@link #ACCEPT_WORDS}. + */ + private static final Pattern ACCEPT_BTN_PATTERN = + Pattern.compile(TextUtils.join("|", ACCEPT_WORDS)); + + /** + * Buttons with text matching {@link #ACCEPT_BTN_PATTERN}. + */ + private static final BySelector ACCEPT_BTN_TEXT_SELECTOR = + By.enabled(true) + .checkable(false) + .clickable(true) + .text(ACCEPT_BTN_PATTERN); + + /** + * Buttons with content description matching {@link #ACCEPT_BTN_PATTERN}. + */ + private static final BySelector ACCEPT_BTN_DESC_SELECTOR = + By.enabled(true) + .checkable(false) + .clickable(true) + .desc(ACCEPT_BTN_PATTERN); + + /** + * Non-null string indicates fatal app crashed. + */ + private static String sFatalAppCrashMsg; + + /** + * Assert if any of these app crashes. + */ + private static final List FATAL_APP_CRASHES = + Arrays.asList(MANAGED_PROVISIONING_PKG_NAME, + "Setup Wizard"); + + /** + * List of packages whose crash should be ignored. + */ + private final List mAppCrashWhitelist; + + /** + * {@link UiDevice} object. + */ + private final UiDevice mUiDevice; + + /** + * Constructor. + * + * @param uiDevice {@link UiDevice} object + */ + private AfwTestUiWatcher(UiDevice uiDevice) throws Exception { + mUiDevice = uiDevice; + mAppCrashWhitelist = new ArrayList(); + mAppCrashWhitelist.add("Google Play services"); + mAppCrashWhitelist.addAll(TestConfig.getDefault().getAppCrashWhitelist()); + } + + /** + * Registers this ui watcher. + * + * @param uiDevice {@link UiDevice} object + */ + public static void register(UiDevice uiDevice) throws Exception { + uiDevice.registerWatcher(UI_WATCHER_NAME, new AfwTestUiWatcher(uiDevice)); + } + + /** + * Unregisters this ui watcher. + * + * @param uiDevice {@link UiDevice} object + */ + public static void unregister(UiDevice uiDevice) { + uiDevice.removeWatcher(UI_WATCHER_NAME); + } + + public static String getFatalAppCrashMsg() { + return sFatalAppCrashMsg; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean checkForCondition() { + + // The order of the conditions matters. + try { + return clearAppStoppedDialog() || checkAcceptButtons(); + } catch (Exception e) { + // Don't throw exception as it will crashes the test runner + Log.e(TAG, UI_WATCHER_NAME, e); + return false; + } + } + + /** + * Clears dialogs: "Unfortunately, {app name} has stopped." + * + * @return {@code true} if any dialog is dismissed, {@code false} otherwise + */ + private boolean clearAppStoppedDialog() throws IOException { + + if (mUiDevice.hasObject(APP_STOPPED_MSG_SELECTOR) + && mUiDevice.hasObject(APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR)) { + + UiObject2 msgWidget = mUiDevice.findObject(APP_STOPPED_MSG_SELECTOR); + if (msgWidget != null) { + String msg = msgWidget.getText(); + Log.w(TAG, String.format("Found app crash dialog: %s", msg)); + if (isFatalAppCrash(msg)) { + Log.w(TAG, String.format("Fatal app crash. Test should abort.", msg)); + sFatalAppCrashMsg = msg; + return true; + } + Log.w(TAG, String.format("Auto closing: %s", msg)); + sFatalAppCrashMsg = null; + return safeClick(mUiDevice.findObject(APP_STOPPED_DIALOG_OK_BUTTON_SELECTOR)); + } + } + + return false; + } + + /** + * Checks if app crash is fatal. + * + * @param appCrashMsg App crash message + * @return {@code true} if app crash is fatal, {@code false} otherwise. + */ + private boolean isFatalAppCrash(String appCrashMsg) throws IOException { + for (String app : FATAL_APP_CRASHES) { + if (appCrashMsg.contains(app)) { + return true; + } + } + + // if muting all app crash dialog is enabled, return + if (TestConfig.getDefault().muteAppCrashDialogs()) { + Log.i(TAG, String.format("%s=true", KEY_MUTE_APP_CRASH_DIALOGS)); + return false; + } + + // otherwise, auto close whitelisted app crashes, assert on others. + for (String app : mAppCrashWhitelist) { + if (appCrashMsg.contains(app)) { + Log.w(TAG, String.format("Whitelisted app crash: %s", app)); + return false; + } + } + + return true; + } + + /** + * Clicks any visible accept button. + * + * @return {@code true} if any button found and clicked successfully; + * {@code false} otherwise + */ + private boolean checkAcceptButtons() { + return safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_TEXT_SELECTOR)) || + safeClickAny(mUiDevice.findObjects(ACCEPT_BTN_DESC_SELECTOR)); + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java new file mode 100644 index 0000000..3763bac --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/BySelectorHelper.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.utils; + +import static com.android.afwtest.common.Constants.ACTION_CHECK; +import static com.android.afwtest.common.Constants.ACTION_SCROLL; + +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; + +import com.android.afwtest.common.test.OemWidget; + +/** + * Helper class for {@link BySelector}. + */ +public final class BySelectorHelper { + + /** + * Gets the corresponding {@link BySelector} for {@link OemWidget} + * + * @param widget {@link OemWidget} object + * @return {@link BySelector} for the given {@link OemWidget} + */ + public static BySelector getSelector(OemWidget widget) { + BySelector selector = By.enabled(true); + + if (!widget.getText().isEmpty()) { + selector.text(widget.getText()); + } + + if (!widget.getDescription().isEmpty()) { + selector.desc(widget.getDescription()); + } + + if (!widget.getResourceId().isEmpty()) { + selector.res(widget.getResourceId()); + } + + if (!widget.getPackage().isEmpty()) { + selector.pkg(widget.getPackage()); + } + + if (!widget.getClassName().isEmpty()) { + selector.clazz(widget.getClassName()); + } + + if (widget.getAction().equals(ACTION_SCROLL)) { + selector.scrollable(true); + } else if (widget.getAction().equals(ACTION_CHECK)) { + selector.checkable(true).checked(false); + } else { + selector.clickable(true); + } + + return selector; + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java new file mode 100644 index 0000000..6a8bab6 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/TextField.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.utils; + +import android.os.SystemClock; +import android.support.test.uiautomator.By; +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.util.Log; +import android.view.KeyEvent; + +import java.util.concurrent.TimeUnit; + +/** + * Text field related util functions. + */ +public final class TextField { + + private static final String TAG = "afwtest.TextField"; + + /** + * Default UI waiting time, in milliseconds. + */ + private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5); + + /** + * Keyboard pop up waiting time, in milliseconds. + */ + private static final long KEYBOARD_START_TIME_MS = TimeUnit.SECONDS.toMillis(2); + + /** + * Private constructor to prevent instantiation. + */ + private TextField() { + } + + /** + * Inputs text into a text field and activate the navigation button. + * + *

Normally {@link UiObject2}.setText() will input text into a text field. + * But on some activities, such as Add Google Account page, the navigation button (e.g. NEXT) + * will not be activated until there is some non-space characters entered into the text + * field. Calling {@link UiObject2}.setText() will set the text but not activate the + * navigate button. + * This util function solves this problem by simulating key pressing event to enter '0' + * into the text filed to activate the navigation button before calling {@link UiObject2}. + * setText() to set the text. + *

+ * + * @param uiDevice {@link UiDevice} object + * @param textFieldSelector {@link BySelector} for the text field + * @param text text to set + * @param navigationBtnSelector navigation button to activate + */ + public static void enterTextAndActivateNavigationBtn( + UiDevice uiDevice, + BySelector textFieldSelector, + String text, + BySelector navigationBtnSelector) throws Exception { + + // Try 3 times + int maxAttempts = 3; + while (maxAttempts > 0) { + + Log.i(TAG, String.format("Activating navigation button: %s.", + navigationBtnSelector.toString())); + + try { + activateNavigationBtn(uiDevice, textFieldSelector, navigationBtnSelector); + // Navigation button activated, exit loop + break; + } catch (Exception e) { + + --maxAttempts; + + // Don't throw in the retry loop + Log.e(TAG, String.format("Failed to activate navigation button, attempts left: %d.", + maxAttempts), e); + } + } + + // Throw exception if activation button is not activated. + if (maxAttempts <= 0) { + throw new RuntimeException(String.format("Failed to activate navigation button", + navigationBtnSelector.toString())); + } + + // Hide keyboard. + uiDevice.pressBack(); + + // Set the text now. + UiObject2 textField = WidgetUtils.safeWait(uiDevice, + textFieldSelector, + DEFAULT_TIMEOUT_MS, + 3); + if (textField == null) { + throw new RuntimeException( + "Failed to find text field: " + textFieldSelector.toString()); + } + + + textField.setText(text); + } + + /** + * Activates the navigation button by simulating a key pressing event to enter '0'. + * + * @param uiDevice {@link UiDevice} object + * @param textFieldSelector {@link BySelector} for the text field + * @param navigationBtnSelector navigation button to activate + */ + private static void activateNavigationBtn(UiDevice uiDevice, + BySelector textFieldSelector, + BySelector navigationBtnSelector) throws Exception { + WidgetUtils.waitAndClick(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3); + + // Clicking the text field will bring up the keyboard and change the view hierachy of + // current screen; textField may become stale. Try to find it again before simulating + // the keyboard events. + SystemClock.sleep(KEYBOARD_START_TIME_MS); + if (WidgetUtils.safeWait(uiDevice, textFieldSelector, DEFAULT_TIMEOUT_MS, 3) == null) { + throw new RuntimeException( + "Failed to find text field: " + textFieldSelector.toString()); + } + + // Simulate an event to enter '0' to the text field, this will activate the navigate button. + if (!uiDevice.pressKeyCode(KeyEvent.KEYCODE_0)) { + throw new RuntimeException(String.format("Failed to enter 0 into the text field %s", + textFieldSelector.toString())); + } + + // Wait for the navigation button to be activated. + BySelector newNavigationBtnSelector = By.copy(navigationBtnSelector).enabled(true); + if (WidgetUtils.safeWait(uiDevice, + newNavigationBtnSelector, + DEFAULT_TIMEOUT_MS, + 3) == null) { + throw new RuntimeException(String.format("Failed to activate navigation button: %s", + navigationBtnSelector.toString())); + } + } +} diff --git a/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java new file mode 100644 index 0000000..f46dea4 --- /dev/null +++ b/libs/UiAutomatorLib/src/com/android/afwtest/uiautomator/utils/WidgetUtils.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2016 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.afwtest.uiautomator.utils; + +import android.support.test.uiautomator.BySelector; +import android.support.test.uiautomator.Direction; +import android.support.test.uiautomator.UiDevice; +import android.support.test.uiautomator.UiObject2; +import android.support.test.uiautomator.Until; +import android.util.Log; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Widget utils. + */ +public class WidgetUtils { + + private static final String TAG = "afwtest.WidgetUtils"; + + /** + * Waiting time for each call to UiDevice.wait(). + */ + private static final long DEFAULT_UI_WAIT_TIME_MS = TimeUnit.SECONDS.toMillis(5); + + /** + * Clicks on given {@link UiObject2} without throwing any exception. + * + * @param obj {@link UiObject2} to click + * @return {@code true} if clicked, {@code false} otherwise + */ + public static boolean safeClick(UiObject2 obj) { + String widgetProps = getWidgetPropertiesAsString(obj); + try { + obj.click(); + Log.d(TAG, String.format("Clicked: %s", widgetProps)); + return true; + } catch(Exception e) { + Log.e(TAG, String.format("Failed to click: %s", widgetProps) , e); + return false; + } + } + + + /** + * Tries to click any of the given list of buttons; return if any button + * clicked successfully. + * + * @param btns list of buttons to click + * @return {@code true} if any button clicked successfully; {@code false} otherwise + */ + public static boolean safeClickAny(List btns) { + for (UiObject2 obj : btns) { + if (safeClick(obj)) { + return true; + } + } + + return false; + } + + /** + * Perform fling gesture on given {@link UiObject2} until it cannot scroll any more without + * throwing any exception. + * + * @param obj {@link UiObject2} to scroll + * @param direction The direction in which to fling + * @return {@code true} if fling performed, {@code false} otherwise + */ + public static boolean safeFling(UiObject2 obj, Direction direction) { + String widgetProps = getWidgetPropertiesAsString(obj); + + try { + // Set limit to 100 times. + for (int i = 0; i < 100; ++i) { + if (!obj.fling(direction)) { + break; + } + } + Log.d(TAG, String.format("Scrolled: %s", widgetProps)); + return true; + } catch(Exception e) { + Log.e(TAG, String.format("Failed to scroll: %s", widgetProps), e); + return false; + } + } + + /** + * Waits for a widget without throwing any exception (N attempts). + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @param timeoutMS timeout in milliseconds + * @param attempts number of attempts. + * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise + */ + public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS, + int attempts) { + for (int i = 0; i < attempts; ++i) { + UiObject2 widget = safeWait(uiDevice, selector, timeoutMS); + if (widget != null) { + return widget; + } + } + + return null; + } + + /** + * Waits for a widget without throwing any exception. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @param timeoutMS timeout in milliseconds + * @return {@link UiObject2} if expected widget appears within timeout, {@code null} otherwise + */ + public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector, long timeoutMS) { + try { + return uiDevice.wait(Until.findObject(selector), timeoutMS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait for widget ", e); + } + + return null; + } + + /** + * Waits for a widget without throwing any exception. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @return {@link UiObject2} if expected widget appears within default timeout, + * {@code null} otherwise + */ + public static UiObject2 safeWait(UiDevice uiDevice, BySelector selector) { + return safeWait(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS); + } + + /** + * Waits for a widget and click on it without throwing any exception. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @return {@code true} if click action was performed, {@code false} otherwise. + */ + public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector) { + try { + return waitAndClick(uiDevice, selector); + } catch (Exception e) { + Log.e(TAG, "Failed to wait and click for widget ", e); + } + return false; + } + + /** + * Waits for a widget and click on it. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @return {@code true} if click action was performed, {@code false} otherwise + */ + public static boolean waitAndClick(UiDevice uiDevice, BySelector selector) + throws Exception { + return waitAndClick(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS); + } + + /** + * Waits for a widget and click on it without throwing any exception. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @param timeoutMS timeout in milliseconds + * @return {@code true} if click action was performed, {@code false} otherwise. + */ + public static boolean safeWaitAndClick(UiDevice uiDevice, BySelector selector, + long timeoutMS) { + try { + return waitAndClick(uiDevice, selector, timeoutMS); + } catch (Exception e) { + Log.e(TAG, "Failed to wait and click for widget ", e); + } + return false; + } + + /** + * Waits for a widget and click on it (N attempts). + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @param timeoutMS timeout in milliseconds + * @param attempts number of attempts + * @return {@code true} if click action was performed, {@code false} otherwise + */ + public static boolean waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS, + int attempts) throws Exception { + for (int i = 0; i < attempts; ++i) { + if(waitAndClick(uiDevice, selector, timeoutMS)) { + return true; + } + } + + return false; + } + + /** + * Waits for a widget and click on it. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the widget to wait + * @param timeoutMS timeout in milliseconds + * @return {@code true} if click action was performed, {@code false} otherwise + */ + public static boolean waitAndClick(UiDevice uiDevice, BySelector selector, long timeoutMS) + throws Exception { + UiObject2 object = uiDevice.wait(Until.findObject(selector), timeoutMS); + if (object == null) { + throw new Exception(String.format("UI object not found: %s", selector.toString())); + } + object.click(); + return true; + } + + /** + * Waits for all elements with given {@link BySelector} to be gone. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the UI elements to wait + * @param timeoutMs timeout in mmilliseconds + */ + public static void waitToBeGone(UiDevice uiDevice, BySelector selector, long timeoutMs) + throws Exception { + uiDevice.wait(Until.gone(selector), timeoutMs); + } + + /** + * Waits for all elements with given {@link BySelector} to be gone. + * + * @param uiDevice {@link UiDevice} object + * @param selector {@link BySelector} of the UI elements to wait + */ + public static void waitToBeGone(UiDevice uiDevice, BySelector selector) throws Exception { + waitToBeGone(uiDevice, selector, DEFAULT_UI_WAIT_TIME_MS); + } + + + /** + * Gets properties of a {@link UiObject2} as a String, for debugging purpose. + * + * @param obj {@link UiObject2} to get properties from + * @return properties of given {@link UiObject2} as String + */ + public static String getWidgetPropertiesAsString(UiObject2 widget) { + try { + return String.format("text=[%s],desc=[%s],res=[%s],pkg=[%s],class=[%s]", + widget.getText(), + widget.getContentDescription(), + widget.getResourceName(), + widget.getApplicationPackage(), + widget.getClassName()); + } catch (Exception e) { + Log.e(TAG, "Failed to get properties from a widget", e); + } + + return null; + } + + /** + * Gets the package name of a {@link UiObject2} safely. + * + * @param widget {@link UiObject2} to get property from + * @return package name of given widget or null if there is any error + */ + public static String getPackageName(UiObject2 widget) { + try { + return widget.getApplicationPackage(); + } catch (Exception e) { + Log.e(TAG, "Failed to get package name from a widget", e); + } + + return null; + } + + /** + * Gets the text of a {@link UiObject2} safely. + * + * @param widget {@link UiObject2} to get property from + * @return text of given widget or null if there is any error + */ + public static String getText(UiObject2 widget) { + try { + return widget.getText(); + } catch (Exception e) { + Log.e(TAG, "Failed to get text from a widget", e); + } + + return null; + } + + /** + * Gets the content description of a {@link UiObject2} safely. + * + * @param widget {@link UiObject2} to get property from + * @return content description of given widget or null if there is any error + */ + public static String getContentDescription(UiObject2 widget) { + try { + return widget.getContentDescription(); + } catch (Exception e) { + Log.e(TAG, "Failed to get text from a widget", e); + } + + return null; + } +} -- cgit v1.2.3