package com.robotium.solo;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.PointF;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTabHost;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CheckedTextView;
import android.widget.CompoundButton;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.GridView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.SlidingDrawer;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.ToggleButton;
import junit.framework.Assert;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Main class for development of Robotium tests.
* Robotium has full support for Views, WebViews, Activities, Dialogs, Menus and Context Menus.
* <br>
* Robotium can be used in conjunction with Android test classes like
* ActivityInstrumentationTestCase2 and SingleLaunchActivityTestCase.
*
*
*
*
* @author Renas Reda, renas.reda@robotium.com
*/
public class Solo {
protected final Asserter asserter;
protected final ViewFetcher viewFetcher;
protected final Checker checker;
protected final Clicker clicker;
protected final Presser presser;
protected final Searcher searcher;
protected final ActivityUtils activityUtils;
protected final DialogUtils dialogUtils;
protected final TextEnterer textEnterer;
protected final Rotator rotator;
protected final Scroller scroller;
protected final Sleeper sleeper;
protected final Swiper swiper;
protected final Tapper tapper;
protected final Illustrator illustrator;
protected final Waiter waiter;
protected final Setter setter;
protected final Getter getter;
protected final WebUtils webUtils;
protected final Sender sender;
protected final ScreenshotTaker screenshotTaker;
protected final Instrumentation instrumentation;
protected final Context context;
protected Activity currentActivity;
protected int sleepStandard = 1;
protected final Zoomer zoomer;
protected final SystemUtils systemUtils;
protected String webUrl = null;
private final Config config;
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks;
private final Application application;
private final AcrossApplication acrossApplication;
private final UiAutomation uiAutomation;
private ArrayList<String> clickeds = new ArrayList<>();
public final static int LANDSCAPE = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; // 0
public final static int PORTRAIT = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; // 1
public final static int RIGHT = KeyEvent.KEYCODE_DPAD_RIGHT;
public final static int LEFT = KeyEvent.KEYCODE_DPAD_LEFT;
public final static int UP = KeyEvent.KEYCODE_DPAD_UP;
public final static int DOWN = KeyEvent.KEYCODE_DPAD_DOWN;
public final static int ENTER = KeyEvent.KEYCODE_ENTER;
public final static int MENU = KeyEvent.KEYCODE_MENU;
public final static int DELETE = KeyEvent.KEYCODE_DEL;
public final static int HOME = KeyEvent.KEYCODE_HOME;
public final static int CLOSED = 0;
public final static int OPENED = 1;
public final static String LOG_TAG = "Robotium";
private final static String DAEMON = "com.heyniu.monitor";
private int width;
private int height;
/**
* Constructor that takes the Instrumentation object and the start Activity.
*
* @param instrumentation the {@link Instrumentation} instance
* @param activity the start {@link Activity} or {@code null}
* if no Activity is specified
*/
public Solo(Instrumentation instrumentation, Activity activity) {
this(new Config(), instrumentation, activity);
if(config.commandLogging){
Log.d(config.commandLoggingTag, "Solo("+instrumentation+", "+activity+")");
}
}
/**
* Constructor that takes the Instrumentation and Config objects.
*
* @param instrumentation the {@link Instrumentation} instance
* @param config the {@link Config} instance
*/
public Solo(Instrumentation instrumentation, Config config) {
this(config, instrumentation, null);
if(config.commandLogging){
Log.d(config.commandLoggingTag, "Solo("+instrumentation+", "+config+")");
}
}
/**
* Constructor that takes the Instrumentation, Config and Activity objects.
*
* @param instrumentation the {@link Instrumentation} instance
* @param config the {@link Config} instance
* @param activity the start {@link Activity} or {@code null}
* if no Activity is specified
*/
public Solo(Instrumentation instrumentation, Config config, Activity activity) {
this(config, instrumentation, activity);
if(config.commandLogging){
Log.v(config.commandLoggingTag, "Solo("+instrumentation+", "+config+", "+activity+")", context);
}
}
/**
* Private constructor.
*
* @param config the {@link Config} instance. If {@code null} one will be created.
* @param instrumentation the {@link Instrumentation} instance
* @param activity the start {@link Activity} or {@code null}
* if no Activity is specified
*/
private Solo(Config config, Instrumentation instrumentation, Activity activity) {
customInit(config, instrumentation, activity);
if(config.commandLogging){
Log.v(config.commandLoggingTag, "Solo("+config+", "+instrumentation+", "+activity+")", activity);
}
this.config = (config == null) ? new Config(): config;
this.instrumentation = instrumentation;
this.context = activity;
this.sleeper = new Sleeper(config.sleepDuration, config.sleepMiniDuration);
this.sender = new Sender(instrumentation, sleeper);
this.activityUtils = new ActivityUtils(config, instrumentation, activity, sleeper);
this.viewFetcher = new ViewFetcher(instrumentation, sleeper);
this.screenshotTaker = new ScreenshotTaker(config, instrumentation, activityUtils, viewFetcher, sleeper);
this.dialogUtils = new DialogUtils(instrumentation, activityUtils, viewFetcher, sleeper);
this.webUtils = new WebUtils(config, instrumentation,viewFetcher, sleeper);
this.scroller = new Scroller(config, instrumentation, viewFetcher, sleeper);
this.searcher = new Searcher(viewFetcher, webUtils, scroller, sleeper);
this.waiter = new Waiter(instrumentation, activityUtils, viewFetcher, searcher,scroller, sleeper);
this.getter = new Getter(instrumentation, activityUtils, waiter);
this.clicker = new Clicker(config, activityUtils, viewFetcher,sender, instrumentation, sleeper, waiter, webUtils, dialogUtils, activity);
this.setter = new Setter(activityUtils, getter, clicker, waiter);
this.asserter = new Asserter(activityUtils, waiter);
this.checker = new Checker(viewFetcher, waiter);
this.zoomer = new Zoomer(instrumentation);
this.swiper = new Swiper(instrumentation);
this.tapper = new Tapper(instrumentation);
this.illustrator = new Illustrator(instrumentation);
this.rotator = new Rotator(instrumentation);
this.presser = new Presser(viewFetcher, clicker, instrumentation, sleeper, waiter, dialogUtils);
this.textEnterer = new TextEnterer(instrumentation, clicker, dialogUtils);
this.systemUtils = new SystemUtils(instrumentation);
this.application = (Application) instrumentation.getTargetContext().getApplicationContext();
this.uiAutomation = instrumentation.getUiAutomation();
this.acrossApplication = new AcrossApplication(activity, instrumentation, config);
this.activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
Log.d(LOG_TAG, "onActivityCreated: " + activity.getClass().getName());
activityListener(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
};
initialize();
}
private void customInit(Config config, Instrumentation instrumentation, Activity activity) {
checkConfig(config);
PackageSingleton.getInstance().setPkg(config.PACKAGE);
startMonitor(instrumentation);
authorizationMonitor(instrumentation);
new Permission(activity, config.PACKAGE, instrumentation).requestPermissionsForShell();
}
private void checkRunner() {
SharedPreferencesHelper helper = new SharedPreferencesHelper(instrumentation.getTargetContext(),
SharedPreferencesHelper.ARGUMENTS);
String runner = helper.getString(SharedPreferencesHelper.RUNNER);
if (runner != null) config.runner = runner;
Log.d(LOG_TAG, "Runner: " + config.runner);
}
private void checkNative() {
SharedPreferencesHelper helper = new SharedPreferencesHelper(instrumentation.getTargetContext(),
SharedPreferencesHelper.ARGUMENTS);
String useNative = helper.getString(SharedPreferencesHelper.USE_NATIVE);
if (useNative != null && useNative.contains("true")) config.useNative = true;
Log.d(LOG_TAG, "Use Native: " + config.useNative);
}
private void checkReptile() {
SharedPreferencesHelper helper = new SharedPreferencesHelper(instrumentation.getTargetContext(),
SharedPreferencesHelper.ARGUMENTS);
String newReptile = helper.getString(SharedPreferencesHelper.NEW_REPTILE);
if (newReptile != null && newReptile.contains("false")) config.newReptile = false;
if (config.mode == Config.Mode.REPTILE)
Log.d(LOG_TAG, "New Reptile: " + config.newReptile);
}
private void authorizationMonitor(Instrumentation instrumentation) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return;
UiAutomation uiAutomation = instrumentation.getUiAutomation();
if (uiAutomation == null) return;
uiAutomation.executeShellCommand("pm grant " + DAEMON + " " + Permission.WRITE_EXTERNAL_STORAGE);
}
/**
* Quit the Monitor.
*/
private void quitMonitor() {
Log.i(LOG_TAG, "Iteration is complete.");
android.content.Intent intent = new android.content.Intent();
intent.setAction("Auto.Monitor.quit");
context.sendBroadcast(intent);
}
/**
* Start the Monitor.
* @param instrumentation instrumentation
*/
private void startMonitor(Instrumentation instrumentation) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
UiAutomation uiAutomation = instrumentation.getUiAutomation();
if (uiAutomation == null) return;
String command = "am start -n " + DAEMON + "/" + DAEMON + ".MainActivity";
Log.i(LOG_TAG, "adb shell " + command);
uiAutomation.executeShellCommand(command);
}
/**
* Required parameter check.
* @param config config
*/
private void checkConfig(Config config) {
if (config.mode == null) {
throw new RuntimeException("The mode is not configured, please check!");
}
if (config.homeActivity == null || config.homeActivity.isEmpty()) {
throw new RuntimeException("The homeActivity is not configured, please check!");
}
if (config.loginActivity == null || config.loginActivity.isEmpty()) {
throw new RuntimeException("The loginActivity is not configured, please check!");
}
if (config.loginAccount == null || config.loginAccount.isEmpty()) {
throw new RuntimeException("The loginAccount is not configured, please check!");
}
if (config.loginPassword == null || config.loginPassword.isEmpty()) {
throw new RuntimeException("The loginPassword is not configured, please check!");
}
if (config.loginId == null || config.loginId.isEmpty()) {
throw new RuntimeException("The loginId is not configured, please check!");
}
if (config.PACKAGE == null || config.PACKAGE.isEmpty() || !config.PACKAGE.contains(".")) {
throw new RuntimeException("The target package is not configured, please check!");
}
}
/**
* Config class used to set the scroll behaviour, default timeouts, screenshot filetype and screenshot save path.
* <br> <br>
* Example of usage:
* <pre>
* public void setUp() throws Exception {
* Config config = new Config();
* config.screenshotFileType = ScreenshotFileType.PNG;
* config.screenshotSavePath = Environment.getExternalStorageDirectory() + "/Robotium/";
* config.shouldScroll = false;
* solo = new Solo(getInstrumentation(), config);
* getActivity();
* }
* </pre>
*
* @author Renas Reda, renas.reda@robotium.com
*/
public static class Config {
/**
* The timeout length of the get, is, set, assert, enter and click methods. Default length is 10 000 milliseconds.
*/
public int timeout_small = 10000;
/**
* The timeout length of the waitFor methods. Default length is 20 000 milliseconds.
*/
public int timeout_large = 20000;
/**
* The screenshot save path. Default save path is /sdcard/AutoClick/package/Screenshots/.
*/
public static final String screenshotSavePath = Environment.getExternalStorageDirectory() + "/AutoClick/%s/Screenshots/";
/**
* The Robotium save path. Default save path is /sdcard/AutoClick/package.
*/
public static final String PATH = "/AutoClick/%s";
/**
* Iterated activities.
*/
public static final String ACTIVITY = "Activities.txt";
/**
* Custom Intent record the activity parameters.
*/
public static final String PARAMS = "ActivityParams.txt";
/**
* Starts the activity parameter json file.
*/
public static final String JSON = "Params.json";
/**
* The log is save in this file.
*/
public static final String LOG = "Log.log";
/**
* The screenshot file type, JPEG or PNG. Use ScreenshotFileType.JPEG or ScreenshotFileType.PNG. Default file type is JPEG.
*/
public ScreenshotFileType screenshotFileType = ScreenshotFileType.JPEG;
/**
* Set to true if the get, is, set, enter, type and click methods should scroll. Default value is true.
*/
public boolean shouldScroll = true;
/**
* Set to true if JavaScript should be used to click WebElements. Default value is false.
*/
public boolean useJavaScriptToClickWebElements = true;
/**
* The screenshot file type, JPEG or PNG.
*
* @author Renas Reda, renas.reda@robotium.com
*
*/
public enum ScreenshotFileType {
JPEG, PNG
}
/**
* Set to true if Activity tracking should be enabled. Default value is true.
*/
public boolean trackActivities = true;
/**
* Set the web frame to be used by Robotium. Default value is document.
*/
public String webFrame = "document";
/**
* Set to true if logging should be enabled. Default value is false.
*/
public boolean commandLogging = true;
/**
* The command logging tag. Default value is "Robotium".
*/
public String commandLoggingTag = "Robotium";
/**
* The sleep duration for the Sleeper sleep method. Default length is 500 milliseconds.
*/
public int sleepDuration = 500;
/**
* The sleep duration for the Sleeper sleepMini method. Default length is 300 milliseconds.
*/
public int sleepMiniDuration = 300;
/**
* If true, every time you start a activity will be screenshot.
*/
public boolean activityScreenShots = true;
/**
* If true, every steps you start a activity will be screenshot.
*/
public boolean iterationScreenShots = true;
/**
* Iteration mode.
* FAST >> Fast mode. Only start activity, quickly check the crash.
* NORMAL >> Normal mode. Start activity and iteration it, check for crash caused by click.
* REPTILE >> Reptile mode. Reptile new activity and click, if have new activity.
* RECORD >> Record mode. Record the required parameters for every activity.
*/
public enum Mode{
FAST, NORMAL, REPTILE, RECORD
}
public Mode mode;
/**
* Application home page.
*/
public String homeActivity;
/**
* Application login activity.
*/
public String loginActivity;
/**
* Login activity log in account.
*/
public String loginAccount;
/**
* Login activity log in password.
*/
public String loginPassword;
/**
* Login activity log in button.
*/
public String loginId;
/**
* Target application, the test program will auto iteration it.
*/
public String PACKAGE;
/**
* The activity within the array will not be iterated.
*/
public String[] ignoreActivities;
/**
* The view within the array will not be iterated.
*/
public String[] ignoreViews;
/**
* If true, keep activities ScreenShots.
*/
public boolean keepActivitiesScreenShots = true;
/**
* If true, Clean up the data at reptile mode.
* Initialize the data.
*/
public boolean newReptile = true;
/**
* If true, use the handleNative method iteration.
*/
public boolean useNative = false;
/**
* Android test runner.
*/
public String runner;
}
/**
* Constructor that takes the instrumentation object.
*
* @param instrumentation the {@link Instrumentation} instance
*/
public Solo(Instrumentation instrumentation) {
this(new Config(), instrumentation, null);
if(config.commandLogging){
Log.d(config.commandLoggingTag, "Solo("+instrumentation+")");
}
}
/**
* Start listening activities.
* @param activity activity
*/
private void activityListener(Activity activity) {
if (config.mode == Config.Mode.RECORD || config.mode == Config.Mode.REPTILE) {
IntentParser.recordExtras(context, activity);
}
}
/**
* Returns the ActivityMonitor used by Robotium.
*
* @return the ActivityMonitor used by Robotium
*/
public ActivityMonitor getActivityMonitor(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getActivityMonitor()");
}
return activityUtils.getActivityMonitor();
}
/**
* Returns the Config used by Robotium.
*
* @return the Config used by Robotium
*/
public Config getConfig(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getConfig()");
}
return config;
}
/**
* Returns an ArrayList of all the View objects located in the focused
* Activity or Dialog.
*
* @return an {@code ArrayList} of the {@link View} objects located in the focused window
*/
public ArrayList<View> getViews() {
try {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getViews()");
}
return viewFetcher.getViews(null, false);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Returns an ArrayList of the View objects contained in the parent View.
*
* @param parent the parent view from which to return the views
* @return an {@code ArrayList} of the {@link View} objects contained in the specified {@code View}
*/
public ArrayList<View> getViews(View parent) {
try {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getViews()");
}
return viewFetcher.getViews(parent, false);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Returns the absolute top parent View of the specified View.
*
* @param view the {@link View} whose top parent is requested
* @return the top parent {@link View}
*/
public View getTopParent(View view) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getTopParent("+view+")");
}
View topParent = viewFetcher.getTopParent(view);
return topParent;
}
/**
* Waits for the specified text to appear. Default timeout is 20 seconds.
*
* @param text the text to wait for, specified as a regular expression
* @return {@code true} if text is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForText(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForText(\""+text+"\")");
}
return (waiter.waitForText(text) != null);
}
/**
* Waits for the specified text to appear.
*
* @param text the text to wait for, specified as a regular expression
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the the amount of time in milliseconds to wait
* @return {@code true} if text is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForText(String text, int minimumNumberOfMatches, long timeout) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForText(\""+text+"\", "+minimumNumberOfMatches+", "+timeout+")");
}
return (waiter.waitForText(text, minimumNumberOfMatches, timeout) != null);
}
/**
* Waits for the specified text to appear.
*
* @param text the text to wait for, specified as a regular expression
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if text is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForText(String text, int minimumNumberOfMatches, long timeout, boolean scroll) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForText(\""+text+"\", "+minimumNumberOfMatches+", "+timeout+", "+scroll+")");
}
return (waiter.waitForText(text, minimumNumberOfMatches, timeout, scroll) != null);
}
/**
* Waits for the specified text to appear.
*
* @param text the text to wait for, specified as a regular expression
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @param onlyVisible {@code true} if only visible text views should be waited for
* @return {@code true} if text is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForText(String text, int minimumNumberOfMatches, long timeout, boolean scroll, boolean onlyVisible) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForText(\""+text+"\", "+minimumNumberOfMatches+", "+timeout+", "+scroll+", "+onlyVisible+")");
}
return (waiter.waitForText(text, minimumNumberOfMatches, timeout, scroll, onlyVisible, true) != null);
}
/**
* Waits for a View matching the specified resource id. Default timeout is 20 seconds.
*
* @param id the R.id of the {@link View} to wait for
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(int id){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+id+")");
}
return waitForView(id, 0, Timeout.getLargeTimeout(), true);
}
/**
* Waits for a View matching the specified resource id.
*
* @param id the R.id of the {@link View} to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(int id, int minimumNumberOfMatches, int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+id+", "+minimumNumberOfMatches+", "+timeout+")");
}
return waitForView(id, minimumNumberOfMatches, timeout, true);
}
/**
* Waits for a View matching the specified resource id.
*
* @param id the R.id of the {@link View} to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(int id, int minimumNumberOfMatches, int timeout, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+id+", "+minimumNumberOfMatches+", "+timeout+", "+scroll+")");
}
int index = minimumNumberOfMatches-1;
if(index < 1)
index = 0;
return (waiter.waitForView(id, index, timeout, scroll) != null);
}
/**
* Waits for a View matching the specified tag. Default timeout is 20 seconds.
*
* @param tag the {@link View#getTag() tag} of the {@link View} to wait for
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(Object tag){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+tag+")");
}
return waitForView(tag, 0, Timeout.getLargeTimeout(), true);
}
/**
* Waits for a View matching the specified tag.
*
* @param tag the {@link View#getTag() tag} of the {@link View} to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(Object tag, int minimumNumberOfMatches, int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+tag+", "+minimumNumberOfMatches+", "+timeout+")");
}
return waitForView(tag, minimumNumberOfMatches, timeout, true);
}
/**
* Waits for a View matching the specified tag
*
* @param tag the {@link View#getTag() tag} of the {@link View} to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForView(Object tag, int minimumNumberOfMatches, int timeout, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+tag+", "+minimumNumberOfMatches+", "+timeout+", "+scroll+")");
}
int index = minimumNumberOfMatches-1;
if(index < 1) {
index = 0;
}
return (waiter.waitForView(tag, index, timeout, scroll) != null);
}
/**
* Waits for a View matching the specified class. Default timeout is 20 seconds.
*
* @param viewClass the {@link View} class to wait for
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public <T extends View> boolean waitForView(final Class<T> viewClass){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+viewClass+")");
}
return waiter.waitForView(viewClass, 0, Timeout.getLargeTimeout(), true);
}
/**
* Waits for the specified View. Default timeout is 20 seconds.
*
* @param view the {@link View} object to wait for
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public <T extends View> boolean waitForView(View view){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+view+")");
}
return waiter.waitForView(view);
}
/**
* Waits for the specified View.
*
* @param view the {@link View} object to wait for
* @param timeout the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public <T extends View> boolean waitForView(View view, int timeout, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+view+", "+timeout+", "+scroll+")");
}
boolean checkIsShown = false;
if(!scroll){
checkIsShown = true;
}
View viewToWaitFor = waiter.waitForView(view, timeout, scroll, checkIsShown);
if(viewToWaitFor != null)
return true;
return false;
}
/**
* Waits for a View matching the specified class.
*
* @param viewClass the {@link View} class to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public <T extends View> boolean waitForView(final Class<T> viewClass, final int minimumNumberOfMatches, final int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+viewClass+", "+minimumNumberOfMatches+", "+timeout+")");
}
int index = minimumNumberOfMatches-1;
if(index < 1)
index = 0;
return waiter.waitForView(viewClass, index, timeout, true);
}
/**
* Waits for a View matching the specified class.
*
* @param viewClass the {@link View} class to wait for
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link View} is displayed and {@code false} if it is not displayed before the timeout
*/
public <T extends View> boolean waitForView(final Class<T> viewClass, final int minimumNumberOfMatches, final int timeout,final boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForView("+viewClass+", "+minimumNumberOfMatches+", "+timeout+", "+scroll+")");
}
int index = minimumNumberOfMatches-1;
if(index < 1)
index = 0;
return waiter.waitForView(viewClass, index, timeout, scroll);
}
/**
* Waits for a WebElement matching the specified By object. Default timeout is 20 seconds.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @return {@code true} if the {@link WebElement} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForWebElement(By by){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForWebElement("+by+")");
}
return (waiter.waitForWebElement(by, 0, Timeout.getLargeTimeout(), true) != null);
}
/**
* Waits for a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param timeout the the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link WebElement} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForWebElement(By by, int timeout, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForWebElement("+by+", "+timeout+", "+scroll+")");
}
return (waiter.waitForWebElement(by, 0, timeout, scroll) != null);
}
/**
* Waits for a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param minimumNumberOfMatches the minimum number of matches that are expected to be found. {@code 0} means any number of matches
* @param timeout the the amount of time in milliseconds to wait
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if the {@link WebElement} is displayed and {@code false} if it is not displayed before the timeout
*/
public boolean waitForWebElement(By by, int minimumNumberOfMatches, int timeout, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForWebElement("+by+", "+minimumNumberOfMatches+","+timeout+", "+scroll+")");
}
return (waiter.waitForWebElement(by, minimumNumberOfMatches, timeout, scroll) != null);
}
/**
* Waits for a condition to be satisfied.
*
* @param condition the condition to wait for
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if condition is satisfied and {@code false} if it is not satisfied before the timeout
*/
public boolean waitForCondition(Condition condition, final int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForCondition("+condition+","+timeout+")");
}
return waiter.waitForCondition(condition, timeout);
}
/**
* Searches for a text in the EditText objects currently displayed and returns true if found. Will automatically scroll when needed.
*
* @param text the text to search for
* @return {@code true} if an {@link EditText} displaying the specified text is found or {@code false} if it is not found
*/
public boolean searchEditText(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchEditText(\""+text+"\")");
}
return searcher.searchWithTimeoutFor(EditText.class, text, 1, true, false);
}
/**
* Searches for a Button displaying the specified text and returns {@code true} if at least one Button
* is found. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @return {@code true} if a {@link Button} displaying the specified text is found and {@code false} if it is not found
*/
public boolean searchButton(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchButton(\""+text+"\")");
}
return searcher.searchWithTimeoutFor(Button.class, text, 0, true, false);
}
/**
* Searches for a Button displaying the specified text and returns {@code true} if at least one Button
* is found. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param onlyVisible {@code true} if only {@link Button} visible on the screen should be searched
* @return {@code true} if a {@link Button} displaying the specified text is found and {@code false} if it is not found
*/
public boolean searchButton(String text, boolean onlyVisible) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchButton(\""+text+"\", "+onlyVisible+")");
}
return searcher.searchWithTimeoutFor(Button.class, text, 0, true, onlyVisible);
}
/**
* Searches for a ToggleButton displaying the specified text and returns {@code true} if at least one ToggleButton
* is found. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @return {@code true} if a {@link ToggleButton} displaying the specified text is found and {@code false} if it is not found
*/
public boolean searchToggleButton(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchToggleButton(\""+text+"\")");
}
return searcher.searchWithTimeoutFor(ToggleButton.class, text, 0, true, false);
}
/**
* Searches for a Button displaying the specified text and returns {@code true} if the
* searched Button is found a specified number of times. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @return {@code true} if a {@link Button} displaying the specified text is found a specified number of times and {@code false}
* if it is not found
*/
public boolean searchButton(String text, int minimumNumberOfMatches) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchButton(\""+text+"\", "+minimumNumberOfMatches+")");
}
return searcher.searchWithTimeoutFor(Button.class, text, minimumNumberOfMatches, true, false);
}
/**
* Searches for a Button displaying the specified text and returns {@code true} if the
* searched Button is found a specified number of times. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @param onlyVisible {@code true} if only {@link Button} visible on the screen should be searched
* @return {@code true} if a {@link Button} displaying the specified text is found a specified number of times and {@code false}
* if it is not found
*/
public boolean searchButton(String text, int minimumNumberOfMatches, boolean onlyVisible) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchButton(\""+text+"\", "+minimumNumberOfMatches+", "+onlyVisible+")");
}
return searcher.searchWithTimeoutFor(Button.class, text, minimumNumberOfMatches, true, onlyVisible);
}
/**
* Searches for a ToggleButton displaying the specified text and returns {@code true} if the
* searched ToggleButton is found a specified number of times. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @return {@code true} if a {@link ToggleButton} displaying the specified text is found a specified number of times and {@code false}
* if it is not found
*/
public boolean searchToggleButton(String text, int minimumNumberOfMatches) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchToggleButton(\""+text+"\", "+minimumNumberOfMatches+")");
}
return searcher.searchWithTimeoutFor(ToggleButton.class, text, minimumNumberOfMatches, true, false);
}
/**
* Searches for the specified text and returns {@code true} if at least one item
* is found displaying the expected text. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @return {@code true} if the search string is found and {@code false} if it is not found
*/
public boolean searchText(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchText(\""+text+"\")");
}
return searcher.searchWithTimeoutFor(TextView.class, text, 0, true, false);
}
/**
* Searches for the specified text and returns {@code true} if at least one item
* is found displaying the expected text. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param onlyVisible {@code true} if only texts visible on the screen should be searched
* @return {@code true} if the search string is found and {@code false} if it is not found
*/
public boolean searchText(String text, boolean onlyVisible) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchText(\""+text+"\", "+onlyVisible+")");
}
return searcher.searchWithTimeoutFor(TextView.class, text, 0, true, onlyVisible);
}
/**
* Searches for the specified text and returns {@code true} if the searched text is found a specified
* number of times. Will automatically scroll when needed.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @return {@code true} if text is found a specified number of times and {@code false} if the text
* is not found
*/
public boolean searchText(String text, int minimumNumberOfMatches) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchText(\""+text+"\", "+minimumNumberOfMatches+")");
}
return searcher.searchWithTimeoutFor(TextView.class, text, minimumNumberOfMatches, true, false);
}
/**
* Searches for the specified text and returns {@code true} if the searched text is found a specified
* number of times.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression.
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @param scroll {@code true} if scrolling should be performed
* @return {@code true} if text is found a specified number of times and {@code false} if the text
* is not found
*/
public boolean searchText(String text, int minimumNumberOfMatches, boolean scroll) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchText(\""+text+"\", "+minimumNumberOfMatches+", "+scroll+")");
}
return searcher.searchWithTimeoutFor(TextView.class, text, minimumNumberOfMatches, scroll, false);
}
/**
* Searches for the specified text and returns {@code true} if the searched text is found a specified
* number of times.
*
* @param text the text to search for. The parameter will be interpreted as a regular expression.
* @param minimumNumberOfMatches the minimum number of matches expected to be found. {@code 0} matches means that one or more
* matches are expected to be found
* @param scroll {@code true} if scrolling should be performed
* @param onlyVisible {@code true} if only texts visible on the screen should be searched
* @return {@code true} if text is found a specified number of times and {@code false} if the text
* is not found
*/
public boolean searchText(String text, int minimumNumberOfMatches, boolean scroll, boolean onlyVisible) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "searchText(\""+text+"\", "+minimumNumberOfMatches+", "+scroll+", "+onlyVisible+")");
}
return searcher.searchWithTimeoutFor(TextView.class, text, minimumNumberOfMatches, scroll, onlyVisible);
}
/**
* Sets the Orientation (Landscape/Portrait) for the current Activity.
*
* @param orientation the orientation to set. <code></code>{@link #LANDSCAPE} for landscape or
* <code></code>{@link #PORTRAIT} for portrait.
*/
public void setActivityOrientation(int orientation)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setActivityOrientation("+orientation+")");
}
activityUtils.setActivityOrientation(orientation);
}
/**
* Returns the current Activity.
*
* @return the current Activity
*/
public Activity getCurrentActivity() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentActivity()");
}
return activityUtils.getCurrentActivity(false);
}
/**
* Asserts that the Activity matching the specified name is active.
*
* @param message the message to display if the assert fails
* @param name the name of the {@link Activity} that is expected to be active. Example is: {@code "MyActivity"}
*/
public void assertCurrentActivity(String message, String name)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "assertCurrentActivity(\""+message+"\", \""+name+"\")");
}
asserter.assertCurrentActivity(message, name);
}
/**
* Asserts that the Activity matching the specified class is active.
*
* @param message the message to display if the assert fails
* @param activityClass the class of the Activity that is expected to be active. Example is: {@code MyActivity.class}
*/
@SuppressWarnings("unchecked")
public void assertCurrentActivity(String message, @SuppressWarnings("rawtypes") Class activityClass)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "assertCurrentActivity("+message+", "+activityClass+")");
}
asserter.assertCurrentActivity(message, activityClass);
}
/**
* Asserts that the Activity matching the specified name is active, with the possibility to
* verify that the expected Activity is a new instance of the Activity.
*
* @param message the message to display if the assert fails
* @param name the name of the Activity that is expected to be active. Example is: {@code "MyActivity"}
* @param isNewInstance {@code true} if the expected {@link Activity} is a new instance of the {@link Activity}
*/
public void assertCurrentActivity(String message, String name, boolean isNewInstance)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "assertCurrentActivity("+message+", "+name+", "+isNewInstance+")");
}
asserter.assertCurrentActivity(message, name, isNewInstance);
}
/**
* Asserts that the Activity matching the specified class is active, with the possibility to
* verify that the expected Activity is a new instance of the Activity.
*
* @param message the message to display if the assert fails
* @param activityClass the class of the Activity that is expected to be active. Example is: {@code MyActivity.class}
* @param isNewInstance {@code true} if the expected {@link Activity} is a new instance of the {@link Activity}
*/
@SuppressWarnings("unchecked")
public void assertCurrentActivity(String message, @SuppressWarnings("rawtypes") Class activityClass,
boolean isNewInstance) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "assertCurrentActivity(\""+message+"\", "+activityClass+", "+isNewInstance+")");
}
asserter.assertCurrentActivity(message, activityClass, isNewInstance);
}
/**
* Asserts that the available memory is not considered low by the system.
*/
public void assertMemoryNotLow()
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "assertMemoryNotLow()");
}
asserter.assertMemoryNotLow();
}
/**
* Waits for a Dialog to open. Default timeout is 20 seconds.
*
* @return {@code true} if the {@link android.app.Dialog} is opened before the timeout and {@code false} if it is not opened
*/
public boolean waitForDialogToOpen() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForDialogToOpen()");
}
return dialogUtils.waitForDialogToOpen(Timeout.getLargeTimeout(), true);
}
/**
* Waits for a Dialog to close. Default timeout is 20 seconds.
*
* @return {@code true} if the {@link android.app.Dialog} is closed before the timeout and {@code false} if it is not closed
*/
public boolean waitForDialogToClose() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForDialogToClose()");
}
return dialogUtils.waitForDialogToClose(Timeout.getLargeTimeout());
}
/**
* Waits for a Dialog to open.
*
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if the {@link android.app.Dialog} is opened before the timeout and {@code false} if it is not opened
*/
public boolean waitForDialogToOpen(long timeout) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForDialogToOpen("+timeout+")");
}
return dialogUtils.waitForDialogToOpen(timeout, true);
}
/**
* Waits for a Dialog to close.
*
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if the {@link android.app.Dialog} is closed before the timeout and {@code false} if it is not closed
*/
public boolean waitForDialogToClose(long timeout) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForDialogToClose("+timeout+")");
}
return dialogUtils.waitForDialogToClose(timeout);
}
/**
* Simulates pressing the hardware back key.
*/
public void goBack()
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "goBack()");
}
hideSoftKeyboard();
sender.goBack();
}
/**
* Clicks the specified coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
*/
public void clickOnScreen(float x, float y) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnScreen("+x+", "+y+")");
}
sleeper.sleep();
clicker.clickOnScreen(x, y, null);
}
/**
* Clicks the specified coordinates rapidly a specified number of times. Requires API level >= 14.
*
* @param x the x coordinate
* @param y the y coordinate
* @param numberOfClicks the number of clicks to perform
*/
public void clickOnScreen(float x, float y, int numberOfClicks) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnScreen("+x+", "+y+", "+numberOfClicks+")");
}
if (Build.VERSION.SDK_INT < 14){
throw new RuntimeException("clickOnScreen(float x, float y, int numberOfClicks) requires API level >= 14");
}
tapper.generateTapGesture(numberOfClicks, new PointF(x, y));
}
/**
* Long clicks the specified coordinates.
*
* @param x the x coordinate
* @param y the y coordinate
*/
public void clickLongOnScreen(float x, float y) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnScreen("+x+", "+y+")");
}
clicker.clickLongOnScreen(x, y, 0, null);
}
/**
* Long clicks the specified coordinates for a specified amount of time.
*
* @param x the x coordinate
* @param y the y coordinate
* @param time the amount of time to long click
*/
public void clickLongOnScreen(float x, float y, int time) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnScreen("+x+", "+y+", "+time+")");
}
clicker.clickLongOnScreen(x, y, time, null);
}
/**
* Clicks a Button displaying the specified text. Will automatically scroll when needed.
*
* @param text the text displayed by the {@link Button}. The parameter will be interpreted as a regular expression
*/
public void clickOnButton(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnButton(\""+text+"\")");
}
clicker.clickOn(Button.class, text);
}
/**
* Clicks an ImageButton matching the specified index.
*
* @param index the index of the {@link ImageButton} to click. 0 if only one is available
*/
public void clickOnImageButton(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnImageButton("+index+")");
}
clicker.clickOn(ImageButton.class, index);
}
/**
* Clicks a ToggleButton displaying the specified text.
*
* @param text the text displayed by the {@link ToggleButton}. The parameter will be interpreted as a regular expression
*/
public void clickOnToggleButton(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnToggleButton(\""+text+"\")");
}
clicker.clickOn(ToggleButton.class, text);
}
/**
* Clicks a MenuItem displaying the specified text.
*
* @param text the text displayed by the MenuItem. The parameter will be interpreted as a regular expression
*/
public void clickOnMenuItem(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnMenuItem(\""+text+"\")");
}
clicker.clickOnMenuItem(text);
}
/**
* Clicks a MenuItem displaying the specified text.
*
* @param text the text displayed by the MenuItem. The parameter will be interpreted as a regular expression
* @param subMenu {@code true} if the menu item could be located in a sub menu
*/
public void clickOnMenuItem(String text, boolean subMenu)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnMenuItem(\""+text+"\", "+subMenu+")");
}
clicker.clickOnMenuItem(text, subMenu);
}
/**
* Clicks the specified WebElement.
*
* @param webElement the WebElement to click
*/
public void clickOnWebElement(WebElement webElement){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnWebElement("+webElement+")");
}
if(webElement == null){
Log.e(LOG_TAG, "WebElement is null and can therefore not be clicked!");
return;
}
clicker.clickOnScreenForWeb(webElement.getLocationX(), webElement.getLocationY());
}
public void parseType(){
}
/**
* Clicks a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
*/
public void clickOnWebElement(By by){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnWebElement("+by+")");
}
clickOnWebElement(by, 0, true);
}
/**
* Clicks a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param match if multiple objects match, this determines which one to click
*/
public void clickOnWebElement(By by, int match){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnWebElement("+by+", "+match+")");
}
clickOnWebElement(by, match, true);
}
/**
* Clicks a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param match if multiple objects match, this determines which one to click
* @param scroll {@code true} if scrolling should be performed
*/
public void clickOnWebElement(By by, int match, boolean scroll){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnWebElement("+by+", "+match+", "+scroll+")");
}
clicker.clickOnWebElement(by, match, scroll, config.useJavaScriptToClickWebElements);
}
/**
* Presses a MenuItem matching the specified index. Index {@code 0} is the first item in the
* first row, Index {@code 3} is the first item in the second row and
* index {@code 6} is the first item in the third row.
*
* @param index the index of the {@link android.view.MenuItem} to press
*/
public void pressMenuItem(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressMenuItem("+index+")");
}
presser.pressMenuItem(index);
}
/**
* Presses a MenuItem matching the specified index. Supports three rows with a specified amount
* of items. If itemsPerRow equals 5 then index 0 is the first item in the first row,
* index 5 is the first item in the second row and index 10 is the first item in the third row.
*
* @param index the index of the {@link android.view.MenuItem} to press
* @param itemsPerRow the amount of menu items there are per row
*/
public void pressMenuItem(int index, int itemsPerRow) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressMenuItem("+index+", "+itemsPerRow+")");
}
presser.pressMenuItem(index, itemsPerRow);
}
/**
* Presses the soft keyboard next button.
*/
/**
* Presses the soft keyboard next button.
*/
public void pressSoftKeyboardNextButton(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressSoftKeyboardNextButton()");
}
presser.pressSoftKeyboard(EditorInfo.IME_ACTION_NEXT);
}
/**
* Presses the soft keyboard search button.
*/
public void pressSoftKeyboardSearchButton(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressSoftKeyboardSearchButton()");
}
presser.pressSoftKeyboard(EditorInfo.IME_ACTION_SEARCH);
}
/**
* Presses the soft keyboard go button.
*/
public void pressSoftKeyboardGoButton(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressSoftKeyboardGoButton()");
}
presser.pressSoftKeyboard(EditorInfo.IME_ACTION_GO);
}
/**
* Presses the soft keyboard done button.
*/
public void pressSoftKeyboardDoneButton(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressSoftKeyboardDoneButton()");
}
presser.pressSoftKeyboard(EditorInfo.IME_ACTION_DONE);
}
/**
* Presses a Spinner (drop-down menu) item.
*
* @param spinnerIndex the index of the {@link Spinner} menu to use
* @param itemIndex the index of the {@link Spinner} item to press relative to the currently selected item.
* A Negative number moves up on the {@link Spinner}, positive moves down
*/
public void pressSpinnerItem(int spinnerIndex, int itemIndex)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pressSpinnerItem("+spinnerIndex+", "+itemIndex+")");
}
presser.pressSpinnerItem(spinnerIndex, itemIndex);
}
/**
* Clicks the specified View.
*
* @param view the {@link View} to click
*/
public void clickOnView(View view) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnView("+view+")");
}
view = waiter.waitForView(view, Timeout.getSmallTimeout());
clicker.clickOnScreen(view);
}
/**
* Clicks the specified View.
*
* @param view the {@link View} to click
* @param immediately {@code true} if View should be clicked without any wait
*/
public void clickOnView(View view, boolean immediately){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnView("+view+", "+immediately+")");
}
if(immediately)
clicker.clickOnScreen(view);
else{
view = waiter.waitForView(view, Timeout.getSmallTimeout());
clicker.clickOnScreen(view);
}
}
/**
* Long clicks the specified View.
*
* @param view the {@link View} to long click
*/
public void clickLongOnView(View view) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnView("+view+")");
}
view = waiter.waitForView(view, Timeout.getSmallTimeout());
clicker.clickOnScreen(view, true, 0);
}
/**
* Long clicks the specified View for a specified amount of time.
*
* @param view the {@link View} to long click
* @param time the amount of time to long click
*/
public void clickLongOnView(View view, int time) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnView("+view+", "+time+")");
}
clicker.clickOnScreen(view, true, time);
}
/**
* Clicks a View or WebElement displaying the specified
* text. Will automatically scroll when needed.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
*/
public void clickOnText(String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnText(\""+text+"\")");
}
clicker.clickOnText(text, false, 1, true, 0);
}
/**
* Clicks a View or WebElement displaying the specified text. Will automatically scroll when needed.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param match if multiple objects match the text, this determines which one to click
*/
public void clickOnText(String text, int match) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnText(\""+text+"\", "+match+")");
}
clicker.clickOnText(text, false, match, true, 0);
}
/**
* Clicks a View or WebElement displaying the specified text.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param match if multiple objects match the text, this determines which one to click
* @param scroll {@code true} if scrolling should be performed
*/
public void clickOnText(String text, int match, boolean scroll) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnText(\""+text+"\", "+match+", "+scroll+")");
}
clicker.clickOnText(text, false, match, scroll, 0);
}
/**
* Long clicks a View or WebElement displaying the specified text. Will automatically scroll when needed.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
*/
public void clickLongOnText(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnText(\""+text+"\")");
}
clicker.clickOnText(text, true, 1, true, 0);
}
/**
* Long clicks a View or WebElement displaying the specified text. Will automatically scroll when needed.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param match if multiple objects match the text, this determines which one to click
*/
public void clickLongOnText(String text, int match)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnText(\""+text+"\", "+match+")");
}
clicker.clickOnText(text, true, match, true, 0);
}
/**
* Long clicks a View or WebElement displaying the specified text.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param match if multiple objects match the text, this determines which one to click
* @param scroll {@code true} if scrolling should be performed
*/
public void clickLongOnText(String text, int match, boolean scroll)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnText(\""+text+"\", "+match+", "+scroll+")");
}
clicker.clickOnText(text, true, match, scroll, 0);
}
/**
* Long clicks a View or WebElement displaying the specified text.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param match if multiple objects match the text, this determines which one to click
* @param time the amount of time to long click
*/
public void clickLongOnText(String text, int match, int time)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnText(\""+text+"\", "+match+", "+time+")");
}
clicker.clickOnText(text, true, match, true, time);
}
/**
* Long clicks a View displaying the specified text and then selects
* an item from the context menu that appears. Will automatically scroll when needed.
*
* @param text the text to click. The parameter will be interpreted as a regular expression
* @param index the index of the menu item to press. {@code 0} if only one is available
*/
public void clickLongOnTextAndPress(String text, int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongOnTextAndPress(\""+text+"\", "+index+")");
}
clicker.clickLongOnTextAndPress(text, index);
}
/**
* Clicks a Button matching the specified index.
*
* @param index the index of the {@link Button} to click. {@code 0} if only one is available
*/
public void clickOnButton(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnButton("+index+")");
}
clicker.clickOn(Button.class, index);
}
/**
* Clicks a RadioButton matching the specified index.
*
* @param index the index of the {@link RadioButton} to click. {@code 0} if only one is available
*/
public void clickOnRadioButton(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnRadioButton("+index+")");
}
clicker.clickOn(RadioButton.class, index);
}
/**
* Clicks a CheckBox matching the specified index.
*
* @param index the index of the {@link CheckBox} to click. {@code 0} if only one is available
*/
public void clickOnCheckBox(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnCheckBox("+index+")");
}
clicker.clickOn(CheckBox.class, index);
}
/**
* Clicks an EditText matching the specified index.
*
* @param index the index of the {@link EditText} to click. {@code 0} if only one is available
*/
public void clickOnEditText(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnEditText("+index+")");
}
clicker.clickOn(EditText.class, index);
}
/**
* Clicks the specified list line and returns an ArrayList of the TextView objects that
* the list line is displaying. Will use the first ListView it finds.
*
* @param line the line to click
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickInList(int line) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInList("+line+")");
}
return clicker.clickInList(line);
}
/**
* Clicks the specified list line in the ListView matching the specified index and
* returns an ArrayList of the TextView objects that the list line is displaying.
*
* @param line the line to click
* @param index the index of the list. {@code 0} if only one is available
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickInList(int line, int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInList("+line+", "+index+")");
}
return clicker.clickInList(line, index, 0, false, 0);
}
/**
* Clicks a View with a specified resource id on the specified line in the list matching the specified index
*
* @param line the line where the View exists
* @param index the index of the List. {@code 0} if only one is available
* @param id the resource id of the View to click
*/
public void clickInList(int line, int index, int id) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInList("+line+", "+index+", " + id +")");
}
clicker.clickInList(line, index, id, false, 0);
}
/**
* Long clicks the specified list line and returns an ArrayList of the TextView objects that
* the list line is displaying. Will use the first ListView it finds.
*
* @param line the line to click
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInList(int line){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInList("+line+")");
}
return clicker.clickInList(line, 0, 0, true, 0);
}
/**
* Long clicks the specified list line in the ListView matching the specified index and
* returns an ArrayList of the TextView objects that the list line is displaying.
*
* @param line the line to click
* @param index the index of the list. {@code 0} if only one is available
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInList(int line, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInList("+line+", "+index+")");
}
return clicker.clickInList(line, index, 0, true, 0);
}
/**
* Long clicks the specified list line in the ListView matching the specified index and
* returns an ArrayList of the TextView objects that the list line is displaying.
*
* @param line the line to click
* @param index the index of the list. {@code 0} if only one is available
* @param time the amount of time to long click
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInList(int line, int index, int time){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInList("+line+", "+index+", "+time+")");
}
return clicker.clickInList(line, index, 0, true, time);
}
/**
* Clicks the specified item index and returns an ArrayList of the TextView objects that
* the item index is displaying. Will use the first RecyclerView it finds.
*
* @param itemIndex the item index to click
* @return an {@code ArrayList} of the {@link TextView} objects located in the item index
*/
public ArrayList<TextView> clickInRecyclerView(int itemIndex) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInRecyclerView("+itemIndex+")");
}
return clicker.clickInRecyclerView(itemIndex);
}
public ArrayList<TextView> clickInRecyclerView(ViewGroup recyclerView, int itemIndex) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInRecyclerView("+itemIndex+")");
}
return clicker.clickInRecyclerView(recyclerView, itemIndex);
}
/**
* Clicks the specified item index in the RecyclerView matching the specified RecyclerView index and
* returns an ArrayList of the TextView objects that the list line is displaying.
*
* @param itemIndex the line to click
* @param recyclerViewIndex the index of the RecyclerView. {@code 0} if only one is available
* @return an {@code ArrayList} of the {@link TextView} objects located in the item index
*/
public ArrayList<TextView> clickInRecyclerView(int itemIndex, int recyclerViewIndex) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInRecyclerView("+itemIndex+", "+recyclerViewIndex+")");
}
return clicker.clickInRecyclerView(itemIndex, recyclerViewIndex, 0, false, 0);
}
/**
* Clicks a View with a specified resource id on the specified item index in the RecyclerView matching the specified RecyclerView index
*
* @param itemIndex the line where the View exists
* @param recyclerViewIndex the index of the RecyclerView. {@code 0} if only one is available
* @param id the resource id of the View to click
*/
public void clickInRecyclerView(int itemIndex, int recyclerViewIndex, int id) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickInRecyclerView("+itemIndex+", "+recyclerViewIndex+", " + id +")");
}
clicker.clickInRecyclerView(itemIndex, recyclerViewIndex, id, false, 0);
}
/**
* Long clicks the specified item index and returns an ArrayList of the TextView objects that
* the item index is displaying. Will use the first RecyclerView it finds.
*
* @param itemIndex the item index to click
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInRecycleView(int itemIndex){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInRecycleView("+itemIndex+")");
}
return clicker.clickInRecyclerView(itemIndex, 0, 0, true, 0);
}
/**
* Long clicks the specified item index in the RecyclerView matching the specified RecyclerView index and
* returns an ArrayList of the TextView objects that the item index is displaying.
*
* @param itemIndex the item index to click
* @param recyclerViewIndex the index of the RecyclerView. {@code 0} if only one is available
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInRecycleView(int itemIndex, int recyclerViewIndex){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInRecycleView("+itemIndex+", "+recyclerViewIndex+")");
}
return clicker.clickInRecyclerView(itemIndex, recyclerViewIndex, 0, true, 0);
}
/**
* Long clicks the specified item index in the RecyclerView matching the specified RecyclerView index and
* returns an ArrayList of the TextView objects that the item index is displaying.
*
* @param itemIndex the item index to click
* @param recyclerViewIndex the index of the RecyclerView. {@code 0} if only one is available
* @param time the amount of time to long click
* @return an {@code ArrayList} of the {@link TextView} objects located in the list line
*/
public ArrayList<TextView> clickLongInRecycleView(int itemIndex, int recyclerViewIndex, int time){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickLongInRecycleView("+itemIndex+", "+recyclerViewIndex+", "+time+")");
}
return clicker.clickInRecyclerView(itemIndex, recyclerViewIndex, 0, true, time);
}
/**
* Clicks an ActionBarItem matching the specified resource id.
*
* @param id the R.id of the ActionBar item to click
*/
public void clickOnActionBarItem(int id){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnActionBarItem("+id+")");
}
clicker.clickOnActionBarItem(id);
}
/**
* Clicks an ActionBar Home/Up button.
*/
public void clickOnActionBarHomeButton() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnActionBarHomeButton()");
}
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
clicker.clickOnActionBarHomeButton();
}
});
}
/**
* An Builder pattern that will allow the client to build a new illustration
*/
public Illustration.Builder createIllustrationBuilder(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "createIllustrationBuilder()");
}
return new Illustration.Builder();
}
/**
* Simulate drawing an illustration on the screen.
* <br> <br>
* Example of usage:
* <pre>
*
* Illustration.Builder builder = createIllustrationBuilder();
* builder.addPoint(200, 20000, MotionEvent.TOOL_TYPE_FINGER);
* builder.addPoint(1000, 1500, MotionEvent.TOOL_TYPE_FINGER);
* Illustration illustration = builder.build();
* illustrate(illustration);
* </pre>
* @param illustration Built-up illustration containing toolType, pressure, and coordinate information.
*/
public void illustrate(Illustration illustration){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "illustrate("+illustration+")");
}
illustrator.illustrate(illustration);
}
/**
* Simulate touching the specified location and dragging it to a new location.
*
*
* @param fromX X coordinate of the initial touch, in screen coordinates
* @param toX X coordinate of the drag destination, in screen coordinates
* @param fromY Y coordinate of the initial touch, in screen coordinates
* @param toY Y coordinate of the drag destination, in screen coordinates
* @param stepCount how many move steps to include in the drag. Less steps results in a faster drag
*/
public void drag(float fromX, float toX, float fromY, float toY,
int stepCount) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "drag("+fromX+", "+toX+", "+fromY+", "+toY+")");
}
dialogUtils.hideSoftKeyboard(null, false, true);
scroller.drag(fromX, toX, fromY, toY, stepCount);
}
/**
* Scrolls down the screen.
*
* @return {@code true} if more scrolling can be performed and {@code false} if it is at the end of
* the screen
*/
@SuppressWarnings("unchecked")
public boolean scrollDown() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollDown()");
}
View recyclerView = viewFetcher.getRecyclerView(true, 0);
if(recyclerView != null){
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class, recyclerView.getClass());
}
else {
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class);
}
return scroller.scroll(Scroller.DOWN);
}
/**
* Scrolls to the bottom of the screen.
*/
@SuppressWarnings("unchecked")
public void scrollToBottom() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollToBottom()");
}
View recyclerView = viewFetcher.getRecyclerView(true, 0);
if(recyclerView != null){
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class, recyclerView.getClass());
}
else {
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class);
}
scroller.scroll(Scroller.DOWN, true);
}
/**
* Scrolls up the screen.
*
* @return {@code true} if more scrolling can be performed and {@code false} if it is at the top of
* the screen
*/
@SuppressWarnings("unchecked")
public boolean scrollUp(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollUp()");
}
View recyclerView = viewFetcher.getRecyclerView(true, 0);
if(recyclerView != null){
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class, recyclerView.getClass());
}
else {
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class);
}
return scroller.scroll(Scroller.UP);
}
/**
* Scrolls to the top of the screen.
*/
@SuppressWarnings("unchecked")
public void scrollToTop() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollToTop()");
}
View recyclerView = viewFetcher.getRecyclerView(true, 0);
if(recyclerView != null){
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class, recyclerView.getClass());
}
else {
waiter.waitForViews(true, AbsListView.class, ScrollView.class, WebView.class);
}
scroller.scroll(Scroller.UP, true);
}
/**
* Scrolls down the specified AbsListView.
*
* @param list the {@link AbsListView} to scroll
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollDownList(AbsListView list) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollDownList("+list+")");
}
return scroller.scrollList(list, Scroller.DOWN, false);
}
/**
* Scrolls to the bottom of the specified AbsListView.
*
* @param list the {@link AbsListView} to scroll
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollListToBottom(AbsListView list) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToBottom("+list+")");
}
return scroller.scrollList(list, Scroller.DOWN, true);
}
/**
* Scrolls up the specified AbsListView.
*
* @param list the {@link AbsListView} to scroll
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollUpList(AbsListView list) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollUpList("+list+")");
}
return scroller.scrollList(list, Scroller.UP, false);
}
/**
* Scrolls to the top of the specified AbsListView.
*
* @param list the {@link AbsListView} to scroll
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollListToTop(AbsListView list) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToTop("+list+")");
}
return scroller.scrollList(list, Scroller.UP, true);
}
/**
* Scrolls down a ListView matching the specified index.
*
* @param index the index of the {@link ListView} to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollDownList(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollDownList("+index+")");
}
return scroller.scrollList(waiter.waitForAndGetView(index, ListView.class), Scroller.DOWN, false);
}
/**
* Scrolls a ListView matching the specified index to the bottom.
*
* @param index the index of the {@link ListView} to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollListToBottom(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToBottom("+index+")");
}
return scroller.scrollList(waiter.waitForAndGetView(index, ListView.class), Scroller.DOWN, true);
}
/**
* Scrolls up a ListView matching the specified index.
*
* @param index the index of the {@link ListView} to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollUpList(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollUpList("+index+")");
}
return scroller.scrollList(waiter.waitForAndGetView(index, ListView.class), Scroller.UP, false);
}
/**
* Scrolls a ListView matching the specified index to the top.
*
* @param index the index of the {@link ListView} to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollListToTop(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToTop("+index+")");
}
return scroller.scrollList(waiter.waitForAndGetView(index, ListView.class), Scroller.UP, true);
}
/**
* Scroll the specified AbsListView to the specified line.
*
* @param absListView the {@link AbsListView} to scroll
* @param line the line to scroll to
*/
public void scrollListToLine(AbsListView absListView, int line){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToLine("+absListView+", "+line+")");
}
scroller.scrollListToLine(absListView, line);
}
/**
* Scroll a AbsListView matching the specified index to the specified line.
*
* @param index the index of the {@link AbsListView} to scroll
* @param line the line to scroll to
*/
public void scrollListToLine(int index, int line){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollListToLine("+index+", "+line+")");
}
scroller.scrollListToLine(waiter.waitForAndGetView(index, AbsListView.class), line);
}
/**
* Scroll the specified RecyclerView to the specified line.
*
* @param recyclerView the {@link RecyclerView} to scroll
* @param line the line to scroll to
*/
public void scrollRecyclerViewToLine(RecyclerView recyclerView, int line){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollRecyclerViewToLine("+recyclerView+", "+line+")");
}
scroller.scrollRecyclerViewToLine(recyclerView, line);
}
/**
* Scrolls a ScrollView.
*
* @param direction the direction to be scrolled
* @return {@code true} if scrolling occurred, false if it did not
*/
public boolean scrollView(final View view, int direction){
return scroller.scrollView(view, direction);
}
/**
* Scrolls down a RecyclerView matching the specified index.
*
* @param index the index of the RecyclerView to scroll. {@code 0} if only one RecyclerView is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollDownRecyclerView(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollDownRecyclerView("+index+")");
}
if(!config.shouldScroll) {
return true;
}
View recyclerView = viewFetcher.getRecyclerView(index, Timeout.getSmallTimeout());
return scroller.scrollView(recyclerView, Scroller.DOWN);
}
/**
* Scrolls a RecyclerView matching the specified index to the bottom.
*
* @param index the index of the RecyclerView to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollRecyclerViewToBottom(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollRecyclerViewToBottom("+index+")");
}
if(!config.shouldScroll) {
return true;
}
View recyclerView = viewFetcher.getRecyclerView(index, Timeout.getSmallTimeout());
scroller.scrollViewAllTheWay(recyclerView, Scroller.DOWN);
return false;
}
/**
* Scrolls up a RecyclerView matching the specified index.
*
* @param index the index of the RecyclerView to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollUpRecyclerView(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollUpRecyclerView("+index+")");
}
if(!config.shouldScroll) {
return true;
}
View recyclerView = viewFetcher.getRecyclerView(index, Timeout.getSmallTimeout());
return scroller.scrollView(recyclerView, Scroller.UP);
}
/**
* Scrolls a RecyclerView matching the specified index to the top.
*
* @param index the index of the RecyclerView to scroll. {@code 0} if only one list is available
* @return {@code true} if more scrolling can be performed
*/
public boolean scrollRecyclerViewToTop(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollRecyclerViewToTop("+index+")");
}
if(!config.shouldScroll) {
return false;
}
View recyclerView = viewFetcher.getRecyclerView(index, Timeout.getSmallTimeout());
scroller.scrollViewAllTheWay(recyclerView, Scroller.UP);
return false;
}
/**
* Scrolls horizontally.
*
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.60
* @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
*/
public void scrollToSide(int side, float scrollPosition, int stepCount) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollToSide("+side+", "+scrollPosition+", "+stepCount+")");
}
switch (side){
case RIGHT: scroller.scrollToSide(Scroller.Side.RIGHT, scrollPosition, stepCount); break;
case LEFT: scroller.scrollToSide(Scroller.Side.LEFT, scrollPosition, stepCount); break;
}
}
/**
* Scrolls horizontally.
*
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.60
*/
public void scrollToSide(int side, float scrollPosition) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollToSide("+scrollPosition+")");
}
scrollToSide(side, scrollPosition, 20);
}
/**
* Scrolls horizontally.
*
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
*/
public void scrollToSide(int side) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollToSide("+side+")");
}
scrollToSide(side, 0.75F);
}
/**
* Scrolls a View horizontally.
*
* @param view the View to scroll
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.60
* @param stepCount how many move steps to include in the scroll. Less steps results in a faster scroll
*/
public void scrollViewToSide(View view, int side, float scrollPosition, int stepCount) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollViewToSide("+view+", "+side+", "+scrollPosition+", "+stepCount+")");
}
waitForView(view);
sleeper.sleep();
switch (side){
case RIGHT: scroller.scrollViewToSide(view, Scroller.Side.RIGHT, scrollPosition, stepCount); break;
case LEFT: scroller.scrollViewToSide(view, Scroller.Side.LEFT, scrollPosition, stepCount); break;
}
}
/**
* Scrolls a View horizontally.
*
* @param view the View to scroll
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
* @param scrollPosition the position to scroll to, from 0 to 1 where 1 is all the way. Example is: 0.60
*/
public void scrollViewToSide(View view, int side, float scrollPosition) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollViewToSide("+view+", "+side+", "+scrollPosition+")");
}
scrollViewToSide(view, side, scrollPosition, 20);
}
/**
* Scrolls a View horizontally.
*
* @param view the View to scroll
* @param side the side to scroll; {@link #RIGHT} or {@link #LEFT}
*/
public void scrollViewToSide(View view, int side) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "scrollViewToSide("+view+", "+side+")");
}
scrollViewToSide(view, side, 0.70F);
}
/**
* Zooms in or out if startPoint1 and startPoint2 are larger or smaller then endPoint1 and endPoint2. Requires API level >= 14.
*
* @param startPoint1 First "finger" down on the screen
* @param startPoint2 Second "finger" down on the screen
* @param endPoint1 Corresponding ending point of startPoint1
* @param endPoint2 Corresponding ending point of startPoint2
*/
public void pinchToZoom(PointF startPoint1, PointF startPoint2, PointF endPoint1, PointF endPoint2)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "pinchToZoom("+startPoint1+", "+startPoint2+", "+endPoint1+", "+endPoint2+")");
}
if (Build.VERSION.SDK_INT < 14){
throw new RuntimeException("pinchToZoom() requires API level >= 14");
}
zoomer.generateZoomGesture(startPoint1, startPoint2, endPoint1, endPoint2);
}
/**
* Swipes with two fingers in a linear path determined by starting and ending points. Requires API level >= 14.
*
* @param startPoint1 First "finger" down on the screen
* @param startPoint2 Second "finger" down on the screen
* @param endPoint1 Corresponding ending point of startPoint1
* @param endPoint2 Corresponding ending point of startPoint2
*/
public void swipe(PointF startPoint1, PointF startPoint2, PointF endPoint1, PointF endPoint2)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "swipe("+startPoint1+", "+startPoint2+", "+endPoint1+", "+endPoint2+")");
}
if (Build.VERSION.SDK_INT < 14){
throw new RuntimeException("swipe() requires API level >= 14");
}
swiper.generateSwipeGesture(startPoint1, startPoint2, endPoint1,
endPoint2);
}
/**
* Draws two semi-circles at the specified centers. Both circles are larger than rotateSmall(). Requires API level >= 14.
*
* @param center1 Center of semi-circle drawn from [0, Pi]
* @param center2 Center of semi-circle drawn from [Pi, 3*Pi]
*/
public void rotateLarge(PointF center1, PointF center2)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "rotateLarge("+center1+", "+center2+")");
}
if (Build.VERSION.SDK_INT < 14){
throw new RuntimeException("rotateLarge(PointF center1, PointF center2) requires API level >= 14");
}
rotator.generateRotateGesture(Rotator.LARGE, center1, center2);
}
/**
* Draws two semi-circles at the specified centers. Both circles are smaller than rotateLarge(). Requires API level >= 14.
*
* @param center1 Center of semi-circle drawn from [0, Pi]
* @param center2 Center of semi-circle drawn from [Pi, 3*Pi]
*/
public void rotateSmall(PointF center1, PointF center2)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "rotateSmall("+center1+", "+center2+")");
}
if (Build.VERSION.SDK_INT < 14){
throw new RuntimeException("rotateSmall(PointF center1, PointF center2) requires API level >= 14");
}
rotator.generateRotateGesture(Rotator.SMALL, center1, center2);
}
/**
* Sets if mobile data should be turned on or off. Requires android.permission.CHANGE_NETWORK_STATE in the AndroidManifest.xml of the application under test.
* NOTE: Setting it to false can kill the adb connection.
*
* @param turnedOn true if mobile data is to be turned on and false if not
*/
public void setMobileData(Boolean turnedOn){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setMobileData("+turnedOn+")");
}
systemUtils.setMobileData(turnedOn);
}
/**
* Sets if wifi data should be turned on or off. Requires android.permission.CHANGE_WIFI_STATE in the AndroidManifest.xml of the application under test.
*
*
* @param turnedOn true if mobile wifi is to be turned on and false if not
*/
public void setWiFiData(Boolean turnedOn){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setWiFiData("+turnedOn+")");
}
systemUtils.setWiFiData(turnedOn);
}
/**
* Sets the date in a DatePicker matching the specified index.
*
* @param index the index of the {@link DatePicker}. {@code 0} if only one is available
* @param year the year e.g. 2011
* @param monthOfYear the month which starts from zero e.g. 0 for January
* @param dayOfMonth the day e.g. 10
*/
public void setDatePicker(int index, int year, int monthOfYear, int dayOfMonth) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setDatePicker("+index+", "+year+", "+monthOfYear+", "+dayOfMonth+")");
}
setDatePicker(waiter.waitForAndGetView(index, DatePicker.class), year, monthOfYear, dayOfMonth);
}
/**
* Sets the date in the specified DatePicker.
*
* @param datePicker the {@link DatePicker} object
* @param year the year e.g. 2011
* @param monthOfYear the month which starts from zero e.g. 03 for April
* @param dayOfMonth the day e.g. 10
*/
public void setDatePicker(DatePicker datePicker, int year, int monthOfYear, int dayOfMonth) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setDatePicker("+datePicker+", "+year+", "+monthOfYear+", "+dayOfMonth+")");
}
datePicker = (DatePicker) waiter.waitForView(datePicker, Timeout.getSmallTimeout());
setter.setDatePicker(datePicker, year, monthOfYear, dayOfMonth);
}
/**
* Sets the time in a TimePicker matching the specified index.
*
* @param index the index of the {@link TimePicker}. {@code 0} if only one is available
* @param hour the hour e.g. 15
* @param minute the minute e.g. 30
*/
public void setTimePicker(int index, int hour, int minute) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setTimePicker("+index+", "+hour+", "+minute+")");
}
setTimePicker(waiter.waitForAndGetView(index, TimePicker.class), hour, minute);
}
/**
* Sets the time in the specified TimePicker.
*
* @param timePicker the {@link TimePicker} object
* @param hour the hour e.g. 15
* @param minute the minute e.g. 30
*/
public void setTimePicker(TimePicker timePicker, int hour, int minute) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setTimePicker("+timePicker+", "+hour+", "+minute+")");
}
timePicker = (TimePicker) waiter.waitForView(timePicker, Timeout.getSmallTimeout());
setter.setTimePicker(timePicker, hour, minute);
}
/**
* Sets the progress of a ProgressBar matching the specified index. Examples of ProgressBars are: {@link android.widget.SeekBar} and {@link android.widget.RatingBar}.
*
* @param index the index of the {@link ProgressBar}
* @param progress the progress to set the {@link ProgressBar}
*/
public void setProgressBar(int index, int progress){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setProgressBar("+index+", "+progress+")");
}
setProgressBar(waiter.waitForAndGetView(index, ProgressBar.class), progress);
}
/**
* Sets the progress of the specified ProgressBar. Examples of ProgressBars are: {@link android.widget.SeekBar} and {@link android.widget.RatingBar}.
*
* @param progressBar the {@link ProgressBar}
* @param progress the progress to set the {@link ProgressBar}
*/
public void setProgressBar(ProgressBar progressBar, int progress){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setProgressBar("+progressBar+", "+progress+")");
}
progressBar = (ProgressBar) waiter.waitForView(progressBar, Timeout.getSmallTimeout());
setter.setProgressBar(progressBar, progress);
}
/**
* Sets the status of the NavigationDrawer. Examples of status are: {@code CLOSED} and {@code OPENED}.
*
* @param status the status that the NavigationDrawer should be set to
*/
public void setNavigationDrawer(final int status){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setNavigationDrawer("+status+")");
}
setter.setNavigationDrawer(status);
}
/**
* Sets the status of a SlidingDrawer matching the specified index. Examples of status are: {@code CLOSED} and {@code OPENED}.
*
* @param index the index of the {@link SlidingDrawer}
* @param status the status to set the {@link SlidingDrawer}
*/
public void setSlidingDrawer(int index, int status){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setSlidingDrawer("+index+", "+status+")");
}
setSlidingDrawer(waiter.waitForAndGetView(index, SlidingDrawer.class), status);
}
/**
* Sets the status of the specified SlidingDrawer. Examples of status are: {@code CLOSED} and {@code OPENED}.
*
* @param slidingDrawer the {@link SlidingDrawer}
* @param status the status to set the {@link SlidingDrawer}
*/
@SuppressWarnings("deprecation")
public void setSlidingDrawer(SlidingDrawer slidingDrawer, int status){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "setSlidingDrawer("+slidingDrawer+", "+status+")");
}
slidingDrawer = (SlidingDrawer) waiter.waitForView(slidingDrawer, Timeout.getSmallTimeout());
setter.setSlidingDrawer(slidingDrawer, status);
}
/**
* Enters text in an EditText matching the specified index.
*
* @param index the index of the {@link EditText}. {@code 0} if only one is available
* @param text the text to enter in the {@link EditText} field
*/
public void enterText(int index, String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "enterText("+index+", \""+text+"\")");
}
textEnterer.setEditText(waiter.waitForAndGetView(index, EditText.class), text);
}
/**
* Enters text in the specified EditText.
*
* @param editText the {@link EditText} to enter text in
* @param text the text to enter in the {@link EditText} field
*/
public void enterText(EditText editText, String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "enterText("+editText+", \""+text+"\")");
}
editText = (EditText) waiter.waitForView(editText, Timeout.getSmallTimeout());
textEnterer.setEditText(editText, text);
}
/**
* Enters text in a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param text the text to enter in the {@link WebElement} field
*/
public void enterTextInWebElement(By by, String text){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "enterTextInWebElement("+by+", \""+text+"\")");
}
if(waiter.waitForWebElement(by, 0, Timeout.getSmallTimeout(), false) == null) {
Log.e(LOG_TAG, "WebElement with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' is not found!");
}
webUtils.enterTextIntoWebElement(by, text);
}
/**
* Types text in an EditText matching the specified index.
*
* @param index the index of the {@link EditText}. {@code 0} if only one is available
* @param text the text to type in the {@link EditText} field
*/
public void typeText(int index, String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeText("+index+", \""+text+"\")");
}
textEnterer.typeText(waiter.waitForAndGetView(index, EditText.class), text);
}
/**
* Types text in the specified EditText.
*
* @param editText the {@link EditText} to type text in
* @param text the text to type in the {@link EditText} field
*/
public void typeText(EditText editText, String text) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeText("+editText+", \""+text+"\")");
}
editText = (EditText) waiter.waitForView(editText, Timeout.getSmallTimeout());
textEnterer.typeText(editText, text);
}
/**
* Types text in the specified EditText.
*
* @param editText the {@link EditText} to type text in
*/
public void typeText(EditText editText) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeText("+editText+")");
}
int type = editText.getInputType();
int length = getMaxLengthForEditText(editText);
switch (type) {
case InputType.TYPE_CLASS_NUMBER:
typeText(editText, RandomUtils.getRandomNumber(length));
break;
case InputType.TYPE_CLASS_PHONE:
typeText(editText, RandomUtils.getRandomPhone());
break;
case InputType.TYPE_CLASS_TEXT:
typeText(editText, RandomUtils.getRandomText(length));
break;
case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS:
typeText(editText, RandomUtils.getRandomEmail(length));
break;
case InputType.TYPE_TEXT_VARIATION_URI:
typeText(editText, RandomUtils.getRandomUrl(length));
break;
case InputType.TYPE_NUMBER_FLAG_SIGNED:
typeText(editText, "-" + RandomUtils.getRandomNumber(length - 1));
break;
case InputType.TYPE_NUMBER_FLAG_DECIMAL:
typeText(editText, "." + RandomUtils.getRandomNumber(length - 1));
break;
default:
typeText(editText, RandomUtils.getRandomText(length));
break;
}
sleep(config.sleepDuration / 10);
hideSoftKeyboard();
}
/**
* Types text in a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param text the text to enter in the {@link WebElement} field
*/
public void typeTextInWebElement(By by, String text){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeTextInWebElement("+by+", \""+text+"\")");
}
typeTextInWebElement(by, text, 0);
}
/**
* Types text in a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param text the text to enter in the {@link WebElement} field
* @param match if multiple objects match, this determines which one will be typed in
*/
public void typeTextInWebElement(By by, String text, int match){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeTextInWebElement("+by+", \""+text+"\", "+match+")");
}
clicker.clickOnWebElement(by, match, true, false);
dialogUtils.hideSoftKeyboard(null, true, true);
instrumentation.sendStringSync(text);
}
/**
* Types text in the specified WebElement.
*
* @param webElement the WebElement to type text in
* @param text the text to enter in the {@link WebElement} field
*/
public void typeTextInWebElement(WebElement webElement, String text){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "typeTextInWebElement("+webElement+", \""+text+"\")");
}
clickOnWebElement(webElement);
dialogUtils.hideSoftKeyboard(null, true, true);
instrumentation.sendStringSync(text);
}
/**
* Clears the value of an EditText.
*
* @param index the index of the {@link EditText} to clear. 0 if only one is available
*/
public void clearEditText(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clearEditText("+index+")");
}
textEnterer.setEditText(waiter.waitForAndGetView(index, EditText.class), "");
}
/**
* Clears the value of an EditText.
*
* @param editText the {@link EditText} to clear
*/
public void clearEditText(EditText editText) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clearEditText("+editText+")");
}
editText = (EditText) waiter.waitForView(editText, Timeout.getSmallTimeout());
textEnterer.setEditText(editText, "");
}
/**
* Clears text in a WebElement matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
*/
public void clearTextInWebElement(By by){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clearTextInWebElement("+by+")");
}
webUtils.enterTextIntoWebElement(by, "");
}
/**
* Clicks an ImageView matching the specified index.
*
* @param index the index of the {@link ImageView} to click. {@code 0} if only one is available
*/
public void clickOnImage(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clickOnImage("+index+")");
}
clicker.clickOn(ImageView.class, index);
}
/**
* Returns an EditText matching the specified index.
*
* @param index the index of the {@link EditText}. {@code 0} if only one is available
* @return an {@link EditText} matching the specified index
*/
public EditText getEditText(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getEditText("+index+")");
}
return getter.getView(EditText.class, index);
}
/**
* Returns a Button matching the specified index.
*
* @param index the index of the {@link Button}. {@code 0} if only one is available
* @return a {@link Button} matching the specified index
*/
public Button getButton(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getButton("+index+")");
}
return getter.getView(Button.class, index);
}
/**
* Returns a TextView matching the specified index.
*
* @param index the index of the {@link TextView}. {@code 0} if only one is available
* @return a {@link TextView} matching the specified index
*/
public TextView getText(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getText("+index+")");
}
return getter.getView(TextView.class, index);
}
/**
* Returns an ImageView matching the specified index.
*
* @param index the index of the {@link ImageView}. {@code 0} if only one is available
* @return an {@link ImageView} matching the specified index
*/
public ImageView getImage(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getImage("+index+")");
}
return getter.getView(ImageView.class, index);
}
/**
* Returns an ImageButton matching the specified index.
*
* @param index the index of the {@link ImageButton}. {@code 0} if only one is available
* @return the {@link ImageButton} matching the specified index
*/
public ImageButton getImageButton(int index) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getImageButton("+index+")");
}
return getter.getView(ImageButton.class, index);
}
/**
* Returns a TextView displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @return the {@link TextView} displaying the specified text
*/
public TextView getText(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getText(\""+text+"\")");
}
return getter.getView(TextView.class, text, false);
}
/**
* Returns a TextView displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @param onlyVisible {@code true} if only visible texts on the screen should be returned
* @return the {@link TextView} displaying the specified text
*/
public TextView getText(String text, boolean onlyVisible)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getText(\""+text+"\", "+onlyVisible+")");
}
return getter.getView(TextView.class, text, onlyVisible);
}
/**
* Returns a Button displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @return the {@link Button} displaying the specified text
*/
public Button getButton(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getButton(\""+text+"\")");
}
return getter.getView(Button.class, text, false);
}
/**
* Returns a Button displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @param onlyVisible {@code true} if only visible buttons on the screen should be returned
* @return the {@link Button} displaying the specified text
*/
public Button getButton(String text, boolean onlyVisible)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getButton(\""+text+"\", "+onlyVisible+")");
}
return getter.getView(Button.class, text, onlyVisible);
}
/**
* Returns an EditText displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @return the {@link EditText} displaying the specified text
*/
public EditText getEditText(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getEditText(\""+text+"\")");
}
return getter.getView(EditText.class, text, false);
}
/**
* Returns an EditText displaying the specified text.
*
* @param text the text that is displayed, specified as a regular expression
* @param onlyVisible {@code true} if only visible EditTexts on the screen should be returned
* @return the {@link EditText} displaying the specified text
*/
public EditText getEditText(String text, boolean onlyVisible)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getEditText(\""+text+"\", "+onlyVisible+")");
}
return getter.getView(EditText.class, text, onlyVisible);
}
/**
* Returns a View matching the specified resource id.
*
* @param id the R.id of the {@link View} to return
* @return a {@link View} matching the specified id
*/
public View getView(int id){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView("+id+")");
}
return getView(id, 0);
}
/**
* Returns a View matching the specified resource id and index.
*
* @param id the R.id of the {@link View} to return
* @param index the index of the {@link View}. {@code 0} if only one is available
* @return a {@link View} matching the specified id and index
*/
public View getView(int id, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView("+id+", "+index+")");
}
View viewToReturn = getter.getView(id, index);
if(viewToReturn == null) {
String resourceName = "";
try {
resourceName = instrumentation.getTargetContext().getResources().getResourceEntryName(id);
} catch (Exception e) {
Log.d(config.commandLoggingTag, "unable to get resource entry name for ("+id+")");
}
int match = index + 1;
if(match > 1){
Log.e(LOG_TAG, match + " Views with id: '" + id + "', resource name: '" + resourceName + "' are not found!");
}
else {
Log.e(LOG_TAG, "View with id: '" + id + "', resource name: '" + resourceName + "' is not found!");
}
}
return viewToReturn;
}
/**
* Returns a View matching the specified tag.
*
* @param tag the tag of the {@link View} to return
* @return a {@link View} matching the specified id
*/
public View getView(Object tag){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView("+tag+")");
}
return getView(tag, 0);
}
/**
* Returns a View matching the specified tag and index.
*
* @param tag the tag of the {@link View} to return
* @param index the index of the {@link View}. {@code 0} if only one is available
* @return a {@link View} matching the specified id and index
*/
public View getView(Object tag, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView("+tag+", "+index+")");
}
View viewToReturn = getter.getView(tag, index);
if(viewToReturn == null) {
int match = index + 1;
if(match > 1){
Log.e(LOG_TAG, match + " Views with id: '" + tag + "' are not found!");
}
else {
Log.e(LOG_TAG, "View with id: '" + tag + "' is not found!");
}
}
return viewToReturn;
}
/**
* Returns a View matching the specified resource id.
*
* @param id the id of the {@link View} to return
* @return a {@link View} matching the specified id
*/
public View getView(String id){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView(\""+id+"\")");
}
return getView(id, 0);
}
private void clearClickeds() {
clicker.clickeds.clear();
}
private void putClickeds(int id, int[] xy) {
clicker.clickeds.put(id, xy);
}
/**
* Returns a View matching the specified resource id and index.
*
* @param id the id of the {@link View} to return
* @param index the index of the {@link View}. {@code 0} if only one is available
* @return a {@link View} matching the specified id and index
*/
public View getView(String id, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView(\""+id+"\", "+index+")");
}
View viewToReturn = getter.getView(id, index);
if(viewToReturn == null) {
int match = index + 1;
if(match > 1){
Log.e(LOG_TAG, match + " Views with id: '" + id + "' are not found!");
}
else {
Log.e(LOG_TAG, "View with id: '" + id + "' is not found!");
}
}
return viewToReturn;
}
/**
* Returns a View matching the specified class and index.
*
* @param viewClass the class of the requested view
* @param index the index of the {@link View}. {@code 0} if only one is available
* @return a {@link View} matching the specified class and index
*/
public <T extends View> T getView(Class<T> viewClass, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getView("+viewClass+", "+index+")");
}
return waiter.waitForAndGetView(index, viewClass);
}
/**
* Returns a WebElement matching the specified By object and index.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @param index the index of the {@link WebElement}. {@code 0} if only one is available
* @return a {@link WebElement} matching the specified index
*/
public WebElement getWebElement(By by, int index){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getWebElement("+by+", "+index+")");
}
int match = index + 1;
WebElement webElement = waiter.waitForWebElement(by, match, Timeout.getSmallTimeout(), true);
if(webElement == null) {
if(match > 1){
Log.e(LOG_TAG, match + " WebElements with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' are not found!");
}
else {
Log.e(LOG_TAG, "WebElement with " + webUtils.splitNameByUpperCase(by.getClass().getSimpleName()) + ": '" + by.getValue() + "' is not found!");
}
}
return webElement;
}
/**
* Returns the current web page URL.
*
* @return the current web page URL
*/
public String getWebUrl() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getWebUrl()");
}
final WebView webView = waiter.waitForAndGetView(0, WebView.class);
if(webView == null) {
Log.w(config.commandLoggingTag, "WebView is not found!");
return "";
}
instrumentation.runOnMainSync(new Runnable() {
public void run() {
webUrl = webView.getUrl();
}
});
return webUrl;
}
/**
* Waits for the current web page URL.
* @param timeout the the amount of time in milliseconds to wait
* @return the current web page URL
*/
public String waitForWebUrl(int timeout){
String url = "";
long endTime = SystemClock.uptimeMillis() + (long)timeout;
while (SystemClock.uptimeMillis() <= endTime) {
sleeper.sleepMini();
url = getWebUrl();
if (!TextUtils.isEmpty(url)) {
return url;
}
}
return url;
}
/**
* Return a list of the tasks that are currently running.
* @param context context
* @return ComponentName Return only the top one.
*/
public ComponentName getRunningTask(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
return am.getRunningTasks(1).get(0).topActivity;
}
/**
* HandleEditText, e.g: type text or type number.
* @param view
*/
public void handleEditText(View view){
if (view instanceof EditText){
EditText editText = (EditText)view;
typeText(editText);
}
}
/**
* HandleWeb, click everyone WebElement.
* @param activity current activity
* @param params start activity params
* @throws ClassNotFoundException
*/
public void handleWeb(String activity, Map<String, Object> params) throws ClassNotFoundException{
String url = waitForWebUrl(config.sleepDuration * 20);
Log.i(LOG_TAG, url);
if (!TextUtils.isEmpty(url)) {
ArrayList<WebElement> webElements = getCurrentWebElements();
for (WebElement element: webElements) {
if (element.getTagName().toUpperCase().contains("INPUT")) {
typeTextInWebElement(By.tagName(element.getTagName()), RandomUtils.getRandomText(10));
sleep(config.sleepDuration * 2);
} else {
takeScreenshot(activity, element);
clickOnWebElement(element);
sleep(config.sleepDuration * 2);
}
String tmp = waitForWebUrl(config.sleepDuration * 20);
if (!tmp.equals(url)) {
goBack();
if (!getCurrentActivity().toString().contains(activity)) {
startActivity(context, params, activity);
sleep(config.sleepDuration);
if (TextUtils.isEmpty(waitForWebUrl(config.sleepDuration * 10))) break;
}
}
}
} else
Log.w(LOG_TAG, activity + " WebUrl is null, " + "timeout.");
}
public void scrollLeft() {
sleep(config.sleepDuration);
drag(width / 2, 0, height / 2, height / 2, 1);
sleep(config.sleepDuration);
}
public void scrollRight() {
sleep(config.sleepDuration);
drag(width / 2, width, height / 2, height / 2, 1);
sleep(config.sleepDuration);
}
/**
* Pull down to refresh for ListView or RecyclerView
* @param timeout timeout
*/
public void pullDown(int timeout){
Log.d(LOG_TAG, "Pull-down refresh.");
sleep(config.sleepDuration);
drag(width / 2, width / 2, height / 2, height, 20);
sleep(timeout);
}
/**
* Pull up to refresh for ListView or RecyclerView
* @param timeout timeout
*/
public void pullUp(int timeout){
Log.d(LOG_TAG, "Pull-up loading.");
sleep(config.sleepDuration);
drag(width / 2, width / 2, height / 2, 0, 20);
sleep(timeout);
}
/**
* Returns the maximum length of the editText
* @param editText editText
* @return maximum length
*/
private int getMaxLengthForEditText(EditText editText){
int maxLength = 0;
for (InputFilter inputFilter : editText.getFilters()) {
if (inputFilter instanceof InputFilter.LengthFilter) {
Class<InputFilter.LengthFilter> clazz = InputFilter.LengthFilter.class;
try {
Field maxField = clazz.getDeclaredField("mMax");
maxField.setAccessible(true);
return (int) maxField.get(inputFilter);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return maxLength;
}
/**
* Returns an ArrayList of the Views currently displayed in the focused Activity or Dialog.
*
* @return an {@code ArrayList} of the {@link View} objects currently displayed in the
* focused window
*/
public ArrayList<View> getCurrentViews() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentViews()");
}
return viewFetcher.getViews(null, true);
}
/**
* Returns an ArrayList of Views matching the specified class located in the focused Activity or Dialog.
*
* @param classToFilterBy return all instances of this class. Examples are: {@code Button.class} or {@code ListView.class}
* @return an {@code ArrayList} of {@code View}s matching the specified {@code Class} located in the current {@code Activity}
*/
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentViews("+classToFilterBy+")");
}
return viewFetcher.getCurrentViews(classToFilterBy, true);
}
/**
* Returns an ArrayList of Views matching the specified class located in the focused Activity or Dialog.
*
* @param classToFilterBy return all instances of this class. Examples are: {@code Button.class} or {@code ListView.class}
* @param includeSubclasses include instances of the subclasses in the {@code ArrayList} that will be returned
* @return an {@code ArrayList} of {@code View}s matching the specified {@code Class} located in the current {@code Activity}
*/
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, boolean includeSubclasses) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentViews("+classToFilterBy+", "+includeSubclasses+")");
}
return viewFetcher.getCurrentViews(classToFilterBy, includeSubclasses);
}
/**
* Returns an ArrayList of Views matching the specified class located under the specified parent.
*
* @param classToFilterBy return all instances of this class. Examples are: {@code Button.class} or {@code ListView.class}
* @param parent the parent {@code View} for where to start the traversal
* @return an {@code ArrayList} of {@code View}s matching the specified {@code Class} located under the specified {@code parent}
*/
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, View parent) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentViews("+classToFilterBy+", "+parent+")");
}
return viewFetcher.getCurrentViews(classToFilterBy, true, parent);
}
/**
* Returns an ArrayList of Views matching the specified class located under the specified parent.
*
* @param classToFilterBy return all instances of this class. Examples are: {@code Button.class} or {@code ListView.class}
* @param includeSubclasses include instances of subclasses in the {@code ArrayList} that will be returned
* @param parent the parent {@code View} for where to start the traversal
* @return an {@code ArrayList} of {@code View}s matching the specified {@code Class} located under the specified {@code parent}
*/
public <T extends View> ArrayList<T> getCurrentViews(Class<T> classToFilterBy, boolean includeSubclasses, View parent) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentViews("+classToFilterBy+", "+includeSubclasses+", "+parent+")");
}
return viewFetcher.getCurrentViews(classToFilterBy, includeSubclasses, parent);
}
/**
* Returns an ArrayList of all the WebElements displayed in the active WebView.
*
* @return an {@code ArrayList} of all the {@link WebElement} objects currently displayed in the active WebView
*/
public ArrayList<WebElement> getWebElements(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getWebElements()");
}
return webUtils.getWebElements(false);
}
/**
* Returns an ArrayList of all the WebElements displayed in the active WebView matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @return an {@code ArrayList} of all the {@link WebElement} objects displayed in the active WebView
*/
public ArrayList<WebElement> getWebElements(By by){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getWebElements("+by+")");
}
return webUtils.getWebElements(by, false);
}
/**
* Returns an ArrayList of the currently displayed WebElements in the active WebView.
*
* @return an {@code ArrayList} of the {@link WebElement} objects displayed in the active WebView
*/
public ArrayList<WebElement> getCurrentWebElements(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentWebElements()");
}
return webUtils.getWebElements(true);
}
/**
* Returns an ArrayList of the currently displayed WebElements in the active WebView matching the specified By object.
*
* @param by the By object. Examples are: {@code By.id("id")} and {@code By.name("name")}
* @return an {@code ArrayList} of the {@link WebElement} objects currently displayed in the active WebView
*/
public ArrayList<WebElement> getCurrentWebElements(By by){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getCurrentWebElements("+by+")");
}
return webUtils.getWebElements(by, true);
}
/**
* Checks if a RadioButton matching the specified index is checked.
*
* @param index of the {@link RadioButton} to check. {@code 0} if only one is available
* @return {@code true} if {@link RadioButton} is checked and {@code false} if it is not checked
*/
public boolean isRadioButtonChecked(int index)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isRadioButtonChecked("+index+")");
}
return checker.isButtonChecked(RadioButton.class, index);
}
/**
* Checks if a RadioButton displaying the specified text is checked.
*
* @param text the text that the {@link RadioButton} displays, specified as a regular expression
* @return {@code true} if a {@link RadioButton} matching the specified text is checked and {@code false} if it is not checked
*/
public boolean isRadioButtonChecked(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isRadioButtonChecked(\""+text+"\")");
}
return checker.isButtonChecked(RadioButton.class, text);
}
/**
* Checks if a CheckBox matching the specified index is checked.
*
* @param index of the {@link CheckBox} to check. {@code 0} if only one is available
* @return {@code true} if {@link CheckBox} is checked and {@code false} if it is not checked
*/
public boolean isCheckBoxChecked(int index)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isCheckBoxChecked("+index+")");
}
return checker.isButtonChecked(CheckBox.class, index);
}
/**
* Checks if a ToggleButton displaying the specified text is checked.
*
* @param text the text that the {@link ToggleButton} displays, specified as a regular expression
* @return {@code true} if a {@link ToggleButton} matching the specified text is checked and {@code false} if it is not checked
*/
public boolean isToggleButtonChecked(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isToggleButtonChecked(\""+text+"\")");
}
return checker.isButtonChecked(ToggleButton.class, text);
}
/**
* Checks if a ToggleButton matching the specified index is checked.
*
* @param index of the {@link ToggleButton} to check. {@code 0} if only one is available
* @return {@code true} if {@link ToggleButton} is checked and {@code false} if it is not checked
*/
public boolean isToggleButtonChecked(int index)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isToggleButtonChecked("+index+")");
}
return checker.isButtonChecked(ToggleButton.class, index);
}
/**
* Checks if a CheckBox displaying the specified text is checked.
*
* @param text the text that the {@link CheckBox} displays, specified as a regular expression
* @return {@code true} if a {@link CheckBox} displaying the specified text is checked and {@code false} if it is not checked
*/
public boolean isCheckBoxChecked(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isCheckBoxChecked(\""+text+"\")");
}
return checker.isButtonChecked(CheckBox.class, text);
}
/**
* Checks if the specified text is checked.
*
* @param text the text that the {@link CheckedTextView} or {@link CompoundButton} objects display, specified as a regular expression
* @return {@code true} if the specified text is checked and {@code false} if it is not checked
*/
@SuppressWarnings("unchecked")
public boolean isTextChecked(String text){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isTextChecked(\""+text+"\")");
}
waiter.waitForViews(false, CheckedTextView.class, CompoundButton.class);
if(viewFetcher.getCurrentViews(CheckedTextView.class, true).size() > 0 && checker.isCheckedTextChecked(text))
return true;
if(viewFetcher.getCurrentViews(CompoundButton.class, true).size() > 0 && checker.isButtonChecked(CompoundButton.class, text))
return true;
return false;
}
/**
* Checks if the specified text is selected in any Spinner located in the current screen.
*
* @param text the text that is expected to be selected, specified as a regular expression
* @return {@code true} if the specified text is selected in any {@link Spinner} and false if it is not
*/
public boolean isSpinnerTextSelected(String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isSpinnerTextSelected(\""+text+"\")");
}
return checker.isSpinnerTextSelected(text);
}
/**
* Checks if the specified text is selected in a Spinner matching the specified index.
*
* @param index the index of the spinner to check. {@code 0} if only one spinner is available
* @param text the text that is expected to be selected, specified as a regular expression
* @return {@code true} if the specified text is selected in the specified {@link Spinner} and false if it is not
*/
public boolean isSpinnerTextSelected(int index, String text)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "isSpinnerTextSelected("+index+",\""+text+"\")");
}
return checker.isSpinnerTextSelected(index, text);
}
/**
* Hides the soft keyboard.
*/
public void hideSoftKeyboard() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "hideSoftKeyboard()");
}
dialogUtils.hideSoftKeyboard(null, true, false);
}
/**
* Unlocks the lock screen.
*/
public void unlockScreen(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "unlockScreen()");
}
final Activity activity = activityUtils.getCurrentActivity(false);
instrumentation.runOnMainSync(new Runnable() {
@Override
public void run() {
if(activity != null){
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
}
}
});
}
/**
* Sends a key: Right, Left, Up, Down, Enter, Menu or Delete.
*
* @param key the key to be sent. Use {@code }{@link #RIGHT}, {@link #LEFT}, {@link #UP}, {@link #DOWN},
* {@link #ENTER}, {@link #MENU}, {@link #DELETE}
*/
public void sendKey(int key)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "sendKey("+key+")");
}
sender.sendKeyCode(key);
}
/**
* Returns to an Activity matching the specified name.
*
* @param name the name of the {@link Activity} to return to. Example is: {@code "MyActivity"}
*/
public void goBackToActivity(String name) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "goBackToActivity(\""+name+"\")");
}
activityUtils.goBackToActivity(name);
}
private void goBackToActivitySync(String name) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
goBackToActivity(name);
}
});
thread.start();
sleep(config.sleepDuration);
thread.interrupt();
}
/**
* Finish the activity.
* @param activity
*/
public void finish(String activity) {
if (config.homeActivity.contains(activity))return;
ArrayList<Activity> activitiesOpened = activityUtils.getAllOpenedActivities();
for (Activity activity2 : activitiesOpened) {
if (activity.contains(activity2.getClass().getSimpleName())) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "finish("+activity2.getClass().getSimpleName()+")");
}
//Main thread finish activity.
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
activity2.finish();
}
});
}
}
clearClickeds();
}
/**
* Waits for an Activity matching the specified name. Default timeout is 20 seconds.
*
* @param name the name of the {@code Activity} to wait for. Example is: {@code "MyActivity"}
* @return {@code true} if {@code Activity} appears before the timeout and {@code false} if it does not
*/
public boolean waitForActivity(String name){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForActivity(\""+name+"\")");
}
return waiter.waitForActivity(name, Timeout.getLargeTimeout());
}
/**
* Waits for an Activity matching the specified name.
*
* @param name the name of the {@link Activity} to wait for. Example is: {@code "MyActivity"}
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if {@link Activity} appears before the timeout and {@code false} if it does not
*/
public boolean waitForActivity(String name, int timeout)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForActivity(\""+name+"\", "+timeout+")");
}
return waiter.waitForActivity(name, timeout);
}
/**
* Waits for an Activity matching the specified class. Default timeout is 20 seconds.
*
* @param activityClass the class of the {@code Activity} to wait for. Example is: {@code MyActivity.class}
* @return {@code true} if {@code Activity} appears before the timeout and {@code false} if it does not
*/
public boolean waitForActivity(Class<? extends Activity> activityClass){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForActivity("+activityClass+")");
}
return waiter.waitForActivity(activityClass, Timeout.getLargeTimeout());
}
/**
* Waits for an Activity matching the specified class.
*
* @param activityClass the class of the {@code Activity} to wait for. Example is: {@code MyActivity.class}
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if {@link Activity} appears before the timeout and {@code false} if it does not
*/
public boolean waitForActivity(Class<? extends Activity> activityClass, int timeout)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForActivity("+activityClass+", "+timeout+")");
}
return waiter.waitForActivity(activityClass, timeout);
}
/**
* Wait for the activity stack to be empty.
*
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if activity stack is empty before the timeout and {@code false} if it is not
*/
public boolean waitForEmptyActivityStack(int timeout)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForEmptyActivityStack("+timeout+")");
}
return waiter.waitForCondition(
new Condition(){
@Override
public boolean isSatisfied() {
return activityUtils.isActivityStackEmpty();
}
}, timeout);
}
/**
* Waits for a Fragment matching the specified tag. Default timeout is 20 seconds.
*
* @param tag the name of the tag
* @return {@code true} if fragment appears and {@code false} if it does not appear before the timeout
*/
public boolean waitForFragmentByTag(String tag){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForFragmentByTag(\""+tag+"\")");
}
return waiter.waitForFragment(tag, 0, Timeout.getLargeTimeout());
}
/**
* Waits for a Fragment matching the specified tag.
*
* @param tag the name of the tag
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if fragment appears and {@code false} if it does not appear before the timeout
*/
public boolean waitForFragmentByTag(String tag, int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForFragmentByTag(\""+tag+"\", "+timeout+")");
}
return waiter.waitForFragment(tag, 0, timeout);
}
/**
* Waits for a Fragment matching the specified resource id. Default timeout is 20 seconds.
*
* @param id the R.id of the fragment
* @return {@code true} if fragment appears and {@code false} if it does not appear before the timeout
*/
public boolean waitForFragmentById(int id){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForFragmentById("+id+")");
}
return waiter.waitForFragment(null, id, Timeout.getLargeTimeout());
}
/**
* Waits for a Fragment matching the specified resource id.
*
* @param id the R.id of the fragment
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if fragment appears and {@code false} if it does not appear before the timeout
*/
public boolean waitForFragmentById(int id, int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForFragmentById("+id+", "+timeout+")");
}
return waiter.waitForFragment(null, id, timeout);
}
/**
* Waits for the specified log message to appear. Default timeout is 20 seconds.
* Requires read logs permission (android.permission.READ_LOGS) in AndroidManifest.xml of the application under test.
*
* @param logMessage the log message to wait for
* @return {@code true} if log message appears and {@code false} if it does not appear before the timeout
*
* @see #clearLog()
*/
public boolean waitForLogMessage(String logMessage){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForLogMessage(\""+logMessage+"\")");
}
return waiter.waitForLogMessage(logMessage, Timeout.getLargeTimeout());
}
/**
* Waits for the specified log message to appear.
* Requires read logs permission (android.permission.READ_LOGS) in AndroidManifest.xml of the application under test.
*
* @param logMessage the log message to wait for
* @param timeout the amount of time in milliseconds to wait
* @return {@code true} if log message appears and {@code false} if it does not appear before the timeout
*
* @see #clearLog()
*/
public boolean waitForLogMessage(String logMessage, int timeout){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "waitForLogMessage(\""+logMessage+"\", "+timeout+")");
}
return waiter.waitForLogMessage(logMessage, timeout);
}
/**
* Clears the log.
*/
public void clearLog(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "clearLog()");
}
waiter.clearLog();
}
/**
* Returns a localized String matching the specified resource id.
*
* @param id the R.id of the String
* @return the localized String
*/
public String getString(int id)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getString("+id+")");
}
return getter.getString(id);
}
/**
* Returns a localized String matching the specified resource id.
*
* @param id the id of the String
* @return the localized String
*/
public String getString(String id)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "getString(\""+id+"\")");
}
return getter.getString(id);
}
/**
* Robotium will sleep for the specified time.
*
* @param time the time in milliseconds that Robotium should sleep
*/
public void sleep(int time)
{
if(config.commandLogging){
Log.d(config.commandLoggingTag, "sleep("+time+")");
}
sleeper.sleep(time);
}
/**
*
* Finalizes the Solo object and removes the ActivityMonitor.
*
* @see #finishOpenedActivities() finishOpenedActivities() to close the activities that have been active
*/
public void finalize() throws Throwable {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "finalize()");
}
activityUtils.finalize();
}
/**
* The Activities that are alive are finished. Usually used in tearDown().
*/
public void finishOpenedActivities(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "finishOpenedActivities()");
}
activityUtils.finishOpenedActivities();
application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
/**
* Takes a screenshot and saves it in the {@link Config} objects save path (default set to: /sdcard/Robotium-Screenshots/).
* Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
*/
public void takeScreenshot(){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "takeScreenshot()");
}
takeScreenshot(null);
}
/**
* Takes a screenshot and saves it with the specified name in the {@link Config} objects save path (default set to: /sdcard/Robotium-Screenshots/).
* Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
*
* @param name the name to give the screenshot
*/
public void takeScreenshot(String name){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "takeScreenshot(\""+name+"\")");
}
takeScreenshot(name, 100);
}
/**
* Takes a screenshot and saves the image with the specified name in the {@link Config} objects save path (default set to: /sdcard/Robotium-Screenshots/).
* Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
*
* @param name the name to give the screenshot
* @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality)
*/
public void takeScreenshot(String name, int quality){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "takeScreenshot(\""+name+"\", "+quality+")");
}
screenshotTaker.takeScreenshot(name, quality);
}
/**
*
* @param name name the name to give the screenshot
* @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality)
* @param watermark_x the watermark x coordinate
* @param watermark_y the watermark y coordinate
*/
public void takeScreenshot(String name, int quality, float watermark_x, float watermark_y){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "takeScreenshot(\""+name+"\", "+quality+")");
}
screenshotTaker.takeScreenshot(name, quality, watermark_x, watermark_y);
}
/**
*
* @param name name the name to give the screenshot
*/
public void takeScreenshot(String name, WebElement element){
if(config.commandLogging){
Log.d(config.commandLoggingTag, "takeScreenshot(\""+name+"\", "+element+")");
}
if (element == null)return;
int xy[] = new int[2];
element.getLocationOnScreen(xy);
float watermark_x = element.getLocationX();
float watermark_y = element.getLocationY();
screenshotTaker.takeScreenshot(name + "/" + TimeUtils.getDate(), 20, watermark_x, watermark_y);
}
/**
* Takes a screenshot sequence and saves the images with the specified name prefix in the {@link Config} objects save path (default set to: /sdcard/Robotium-Screenshots/).
*
* The name prefix is appended with "_" + sequence_number for each image in the sequence,
* where numbering starts at 0.
*
* Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in AndroidManifest.xml of the application under test.
*
* At present multiple simultaneous screenshot sequences are not supported.
* This method will throw an exception if stopScreenshotSequence() has not been
* called to finish any prior sequences.
* Calling this method is equivalend to calling startScreenshotSequence(name, 80, 400, 100);
*
* @param name the name prefix to give the screenshot
*/
public void startScreenshotSequence(String name) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "startScreenshotSequence(\""+name+"\")");
}
startScreenshotSequence(name,
80, // quality
400, // 400 ms frame delay
100); // max frames
}
/**
* Takes a screenshot sequence and saves the images with the specified name prefix in the {@link Config} objects save path (default set to: /sdcard/Robotium-Screenshots/).
*
* The name prefix is appended with "_" + sequence_number for each image in the sequence,
* where numbering starts at 0.
*
* Requires write permission (android.permission.WRITE_EXTERNAL_STORAGE) in the
* AndroidManifest.xml of the application under test.
*
* Taking a screenshot will take on the order of 40-100 milliseconds of time on the
* main UI thread. Therefore it is possible to mess up the timing of tests if
* the frameDelay value is set too small.
*
* At present multiple simultaneous screenshot sequences are not supported.
* This method will throw an exception if stopScreenshotSequence() has not been
* called to finish any prior sequences.
*
* @param name the name prefix to give the screenshot
* @param quality the compression rate. From 0 (compress for lowest size) to 100 (compress for maximum quality)
* @param frameDelay the time in milliseconds to wait between each frame
* @param maxFrames the maximum number of frames that will comprise this sequence
*/
public void startScreenshotSequence(String name, int quality, int frameDelay, int maxFrames) {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "startScreenshotSequence(\""+name+"\", "+quality+", "+frameDelay+", "+maxFrames+")");
}
screenshotTaker.startScreenshotSequence(name, quality, frameDelay, maxFrames);
}
/**
* Causes a screenshot sequence to end.
*
* If this method is not called to end a sequence and a prior sequence is still in
* progress, startScreenshotSequence() will throw an exception.
*/
public void stopScreenshotSequence() {
if(config.commandLogging){
Log.d(config.commandLoggingTag, "stopScreenshotSequence()");
}
screenshotTaker.stopScreenshotSequence();
}
/**
* Start the Monitor application
*/
public void startApplication(ComponentName cn) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.putExtra("package", cn.getPackageName());
intent.setComponent(cn);
Log.i(LOG_TAG, "start monitor application: " + cn.getPackageName() + "/" + cn.getClassName());
context.startActivity(intent);
}
/**
* Start the Monitor service
*/
public void startService(ComponentName cn) {
Intent intent = new Intent();
intent.setComponent(cn);
intent.putExtra("package", cn.getPackageName());
Log.i(LOG_TAG, "start monitor service: " + cn.getPackageName() + "/" + cn.getClassName());
context.startService(intent);
}
/**
* Start target activity
* Returns if retry >= 3, abandon start the target activity.
*/
private boolean startActivity(Context context, Map<String, Object> params, String activity) throws ClassNotFoundException {
Class<?> target;
target = Class.forName(activity);
context.startActivity(IntentParser.getIntent(context, target, params));
sleep(config.sleepDuration);
hideSoftKeyboard();
Log.i(LOG_TAG, "start activity: " + activity);
// In some cases, the target activity will be overwritten and will need to be retried.
int retry = 0;
while (retry < 3){
String[] strings = activity.split("\\.");
boolean result = waitForActivity(strings[strings.length - 1], 2000);
if (!result){
finish(activity);
context.startActivity(IntentParser.getIntent(context, target, params));
sleep(config.sleepDuration);
hideSoftKeyboard();
Log.i(LOG_TAG, "restart activity: " + activity);
retry++;
}else break;
}
currentActivity = getCurrentActivity();
if (currentActivity == null) {
throw new NullPointerException("currentActivity is null.");
}
// Abandon start the target activity, failure to start the target activity may be due to protocol jumps.
if (retry >= 3) Log.e(LOG_TAG, "Abandon start " + activity + " , failure to start the target activity may be due to protocol jumps.");
return retry >= 3;
}
/**
* Reads the json file, generates the parameters required for the iteration, and ignores part of the activity.
* @throws Exception
*/
private void handleParams() throws Exception {
String json = FileUtils.readJson();
Log.i(LOG_TAG, "json: " + json);
ArrayList<ParamsEntity> arrayList = filterActivities(JsonParser.fromJson(json), activityUtils.getAllActivities(context));
Log.d(LOG_TAG, "Params size: " + arrayList.size());
for (ParamsEntity params : arrayList) {
boolean isIgnore = false;
Log.d(LOG_TAG, "Activity: " + params.getName());
if(config.mode == Config.Mode.REPTILE && params.getName().contains(config.homeActivity))continue;
for (String name: config.ignoreActivities) {
if (params.getName().contains(name)) {
isIgnore = true;
break;
}
}
Log.d(LOG_TAG, "Ignore: " + isIgnore);
if (!isIgnore) {
Map<String, Object> hashMap = new HashMap<String, Object>();
for (ParamEntity param : params.getParams()) {
Log.d(LOG_TAG, param.getKey() + " " + param.getValue());
hashMap.put(param.getKey().split("\\|")[1], IntentParser.parseType(param.getValue()));
}
iteration(params.getName(), hashMap.isEmpty() ? null : hashMap,
config.mode != Config.Mode.FAST && params.isIteration(), params.isWeb());
}
}
}
/**
* If the activity not in target application, remove it.
*/
private ArrayList<ParamsEntity> filterActivities(ArrayList<ParamsEntity> arrayList, ActivityInfo[] activities) {
ArrayList<ParamsEntity> filterActivities = new ArrayList<>();
for (ParamsEntity params : arrayList) {
for (ActivityInfo activity: activities){
if (activity.name.contains(params.getName())) filterActivities.add(params);
}
}
return filterActivities;
}
/**
* Handle Login.
* By reading the configuration file to complete the operation.
*/
private void handleLogin(){
String[] strings = config.loginActivity.split("\\.");
waitForActivity(strings[strings.length - 1]);
sleep(config.sleepDuration);
clearEditText(0);
sleep(100);
typeText(0, config.loginAccount);
sleep(100);
clearEditText(1);
sleep(100);
typeText(1, config.loginPassword);
clickOnView(getView(config.loginId));
strings = config.homeActivity.split("\\.");
boolean result = waitForActivity(strings[strings.length - 1]);
if (!result) {
takeScreenshot();
try {
startActivity(context, null, config.loginActivity);
handleLogin();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
/**
* Login operation.
* @throws Exception
*/
public void login() throws Exception{
String[] strings = config.homeActivity.split("\\.");
String homeActivity = strings[strings.length - 1];
boolean isSuccess = waitForActivity(homeActivity, 5000);
// 如果等不到主页则迭代当前页面(可能为授权页面弹框)
if (!isSuccess) {
iterationNode(null, "", null);
isSuccess = waitForActivity(homeActivity, 3000);
// 处理完弹框还等待不到主页,则可能是进入了引导页面
while (!isSuccess){
scrollLeft();
iterationNode(null, "", null);
isSuccess = waitForActivity(homeActivity, 2000);
}
}
isSuccess = waitForActivity(homeActivity, 5000);
// 可能存在类似新手引导的界面覆盖了主页,finish
if (!isSuccess) finish(getCurrentActivity().getComponentName().getClassName());
startActivity(context, null, config.loginActivity);
handleLogin();
}
/**
* Fast mode or Normal mode.
* @throws Exception
*/
private void FastOrNormalMode() throws Exception{
String[] strings = config.homeActivity.split("\\.");
waitForActivity(strings[strings.length - 1]);
sleep(config.sleepDuration * 6);
if (!FileUtils.existsJson()) {
JsonParser.createJson();
}
handleParams();
}
/**
* Reptile mode.
* @throws Exception
*/
private void reptileMode() throws Exception{
String[] strings = config.homeActivity.split("\\.");
waitForActivity(strings[strings.length - 1]);
sleep(config.sleepDuration * 10);
if (config.newReptile) {
iteration(config.homeActivity, null, true, false);
loopReptile();
} else {
boolean result = FileUtils.existsJson();
if (!result) throw new RuntimeException(config.homeActivity +
" may be happen crash, please check the log.");
handleParams();
loopReptile();
}
}
private void loopReptile() throws Exception{
JsonParser.updateJson();
while (FileUtils.existsJson()) {
handleParams();
JsonParser.updateJson();
}
}
private void checkResult() {
if (config.mode == Config.Mode.RECORD) return;
boolean isExists = FileUtils.existsForActivity();
if (!isExists) return;
boolean isFinish = JsonParser.isFinish();
Log.i(LOG_TAG, "Finish: " + isFinish);
if (!isFinish) {
JsonParser.updateParams();
} else {
quitMonitor();
}
sleep(config.sleepDuration);
}
/**
* Record mode.
* Sleep 1 hour, during which you can operate the app, record the activity parameters.
*/
private void recordMode() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) Assert.fail("The current api version less than 14.");
sleep(3600 * 1000);
}
private void iterationNode(AccessibilityNodeInfo nodeInfo, String activity, Map<String, Object> params) {
try{
if (nodeInfo == null) nodeInfo = uiAutomation.getRootInActiveWindow();
if (nodeInfo != null) {
for (int i = 0; i < nodeInfo.getChildCount(); i ++){
AccessibilityNodeInfo node = nodeInfo.getChild(i);
if (node == null) node = uiAutomation.getRootInActiveWindow();
performClick(node, activity, params);
}
}
}catch (NullPointerException | StackOverflowError e){
e.printStackTrace();
}
}
private boolean viewInIgnoreViews(String string){
if (string == null) return false;
for (String s: config.ignoreViews) {
if (s.contains(string)) return true;
}
return false;
}
private void performClick(AccessibilityNodeInfo node, String activity, Map<String, Object> params) {
if (node != null && node.isClickable()) {
String string = getBoundsInScreen(node);
boolean clicked = clickeds.contains(string);
boolean status = viewInIgnoreViews(node.getViewIdResourceName());
if (!clicked && !status) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && node.canOpenPopup()) iterationNode(null, activity, params);
if (node.getClassName().toString().contains("android.widget.EditText")) {
String[] id = node.getViewIdResourceName().split("/");
handleEditText(getView(id[id.length - 1]));
sleep(config.sleepMiniDuration);
}
if (config.iterationScreenShots) {
screenshotTaker.takeScreenshotForUiAutomation(string, activity);
}
Log.w(LOG_TAG, node.toString());
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
clickeds.add(string);
sleep(config.sleepDuration);
try {
handleJump(currentActivity, activity, params);
} catch (Exception e) {
e.printStackTrace();
}
}
if (node.getChildCount() > 0) iterationNode(node, activity, params);
} else iterationNode(node, activity, params);
}
private String getBoundsInScreen(AccessibilityNodeInfo node) {
if (node != null) {
Pattern pattern = Pattern.compile("boundsInScreen:.+?;");
Matcher matcher = pattern.matcher(node.toString());
if (matcher.find()) return matcher.group(0);
}
return "";
}
/**
* Start the iteration.
* This method is iterating the total switch, only call it.
* @throws Exception
*/
public void startIteration() throws Exception{
CrashHandler.getInstance().init(instrumentation.getTargetContext().getApplicationContext(),
config);
checkResult();
switch (config.mode) {
case FAST:
FastOrNormalMode();
break;
case NORMAL:
FastOrNormalMode();
break;
case REPTILE:
reptileMode();
break;
case RECORD:
recordMode();
break;
default:
recordMode();
break;
}
}
/**
* Iteration method.
* @param activity the start activity
* @param params start activity params
* @param iteration if true, click everyone view.
* @param isWeb if true, the activity is webView.
* @throws Exception
*/
public void iteration(String activity, Map<String, Object> params, boolean iteration, boolean isWeb) throws Exception {
boolean result = startActivity(context, params, activity);
// Abandon start the target activity, failure to start the target activity may be due to protocol jumps.
if (result) return;
sleep(config.sleepDuration * 4);
Log.i(LOG_TAG, "current activity: " + currentActivity);
FileUtils.writeActivity(TimeUtils.getDate() + " starting iteration: " + activity);
if (config.activityScreenShots) {
String[] s = activity.split("\\.");
takeScreenshot("Activities/" + s[s.length - 1]);
}
if (iteration) {
if (isWeb) handleWeb(activity, params);
if (config.useNative) handleNative(activity, params);
else {
if (Build.VERSION.SDK_INT >= 18) {
if (activity.contains(config.homeActivity)) handleFragmentTabHost(activity, params, true);
else handleViewPager(activity, params, true);
}else handleNative(activity, params);
}
}
finish(activity);
clickeds.clear();
FileUtils.writeActivity(TimeUtils.getDate() + " stopped iteration: " + activity);
if (!config.keepActivitiesScreenShots) FileUtils.deleteScreenShots(activity);
}
/**
* Handle native view
* @param activity
* @param params
* @throws Exception
*/
private void handleNative(String activity, Map<String, Object> params) throws Exception{
handleFragmentTabHost(activity, params, false);
handleViewPager(activity, params, false);
handleRecyclerView(activity, params);
handleListView(activity, params);
handleGridView(activity, params);
handleScrollView(activity, params);
handleOtherView(activity, params);
}
/**
* Loop handle ScrollView, if child is LinearLayout.
* @param view
* @param activity
* @param params
* @throws Exception
*/
private void loopScrollView(View view, String activity, Map<String, Object> params) throws Exception{
ViewGroup viewGroup = (ViewGroup) view;
for (int j = 0; j < viewGroup.getChildCount(); j ++) {
View view1 = viewGroup.getChildAt(j);
Log.d(Solo.LOG_TAG, "ScrollView child child: " + view1);
if (view1 != null) {
if (view1 instanceof LinearLayout) loopScrollView(view1, activity, params);
else {
scrollView(view1, DOWN);
sleep(config.sleepDuration);
handleView(view1, activity, params, waitForDialogToOpen(50));
handleJump(currentActivity, activity, params);
}
}
}
}
/**
* Handle ScrollView
* @param activity
* @param params
* @throws Exception
*/
private void handleScrollView(String activity, Map<String, Object> params) throws Exception{
ArrayList<ScrollView> scrollViews = getCurrentViews(ScrollView.class);
if (scrollViews.size() > 0) {
if (scrollViews.get(0) == null) return;
ScrollView scrollView = scrollViews.get(0);
Log.d(Solo.LOG_TAG, "Find scrollView: " + scrollView.toString());
Log.d(Solo.LOG_TAG, "ScrollView ChildCount: " + scrollView.getChildCount());
for (int i = 0; i < scrollView.getChildCount(); i ++){
View view = scrollView.getChildAt(i);
Log.d(Solo.LOG_TAG, "ScrollView child: " + view);
if (view != null) {
if (!(view instanceof RelativeLayout) && view instanceof ViewGroup)
loopScrollView(view, activity, params);
else {
scrollView(view, DOWN);
sleep(config.sleepDuration);
handleView(view, activity, params, waitForDialogToOpen(50));
handleJump(currentActivity, activity, params);
}
}
}
}
}
/**
* Handle GridView
* @param activity
* @param params
* @throws Exception
*/
private void handleGridView(String activity, Map<String, Object> params) throws Exception{
ArrayList<GridView> gridViews = getCurrentViews(GridView.class);
if (gridViews.size() > 0) {
if (gridViews.get(0) == null) return;
GridView gridView = gridViews.get(0);
Log.d(Solo.LOG_TAG, "Find gridView: " + gridView.toString());
for (int i = 0; i < gridView.getChildCount(); i ++) {
Log.d(Solo.LOG_TAG, "GridView child: " + gridView.getChildAt(i));
handleView(gridView.getChildAt(i), activity, params, false);
boolean isRestart = handleJump(currentActivity, activity, params);
if (isRestart) {
reHandleGridView(activity, params, i);
break;
}
}
}
}
/**
* When the GridView jumps, retry click on GridView.
* @param activity
* @param params
* @param index
* @throws Exception
*/
private void reHandleGridView(String activity, Map<String, Object> params, int index) throws Exception{
ArrayList<GridView> gridViews = getCurrentViews(GridView.class);
if (gridViews.size() > 0) {
if (gridViews.get(0) == null) return;
GridView gridView = gridViews.get(0);
for (int i = 0; i < gridView.getChildCount(); i ++) {
if (i > index) {
Log.d(Solo.LOG_TAG, "GridView child: " + gridView.getChildAt(i));
handleView(gridView.getChildAt(i), activity, params, false);
boolean isRestart = handleJump(currentActivity, activity, params);
if (isRestart) {
reHandleGridView(activity, params, i);
break;
}
}
}
}
}
/**
* Handle Other View e.g: TextView, ImageView, Button, EditText ...
* @param activity
* @param params
* @throws Exception
*/
private void handleOtherView(String activity, Map<String, Object> params) throws Exception{
ArrayList<View> views = getCurrentViews();
for (View view: views) {
if (view.isClickable()) {
handleEditText(view);
handleJump(currentActivity, activity, params);
handleView(view, activity, params, waitForDialogToOpen(50));
}
}
}
/**
* Returns the visibility of this view and all of its ancestors
*
*/
private boolean isShown(Activity context){
if (context == null) return false;
Window window = context.getWindow();
if (window != null) {
View decorView = window.getDecorView();
if (decorView != null) {
return decorView.isShown();
}
}
return false;
}
/**
* Handle Jump
* If the current activity is not the target page, press the back button,
* if not returned after the target activity is to restart the target activity
* @param activity activity name
* @param params The start parameter of the activity
* @return if restart activity return true
* @throws Exception
*/
private boolean handleJump(Activity context, String activity, Map<String, Object> params) throws Exception{
// if (config.useNative)
ComponentName cn = getRunningTask(context);
String pkg = cn.getPackageName();
String act = cn.getClassName();
Log.i(Solo.LOG_TAG, "current ComponentName: " + pkg + "/" + act);
// If the iteration comes to the login activity, login.
if (!activity.contains(config.loginActivity) && act.contains(config.loginActivity)){
handleLogin();
sleep(config.sleepDuration * 4);
return false;
}
if (!act.contains(activity)) {
Log.i(Solo.LOG_TAG, "package act: " + act);
Log.i(Solo.LOG_TAG, "package target: " + config.PACKAGE);
goBack();
sleep(config.sleepDuration);
cn = getRunningTask(context);
act = cn.getClassName();
if (!act.contains(activity)) {
String[] names = activity.split("\\.");
goBackToActivitySync(names[names.length - 1]);
cn = getRunningTask(context);
act = cn.getClassName();
if (!act.contains(activity)) {
finish(activity);
Log.i(Solo.LOG_TAG, "ReStart Activity from getRunningTask: " + activity);
startActivity(context, params, activity);
sleep(config.sleepDuration * 4);
return true;
}
}
return false;
}
boolean isShown = isShown(context);
Log.i(Solo.LOG_TAG, "current Activity " + context + " isShown: " + isShown);
if (!isShown) {
goBack();
sleep(config.sleepDuration / 5);
isShown = isShown(context);
Log.i(Solo.LOG_TAG, "current Activity " + context + " isShown: " + isShown);
if (!isShown) {
String[] names = activity.split("\\.");
goBackToActivitySync(names[names.length - 1]);
isShown = isShown(context);
if (!isShown) {
finish(activity);
Log.i(Solo.LOG_TAG, "ReStart Activity from isShown: " + activity);
startActivity(context, params, activity);
sleep(config.sleepDuration * 4);
return true;
}
}
}
return false;
}
/**
* Handle ViewPager
* @param activity
* @param params
* @throws Exception
*/
private void handleViewPager(String activity, Map<String, Object> params, boolean isUiAutomation) throws Exception{
ArrayList<ViewPager> viewPagers = getCurrentViews(ViewPager.class);
if (viewPagers.size() > 0) {
Log.d(Solo.LOG_TAG, "Find viewPager: " + viewPagers.get(0).toString());
ViewPager viewPager = viewPagers.get(0);
if (viewPager == null || viewPager.getAdapter() == null) {
Log.d(Solo.LOG_TAG, "viewPager or viewPager.getAdapter() is null, return.");
return;
}
int size = viewPager.getAdapter().getCount();
Log.d(Solo.LOG_TAG, "ViewPager size: " + size);
for (int i = 0; i < size; i ++) {
if (viewPager == null) return;
final int tab = i;
if (viewPager.getAdapter() instanceof PagerAdapter) {
viewPager.post(new Runnable() {
@Override
public void run() {
viewPager.setCurrentItem(tab);
}
});
Log.d(Solo.LOG_TAG, "ViewPager setCurrentItem(" + i + ")");
sleep(config.sleepDuration * 2);
if (isUiAutomation) iterationNode(null, activity, params);
else {
handleListView(activity, params);
handleRecyclerView(activity, params);
handleGridView(activity, params);
handleScrollView(activity, params);
handleOtherView(activity, params);
}
} else
if (viewPager.getAdapter() instanceof FragmentPagerAdapter || viewPager.getAdapter() instanceof FragmentStatePagerAdapter) {
handleRecyclerView(activity, params);
}
}
} else iterationNode(null, activity, params);
}
/**
* Handle fragmentTabHost
* @param activity
* @param params
*/
private void handleFragmentTabHost(String activity, Map<String, Object> params, boolean isUiAutomation) throws Exception{
ArrayList<FragmentTabHost> fragmentTabHosts = getCurrentViews(FragmentTabHost.class);
if (fragmentTabHosts.size() > 0) {
FragmentTabHost fragmentTabHost = fragmentTabHosts.get(0);
if (fragmentTabHost == null) {
return;
}
Log.d(Solo.LOG_TAG, "Find fragmentTabHost: " + fragmentTabHost.toString());
Field fieldFragment = FragmentTabHost.class.getDeclaredField("mTabs");
fieldFragment.setAccessible(true);
int size = ((ArrayList) fieldFragment.get(fragmentTabHost)).size();
Log.d(Solo.LOG_TAG, "FragmentTabHost size: " + size);
for (int i = 0; i < size; i ++) {
if (fragmentTabHost == null) return;
final int tab = i;
try {
fragmentTabHost.post(new Runnable() {
@Override
public void run() {
fragmentTabHost.setCurrentTab(tab);
}
});
} catch (IllegalStateException ignored){}
Log.d(Solo.LOG_TAG, "FragmentTabHost setCurrentTab(" + i + ")");
sleep(config.sleepDuration * 4);
if (isUiAutomation) {
handleViewPager(activity, params, true);
}
else {
handleListView(activity, params);
handleRecyclerView(activity, params);
handleGridView(activity, params);
handleViewPager(activity, params, false);
handleScrollView(activity, params);
handleOtherView(activity, params);
}
}
} else iterationNode(null, activity, params);
}
/**
* Handle ListView
*Simulate pull-down refresh and pull-up loading.
*/
private void handleListView(String activity, Map<String, Object> params) throws Exception{
ArrayList<ListView> listViews = getCurrentViews(ListView.class);
if (listViews.size() > 0) {
ListView listView = listViews.get(0);
if (listView == null) return;
Log.d(Solo.LOG_TAG, "Find listView: " + listView.toString());
final int size = listView.getCount() - 1;
Log.d(Solo.LOG_TAG, "handleListView ListView size: " + size);
int lastPosition = listView.getLastVisiblePosition();
if (size > 0) {
// Pull-down refresh
pullDown(config.sleepDuration * 4);
}
if (size >= lastPosition) {
// Switch to the last item for listView.
Log.d(Solo.LOG_TAG, "Switch to the last item for listView.");
scrollListToLine(listView, size - 1);
// Pull-up loading
pullUp(config.sleepDuration * 4);
}
//ListView recovery
Log.d(Solo.LOG_TAG, "ListView recovery.");
scrollListToLine(listView, 0);
sleep(config.sleepDuration);
loopListView(activity, params);
}
}
/**
* Handle ListView Item
* @param i the ListView Item
* @param activity
* @param params
* @return
* @throws Exception
*/
private boolean handleListViewItem(int i, String activity, Map<String, Object> params) throws Exception{
clickInList(i);
sleep(config.sleepDuration);
boolean isRestart = handleJump(currentActivity, activity, params);
if (isRestart) {
reClickOnListView(activity, params, i);
return true;
}
return false;
}
/**
* Handle RecyclerView Item
* @param i the RecyclerView Item
* @param activity
* @param params
* @return
* @throws Exception
*/
private boolean handleRecyclerViewItem(RecyclerView recyclerView, int i, String activity, Map<String, Object> params)
throws Exception{
Log.d(Solo.LOG_TAG, "RecyclerView item: " + recyclerView.getChildAt(i));
clickInRecyclerView(recyclerView, i);
sleep(config.sleepDuration);
boolean isReStart = handleJump(currentActivity, activity, params);
if (isReStart) {
reClickOnRecyclerView(activity, params, i);
return true;
}
return false;
}
/**
* Loop Handle ListView
* @param activity
* @param params
* @throws Exception
*/
private void loopListView(String activity, Map<String, Object> params) throws Exception{
ArrayList<ListView> listViews = getCurrentViews(ListView.class);
if (listViews.size() > 0) {
ListView listView = listViews.get(0);
if (listView == null) {
return;
}
int size = listView.getCount() - 1;
int lastPosition = listView.getLastVisiblePosition();
int firstPosition = listView.getFirstVisiblePosition();
int visibleItem = lastPosition - firstPosition;
Log.d(LOG_TAG, "ListView size " + size);
Log.d(LOG_TAG, "ListView firstPosition " + firstPosition);
Log.d(LOG_TAG, "ListView lastPosition " + lastPosition);
Log.d(LOG_TAG, "ListView visibleItem " + visibleItem);
for (int i = 0; i <= visibleItem; i ++) {
boolean isRestart = handleListViewItem(i, activity, params);
if (isRestart) break;
if (i == visibleItem && size > lastPosition) {
// More than one page (screen).
if (size - lastPosition >= visibleItem) {
Log.d(LOG_TAG, "ListView setSelection to " + lastPosition);
scrollListToLine(listView, lastPosition);
sleep(config.sleepDuration);
loopListView(activity, params);
break;
} else {
// Not enough pages (screen).
scrollDown();
sleep(config.sleepDuration);
lastPosition = listView.getLastVisiblePosition();
int newFirstPosition = listView.getFirstVisiblePosition();
visibleItem = lastPosition - newFirstPosition;
for (int j = firstPosition; j <= newFirstPosition; j ++) {
// Click from the bottom up.
isRestart = handleListViewItem(visibleItem, activity, params);
if (isRestart) break;
visibleItem --;
}
}
}
}
}
}
/**
* When the ListView jumps, retry click on ListView.
* @param activity
* @param params
* @param index
* @throws Exception
*/
private void reClickOnListView(String activity, Map<String, Object> params, int index) throws Exception{
ArrayList<ListView> listViews = getCurrentViews(ListView.class);
if (listViews.size() > 0){
ListView listView = listViews.get(0);
int size = listView.getCount() - 1;
Log.d(Solo.LOG_TAG, "reClickOnListView ListView size:" + size);
int lastPosition = listView.getLastVisiblePosition();
for (int i = 0; i <= lastPosition; i++) {
if (i > index){
handleJump(currentActivity, activity, params);
clickInList(i);
sleep(config.sleepDuration);
boolean isRestart = handleJump(currentActivity, activity, params);
if (isRestart) {
reClickOnListView(activity, params, i);
}
if (i == lastPosition) {
final int temp = i;
Log.d(LOG_TAG, "ListView setSelection to " + temp);
listView.post(new Runnable() {
@Override
public void run() {
listView.setSelection(temp);
}
});
sleep(config.sleepDuration);
lastPosition = listView.getLastVisiblePosition();
}
}
}
}
}
private void loopRecyclerView(String activity, Map<String, Object> params) throws Exception{
ArrayList<RecyclerView> recyclerViews = getCurrentViews(RecyclerView.class);
if (recyclerViews.size() > 0) {
RecyclerView recyclerView = recyclerViews.get(0);
if (recyclerView == null || recyclerView.getAdapter() == null) {
Log.d(Solo.LOG_TAG, "recyclerView or recyclerView.getAdapter() is null, return.");
return;
}
int size = recyclerView.getAdapter().getItemCount();
Log.d(Solo.LOG_TAG, "RecyclerView size: " + size);
int firstPosition;
int lastPosition;
int visibleItem;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager manager = (LinearLayoutManager) layoutManager;
firstPosition = manager.findFirstVisibleItemPosition();
// May be Covered.
lastPosition = manager.findLastVisibleItemPosition() - 1;
visibleItem = lastPosition - firstPosition;
Log.d(LOG_TAG, "RecyclerView firstPosition: " + firstPosition);
Log.d(LOG_TAG, "RecyclerView lastPosition: " + lastPosition);
for (int i = 1; i <= visibleItem; i++) {
boolean isRestart = handleRecyclerViewItem(recyclerView, i, activity, params);
if (isRestart) break;
if (i == visibleItem && size > lastPosition) {
// More than one page (screen).
if (size - (lastPosition + 1) > visibleItem) {
scrollLoopRecyclerView(manager);
loopRecyclerView(activity, params);
break;
} else {
// Not enough pages (screen).
scrollLoopRecyclerView(manager);
lastPosition = manager.findLastVisibleItemPosition();
int newFirstPosition = manager.findFirstVisibleItemPosition();
visibleItem = lastPosition - newFirstPosition;
for (int j = firstPosition; j <= newFirstPosition; j ++) {
// Click from the bottom up.
isRestart = handleRecyclerViewItem(recyclerView, visibleItem, activity, params);
if (isRestart) break;
visibleItem --;
}
}
}
}
}
}
}
/**
* Scrolls horizontally or vertically for RecyclerView.
* @param manager the RecyclerView LinearLayoutManager
*/
private void scrollLoopRecyclerView(LinearLayoutManager manager) {
if (manager.getOrientation() == LinearLayoutManager.HORIZONTAL) {
Log.d(LOG_TAG, "RecyclerView scrollRight.");
scrollToSide(RIGHT, 0.95f, 20);
} else {
Log.d(LOG_TAG, "RecyclerView scrollDown.");
scrollDown();
}
sleep(config.sleepDuration);
}
/**
* Handle RecyclerView
* if view instanceof RecyclerView, simulate pull-down refresh and pull-up loading and
* get RecyclerView item count, iteration it.
* @param activity activity name
* @param params The start parameter of the activity
* @throws Exception
*/
private void handleRecyclerView(String activity, Map<String, Object> params) throws Exception{
ArrayList<RecyclerView> recyclerViews = getCurrentViews(RecyclerView.class);
if (recyclerViews.size() > 0) {
RecyclerView recyclerView = recyclerViews.get(0);
if (recyclerView == null || recyclerView.getAdapter() == null) {
Log.d(Solo.LOG_TAG, "recyclerView or recyclerView.getAdapter() is null, return.");
return;
}
Log.d(Solo.LOG_TAG, "Find recyclerView: " + recyclerView.toString());
int size = recyclerView.getAdapter().getItemCount();
int lastPosition;
if(size > 0){
// Pull-down refresh
pullDown(config.sleepDuration * 4);
}
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
if (layoutManager instanceof LinearLayoutManager) {
lastPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
if (size > lastPosition) {
// Switch to the last item for recyclerView
scrollRecyclerViewToLine(recyclerView, size - 1);
// Pull-up loading
pullUp(config.sleepDuration * 4);
}
//RecyclerView recovery
scrollRecyclerViewToLine(recyclerView, 0);
sleep(config.sleepDuration);
loopRecyclerView(activity, params);
}
}
}
/**
* When the recyclerView jumps, retry click on recyclerView.
* @param index jump index
*/
private void reClickOnRecyclerView(String activity, Map<String, Object> params, int index) throws Exception{
ArrayList<RecyclerView> recyclerViews = getCurrentViews(RecyclerView.class);
if (recyclerViews.size() > 0){
RecyclerView recyclerView = recyclerViews.get(0);
if (recyclerView == null || recyclerView.getAdapter() == null) {
Log.d(Solo.LOG_TAG, "recyclerView or recyclerView.getAdapter() is null, return.");
return;
}
for (int j = 0; j < recyclerView.getChildCount(); j++) {
if (j > index){
clickInRecyclerView(recyclerView, j);
sleep(config.sleepDuration);
boolean isReStart = handleJump(currentActivity, activity, params);
if (isReStart) {
reClickOnRecyclerView(activity, params, j);
break;
}
}
}
}
}
/**
* Handle View >> if listener is not null click it.
* @param view view
* @param activity activity name
*/
private void handleView(View view, String activity, Map<String, Object> params, boolean isDialog)
throws Exception{
if (view == null)return;
if (!view.isShown())return;
if (isDialog) {
handleDialog(activity, params);
return;
}
if (view.isClickable()) clickView(view);
}
/**
* Click the target view.
* @param view
*/
private void clickView(View view) {
clickOnView(view);
sleep(config.sleepDuration);
}
/**
* Handle Dialog
* If dialog is opened, get views from dialog, iteration it.
* @param activity activity name
*/
private void handleDialog(String activity, Map<String, Object> params) throws Exception{
if (waitForDialogToOpen(50)) {
Log.d(Solo.LOG_TAG, "dialog is open");
ArrayList<View> views = getCurrentViews();
Random r = new Random();
int index = 0;
while (index < 20 ){
View view = views.get(r.nextInt(views.size() - 1));
if (view.isClickable()) {
clickView(view);
handleJump(currentActivity, activity, params);
break;
}
index ++;
}
if (waitForDialogToOpen(config.sleepDuration / 10)) {
goBack();
sleep(config.sleepDuration);
}
}
}
private int changeSleepStandard(){
if (height <= 1280){
return config.sleepDuration *= 3;
}
if (height <= 1920){
return config.sleepDuration *= 2;
}
return config.sleepDuration;
}
/**
* Work across application boundaries for permission.
*/
public void acrossForPermission(){
acrossApplication.acrossForPermission();
}
/**
* Work across application boundaries for camera.
* @param viewId The fully qualified resource name of the view id to find. e.g: com.sec.android.app.camera:id/okay
*/
public void acrossForCamera(String viewId){
acrossApplication.acrossForCamera(viewId);
}
/**
* Work across application boundaries for notification.
* Listen to the notification bar 10 seconds at a time.
*/
public void acrossForNotification(){
acrossApplication.acrossForNotification();
}
/**
* Enter text for the edit text.
* @param text the text, Does not support Chinese.
*/
public void acrossForEnterText(String text){
acrossApplication.acrossForEnterText(text);
}
/**
* Work across application boundaries for click.
* @param x the x coordinate
* @param y the y coordinate
*/
public void acrossForClick(float x, float y){
acrossApplication.acrossForClick(x, y);
}
/**
* Work across application boundaries for click.
* @param nodeInfo {@link AccessibilityNodeInfo}
*/
public void acrossForClick(AccessibilityNodeInfo nodeInfo){
acrossApplication.acrossForClick(nodeInfo);
}
/**
* Work across application boundaries for QQ login.
*/
public void acrossForQQLogin(String account, String password){
acrossApplication.acrossForQQLogin(account, password);
}
/**
* Initialize timeout using 'adb shell setprop' or use setLargeTimeout() and setSmallTimeout(). Will fall back to the default values set by {@link Config}.
*/
private void initialize(){
if(config.commandLogging){
Log.v(config.commandLoggingTag, "initialize()", context);
}
checkReptile();
checkNative();
checkRunner();
Log.v(LOG_TAG, "setUp()", context);
Log.v(LOG_TAG, "Iteration mode is " + config.mode.toString().toLowerCase(), context);
// If mode is reptile to clean up the data.
if (config.mode == Config.Mode.REPTILE && config.newReptile) {
clearData();
}
Timeout.setLargeTimeout(initializeTimeout("solo_large_timeout", config.timeout_large));
Timeout.setSmallTimeout(initializeTimeout("solo_small_timeout", config.timeout_small));
int [] wh = DesignUtils.getDisplayWH(context);
width = wh[0];
height = wh[1];
Log.v(LOG_TAG, "The device width: " + width, context);
Log.v(LOG_TAG, "The device height: " + height, context);
config.sleepDuration = changeSleepStandard();
application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
}
/**
* Initialize the data.
*/
private void clearData() {
Log.v(LOG_TAG, "Clear data.", context);
FileUtils.deleteJson();
FileUtils.deleteActivity();
FileUtils.deleteParams();
FileUtils.deleteLog();
}
/**
* Parse a timeout value set using adb shell.
*
* There are two options to set the timeout. Set it using adb shell (requires root access):
* <br><br>
* 'adb shell setprop solo_large_timeout milliseconds'
* <br>
* 'adb shell setprop solo_small_timeout milliseconds'
* <br>
* Example: adb shell setprop solo_small_timeout 10000
* <br><br>
* Set the values directly using setLargeTimeout() and setSmallTimeout
*
* @param property name of the property to read the timeout from
* @param defaultValue default value for the timeout
* @return timeout in milliseconds
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private static int initializeTimeout(String property, int defaultValue) {
try {
Class clazz = Class.forName("android.os.SystemProperties");
Method method = clazz.getDeclaredMethod("get", String.class);
String value = (String) method.invoke(null, property);
return Integer.parseInt(value);
} catch (Exception e) {
return defaultValue;
}
}
}