All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zebrunner.carina.utils.android.IAndroidUtils Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright 2020-2022 Zebrunner Inc (https://www.zebrunner.com).
 *
 * 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.zebrunner.carina.utils.android;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.remote.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableMap;
import com.zebrunner.carina.utils.android.Permissions.Permission;
import com.zebrunner.carina.utils.android.Permissions.PermissionAction;
import com.zebrunner.carina.utils.android.Permissions.PermissionType;
import com.zebrunner.carina.utils.android.recorder.utils.CmdLine;
import com.zebrunner.carina.utils.common.CommonUtils;
import com.zebrunner.carina.utils.commons.SpecialKeywords;
import com.zebrunner.carina.utils.config.Configuration;
import com.zebrunner.carina.utils.mobile.IMobileUtils;
import com.zebrunner.carina.utils.report.SessionContext;
import com.zebrunner.carina.webdriver.IDriverPool;
import com.zebrunner.carina.webdriver.config.WebDriverConfiguration;
import com.zebrunner.carina.webdriver.decorator.ExtendedWebElement;

import io.appium.java_client.AppiumBy;
import io.appium.java_client.ExecutesMethod;
import io.appium.java_client.android.Activity;
import io.appium.java_client.android.AndroidBatteryInfo;
import io.appium.java_client.android.AuthenticatesByFinger;
import io.appium.java_client.android.CanReplaceElementValue;
import io.appium.java_client.android.GsmCallActions;
import io.appium.java_client.android.GsmSignalStrength;
import io.appium.java_client.android.GsmVoiceState;
import io.appium.java_client.android.HasAndroidClipboard;
import io.appium.java_client.android.HasAndroidDeviceDetails;
import io.appium.java_client.android.HasAndroidSettings;
import io.appium.java_client.android.HasSupportedPerformanceDataType;
import io.appium.java_client.android.NetworkSpeed;
import io.appium.java_client.android.PowerACState;
import io.appium.java_client.android.StartsActivity;
import io.appium.java_client.android.SupportsNetworkStateManagement;
import io.appium.java_client.android.SupportsSpecialEmulatorCommands;
import io.appium.java_client.android.connection.HasNetworkConnection;
import io.appium.java_client.android.geolocation.AndroidGeoLocation;
import io.appium.java_client.android.geolocation.SupportsExtendedGeolocationCommands;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.android.nativekey.KeyEventFlag;
import io.appium.java_client.android.nativekey.PressesKey;
import io.appium.java_client.battery.HasBattery;
import io.appium.java_client.clipboard.ClipboardContentType;

/**
 * Contains utility methods for working with android devices
 */
public interface IAndroidUtils extends IMobileUtils {
    // todo add methods from ListensToLogcatMessages
    // todo add methods from ExecuteCDPCommand
    // TODO: review carefully and remove duplicates and migrate completely to fluent waits
    static final Logger UTILS_LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    static final int SCROLL_MAX_SEARCH_SWIPES = 55;
    static final long SCROLL_TIMEOUT = 300;
    static final AdbExecutor executor = new AdbExecutor();
    static final String LANGUAGE_CHANGE_APP_PATH = "app/ADB_Change_Language.apk";

    /**
     * Send a key-press event to the keyboard
     *
     * @param key keyboard key, see {@link AndroidKey}
     * @throws UnsupportedOperationException if driver does not support this feature
     * @see This method send key without leaving the touch-mode
     */
    default void pressKeyboardKey(AndroidKey key) {
        PressesKey driver = null;
        try {
            driver = (PressesKey) getDriver();
        } catch (ClassCastException e) {
            throw new UnsupportedOperationException("Driver is not support pressKeyboardKey method", e);
        }

        driver.pressKey(new KeyEvent(key)
                .withFlag(KeyEventFlag.SOFT_KEYBOARD)
                .withFlag(KeyEventFlag.KEEP_TOUCH_MODE)
                .withFlag(KeyEventFlag.EDITOR_ACTION));
    }

    /**
     * Send a key-press event to the keyboard
* * @param key keyboard key, see {@link AndroidKey} * @param flags event flags, see {@link KeyEventFlag} * @throws UnsupportedOperationException if driver does not support this feature */ default void pressKeyboardKey(AndroidKey key, KeyEventFlag... flags) { PressesKey driver = null; try { driver = (PressesKey) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support pressKeyboardKey method", e); } KeyEvent keyEvent = new KeyEvent(key); if (flags != null && flags.length > 0) { for (KeyEventFlag keyEventFlag : flags) { keyEvent = keyEvent.withFlag(keyEventFlag); } } driver.pressKey(keyEvent); } /** * Send a key-press events to the keyboard * * @param keys keyboard keys, see {@link AndroidKey} * @throws UnsupportedOperationException if driver does not support this feature */ default void pressKeyboardKeys(List keys) { PressesKey driver; try { driver = (PressesKey) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support pressKeyboardKey(s) method", e); } keys.forEach(key -> driver.pressKey(new KeyEvent(key))); } /** * Send a long press key event to the device * * @param keyEvent The generated native key event */ default void longPressKey(KeyEvent keyEvent) { PressesKey driver; try { driver = (PressesKey) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support longPressKey method", e); } driver.longPressKey(keyEvent); } /** * Send a key-press {@link AndroidKey#BACK} event to the keyboard * * @throws UnsupportedOperationException if driver does not support this feature */ default void pressBack() { PressesKey driver; try { driver = (PressesKey) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support pressBack method", e); } driver.pressKey(new KeyEvent(AndroidKey.BACK)); } /** * Send a key-press {@link AndroidKey#SEARCH} event to the keyboard * * @throws UnsupportedOperationException if driver does not support this feature */ default void pressSearchKey() { PressesKey driver; try { driver = (PressesKey) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support pressSearchKey method", e); } driver.pressKey(new KeyEvent(AndroidKey.SEARCH)); } /** * Press next key */ default void pressNextKey() { // todo investigate to use keyEvent with pressKey instead pressBottomRightKey(); } // Change Device Language section /** * change device language using ADBChangeLanguage application via ADB * *

* Usage:
* - install this app
* - setup adb connection to your device
* (http://developer.android.com/tools/help/adb.html) - Android OS 4.2 onwards * (tip: you can copy the command here and paste it to your command console):
* {@code adb shell pm grant net.sanapeli.adbchangelanguage} * android.permission.CHANGE_CONFIGURATION * *

* English: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -e language en}
* Russian: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -elanguage ru}
* Spanish: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -e language es}
* * * @param language to set, for example {@code es}, {@code en}, etc. * * @see ADBChangeLanguage apk * @return was the language change successful */ default boolean setDeviceLanguage(String language) { int deviceRefreshTimeSec = 20; return setDeviceLanguage(language, deviceRefreshTimeSec); } /** * change device language using ADBChangeLanguage application via ADB * *

* Usage:
* - install this app
* - setup adb connection to your device
* (http://developer.android.com/tools/help/adb.html) - Android OS 4.2 onwards * (tip: you can copy the command here and paste it to your command console):
* {@code adb shell pm grant net.sanapeli.adbchangelanguage android.permission.CHANGE_CONFIGURATION} *

* English: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -e language en}
* Russian: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -elanguage ru}
* Spanish: {@code adb shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -e language es}
* * @param language to set, for example {@code es}, {@code en}, etc. * @param waitTime int wait in seconds before device refresh * * @see ADBChangeLanguage apk * @return was the language change successful */ default boolean setDeviceLanguage(String language, int waitTime) { boolean status = false; UTILS_LOGGER.info("Do not concat language for Android. Keep: {}", language); language = language.replace("_", "-"); UTILS_LOGGER.info("Refactor language to : {}", language); String actualDeviceLanguage = getDeviceLanguage(); if (language.contains(actualDeviceLanguage.toLowerCase()) || actualDeviceLanguage.toLowerCase().contains(language)) { UTILS_LOGGER.info("Device already have expected language: {}", actualDeviceLanguage); return true; } String setLocalizationChangePermissionCmd = "shell pm grant net.sanapeli.adbchangelanguage android.permission.CHANGE_CONFIGURATION"; String setLocalizationCmd = "shell am start -n net.sanapeli.adbchangelanguage/.AdbChangeLanguage -e language " + language; UTILS_LOGGER.info("Try set localization change permission with following cmd: {}", setLocalizationChangePermissionCmd); String expandOutput = executeAdbCommand(setLocalizationChangePermissionCmd); String pathToInstalledAppCmd = "shell pm path net.sanapeli.adbchangelanguage"; String pathToInstalledApp = executeAdbCommand(pathToInstalledAppCmd); if (expandOutput.contains("Unknown package: net.sanapeli.adbchangelanguage") || pathToInstalledApp.isEmpty()) { UTILS_LOGGER.info("Looks like 'ADB Change Language apk' is not installed. Install it and try again."); installApk(LANGUAGE_CHANGE_APP_PATH, true); expandOutput = executeAdbCommand(setLocalizationChangePermissionCmd); } UTILS_LOGGER.info("Output after set localization change permission using 'ADB Change Language apk': {}", expandOutput); UTILS_LOGGER.info("Try set localization to '{}' with following cmd: {}", language, setLocalizationCmd); String changeLocaleOutput = executeAdbCommand(setLocalizationCmd); UTILS_LOGGER.info("Output after set localization to '{}' using 'ADB Change Language apk' : {}", language, changeLocaleOutput); if (waitTime > 0) { UTILS_LOGGER.info("Wait for at least '{}' seconds before device refresh.", waitTime); CommonUtils.pause(waitTime); } actualDeviceLanguage = getDeviceLanguage(); UTILS_LOGGER.info("Actual Device Language: {}", actualDeviceLanguage); if (language.contains(actualDeviceLanguage.toLowerCase()) || actualDeviceLanguage.toLowerCase().contains(language)) { status = true; } else { if (getDeviceLanguage().isEmpty()) { UTILS_LOGGER.info("Adb return empty response without errors."); status = true; } else { String currentAndroidVersion = IDriverPool.getDefaultDevice() .getOsVersion(); UTILS_LOGGER.info("currentAndroidVersion={}", currentAndroidVersion); if (currentAndroidVersion.contains("7.")) { UTILS_LOGGER.info("Adb return language command do not work on some Android 7+ devices." + " Check that there are no error."); status = !getDeviceLanguage().toLowerCase().contains("error"); } } } return status; } /** * Get the current language on the device * and * * @return language, for example {@code fr}, or {@code fr-CA} * @deprecated this method calls adb bypassing the driver, so use {@link #getSystemDeviceLanguage()} instead */ @Deprecated(forRemoval = true, since = "8.x") default String getDeviceLanguage() { // get language only, for example 'fr' String locale = executeAdbCommand("shell getprop persist.sys.language"); if (locale.isEmpty()) { // get locale, for example 'fr-CA' locale = executeAdbCommand("shell getprop persist.sys.locale"); } return locale; } /** * Get the current language on the device * * @return language, for example {@code fr}, or {@code fr-CA} */ default String getSystemDeviceLanguage() { // get language only, for example 'fr' String locale = executeShell("getprop persist.sys.language").trim(); if (locale.isEmpty()) { // get locale, for example 'fr-CA' locale = executeShell("getprop persist.sys.locale"); } // executeShell return value like as 'fr-CA/n', so need to trim return locale.trim(); } // End Language Change section /** * Install android Apk by path to apk file * * @param apkPath path to apk */ default void installApk(final String apkPath) { installApk(apkPath, false); } /** * Install android Apk by path to apk or by name in classpath * * @param apkPath path to apk * @param inClasspath whether to search for apk in classpath */ default void installApk(final String apkPath, boolean inClasspath) { try { Path filePath = Path.of(apkPath); if (!inClasspath) { if (Files.notExists(filePath)) { throw new IOException(String.format("Apk is not exists. Path: '%s'", filePath)); } } else { URL baseResource = ClassLoader.getSystemResource(apkPath); if (baseResource == null) { throw new UncheckedIOException( new FileNotFoundException(String.format("Apk is not exists in the classpath. Path: '%s'", apkPath))); } UTILS_LOGGER.debug("Resource was found: {}", baseResource.getPath()); String fileName = FilenameUtils.getBaseName(baseResource.getPath()) + "." + FilenameUtils.getExtension(baseResource.getPath()); // make temporary copy of resource in artifacts folder filePath = SessionContext.getArtifactsFolder().resolve(fileName); if (Files.notExists(filePath)) { InputStream link = (ClassLoader.getSystemResourceAsStream(apkPath)); if (link == null) { throw new IOException("Cannot get apk using ClassLoader."); } Files.copy(link, filePath); } } executeAdbCommand("install " + filePath); } catch (IOException e) { throw new UncheckedIOException(String.format("Cannot install Apk. Message: '%s'", e.getMessage()), e); } } public enum SelectorType { TEXT, TEXT_CONTAINS, TEXT_STARTS_WITH, ID, DESCRIPTION, DESCRIPTION_CONTAINS, CLASS_NAME } /** * Scrolls into view in specified container by text only and return found element * *

* example of usage: {@code ExtendedWebElement res = AndroidUtils.scroll("News", newsListContainer);} * * @param scrollToElement text to scroll to. Defaults to text Selector Type * @param container the element in which the text will be searched. Defaults to id Selector Type * * @return if element was found, return {@link ExtendedWebElement}, otherwise return {@code null} * **/ default ExtendedWebElement scroll(String scrollToElement, ExtendedWebElement container) { return scroll(scrollToElement, container, SelectorType.ID, SelectorType.TEXT); } /** * Scrolls into view in a container specified by it's instance (index) * * @param scrollToEle has to be id, text, contentDesc or className * @param scrollableContainer ExtendedWebElement type * @param containerSelectorType has to be id, text, textContains, textStartsWith, Description, DescriptionContains or className * @param containerInstance has to an instance number of desired container * @param eleSelectorType has to be id, text, textContains, textStartsWith, Description, DescriptionContains or className * @return ExtendedWebElement *

* example of usage: ExtendedWebElement res = * AndroidUtils.scroll("News", newsListContainer, * AndroidUtils.SelectorType.CLASS_NAME, 1, * AndroidUtils.SelectorType.TEXT); **/ default ExtendedWebElement scroll(String scrollToEle, ExtendedWebElement scrollableContainer, SelectorType containerSelectorType, int containerInstance, SelectorType eleSelectorType) { ExtendedWebElement extendedWebElement = null; long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); // TODO: support multi threaded WebDriver's removing DriverPool usage WebDriver drv = getDriver(); // workaorund for appium issue: https://github.com/appium/appium/issues/10159 if (scrollToEle.contains(",")) { scrollToEle = StringUtils.join(StringUtils.split(scrollToEle, ","), ",", 0, 2); if (eleSelectorType.equals(SelectorType.TEXT)) { eleSelectorType = SelectorType.TEXT_CONTAINS; } } for (int i = 0; i < SCROLL_MAX_SEARCH_SWIPES; i++) { try { By scrollBy = AppiumBy.androidUIAutomator("new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ".instance(" + containerInstance + "))" + ".setMaxSearchSwipes(" + SCROLL_MAX_SEARCH_SWIPES + ")" + ".scrollIntoView(" + getScrollToElementSelector(scrollToEle, eleSelectorType) + ")"); WebElement ele = drv.findElement(scrollBy); if (ele.isDisplayed()) { UTILS_LOGGER.info("Element found!!!"); // initializing with driver context because scrollBy consists from container and element selectors extendedWebElement = new ExtendedWebElement(scrollBy, scrollToEle, drv, drv); break; } } catch (NoSuchElementException noSuchElement) { UTILS_LOGGER.error(String.format("%s %s:%s", SpecialKeywords.NO_SUCH_ELEMENT_ERROR, eleSelectorType, scrollToEle), noSuchElement); } for (int j = 0; j < i; j++) { checkTimeout(startTime); AppiumBy.androidUIAutomator( "new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ".instance(" + containerInstance + ")).scrollForward()"); UTILS_LOGGER.info("Scroller got stuck on a page, scrolling forward to next page of elements.."); } } return extendedWebElement; } /** * Scrolls into view in specified container * * @param scrollToEle has to be id, text, contentDesc or className * @param scrollableContainer ExtendedWebElement type * @param containerSelectorType has to be id, text, textContains, textStartsWith, Description, DescriptionContains or className * @param containerInstance has to an instance number of desired container * @param eleSelectorType has to be id, text, textContains, textStartsWith, Description, DescriptionContains or className * @param eleSelectorInstance has to an instance number of desired container * @return ExtendedWebElement *

* example of usage: ExtendedWebElement res = * AndroidUtils.scroll("News", newsListContainer, * AndroidUtils.SelectorType.CLASS_NAME, 1, * AndroidUtils.SelectorType.TEXT, 2); **/ default ExtendedWebElement scroll(String scrollToEle, ExtendedWebElement scrollableContainer, SelectorType containerSelectorType, int containerInstance, SelectorType eleSelectorType, int eleSelectorInstance) { ExtendedWebElement extendedWebElement = null; long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); // TODO: support multi threaded WebDriver's removing DriverPool usage WebDriver drv = getDriver(); // workaorund for appium issue: https://github.com/appium/appium/issues/10159 if (scrollToEle.contains(",")) { scrollToEle = StringUtils.join(StringUtils.split(scrollToEle, ","), ",", 0, 2); if (eleSelectorType.equals(SelectorType.TEXT)) { eleSelectorType = SelectorType.TEXT_CONTAINS; } } for (int i = 0; i < SCROLL_MAX_SEARCH_SWIPES; i++) { try { By scrollBy = AppiumBy.androidUIAutomator("new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ".instance(" + containerInstance + "))" + ".setMaxSearchSwipes(" + SCROLL_MAX_SEARCH_SWIPES + ")" + ".scrollIntoView(" + getScrollToElementSelector(scrollToEle, eleSelectorType) + ".instance(" + eleSelectorInstance + "))"); WebElement ele = drv.findElement(scrollBy); if (ele.isDisplayed()) { UTILS_LOGGER.info("Element found!!!"); // initializing with driver context because scrollBy consists from container and element selectors extendedWebElement = new ExtendedWebElement(scrollBy, scrollToEle, drv, drv); break; } } catch (NoSuchElementException noSuchElement) { UTILS_LOGGER.error(String.format("%s%s:%s", SpecialKeywords.NO_SUCH_ELEMENT_ERROR, eleSelectorType, scrollToEle), noSuchElement); } for (int j = 0; j < i; j++) { checkTimeout(startTime); AppiumBy.androidUIAutomator( "new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ".instance(" + containerInstance + ")).scrollForward()"); UTILS_LOGGER.info("Scroller got stuck on a page, scrolling forward to next page of elements.."); } } return extendedWebElement; } /** * Scrolls into view in specified container * * @param scrollToEle has to be id, text, contentDesc or className * @param scrollableContainer ExtendedWebElement type * @param containerSelectorType container Selector type: has to be id, text, textContains, textStartsWith, Description, DescriptionContains or * className * @param eleSelectorType scrollToEle Selector type: has to be id, text, textContains, textStartsWith, Description, DescriptionContains or * className * * @return ExtendedWebElement *

* example of usage: {@code ExtendedWebElement res = AndroidUtils.scroll("News", newsListContainer, AndroidUtils.SelectorType.CLASS_NAME, * AndroidUtils.SelectorType.TEXT);} **/ default ExtendedWebElement scroll(String scrollToEle, ExtendedWebElement scrollableContainer, SelectorType containerSelectorType, SelectorType eleSelectorType) { ExtendedWebElement extendedWebElement = null; long startTime = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()); // TODO: support multi threaded WebDriver's removing DriverPool usage WebDriver drv = getDriver(); // workaorund for appium issue: https://github.com/appium/appium/issues/10159 if (scrollToEle.contains(",")) { scrollToEle = StringUtils.join(StringUtils.split(scrollToEle, ","), ",", 0, 2); if (eleSelectorType.equals(SelectorType.TEXT)) { eleSelectorType = SelectorType.TEXT_CONTAINS; } } for (int i = 0; i < SCROLL_MAX_SEARCH_SWIPES; i++) { try { By scrollBy = AppiumBy.androidUIAutomator( "new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ")" + ".setMaxSearchSwipes(" + SCROLL_MAX_SEARCH_SWIPES + ")" + ".scrollIntoView(" + getScrollToElementSelector(scrollToEle, eleSelectorType) + ")"); WebElement ele = drv.findElement(scrollBy); if (ele.isDisplayed()) { UTILS_LOGGER.info("Element found!!!"); // initializing with driver context because scrollBy consists from container and element selectors extendedWebElement = new ExtendedWebElement(scrollBy, scrollToEle, drv, drv); break; } } catch (NoSuchElementException noSuchElement) { UTILS_LOGGER.error(String.format("%s%s:%s", SpecialKeywords.NO_SUCH_ELEMENT_ERROR, eleSelectorType, scrollToEle), noSuchElement); } for (int j = 0; j < i; j++) { checkTimeout(startTime); AppiumBy.androidUIAutomator("new UiScrollable(" + getScrollContainerSelector(scrollableContainer, containerSelectorType) + ").scrollForward()"); UTILS_LOGGER.info("Scroller got stuck on a page, scrolling forward to next page of elements.."); } } return extendedWebElement; } /** * Scrolls into view in specified container * * @param scrollableContainer ExtendedWebElement type * @param containerSelectorType Selector type: has to be id, text, contentDesc or className * @return scrollViewContainerFinder String * **/ default String getScrollContainerSelector(ExtendedWebElement scrollableContainer, SelectorType containerSelectorType) { String scrollableContainerBy; String scrollViewContainerFinder = ""; switch (containerSelectorType) { case TEXT: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.text:", "").trim(); scrollViewContainerFinder = "new UiSelector().text(\"" + scrollableContainerBy + "\")"; break; case TEXT_CONTAINS: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.textContains:", "").trim(); scrollViewContainerFinder = "new UiSelector().textContains(\"" + scrollableContainerBy + "\")"; break; case TEXT_STARTS_WITH: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.textStartsWith:", "") .trim(); scrollViewContainerFinder = "new UiSelector().textStartsWith(\"" + scrollableContainerBy + "\")"; break; case ID: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.id:", "") .trim(); scrollViewContainerFinder = "new UiSelector().resourceId(\"" + scrollableContainerBy + "\")"; break; case DESCRIPTION: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.description:", "") .trim(); scrollViewContainerFinder = "new UiSelector().description(\"" + scrollableContainerBy + "\")"; break; case DESCRIPTION_CONTAINS: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.descriptionContains:", "") .trim(); scrollViewContainerFinder = "new UiSelector().descriptionContains(\"" + scrollableContainerBy + "\")"; break; case CLASS_NAME: scrollableContainerBy = scrollableContainer.getBy() .toString() .replace("By.className:", "") .trim(); scrollViewContainerFinder = "new UiSelector().className(\"" + scrollableContainerBy + "\")"; break; default: UTILS_LOGGER.info("Please provide valid selectorType for element to be found..."); break; } return scrollViewContainerFinder; } /** * Scrolls into view in specified container * * @param scrollToEle String type * @param eleSelectorType Selector type: has to be id, text, contentDesc or className * @return String **/ default String getScrollToElementSelector(String scrollToEle, SelectorType eleSelectorType) { String neededElementFinder = ""; String scrollToEleTrimmed; switch (eleSelectorType) { case TEXT: neededElementFinder = "new UiSelector().text(\"" + scrollToEle + "\")"; break; case TEXT_CONTAINS: neededElementFinder = "new UiSelector().textContains(\"" + scrollToEle + "\")"; break; case TEXT_STARTS_WITH: neededElementFinder = "new UiSelector().textStartsWith(\"" + scrollToEle + "\")"; break; case ID: scrollToEleTrimmed = scrollToEle.replace("By.id:", "").trim(); neededElementFinder = "new UiSelector().resourceId(\"" + scrollToEleTrimmed + "\")"; break; case DESCRIPTION: neededElementFinder = "new UiSelector().description(\"" + scrollToEle + "\")"; break; case DESCRIPTION_CONTAINS: neededElementFinder = "new UiSelector().descriptionContains(\"" + scrollToEle + "\")"; break; case CLASS_NAME: scrollToEleTrimmed = scrollToEle.replace("By.className:", "").trim(); neededElementFinder = "new UiSelector().className(\"" + scrollToEleTrimmed + "\")"; break; default: UTILS_LOGGER.info("Please provide valid selectorType for element to be found..."); break; } return neededElementFinder; } /** * Scroll Timeout check * * @param startTime Long initial time for timeout count down **/ default void checkTimeout(long startTime) { long elapsed = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - startTime; if (elapsed > SCROLL_TIMEOUT) { throw new NoSuchElementException("Scroll timeout has been reached.."); } } /** * Get current pack in focus * * @return String * @deprecated this method calls adb bypassing the driver, so use {@link #getCurrentPackage()} instead */ @Deprecated(since = "8.x", forRemoval = true) default String getCurrentDeviceFocus() { return executeAdbCommand("shell dumpsys window windows | grep -E 'mCurrentFocus|mFocusedApp'"); } /** * Get current device package * * @return current package name, for example {@code com.android.settings} * @throws UnsupportedOperationException if driver does not support this feature */ default String getCurrentPackage() { StartsActivity startsActivity; try { startsActivity = (StartsActivity) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getCurrentPackage method", e); } return startsActivity.getCurrentPackage(); } /** * execute ADB command bypassing the driver * * @param command adb command * @return String command output in one line */ default String executeAdbCommand(String command) { String deviceName = getDevice().getAdbName(); if (!deviceName.isEmpty()) { // add remoteURL/udid reference command = "-s " + deviceName + " " + command; } else { UTILS_LOGGER.warn("nullDevice detected fot current thread!"); } String result = ""; UTILS_LOGGER.info("Command: {}", command); String[] listOfCommands = command.split(" "); String[] execCmd = CmdLine.insertCommandsAfter(executor.getDefaultCmd(), listOfCommands); try { String cmd = CmdLine.arrayToString(execCmd); UTILS_LOGGER.info("Try to execute following cmd: {}", cmd); List execOutput = executor.execute(execCmd); UTILS_LOGGER.info("Output after execution ADB command: {}", execOutput); result = execOutput.toString() .replaceAll("[\\[\\]]", "") .replace(", ", " ") .trim(); UTILS_LOGGER.info("Returning Output: {}", result); } catch (Exception e) { UTILS_LOGGER.error("Error while executing adb command: " + command, e); } return result; } /** * Execute android-specific commands throw driver using adb * * @param command adb-shell command represented as single String where 1st literal is a command itself. * Everything that follow is treated as arguments.
* * IMPORTANT: "adb -s {UDID} shell" - should be omitted in {@code command} param.
* Example: "adb -s {UDID} shell list packages" - list packages
* * IMPORTANT: shell arguments with space symbols are unsupported! Use {@link #executeShell(List)} instead * * @return response (might be empty) * @throws UnsupportedOperationException if driver does not support this feature * * @see Platform-specific extensions */ default String executeShell(String command) { UTILS_LOGGER.info("ADB command to be executed: adb shell {}", command); List literals = Arrays.asList(command.split(" ")); return executeShell(literals); } /** * Execute android-specific commands throw driver using adb * * @param commands list of commands and arguments
* adb-shell command represented as single String where 1st literal is a command itself. * Everything that follow is treated as arguments.
* * IMPORTANT: "adb -s {UDID} shell" - should be omitted in {@code command} param.
* Example: "adb -s {UDID} shell list packages" - list packages
* * @return response (might be empty) * @throws UnsupportedOperationException if driver does not support this feature * * @see Platform-specific extensions */ default String executeShell(List commands) { JavascriptExecutor driver = null; try { driver = (JavascriptExecutor) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver does not support executeShell method", e); } String command = commands.get(0); List args = commands.subList(1, commands.size()); Map preparedCommand = Map.of("command", command, "args", args); String output = driver.executeScript("mobile: shell", preparedCommand) .toString(); if (!StringUtils.isEmpty(output)) { UTILS_LOGGER.debug("ADB command output: {}}", output); } return output; } /** * For internal use only * Start URI that may take users directly to the specific content in the app * * @param url the URL to start, for example {@code theapp://login/} * @param packageName the name of the package to start the URI with, for example {@code com.mycompany} * @return Response * @see * Reliably Opening Deep Links Across Platforms and Devices */ default Response executeDeepLink(String url, String packageName) { WebDriver driver = getDriver(); Map preparedCommand = Map.of("url", url, "package", packageName); return executeMobileScript(driver, "mobile: deepLink", preparedCommand); } /** * For internal use only * * Execute scripts * * @param driver WebDriver instance * @param scriptType name of script type, for example {@code mobile:deepLink} */ default Response executeMobileScript(WebDriver driver, String scriptType, Map arguments) { Map command = ImmutableMap.of( "script", scriptType, "args", arguments); ExecutesMethod executesMethod = null; try { executesMethod = (ExecutesMethod) driver; } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver does not support executeMobileScript method", e); } return executesMethod.execute(DriverCommand.EXECUTE_SCRIPT, command); } /** * Bring up the application switcher dialog */ default void displayRecentApps() { // todo investigate replace by pressKeyboardKey(AndroidKey.APP_SWITCH, null); executeShell("input keyevent KEYCODE_APP_SWITCH"); } /** * Emulate tap at native 'Home' button.
* All applications will be closed to background. */ default void pressHome() { executeShell("input keyevent 3"); } /** * Get GPS service status. * * @return true if GPS enabled */ default boolean isGPSEnabled() { String response = executeShell("settings get secure location_providers_allowed"); // Response reflects which services are used for obtaining location: // "gps" - GPS only (device only); // "gps,network" - GPS + Wi-Fi + Bluetooth or cellular networks (High accuracy mode); //"network" - Using Wi-Fi, Bluetooth or cellular networks (Battery saving mode); return response.contains("gps"); } /** * Enable GPS */ default void enableGPS() { executeShell("settings put secure location_providers_allowed +gps"); } /** * Disable GPS
* Works if ONLY DEVICE (GPS sensor) is user for obtaining location */ default void disableGPS() { executeShell("settings put secure location_providers_allowed -gps"); } /** * Save screenshot to specified folder on device's OS using provided path * * @param filepath path to save screenshot to device's OS, for example {@code /storage/emulated/0/Download/scr.png}. */ default void takeScreenShot(String filepath) { UTILS_LOGGER.info("Screenshot will be saved to: {}", filepath); executeShell(String.format("screencap -p %s", filepath)); } /** * Get app's version for the app that is already installed to devices, based on its package name.
* * @param packageName name of the package * @return appVersion version of app (versionCode from system dump), for example {@code 11200050} */ default String getAppVersion(String packageName) { String output = executeShell("dumpsys package ".concat(packageName)); // we search for "versionCode" parameter in system dump. String versionCode = StringUtils.substringBetween(output, "versionCode=", " "); UTILS_LOGGER.info("Version code for '{}' package name is {}", packageName, versionCode); return versionCode; } /** * Get app's version name for the app that is already installed to devices, based on its package name
* * @param packageName name of the package * @return version of app(versionName from system dump), for example {@code 11.2.0} */ default String getAppVersionName(String packageName) { String command = "dumpsys package ".concat(packageName); String output = this.executeShell(command); // we search for "versionName" parameter in system dump. String versionName = StringUtils.substringBetween(output, "versionName=", "\n"); UTILS_LOGGER.info("Version name for '{}' package name is {}", packageName, versionName); return versionName; } /** * Open android device native settings */ default void openDeviceSettings() { executeShell("am start -a android.settings.SETTINGS"); } /** * Open development settings */ default void openDeveloperOptions() { executeShell("am start -n com.android.settings/.DevelopmentSettings"); } /** * Reset test specific application by package name
*

* App's settings will be reset. User will be logged out. Application will be closed to background. * * @param packageName name of the package */ default void clearAppCache(String packageName) { UTILS_LOGGER.info("Will clear data for the following app: {}", packageName); String response = executeShell(String.format("pm clear %s", packageName)); UTILS_LOGGER.info("Output after resetting custom application by package ({}): {}", packageName, response); if (!response.contains("Success")) { UTILS_LOGGER.warn("App data was not cleared for {} app", packageName); } } /** * Trigger a deeplink (link to specific place within the application) or event open URL in mobile browser
*
*

* NOTE, that to open URL in browser, URL should start with "https://www.{place your link here}"
* NOTE that not all deeplinks require package name * * @param link URL to trigger */ default void triggerDeeplink(String link) { UTILS_LOGGER.info("Following link will be triggered via ADB: {}", link); executeShell(String.format("am start -a android.intent.action.VIEW %s", link)); } /** * Start URI that may take users directly to the specific content in the app * * @param url the URL to start, for example {@code theapp://login/} * @param packageName the name of the package to start the URI with, for example {@code com.mycompany} * @see * Reliably Opening Deep Links Across Platforms and Devices */ default void triggerDeeplink(String url, String packageName) { try { executeDeepLink(url, packageName); } catch (WebDriverException wde) { // TODO: need to pay attention UTILS_LOGGER.warn("org.openqa.selenium.WebDriverException is caught and ignored.", wde); } } /** * To get list of granted/denied/requested permission for specified application *

* if response is not correct, return null * * @param appPackage the application package to get permissions from, for example {@code } * @param type permission type. See {@link PermissionType} * @return ArrayList String * @throws UnsupportedOperationException if driver does not support this feature */ @SuppressWarnings("unchecked") default List getAppPermissions(String appPackage, PermissionType type) { Map preparedCommand = ImmutableMap.of("type", type.getType(), "package", appPackage); return (List) executeMobileScript(getDriver(), "mobile: getPermissions", preparedCommand); } /** * Change package permissions in runtime * * @param packageName String * @param action permission action, see {@link PermissionAction} * @param permissions list of permissions {@link Permission} * @throws UnsupportedOperationException if driver does not support this feature */ default void changePermissions(String packageName, PermissionAction action, Permission... permissions) { List permissionsStr = Arrays.stream(permissions) .map(Permission::getPermission) .collect(Collectors.toList()); Map preparedCommand = ImmutableMap.of( "action", action.getAction(), "appPackage", packageName, "permissions", permissionsStr); executeMobileScript(getDriver(), "mobile: changePermissions", preparedCommand); } /** * Method to enter text to ACTIVATED input field. *

* NOTE: that it might be necessary to escape some special characters. Space-symbol is already escaped. * NOTE2: input field should be cleared previously. * * @param text text to enter in the field */ default void typeWithADB(String text) { UTILS_LOGGER.info("Will enter '{}' to an active input field via ADB.", text); // In this method characters are entered one by one because sometimes some characters might be omitted if to enter whole text at a time. char[] array = text.toCharArray(); for (char sym : array) { String ch = (sym == ' ') ? "%s" : String.valueOf(sym); executeShell(String.format("input text %s", ch)); } } /** * Is airplane mode enabled or not * * @return true if airplane mode is enabled * @throws UnsupportedOperationException if driver does not support this feature */ default boolean isAirplaneModeEnabled() { HasNetworkConnection driver = null; try { driver = (HasNetworkConnection) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support isAirplaneModeEnabled method", e); } boolean enabled = driver.getConnection() .isAirplaneModeEnabled(); UTILS_LOGGER.info("AirplaneMode enabled: {}", enabled); return enabled; } /** * Is Wi-Fi connection enabled or not * * @return true if Wi-Fi connection is enabled * @throws UnsupportedOperationException if driver does not support this feature */ default boolean isWifiEnabled() { HasNetworkConnection driver = null; try { driver = (HasNetworkConnection) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support isWifiEnabled method", e); } boolean enabled = driver.getConnection().isWiFiEnabled(); UTILS_LOGGER.info("Wi-Fi enabled: {}", enabled); return enabled; } /** * Turns on Wi-Fi, if it's off * * @throws UnsupportedOperationException if driver does not support this feature */ default void enableWifi() { boolean enabled = isWifiEnabled(); if (enabled) { UTILS_LOGGER.info("Wifi is already enabled. No actions needed"); return; } SupportsNetworkStateManagement driver = null; try { driver = (SupportsNetworkStateManagement) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support enableWifi method", e); } driver.toggleWifi(); } /** * Turns off Wi-Fi, if it's on * * @throws UnsupportedOperationException if driver does not support this feature */ default void disableWifi() { boolean enabled = isWifiEnabled(); if (!enabled) { UTILS_LOGGER.info("Wifi is already disabled. No actions needed"); return; } SupportsNetworkStateManagement driver = null; try { driver = (SupportsNetworkStateManagement) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support disableWifi method", e); } driver.toggleWifi(); } /** * Method enters an App's menu within device System Settings * * @param appName - Name of the app as it appears in the device's Apps list (Language specific) */ default void openAppMenuFromDeviceSettings(String appName) { executeAdbCommand("shell am start -a android.settings.APPLICATION_SETTINGS"); // initializing appItem with ExtendedWebElement constructor that initialize search context ExtendedWebElement appItem = new ExtendedWebElement(By.xpath(String.format("//*[contains(@text, '%s')]", appName)), "notifications", getDriver(), getDriver()); swipe(appItem); appItem.click(); } /** * Toggles a specified app's ability to recieve Push Notifications on the system level * * @param appName - The app name as it appears within device System Settings * @param setValue - The value you wish to set the toggle to */ default void toggleAppNotificationsFromDeviceSettings(String appName, boolean setValue) { openAppMenuFromDeviceSettings(appName); WebDriver driver = getDriver(); // initializing with driver context ExtendedWebElement element = new ExtendedWebElement(By.xpath("//*[contains(@text, 'Notifications') or contains(@text, 'notifications')]"), "notifications", driver, driver); element.click(); // initializing with driver context element = new ExtendedWebElement(By.xpath("//*[@resource-id='com.android.settings:id/switch_text']/following-sibling::android.widget.Switch"), "toggle", driver, driver); if (Boolean.parseBoolean(element.getAttribute("checked")) != setValue) { element.click(); } } /** * @return - Returns if the device in use has a running LTE connection * @throws UnsupportedOperationException if driver does not support this feature */ default boolean isCarrierConnectionAvailable() { HasNetworkConnection hasNetworkConnection = null; try { hasNetworkConnection = (HasNetworkConnection) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver does not support isCarrierConnectionAvailable method", e); } boolean status = hasNetworkConnection.getConnection() .isDataEnabled(); boolean linkProperties = false; String linkProp = executeAdbCommand("shell dumpsys telephony.registry | grep mPreciseDataConnectionState"); UTILS_LOGGER.info("PROP: {}", linkProp); if (!linkProp.isEmpty()) { linkProperties = !StringUtils.substringBetween(linkProp, "APN: ", " ").equals("null"); } UTILS_LOGGER.info("STATUS ENABLED: {}", status); UTILS_LOGGER.info("CARRIER AVAILABLE: {}", linkProperties); return hasNetworkConnection.getConnection().isDataEnabled() && linkProperties; } /** * Get device model
* * Important: before carina 8.x this method calls adb bypassing the driver, * but now it gets device model using driver * * @return device model, for example {@code G3112} */ default String getDeviceModel() { // executeShell returns model with \n, for example G3112\n, so need to trim return executeShell("getprop ro.product.model") .trim(); } default void openStatusBar() { executeShell("cmd statusbar expand-notifications"); } default void closeStatusBar() { executeShell("cmd statusbar collapse"); } /** * Get device timezone * * @return device timezone, for example {@code Europe/Moscow} */ default String getDeviceTimezone() { // executeShell returns timezone with \n, for example Europe/Moscow\n, so need to trim return executeShell("getprop persist.sys.timezone") .trim(); } /** * Set android device default timezone and language based on config or to GMT and En * without restoring actual focused apk */ default void setDeviceDefaultTimeZoneLanguage() { setDeviceDefaultTimeZoneLanguage(false); } /** * Set default timezone and language based on config or to GMT and En * * @param returnAppFocus - if true store actual Focused apk and activity, than restore after setting Timezone and Language. */ default void setDeviceDefaultTimeZoneLanguage(boolean returnAppFocus) { try { Activity activity = null; String os = IDriverPool.getDefaultDevice().getOs(); if (SpecialKeywords.ANDROID.equalsIgnoreCase(os)) { AndroidService androidService = AndroidService.getInstance(); if (returnAppFocus) { activity = new Activity(getCurrentPackage(), getCurrentActivity()); } DeviceTimeZone.TimeZoneFormat timeZone = Configuration.get(WebDriverConfiguration.Parameter.DEFAULT_DEVICE_TIMEZONE) .map(DeviceTimeZone.TimeZoneFormat::parse).orElse(DeviceTimeZone.TimeZoneFormat.GMT); DeviceTimeZone.TimeFormat timeFormat = Configuration.get(WebDriverConfiguration.Parameter.DEFAULT_DEVICE_TIME_FORMAT) .map(DeviceTimeZone.TimeFormat::parse).orElse(DeviceTimeZone.TimeFormat.FORMAT_12); String deviceLanguage = Configuration.get(WebDriverConfiguration.Parameter.DEFAULT_DEVICE_LANGUAGE).orElse("en_US"); UTILS_LOGGER.info("Set device timezone to {}", timeZone); UTILS_LOGGER.info("Set device time format to {}", timeFormat); UTILS_LOGGER.info("Set device language to {}", deviceLanguage); boolean timeZoneChanged = androidService.setDeviceTimeZone(timeZone.getTimeZone(), timeZone.getSettingsTZ(), timeFormat); boolean languageChanged = setDeviceLanguage(deviceLanguage); UTILS_LOGGER.info("Device TimeZone was changed to timeZone '{}' : {}. Device Language was changed to language '{}': {}", timeZone, timeZoneChanged, deviceLanguage, languageChanged); if (returnAppFocus) { androidService.startActivity(activity); } } else { UTILS_LOGGER.info("Current OS is {}. But we can set default TimeZone and Language only for Android.", os); } } catch (Exception e) { UTILS_LOGGER.error("Error while setting to device default timezone and language!", e); } } /** * Retrieves battery info from the device under test * * @return BatteryInfo instance, containing the battery information * @throws UnsupportedOperationException if driver does not support this feature */ default AndroidBatteryInfo getBatteryInfo() { HasBattery driver = null; try { driver = (HasBattery) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getBatteryInfo method", e); } return driver.getBatteryInfo(); } /** * This method should start arbitrary activity during a test. If the activity belongs to * another application, that application is started and the activity is opened. *

* Usage: *

* *
     * {
     *     @code
     *     Activity activity = new Activity("app package goes here", "app activity goes here");
     *     activity.setWaitAppPackage("app wait package goes here");
     *     activity.setWaitAppActivity("app wait activity goes here");
     *     driver.startActivity(activity);
     * }
     * 
* * @param activity The {@link Activity} object * @throws UnsupportedOperationException if driver does not support this feature */ default void startActivity(Activity activity) { StartsActivity driver = null; try { driver = (StartsActivity) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support startActivity method", e); } driver.startActivity(activity); } /** * Get the current activity being run on the mobile device * * @return a current activity being run on the mobile device * @throws UnsupportedOperationException if driver does not support this feature */ default String getCurrentActivity() { StartsActivity driver = null; try { driver = (StartsActivity) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getCurrentActivity method", e); } return driver.currentActivity(); } /** * Get the current activity being run on the mobile device with package name (apkPackage/apkActivity) * * @return {@code apkPackage/apkActivity} string */ default String getCurrentPackageActivity() { StartsActivity driver = null; try { driver = (StartsActivity) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getCurrentPackageActivity method", e); } return driver.getCurrentPackage() + "/" + driver.currentActivity(); } /** * Retrieve the display density of the Android device * * @return The density value in dpi * @throws UnsupportedOperationException if driver does not support this feature */ default Long getDisplayDensity() { HasAndroidDeviceDetails driver = null; try { driver = (HasAndroidDeviceDetails) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getDisplayDensity method", e); } return driver.getDisplayDensity(); } /** * Retrieve visibility and bounds information of the status and navigation bars * * @return The map where keys are bar types and values are mappings of bar properties * @throws UnsupportedOperationException if driver does not support this feature */ default Map> getSystemBars() { HasAndroidDeviceDetails driver = null; try { driver = (HasAndroidDeviceDetails) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getSystemBars method", e); } return driver.getSystemBars(); } /** * returns the information type of the system state which is supported to read * as like cpu, memory, network traffic, and battery * * @return output - array like below * [cpuinfo, batteryinfo, networkinfo, memoryinfo] * @throws UnsupportedOperationException if driver does not support this feature */ default List getSupportedPerformanceDataTypes() { HasSupportedPerformanceDataType driver = null; try { driver = (HasSupportedPerformanceDataType) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getSupportedPerformanceDataTypes method", e); } return driver.getSupportedPerformanceDataTypes(); } /** * returns the resource usage information of the application. the resource is one of the system state * which means cpu, memory, network traffic, and battery * * @param packageName the package name of the application * @param dataType the type of system state which wants to read. * It should be one of the supported performance data types, * the return value of the function "getSupportedPerformanceDataTypes" * @param dataReadTimeout the number of attempts to read * @return table of the performance data, The first line of the table represents the type of data. * The remaining lines represent the values of the data. * in case of battery info : [[power], [23]] * in case of memory info : * [[totalPrivateDirty, nativePrivateDirty, dalvikPrivateDirty, eglPrivateDirty, glPrivateDirty, * totalPss, nativePss, dalvikPss, eglPss, glPss, nativeHeapAllocatedSize, nativeHeapSize], * [18360, 8296, 6132, null, null, 42588, 8406, 7024, null, null, 26519, 10344]] * in case of network info : * [[bucketStart, activeTime, rxBytes, rxPackets, txBytes, txPackets, operations, bucketDuration,], * [1478091600000, null, 1099075, 610947, 928, 114362, 769, 0, 3600000], * [1478095200000, null, 1306300, 405997, 509, 46359, 370, 0, 3600000]] * in case of network info : * [[st, activeTime, rb, rp, tb, tp, op, bucketDuration], * [1478088000, null, null, 32115296, 34291, 2956805, 25705, 0, 3600], * [1478091600, null, null, 2714683, 11821, 1420564, 12650, 0, 3600], * [1478095200, null, null, 10079213, 19962, 2487705, 20015, 0, 3600], * [1478098800, null, null, 4444433, 10227, 1430356, 10493, 0, 3600]] * in case of cpu info : [[user, kernel], [0.9, 1.3]] * @throws UnsupportedOperationException if driver does not support this feature */ default List> getPerformanceData(String packageName, String dataType, int dataReadTimeout) { HasSupportedPerformanceDataType driver = null; try { driver = (HasSupportedPerformanceDataType) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support getPerformanceData method", e); } return driver.getPerformanceData(packageName, dataType, dataReadTimeout); } /** * Authenticate users by using their finger print scans on supported emulators * * @param fingerPrintId finger prints stored in Android Keystore system (from 1 to 10) * @throws UnsupportedOperationException if driver does not support this feature */ default void authByFingerPrint(int fingerPrintId) { AuthenticatesByFinger driver = null; try { driver = (AuthenticatesByFinger) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support authByFingerPrint method", e); } driver.fingerPrint(fingerPrintId); } /** * Emulate send SMS event on the connected emulator * * @param phoneNumber The phone number of message sender * @param message The message content * @throws UnsupportedOperationException if driver does not support this feature */ default void sendSMS(String phoneNumber, String message) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support sendSMS method", e); } driver.sendSMS(phoneNumber, message); } /** * Emulate GSM call event on the connected emulator * * @param phoneNumber The phone number of the caller * @param gsmCallActions One of available {@link GsmCallActions} values * @throws UnsupportedOperationException if driver does not support this feature */ default void makeGsmCall(String phoneNumber, GsmCallActions gsmCallActions) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support makeGsmCall method", e); } driver.makeGsmCall(phoneNumber, gsmCallActions); } /** * Emulate GSM signal strength change event on the connected emulator * * @param gsmSignalStrength One of available {@link GsmSignalStrength} values * @throws UnsupportedOperationException if driver does not support this feature */ default void setGsmSignalStrength(GsmSignalStrength gsmSignalStrength) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setGsmSignalStrength method", e); } driver.setGsmSignalStrength(gsmSignalStrength); } /** * Emulate GSM voice event on the connected emulator * * @param gsmVoiceState One of available {@link GsmVoiceState} values * @throws UnsupportedOperationException if driver does not support this feature */ default void setGsmVoice(GsmVoiceState gsmVoiceState) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setGsmVoice method", e); } driver.setGsmVoice(gsmVoiceState); } /** * Emulate network speed change event on the connected emulator * * @param networkSpeed One of available {@link NetworkSpeed} values * @throws UnsupportedOperationException if driver does not support this feature */ default void setNetworkSpeed(NetworkSpeed networkSpeed) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setNetworkSpeed method", e); } driver.setNetworkSpeed(networkSpeed); } /** * Emulate power capacity change on the connected emulator * * @param percent Percentage value in range [0, 100] * @throws UnsupportedOperationException if driver does not support this feature */ default void setPowerCapacity(int percent) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setPowerCapacity method", e); } driver.setPowerCapacity(percent); } /** * Emulate power state change on the connected emulator * * @param powerACState One of available {@link PowerACState} values * @throws UnsupportedOperationException if driver does not support this feature */ default void setPowerAC(PowerACState powerACState) { SupportsSpecialEmulatorCommands driver = null; try { driver = (SupportsSpecialEmulatorCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setPowerAC method", e); } driver.setPowerAC(powerACState); } /** * Set the content of device's clipboard * * @param label clipboard data label * @param contentType one of supported content types * @param base64Content base64-encoded content to be set * @throws UnsupportedOperationException if driver does not support this feature */ default void setClipboard(String label, ClipboardContentType contentType, byte[] base64Content) { HasAndroidClipboard driver = null; try { driver = (HasAndroidClipboard) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setClipboard method", e); } driver.setClipboard(label, contentType, base64Content); } /** * Set the clipboard text * * @param label clipboard data label * @param text The actual text to be set * @throws UnsupportedOperationException if driver does not support this feature */ default void setClipboardText(String label, String text) { setClipboard(label, ClipboardContentType.PLAINTEXT, Base64 .getEncoder() .encode(text.getBytes(StandardCharsets.UTF_8))); } /** * Replaces element value with the given one * * @param element The destination element * @param value The value to set * @throws UnsupportedOperationException if driver does not support this feature */ default void replaceElementValue(RemoteWebElement element, String value) { CanReplaceElementValue driver = null; try { driver = (CanReplaceElementValue) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support replaceElementValue method", e); } driver.replaceElementValue(element, value); } /** * Allows to set geo location with extended parameters available for Android platform * * @param location the location object to set, see {@link AndroidGeoLocation} * @throws UnsupportedOperationException if driver does not support this feature */ default void setLocation(AndroidGeoLocation location) { SupportsExtendedGeolocationCommands driver = null; try { driver = (SupportsExtendedGeolocationCommands) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setLocation method", e); } driver.setLocation(location); } /** * Switch to focused app to interact with it (be it a native app or a browser)
* Application will not be restarted
*/ default void switchToApp() { switchToApp(getCurrentPackage(), getCurrentActivity(), false); } /** * Switch to focused app to interact with it (be it a native app or a browser)
* * @param isRerun set true if you want to reopen app */ default void switchToApp(boolean isRerun) { switchToApp(getCurrentPackage(), getCurrentActivity(), isRerun); } /** * Switch to another app to interact with it (be it a native app or a browser)
* If you need more control over the activity settings of the launched application, use {@link #startActivity(Activity)} * * @param packageName name of the package, for example {@code com.solvd.carinademoapplication} * @param activityName name of the activity in app, for example {@code .ActivityTestScreens} * @param isRerun set true if you want to reopen app */ default void switchToApp(String packageName, String activityName, boolean isRerun) { Activity activity = new Activity(packageName, activityName); activity.setAppWaitPackage(packageName); activity.setAppWaitActivity(activityName); activity.setStopApp(isRerun); startActivity(activity); } /** * Set the `ignoreUnimportantViews` setting. *Android-only method*. * Sets whether Android devices should use `setCompressedLayoutHeirarchy()` * which ignores all views which are marked IMPORTANT_FOR_ACCESSIBILITY_NO * or IMPORTANT_FOR_ACCESSIBILITY_AUTO (and have been deemed not important * by the system), in an attempt to make things less confusing or faster. * * @param compress ignores unimportant views if true, doesn't ignore otherwise. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings ignoreUnimportantViews(Boolean compress) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support ignoreUnimportantViews method", e); } return driver.ignoreUnimportantViews(compress); } /** * Invoke {@code setWaitForIdleTimeout} in {@code com.android.uiautomator.core.Configurator}. * * @param timeout a negative value would reset to its default value. Minimum time unit * resolution is one millisecond. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings configuratorSetWaitForIdleTimeout(Duration timeout) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support configuratorSetWaitForIdleTimeout method", e); } return driver.configuratorSetWaitForIdleTimeout(timeout); } /** * Invoke {@code setWaitForSelectorTimeout} in {@code com.android.uiautomator.core.Configurator}. * * @param timeout a negative value would reset to its default value. Minimum time unit * resolution is one millisecond. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings configuratorSetWaitForSelectorTimeout(Duration timeout) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support configuratorSetWaitForSelectorTimeout method", e); } return driver.configuratorSetWaitForSelectorTimeout(timeout); } /** * Invoke {@code setScrollAcknowledgmentTimeout} in {@code com.android.uiautomator.core.Configurator}. * * @param timeout a negative value would reset to its default value. Minimum time unit * resolution is one millisecond * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings configuratorSetScrollAcknowledgmentTimeout(Duration timeout) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support configuratorSetScrollAcknowledgmentTimeout method", e); } return driver.configuratorSetScrollAcknowledgmentTimeout(timeout); } /** * Invoke {@code configuratorSetKeyInjectionDelay} in {@code com.android.uiautomator.core.Configurator}. * * @param delay a negative value would reset to its default value. Minimum time unit * resolution is one millisecond. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings configuratorSetKeyInjectionDelay(Duration delay) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support configuratorSetKeyInjectionDelay method", e); } return driver.configuratorSetKeyInjectionDelay(delay); } /** * Invoke {@code setActionAcknowledgmentTimeout} in {@code com.android.uiautomator.core.Configurator}. * * @param timeout a negative value would reset to its default value. Minimum time unit * resolution is one millisecond * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings configuratorSetActionAcknowledgmentTimeout(Duration timeout) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support configuratorSetActionAcknowledgmentTimeout method", e); } return driver.configuratorSetActionAcknowledgmentTimeout(timeout); } /** * Setting this value to true will enforce source tree dumper * to transliterate all class names used as XML tags to the limited * set of ASCII characters supported by Apache Harmony * lib and used by default in Android to avoid possible * XML parsing exceptions caused by XPath lookup. * The Unicode to ASCII transliteration is based on * JUnidecode library (https://github.com/gcardone/junidecode). * Works for UIAutomator2 only. * * @param enabled either true or false. The default value if false. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings normalizeTagNames(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support normalizeTagNames method", e); } return driver.normalizeTagNames(enabled); } /** * Whether to return compact (standards-compliant) and faster responses in find element/s * (the default setting). If set to false then the response may also contain other * available element attributes. * * @param enabled Either true or false. The default value if true. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings setShouldUseCompactResponses(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setShouldUseCompactResponses method", e); } return driver.setShouldUseCompactResponses(enabled); } /** * Which attributes should be returned if compact responses are disabled. * It works only if shouldUseCompactResponses is false. Defaults to "" (empty string). * * @param attrNames the comma-separated list of fields to return with each element. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings setElementResponseAttributes(String attrNames) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setElementResponseAttributes method", e); } return driver.setElementResponseAttributes(attrNames); } /** * Set whether the source output/xpath search should consider all elements, visible and invisible. * Disabling this setting speeds up source and xml search. Works for UIAutomator2 only. * * @param enabled Either true or false. The default value if false. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings allowInvisibleElements(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support allowInvisibleElements method", e); } return driver.allowInvisibleElements(enabled); } /** * Whether to enable or disable the notification listener. * No toast notifications are going to be added into page source output if * this setting is disabled. * Works for UIAutomator2 only. * * @param enabled either true or false. The default value if true. * @return {@link HasAndroidSettings} instance for chaining */ default HasAndroidSettings enableNotificationListener(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support enableNotificationListener method", e); } return driver.enableNotificationListener(enabled); } /** * Whether to enable or disable shutdown the server through * the broadcast receiver on ACTION_POWER_DISCONNECTED. * * @param enabled either true or false. The default value if true. * @return {@link HasAndroidSettings} instance for chaining */ default HasAndroidSettings shutdownOnPowerDisconnect(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support shutdownOnPowerDisconnect method", e); } return driver.shutdownOnPowerDisconnect(enabled); } /** * Turn on or off the tracking of scroll events as they happen. * If {@code true}, a field {@code lastScrollData} is added to the results of * {@code getSession}, which can then be used to check on scroll progress. * Turning this feature off significantly increases touch action performance. * * @param enabled either true or false. The default value if true. * @return {@link HasAndroidSettings} instance for chaining. */ default HasAndroidSettings setTrackScrollEvents(boolean enabled) { HasAndroidSettings driver = null; try { driver = (HasAndroidSettings) getDriver(); } catch (ClassCastException e) { throw new UnsupportedOperationException("Driver is not support setTrackScrollEvents method", e); } return driver.setTrackScrollEvents(enabled); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy