
io.appium.java_client.AppiumDriver Maven / Gradle / Ivy
/*
+Copyright 2014 Appium contributors
+Copyright 2014 Software Freedom Conservancy
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ */
package io.appium.java_client;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.appium.java_client.internal.JsonToMobileElementConverter;
import org.openqa.selenium.*;
import org.openqa.selenium.html5.Location;
import org.openqa.selenium.html5.LocationContext;
import org.openqa.selenium.remote.*;
import org.openqa.selenium.remote.html5.RemoteLocationContext;
import javax.xml.bind.DatatypeConverter;
import java.net.URL;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static io.appium.java_client.MobileCommand.*;
public class AppiumDriver extends RemoteWebDriver implements MobileDriver, ContextAware, Rotatable, FindsByIosUIAutomation,
FindsByAndroidUIAutomator, FindsByAccessibilityId, LocationContext {
private final static ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true);
private URL remoteAddress;
private ComplexFind complexFind;
private RemoteLocationContext locationContext;
private ExecuteMethod executeMethod;
public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities){
super(remoteAddress, desiredCapabilities);
this.setElementConverter(new JsonToMobileElementConverter(this));
this.executeMethod = new AppiumExecutionMethod(this);
this.remoteAddress = remoteAddress;
complexFind = new ComplexFind(this);
locationContext = new RemoteLocationContext(executeMethod);
ImmutableMap.Builder builder = ImmutableMap.builder();
builder
.put(RESET, postC("/session/:sessionId/appium/app/reset"))
.put(GET_STRINGS, postC("/session/:sessionId/appium/app/strings"))
.put(KEY_EVENT, postC("/session/:sessionId/appium/device/keyevent"))
.put(CURRENT_ACTIVITY, getC("/session/:sessionId/appium/device/current_activity"))
.put(SET_VALUE, postC("/session/:sessionId/appium/element/:id/value"))
.put(PULL_FILE, postC("/session/:sessionId/appium/device/pull_file"))
.put(PULL_FOLDER, postC("/session/:sessionId/appium/device/pull_folder"))
.put(HIDE_KEYBOARD, postC("/session/:sessionId/appium/device/hide_keyboard"))
.put(PUSH_FILE, postC("/session/:sessionId/appium/device/push_file"))
.put(RUN_APP_IN_BACKGROUND, postC("/session/:sessionId/appium/app/background"))
.put(PERFORM_TOUCH_ACTION, postC("/session/:sessionId/touch/perform"))
.put(PERFORM_MULTI_TOUCH, postC("/session/:sessionId/touch/multi/perform"))
.put(IS_APP_INSTALLED, postC("/session/:sessionId/appium/device/app_installed"))
.put(INSTALL_APP, postC("/session/:sessionId/appium/device/install_app"))
.put(REMOVE_APP, postC("/session/:sessionId/appium/device/remove_app"))
.put(LAUNCH_APP, postC("/session/:sessionId/appium/app/launch"))
.put(CLOSE_APP, postC("/session/:sessionId/appium/app/close"))
.put(END_TEST_COVERAGE, postC("/session/:sessionId/appium/app/end_test_coverage"))
.put(LOCK, postC("/session/:sessionId/appium/device/lock"))
.put(SHAKE, postC("/session/:sessionId/appium/device/shake"))
.put(COMPLEX_FIND, postC("/session/:sessionId/appium/app/complex_find"))
.put(OPEN_NOTIFICATIONS, postC("/session/:sessionId/appium/device/open_notifications"))
.put(GET_NETWORK_CONNECTION, getC("/session/:sessionId/network_connection"))
.put(SET_NETWORK_CONNECTION, postC("/session/:sessionId/network_connection"))
;
ImmutableMap mobileCommands = builder.build();
HttpCommandExecutor mobileExecutor = new HttpCommandExecutor(mobileCommands, remoteAddress);
super.setCommandExecutor(mobileExecutor);
super.setErrorHandler(errorHandler);
}
@Override
public Response execute(String driverCommand, Map parameters) {
return super.execute(driverCommand, parameters);
}
@Override
protected Response execute(String command) {
return execute(command, ImmutableMap.of());
}
@Override
public ExecuteMethod getExecuteMethod() {
return executeMethod;
}
/**
* Reset the currently running app for this session
*/
public void resetApp() {
execute(MobileCommand.RESET);
}
/**
* Get all defined Strings from an Android app for the default language
*
* @return a string of all the localized strings defined in the app
*/
public String getAppStrings() {
Response response = execute(GET_STRINGS);
return response.getValue().toString();
}
/**
* Get all defined Strings from an Android app for the specified language
*
* @param language strings language code
* @return a string of all the localized strings defined in the app
*/
public String getAppStrings(String language) {
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("language", language);
ImmutableMap parameters = builder.build();
Response response = execute(GET_STRINGS, parameters);
return response.getValue().toString();
}
/**
* Send a key event to the device
*
* @param key code for the key pressed on the device
*/
public void sendKeyEvent(int key) {
sendKeyEvent(key, null);
}
/**
* Send a key event along with an Android metastate to an Android device
* Metastates are things like *shift* to get uppercase characters
*
* @param key code for the key pressed on the Android device
* @param metastate metastate for the keypress
*/
public void sendKeyEvent(int key, Integer metastate) {
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("keycode", key);
if (metastate != null) { builder.put("metastate", metastate); }
ImmutableMap parameters = builder.build();
execute(KEY_EVENT, parameters);
}
/**
* Get the current activity being run on the mobile device
*/
public String currentActivity() {
Response response = execute(CURRENT_ACTIVITY);
return response.getValue().toString();
}
/**
*
* @param remotePath On Android and iOS, this is either the path to the file (relative to the root of the app's file system).
* On iOS only, if path starts with /AppName.app, which will be replaced with the application's .app directory
* @return A byte array of Base64 encoded data.
*/
public byte[] pullFile(String remotePath) {
Response response = execute(PULL_FILE, ImmutableMap.of("path", remotePath));
String base64String = response.getValue().toString();
return DatatypeConverter.parseBase64Binary(base64String);
}
/**
* Save base64 encoded data as a file on the remote mobile device.
* This is an Android only method.
* @param remotePath Path to file to write data to on remote device
* @param base64Data Base64 encoded byte array of data to write to remote device
*/
public void pushFile(String remotePath, byte[] base64Data) {
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("path", remotePath).put("data", base64Data);
execute(PUSH_FILE, builder.build());
}
/**
* Pull a folder from the simulator/device. Does not work on iOS Real Devices, but works on simulators
*
* @param remotePath On Android and iOS, this is either the path to the file (relative to the root of the app's file system).
* On iOS only, if path starts with /AppName.app, which will be replaced with the application's .app directory
* @return A byte array of Base64 encoded data, representing a ZIP ARCHIVE of the contents of the requested folder.
*/
public byte[] pullFolder(String remotePath) {
Response response = execute(PULL_FOLDER, ImmutableMap.of("path", remotePath));
String base64String = response.getValue().toString();
return DatatypeConverter.parseBase64Binary(base64String);
}
/**
* Hides the keyboard if it is showing.
* On iOS, there are multiple strategies for hiding the keyboard. Defaults to the "tapOutside" strategy (taps outside the keyboard).
* Switch to using hideKeyboard(HideKeyboardStrategy.PRESS_KEY, "Done") if this doesn't work.
*/
public void hideKeyboard() {
execute(HIDE_KEYBOARD);
}
/**
* Hides the keyboard if it is showing. Available strategies are PRESS_KEY and TAP_OUTSIDE.
* One taps outside the keyboard, the other presses a key of your choosing (probably the 'Done' key).
* Hiding the keyboard often depends on the way an app is implemented, no single strategy always works.
*
* These parameters are only for iOS, and ignored by Android.
*
* @param strategy HideKeyboardStrategy
* @param keyName a String, representing the text displayed on the button of the keyboard you want to press. For example: "Done"
*/
public void hideKeyboard(String strategy, String keyName) {
ImmutableMap parameters = ImmutableMap.of("strategy", strategy);
if (keyName != null) {
parameters = parameters.of("key", keyName);
}
execute(HIDE_KEYBOARD, parameters);
}
/**
* Hides the keyboard by pressing the button specified by keyName if it is showing.
* This is an iOS only command.
* @param keyName The button pressed by the mobile driver to attempt hiding the keyboard
*/
public void hideKeyboard(String keyName) {
execute(HIDE_KEYBOARD, ImmutableMap.of("keyName", keyName));
}
/**
* Runs the current app as a background app for the number of seconds requested.
* This is a synchronous method, it returns after the back has been returned to the foreground.
* @param seconds Number of seconds to run App in background
*/
public void runAppInBackground(int seconds) {
execute(RUN_APP_IN_BACKGROUND, ImmutableMap.of("seconds", seconds));
}
/**
* Open the notification shade, on Android devices.
* Android only method.
*/
public void openNotifications() { execute(OPEN_NOTIFICATIONS); }
/**
* Performs a chain of touch actions, which together can be considered an entire gesture.
* See the Webriver 3 spec https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html
*
* It's more convenient to call the perform() method of the TouchAction object itself.
*
* @param touchAction A TouchAction object, which contains a list of individual touch actions to perform
* @return the same touchaction object
*/
public TouchAction performTouchAction(TouchAction touchAction) {
ImmutableMap parameters = touchAction.getParameters();
touchAction.clearParameters();
execute(PERFORM_TOUCH_ACTION, parameters);
return touchAction;
}
/**
* Performs multiple TouchAction gestures at the same time, to simulate multiple fingers/touch inputs.
* See the Webriver 3 spec https://dvcs.w3.org/hg/webdriver/raw-file/default/webdriver-spec.html
*
* It's more convenient to call the perform() method of the MultiTouchAction object.
*
* @param multiAction the MultiTouchAction object to perform.
*/
public void performMultiTouchAction(MultiTouchAction multiAction) {
ImmutableMap parameters = multiAction.getParameters();
execute(PERFORM_MULTI_TOUCH, parameters);
}
/**
* Convenience method for tapping the center of an element on the screen
* @param fingers number of fingers/appendages to tap with
* @param element element to tap
* @param duration how long between pressing down, and lifting fingers/appendages
*/
public void tap(int fingers, WebElement element, int duration) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
for (int i = 0; i < fingers; i++) {
multiTouch.add(createTap(element, duration));
}
multiTouch.perform();
}
/**
* Convenience method for tapping a position on the screen
* @param fingers number of fingers/appendages to tap with
* @param x x coordinate
* @param y y coordinate
* @param duration
*/
public void tap(int fingers, int x, int y, int duration) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
for (int i = 0; i < fingers; i++) {
multiTouch.add(createTap(x, y, duration));
}
multiTouch.perform();
}
/**
* Convenience method for swiping across the screen
* @param startx starting x coordinate
* @param starty starting y coordinate
* @param endx ending x coordinate
* @param endy ending y coordinate
* @param duration amount of time in milliseconds for the entire swipe action to take
*/
public void swipe(int startx, int starty, int endx, int endy, int duration) {
TouchAction touchAction = new TouchAction(this);
//appium converts press-wait-moveto-release to a swipe action
touchAction.press(startx, starty).waitAction(duration).moveTo(endx, endy).release();
touchAction.perform();
}
/**
* Convenience method for pinching an element on the screen.
* "pinching" refers to the action of two appendages pressing the screen and sliding towards each other.
* NOTE:
* This convenience method places the initial touches around the element, if this would happen to place one of them
* off the screen, appium with return an outOfBounds error. In this case, revert to using the MultiTouchAction api
* instead of this method.
*
* @param el The element to pinch
*/
public void pinch(WebElement el) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
Dimension dimensions = el.getSize();
Point upperLeft = el.getLocation();
Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2, upperLeft.getY() + dimensions.getHeight() / 2);
TouchAction action0 = new TouchAction(this).press(el, center.getX(), center.getY() - 100).moveTo(el).release();
TouchAction action1 = new TouchAction(this).press(el, center.getX(), center.getY() + 100).moveTo(el).release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
/**
* Convenience method for pinching an element on the screen.
* "pinching" refers to the action of two appendages pressing the screen and sliding towards each other.
* NOTE:
* This convenience method places the initial touches around the element at a distance, if this would happen to place
* one of them off the screen, appium will return an outOfBounds error. In this case, revert to using the
* MultiTouchAction api instead of this method.
*
* @param x x coordinate to terminate the pinch on
* @param y y coordinate to terminate the pinch on
*/
public void pinch(int x, int y) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
TouchAction action0 = new TouchAction(this).press(x, y-100).moveTo(x, y).release();
TouchAction action1 = new TouchAction(this).press(x, y+100).moveTo(x, y).release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
/**
* Convenience method for "zooming in" on an element on the screen.
* "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other.
* NOTE:
* This convenience method slides touches away from the element, if this would happen to place one of them
* off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api
* instead of this method.
*
* @param el The element to pinch
*/
public void zoom(WebElement el) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
Dimension dimensions = el.getSize();
Point upperLeft = el.getLocation();
Point center = new Point(upperLeft.getX() + dimensions.getWidth() / 2, upperLeft.getY() + dimensions.getHeight() / 2);
TouchAction action0 = new TouchAction(this).press(el).moveTo(el, center.getX(), center.getY() - 100).release();
TouchAction action1 = new TouchAction(this).press(el).moveTo(el, center.getX(), center.getY() + 100).release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
/**
* Convenience method for "zooming in" on an element on the screen.
* "zooming in" refers to the action of two appendages pressing the screen and sliding away from each other.
* NOTE:
* This convenience method slides touches away from the element, if this would happen to place one of them
* off the screen, appium will return an outOfBounds error. In this case, revert to using the MultiTouchAction api
* instead of this method.
*
* @param x x coordinate to start zoom on
* @param y y coordinate to start zoom on
*/
public void zoom(int x, int y) {
MultiTouchAction multiTouch = new MultiTouchAction(this);
TouchAction action0 = new TouchAction(this).press(x, y).moveTo(x, y-100).release();
TouchAction action1 = new TouchAction(this).press(x, y).moveTo(x, y+100).release();
multiTouch.add(action0).add(action1);
multiTouch.perform();
}
/**
* In iOS apps, named TextFields have the same accessibility Id as their containing TableElement.
* This is a convenience method for getting the named TextField, rather than its containing element.
* @param name accessiblity id of TextField
* @return The textfield with the given accessibility id
*/
public WebElement getNamedTextField(String name) {
MobileElement element = (MobileElement) findElementByAccessibilityId(name);
if (element.getTagName() != "TextField") {
return element.findElementByAccessibilityId(name);
}
return element;
}
/**
* Checks if an app is installed on the device
* @param bundleId bundleId of the app
* @return True if app is installed, false otherwise
*/
public boolean isAppInstalled(String bundleId) {
Response response = execute(IS_APP_INSTALLED, ImmutableMap.of("bundleId", bundleId));
return Boolean.parseBoolean(response.getValue().toString());
}
/**
* Install an app on the mobile device
* @param appPath path to app to install
*/
public void installApp(String appPath) {
execute(INSTALL_APP, ImmutableMap.of("appPath", appPath));
}
/**
* Remove the specified app from the device (uninstall)
* @param bundleId the bunble identifier (or app id) of the app to remove
*/
public void removeApp(String bundleId) {
execute(REMOVE_APP, ImmutableMap.of("bundleId", bundleId));
}
/**
* Launch the app which was provided in the capabilities at session creation
*/
public void launchApp() {
execute(LAUNCH_APP);
}
/**
* Close the app which was provided in the capabilities at session creation
*/
public void closeApp() {
execute(CLOSE_APP);
}
/**
* Get test-coverage data
* Android-only method
* @param intent intent to broadcast
* @param path path to .ec file
*/
public void endTestCoverage(String intent, String path) {
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("intent", intent).put("path", path);
execute(END_TEST_COVERAGE, builder.build());
}
/**
* Lock the device (bring it to the lock screen) for a given number of seconds
* @param seconds number of seconds to lock the screen for
*/
public void lockScreen(int seconds) {
execute(LOCK, ImmutableMap.of("seconds", seconds));
}
/**
* Simulate shaking the device
* This is an iOS-only method
*/
public void shake() {
execute(SHAKE);
}
public MobileElement complexFind(String complex) {
return complexFind.execute(complex);
}
public MobileElement scrollTo(String text) {
return complexFind.scrollTo(text);
}
public MobileElement scrollToExact(String text) {
return complexFind.scrollToExact(text);
}
/**
* Get the current network settings of the device.
* This is an Android-only method
*
* @return NetworkConnectionSetting objects will let you inspect the status of AirplaneMode, Wifi, Data connections
*/
public NetworkConnectionSetting getNetworkConnection() {
Response response = execute(GET_NETWORK_CONNECTION);
return new NetworkConnectionSetting(Integer.parseInt(response.getValue().toString()));
}
/**
* Set the network connection of the device.
* This is an Android-only method
*
* @param connection The NetworkConnectionSetting configuration to use for the device
*/
public void setNetworkConnection(NetworkConnectionSetting connection) {
// the new version of the webdriver protocol is going forward with sending JSON message which look like
// {name: "name of endpoint", parameters: "JSON parameters"}
// this is for webdrivers which run on protocols besides HTTP (like TCP)
// we're implementing that pattern here, for this new method, but haven't translated it to all other commands yet
ImmutableMap.Builder builder = ImmutableMap.builder();
builder.put("name", "network_connection")
.put("parameters", ImmutableMap.of("type", connection.value));
execute(SET_NETWORK_CONNECTION, builder.build());
}
@Override
public WebDriver context(String name) {
if (name == null) {
throw new IllegalArgumentException("Must supply a context name");
}
execute(DriverCommand.SWITCH_TO_CONTEXT, ImmutableMap.of("name", name));
return AppiumDriver.this;
}
@Override
public Set getContextHandles() {
Response response = execute(DriverCommand.GET_CONTEXT_HANDLES);
Object value = response.getValue();
try {
List returnedValues = (List)value;
return new LinkedHashSet(returnedValues);
} catch (ClassCastException ex) {
throw new WebDriverException("Returned value cannot be converted to List: " + value, ex);
}
}
@Override
public String getContext() {
String contextName = String.valueOf(execute(DriverCommand.GET_CURRENT_CONTEXT_HANDLE).getValue());
if (contextName.equals("null")) {
return null;
}
return contextName;
}
@Override
public void rotate(ScreenOrientation orientation) {
execute(DriverCommand.SET_SCREEN_ORIENTATION, ImmutableMap.of("orientation", orientation.value().toUpperCase()));
}
@Override
public ScreenOrientation getOrientation() {
Response response = execute(DriverCommand.GET_SCREEN_ORIENTATION);
String orientation = response.getValue().toString().toLowerCase();
if (orientation.equals(ScreenOrientation.LANDSCAPE.value())) {
return ScreenOrientation.LANDSCAPE;
} else if (orientation.equals(ScreenOrientation.PORTRAIT.value())) {
return ScreenOrientation.PORTRAIT;
} else {
throw new WebDriverException("Unexpected orientation returned: " + orientation);
}
}
@Override
public WebElement findElementByIosUIAutomation(String using) {
return findElement("-ios uiautomation", using);
}
@Override
public List findElementsByIosUIAutomation(String using) {
return findElements("-ios uiautomation", using);
}
@Override
public WebElement findElementByAndroidUIAutomator(String using) {
return findElement("-android uiautomator", using);
}
@Override
public List findElementsByAndroidUIAutomator(String using) {
return findElements("-android uiautomator", using);
}
@Override
public WebElement findElementByAccessibilityId(String using) {
return findElement("accessibility id", using);
}
@Override
public List findElementsByAccessibilityId(String using) {
return findElements("accessibility id", using);
}
@Override
public Location location() {
return locationContext.location();
}
@Override
public void setLocation(Location location) {
locationContext.setLocation(location);
}
private TouchAction createTap(WebElement element, int duration) {
TouchAction tap = new TouchAction(this);
return tap.press(element).waitAction(duration).release();
}
private TouchAction createTap(int x, int y, int duration) {
TouchAction tap = new TouchAction(this);
return tap.press(x, y).waitAction(duration).release();
}
private static CommandInfo getC(String url) {
return new CommandInfo(url, HttpVerb.GET);
}
private static CommandInfo postC(String url) {
return new CommandInfo(url, HttpVerb.POST);
}
private static CommandInfo deleteC(String url) {
return new CommandInfo(url, HttpVerb.DELETE);
}
public URL getRemoteAddress() {
return remoteAddress;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy