From 0f374c9266e5efd7ce025d9ad8d4b6c912c2cdec Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 1 Feb 2018 19:46:05 +0100 Subject: [PATCH 1/6] Update applications management endpoints --- .../appium/java_client/InteractsWithApps.java | 123 ++++++++++++++- .../io/appium/java_client/MobileCommand.java | 37 +++-- .../AndroidInstallApplicationOptions.java | 149 ++++++++++++++++++ .../AndroidRemoveApplicationOptions.java | 76 +++++++++ .../AndroidTerminateApplicationOptions.java | 52 ++++++ .../appmanagement/ApplicationState.java | 45 ++++++ .../BaseActivateApplicationOptions.java | 22 +++ .../BaseInstallApplicationOptions.java | 22 +++ .../appmanagement/BaseOptions.java | 29 ++++ .../BaseRemoveApplicationOptions.java | 22 +++ .../BaseTerminateApplicationOptions.java | 22 +++ 11 files changed, 583 insertions(+), 16 deletions(-) create mode 100644 src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/ApplicationState.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/BaseActivateApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/BaseInstallApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/BaseOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/BaseRemoveApplicationOptions.java create mode 100644 src/main/java/io/appium/java_client/appmanagement/BaseTerminateApplicationOptions.java diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 12a497131..75d0dac1c 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -16,23 +16,34 @@ package io.appium.java_client; +import static io.appium.java_client.MobileCommand.ACTIVATE_APP; import static io.appium.java_client.MobileCommand.CLOSE_APP; import static io.appium.java_client.MobileCommand.INSTALL_APP; import static io.appium.java_client.MobileCommand.IS_APP_INSTALLED; import static io.appium.java_client.MobileCommand.LAUNCH_APP; +import static io.appium.java_client.MobileCommand.QUERY_APP_STATE; import static io.appium.java_client.MobileCommand.REMOVE_APP; import static io.appium.java_client.MobileCommand.RESET; import static io.appium.java_client.MobileCommand.RUN_APP_IN_BACKGROUND; +import static io.appium.java_client.MobileCommand.TERMINATE_APP; import static io.appium.java_client.MobileCommand.prepareArguments; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.ApplicationState; +import io.appium.java_client.appmanagement.BaseActivateApplicationOptions; +import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; +import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; +import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; +import javax.annotation.Nullable; import java.time.Duration; import java.util.AbstractMap; public interface InteractsWithApps extends ExecutesMethod { + /** - * Launch the app which was provided in the capabilities at session creation. + * Launches the app, which was provided in the capabilities at session creation, + * and (re)starts the session. */ default void launchApp() { execute(LAUNCH_APP); @@ -44,7 +55,23 @@ default void launchApp() { * @param appPath path to app to install. */ default void installApp(String appPath) { - execute(INSTALL_APP, ImmutableMap.of("appPath", appPath)); + installApp(appPath, null); + } + + /** + * Install an app on the mobile device. + * + * @param appPath path to app to install or a remote URL. + * @param options Set of the corresponding instllation options for + * the particular platform. + */ + default void installApp(String appPath, @Nullable BaseInstallApplicationOptions options) { + String[] parameters = options == null ? new String[]{"appPath"} : + new String[]{"appPath", "options"}; + Object[] values = options == null ? new Object[]{appPath} : + new Object[]{appPath, options.build()}; + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(INSTALL_APP, prepareArguments(parameters, values))); } /** @@ -59,7 +86,7 @@ default boolean isAppInstalled(String bundleId) { } /** - * Reset the currently running app for this session. + * Resets the currently running app together with the session. */ default void resetApp() { execute(RESET); @@ -79,17 +106,99 @@ default void runAppInBackground(Duration duration) { /** * Remove the specified app from the device (uninstall). * - * @param bundleId the bunble identifier (or app id) of the app to remove. + * @param bundleId the bundle identifier (or app id) of the app to remove. + */ + default boolean removeApp(String bundleId) { + return removeApp(bundleId, null); + } + + /** + * Remove the specified app from the device (uninstall). + * + * @param bundleId the bundle identifier (or app id) of the app to remove. + * @param options the set of uninstall options supported by the + * particular platform. + * @return true if the uninstall was successful. */ - default void removeApp(String bundleId) { - execute(REMOVE_APP, ImmutableMap.of("bundleId", bundleId)); + default boolean removeApp(String bundleId, @Nullable BaseRemoveApplicationOptions options) { + String[] parameters = options == null ? new String[]{"bundleId"} : + new String[]{"bundleId", "options"}; + Object[] values = options == null ? new Object[]{bundleId} : + new Object[]{bundleId, options.build()}; + return CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(REMOVE_APP, prepareArguments(parameters, values))); } /** - * Close the app which was provided in the capabilities at session creation. + * Close the app which was provided in the capabilities at session creation + * and quits the session. */ default void closeApp() { execute(CLOSE_APP); } + /** + * Activates the given app if it installed, but not running or if it is running in the + * background. + * + * @param bundleId the bundle identifier (or app id) of the app to activate. + */ + default void activateApp(String bundleId) { + activateApp(bundleId, null); + } + + /** + * Activates the given app if it installed, but not running or if it is running in the + * background. + * + * @param bundleId the bundle identifier (or app id) of the app to activate. + * @param options the set of activation options supported by the + * particular platform. + */ + default void activateApp(String bundleId, @Nullable BaseActivateApplicationOptions options) { + String[] parameters = options == null ? new String[]{"bundleId"} : + new String[]{"bundleId", "options"}; + Object[] values = options == null ? new Object[]{bundleId} : + new Object[]{bundleId, options.build()}; + CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(ACTIVATE_APP, prepareArguments(parameters, values))); + } + + /** + * Queries the state of an application. + * + * @param bundleId the bundle identifier (or app id) of the app to query the state of. + * @return one of possible {@link ApplicationState} values, + */ + default ApplicationState queryAppState(String bundleId) { + return ApplicationState.ofCode(CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(QUERY_APP_STATE, ImmutableMap.of("bundleId", bundleId)))); + } + + /** + * Terminate the particular application if it is running. + * + * @param bundleId the bundle identifier (or app id) of the app to be terminated. + * @return true if the app was running before and has been successfully stopped. + */ + default boolean terminateApp(String bundleId) { + return terminateApp(bundleId, null); + } + + /** + * Terminate the particular application if it is running. + * + * @param bundleId the bundle identifier (or app id) of the app to be terminated. + * @param options the set of termination options supported by the + * particular platform. + * @return true if the app was running before and has been successfully stopped. + */ + default boolean terminateApp(String bundleId, @Nullable BaseTerminateApplicationOptions options) { + String[] parameters = options == null ? new String[]{"bundleId"} : + new String[]{"bundleId", "options"}; + Object[] values = options == null ? new Object[]{bundleId} : + new Object[]{bundleId, options.build()}; + return CommandExecutionHelper.execute(this, + new AbstractMap.SimpleEntry<>(TERMINATE_APP, prepareArguments(parameters, values))); + } } diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index c58997068..da4a6e5e2 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -44,14 +44,20 @@ public class MobileCommand { public static final String RUN_APP_IN_BACKGROUND; protected static final String PERFORM_TOUCH_ACTION; protected static final String PERFORM_MULTI_TOUCH; - protected static final String IS_APP_INSTALLED; - protected static final String INSTALL_APP; - protected static final String REMOVE_APP; protected static final String LAUNCH_APP; protected static final String CLOSE_APP; protected static final String GET_DEVICE_TIME; protected static final String GET_SESSION; + //region Applications Management + protected static final String IS_APP_INSTALLED; + protected static final String INSTALL_APP; + protected static final String ACTIVATE_APP; + protected static final String QUERY_APP_STATE; + protected static final String TERMINATE_APP; + protected static final String REMOVE_APP; + //endregion + protected static final String GET_PERFORMANCE_DATA; protected static final String GET_SUPPORTED_PERFORMANCE_DATA_TYPES; @@ -97,14 +103,20 @@ public class MobileCommand { RUN_APP_IN_BACKGROUND = "runAppInBackground"; PERFORM_TOUCH_ACTION = "performTouchAction"; PERFORM_MULTI_TOUCH = "performMultiTouch"; - IS_APP_INSTALLED = "isAppInstalled"; - INSTALL_APP = "installApp"; - REMOVE_APP = "removeApp"; LAUNCH_APP = "launchApp"; CLOSE_APP = "closeApp"; GET_DEVICE_TIME = "getDeviceTime"; GET_SESSION = "getSession"; + //region Applications Management + IS_APP_INSTALLED = "isAppInstalled"; + QUERY_APP_STATE = "queryAppState"; + TERMINATE_APP = "terminateApp"; + ACTIVATE_APP = "activateApp"; + REMOVE_APP = "removeApp"; + INSTALL_APP = "installApp"; + //endregion + GET_PERFORMANCE_DATA = "getPerformanceData"; GET_SUPPORTED_PERFORMANCE_DATA_TYPES = "getSuppportedPerformanceDataTypes"; @@ -148,9 +160,6 @@ public class MobileCommand { commandRepository.put(RUN_APP_IN_BACKGROUND, postC("/session/:sessionId/appium/app/background")); commandRepository.put(PERFORM_TOUCH_ACTION, postC("/session/:sessionId/touch/perform")); commandRepository.put(PERFORM_MULTI_TOUCH, postC("/session/:sessionId/touch/multi/perform")); - commandRepository.put(IS_APP_INSTALLED, postC("/session/:sessionId/appium/device/app_installed")); - commandRepository.put(INSTALL_APP, postC("/session/:sessionId/appium/device/install_app")); - commandRepository.put(REMOVE_APP, postC("/session/:sessionId/appium/device/remove_app")); commandRepository.put(LAUNCH_APP, postC("/session/:sessionId/appium/app/launch")); commandRepository.put(CLOSE_APP, postC("/session/:sessionId/appium/app/close")); commandRepository.put(LOCK, postC("/session/:sessionId/appium/device/lock")); @@ -166,6 +175,16 @@ public class MobileCommand { postC("/session/:sessionId/appium/start_recording_screen")); commandRepository.put(STOP_RECORDING_SCREEN, postC("/session/:sessionId/appium/stop_recording_screen")); + + //region Applications Management + commandRepository.put(IS_APP_INSTALLED, postC("/session/:sessionId/appium/device/app_installed")); + commandRepository.put(INSTALL_APP, postC("/session/:sessionId/appium/device/install_app")); + commandRepository.put(ACTIVATE_APP, postC("/session/:sessionId/appium/device/activate_app")); + commandRepository.put(REMOVE_APP, postC("/session/:sessionId/appium/device/remove_app")); + commandRepository.put(TERMINATE_APP, postC("/session/:sessionId/appium/device/terminate_app")); + commandRepository.put(QUERY_APP_STATE, getC("/session/:sessionId/appium/device/app_state")); + //endregion + //iOS commandRepository.put(SHAKE, postC("/session/:sessionId/appium/device/shake")); commandRepository.put(TOUCH_ID, postC("/session/:sessionId/appium/simulator/touch_id")); diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java new file mode 100644 index 000000000..d0b8e980f --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java @@ -0,0 +1,149 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +import com.google.common.collect.ImmutableMap; + +import java.time.Duration; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + +public class AndroidInstallApplicationOptions extends + BaseInstallApplicationOptions { + private Boolean replace; + private Duration timeout; + private Boolean allowTestPackages; + private Boolean useSdcard; + private Boolean grantPermissions; + + /** + * Enables the possibility to upgrade/reinstall the application + * if it is already present on the device (the default behavior). + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withReplaceEnabled() { + this.replace = true; + return this; + } + + /** + * Disables the possibility to upgrade/reinstall the application + * if it is already present on the device. + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withReplaceDisabled() { + this.replace = false; + return this; + } + + /** + * The time to wait until the app is installed (60000ms by default). + * + * @param timeout the actual timeout value. The minimum time resolution + * unit is one millisecond. + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withTimeout(Duration timeout) { + checkArgument(!checkNotNull(timeout).isNegative(), "The timeout value cannot be negative"); + this.timeout = timeout; + return this; + } + + /** + * Allows to install packages marked as test in the manifest. + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withAllowTestPackagesEnabled() { + this.allowTestPackages = true; + return this; + } + + /** + * Disables a possibility to install packages marked as test in + * the manifest (the default setting). + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withAllowTestPackagesDisabled() { + this.allowTestPackages = false; + return this; + } + + /** + * Forces the application to be installed of SD card + * instead of the internal memory. + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withUseSdcardEnabled() { + this.useSdcard = true; + return this; + } + + /** + * Forces the application to be installed to the internal memory + * (the default behavior). + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withUseSdcardDisabled() { + this.useSdcard = false; + return this; + } + + /** + * Grants all the permissions requested in the + * application's manifest automatically after the installation + * is completed under Android 6+. + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withGrantPermissionsEnabled() { + this.grantPermissions = true; + return this; + } + + /** + * Does not grant all the permissions requested in the + * application's manifest automatically after the installation + * is completed (the default behavior). + * + * @return self instance for chaining. + */ + public AndroidInstallApplicationOptions withGrantPermissionsDisabled() { + this.grantPermissions = false; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Map build() { + final ImmutableMap.Builder builder = new ImmutableMap.Builder(); + ofNullable(replace).map(x -> builder.put("replace", x)); + ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); + ofNullable(allowTestPackages).map(x -> builder.put("allowTestPackages", x)); + ofNullable(useSdcard).map(x -> builder.put("useSdcard", x)); + ofNullable(grantPermissions).map(x -> builder.put("grantPermissions", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java new file mode 100644 index 000000000..e5a261e58 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java @@ -0,0 +1,76 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +import com.google.common.collect.ImmutableMap; + +import java.time.Duration; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + +public class AndroidRemoveApplicationOptions extends + BaseRemoveApplicationOptions { + private Duration timeout; + private Boolean keepData; + + /** + * The time to wait until the app is removed (20000ms by default). + * + * @param timeout the actual timeout value. The minimum time resolution + * unit is one millisecond. + * @return self instance for chaining. + */ + public AndroidRemoveApplicationOptions withTimeout(Duration timeout) { + checkArgument(!checkNotNull(timeout).isNegative(), + "The timeout value cannot be negative"); + this.timeout = timeout; + return this; + } + + /** + * Forces uninstall to keep the application data and caches. + * + * @return self instance for chaining. + */ + public AndroidRemoveApplicationOptions withKeepDataEnabled() { + this.keepData = true; + return this; + } + + /** + * Forces uninstall to delete the application data and caches + * (the default behavior). + * + * @return self instance for chaining. + */ + public AndroidRemoveApplicationOptions withKeepDataDisabled() { + this.keepData = false; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Map build() { + final ImmutableMap.Builder builder = new ImmutableMap.Builder(); + ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); + ofNullable(keepData).map(x -> builder.put("keepData", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java new file mode 100644 index 000000000..8bae3b525 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java @@ -0,0 +1,52 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +import com.google.common.collect.ImmutableMap; + +import java.time.Duration; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + +public class AndroidTerminateApplicationOptions extends + BaseTerminateApplicationOptions { + private Duration timeout; + + /** + * The time to wait until the app is terminated (500ms by default). + * + * @param timeout the actual timeout value. The minimum time resolution + * unit is one millisecond. + * @return self instance for chaining. + */ + public AndroidTerminateApplicationOptions withTimeout(Duration timeout) { + checkArgument(!checkNotNull(timeout).isNegative(), "The timeout value cannot be negative"); + this.timeout = timeout; + return this; + } + + @SuppressWarnings("unchecked") + @Override + public Map build() { + final ImmutableMap.Builder builder = new ImmutableMap.Builder(); + ofNullable(timeout).map(x -> builder.put("timeout", x.toMillis())); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java new file mode 100644 index 000000000..1adb7e6e7 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java @@ -0,0 +1,45 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +import java.util.Arrays; + +public enum ApplicationState { + NOT_INSTALLED(0), NOT_RUNNING(1), RUNNING_IN_BACKGROUND_SUSPENDED(2), + RUNNING_IN_BACKGROUND(3), RUNNING_IN_FOREGROUND(4); + + private int code; + + ApplicationState(int code) { + this.code = code; + } + + /** + * Creates {@link ApplicationState} instance based on the code. + * + * @param code the code received from state querying endpoint. + * @return {@link ApplicationState} instance. + */ + public static ApplicationState ofCode(int code) { + return Arrays.stream(ApplicationState.values()) + .filter(x -> code == x.code) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + String.format("Application state %s is unknown", code)) + ); + } +} diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseActivateApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseActivateApplicationOptions.java new file mode 100644 index 000000000..08158d32b --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/BaseActivateApplicationOptions.java @@ -0,0 +1,22 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +public abstract class BaseActivateApplicationOptions> + extends BaseOptions { + +} diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseInstallApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseInstallApplicationOptions.java new file mode 100644 index 000000000..ac58ab7dc --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/BaseInstallApplicationOptions.java @@ -0,0 +1,22 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +public abstract class BaseInstallApplicationOptions> + extends BaseOptions { + +} diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java new file mode 100644 index 000000000..a20c27e0a --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java @@ -0,0 +1,29 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +import java.util.Map; + +public abstract class BaseOptions> { + + /** + * Creates a map based on the provided options. + * + * @return options mapping. + */ + abstract public Map build(); +} diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseRemoveApplicationOptions.java new file mode 100644 index 000000000..e43ce5631 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/BaseRemoveApplicationOptions.java @@ -0,0 +1,22 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +public abstract class BaseRemoveApplicationOptions> + extends BaseOptions { + +} diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseTerminateApplicationOptions.java new file mode 100644 index 000000000..204f87a66 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/BaseTerminateApplicationOptions.java @@ -0,0 +1,22 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * 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.appmanagement; + +public abstract class BaseTerminateApplicationOptions> + extends BaseOptions { + +} From 146db48868091bf9fc2db7c9bb4436e2ef24fc5c Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 2 Feb 2018 09:34:05 +0100 Subject: [PATCH 2/6] Address checkstyle issues --- src/main/java/io/appium/java_client/InteractsWithApps.java | 1 + .../java/io/appium/java_client/appmanagement/BaseOptions.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 75d0dac1c..c1e7a1a3b 100644 --- a/src/main/java/io/appium/java_client/InteractsWithApps.java +++ b/src/main/java/io/appium/java_client/InteractsWithApps.java @@ -107,6 +107,7 @@ default void runAppInBackground(Duration duration) { * Remove the specified app from the device (uninstall). * * @param bundleId the bundle identifier (or app id) of the app to remove. + * @return true if the uninstall was successful. */ default boolean removeApp(String bundleId) { return removeApp(bundleId, null); diff --git a/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java b/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java index a20c27e0a..1c1327a86 100644 --- a/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java +++ b/src/main/java/io/appium/java_client/appmanagement/BaseOptions.java @@ -25,5 +25,5 @@ public abstract class BaseOptions> { * * @return options mapping. */ - abstract public Map build(); + public abstract Map build(); } From b869ea25c243732db2b4fe2b21b2bcb8af7d30cf Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sun, 4 Feb 2018 10:29:44 +0100 Subject: [PATCH 3/6] Add integration tests --- .../android/AndroidDriverTest.java | 21 +++++++++++++--- .../java_client/android/BaseAndroidTest.java | 1 + .../appium/java_client/ios/AppXCUITTest.java | 1 + .../java_client/ios/XCUIAutomationTest.java | 24 +++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java index acbf1adb2..f523afa1b 100644 --- a/src/test/java/io/appium/java_client/android/AndroidDriverTest.java +++ b/src/test/java/io/appium/java_client/android/AndroidDriverTest.java @@ -16,12 +16,16 @@ package io.appium.java_client.android; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.lessThan; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import io.appium.java_client.appmanagement.ApplicationState; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.junit.Test; @@ -118,6 +122,17 @@ public class AndroidDriverTest extends BaseAndroidTest { assert (timeAfter - time > 3000); } + @Test public void testApplicationsManagement() throws InterruptedException { + String appId = driver.getCurrentPackage(); + assertThat(driver.queryAppState(appId), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); + Thread.sleep(500); + driver.runAppInBackground(Duration.ofSeconds(-1)); + assertThat(driver.queryAppState(appId), lessThan(ApplicationState.RUNNING_IN_FOREGROUND)); + Thread.sleep(500); + driver.activateApp(appId); + assertThat(driver.queryAppState(appId), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); + } + @Test public void pullFileTest() { byte[] data = driver.pullFile("data/system/registered_services/android.content.SyncAdapter.xml"); @@ -149,7 +164,7 @@ public class AndroidDriverTest extends BaseAndroidTest { } @Test public void getSupportedPerformanceDataTypesTest() { - driver.startActivity(new Activity("io.appium.android.apis", ".ApiDemos")); + driver.startActivity(new Activity(APP_ID, ".ApiDemos")); List dataTypes = new ArrayList<>(); dataTypes.add("cpuinfo"); @@ -169,7 +184,7 @@ public class AndroidDriverTest extends BaseAndroidTest { } @Test public void getPerformanceDataTest() throws Exception { - driver.startActivity(new Activity("io.appium.android.apis", ".ApiDemos")); + driver.startActivity(new Activity(APP_ID, ".ApiDemos")); List supportedPerformanceDataTypes = driver.getSupportedPerformanceDataTypes(); @@ -185,7 +200,7 @@ public class AndroidDriverTest extends BaseAndroidTest { } @Test public void getCurrentPackageTest() { - assertEquals("io.appium.android.apis",driver.getCurrentPackage()); + assertEquals(APP_ID, driver.getCurrentPackage()); } } diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java index 3248e1072..1510e4e89 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -28,6 +28,7 @@ import java.io.File; public class BaseAndroidTest { + public static final String APP_ID = "io.appium.android.apis"; private static AppiumDriverLocalService service; protected static AndroidDriver driver; diff --git a/src/test/java/io/appium/java_client/ios/AppXCUITTest.java b/src/test/java/io/appium/java_client/ios/AppXCUITTest.java index 0fb59537a..da710159b 100644 --- a/src/test/java/io/appium/java_client/ios/AppXCUITTest.java +++ b/src/test/java/io/appium/java_client/ios/AppXCUITTest.java @@ -11,6 +11,7 @@ import java.io.File; public class AppXCUITTest extends BaseIOSTest { + public static final String BUNDLE_ID = "io.appium.TestApp"; /** * initialization. diff --git a/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java b/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java index edd79ac0d..b3ef262cf 100644 --- a/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java +++ b/src/test/java/io/appium/java_client/ios/XCUIAutomationTest.java @@ -18,13 +18,17 @@ import static io.appium.java_client.touch.offset.ElementOption.element; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import io.appium.java_client.appmanagement.ApplicationState; +import io.appium.java_client.remote.MobileCapabilityType; import org.junit.After; import org.junit.Test; import org.openqa.selenium.DeviceRotation; @@ -62,6 +66,26 @@ public class XCUIAutomationTest extends AppXCUITTest { assertThat(System.currentTimeMillis() - msStarted, greaterThan(3000L)); } + @Test public void testApplicationsManagement() throws InterruptedException { + // This only works since Xcode9 + try { + if (Double.parseDouble( + (String) driver.getCapabilities() + .getCapability(MobileCapabilityType.PLATFORM_VERSION)) < 11) { + return; + } + } catch (NumberFormatException | NullPointerException e) { + return; + } + assertThat(driver.queryAppState(BUNDLE_ID), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); + Thread.sleep(500); + driver.runAppInBackground(Duration.ofSeconds(-1)); + assertThat(driver.queryAppState(BUNDLE_ID), lessThan(ApplicationState.RUNNING_IN_FOREGROUND)); + Thread.sleep(500); + driver.activateApp(BUNDLE_ID); + assertThat(driver.queryAppState(BUNDLE_ID), equalTo(ApplicationState.RUNNING_IN_FOREGROUND)); + } + @Test public void testPutIntoBackgroundWithoutRestore() { assertThat(driver.findElementsById("IntegerA"), is(not(empty()))); driver.runAppInBackground(Duration.ofSeconds(-1)); From 438af22c1b72ec22269711683d234abed86a8149 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 8 Feb 2018 09:16:23 +0100 Subject: [PATCH 4/6] Use POST instead of GET --- src/main/java/io/appium/java_client/MobileCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index da4a6e5e2..19bf9d10f 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -182,7 +182,7 @@ public class MobileCommand { commandRepository.put(ACTIVATE_APP, postC("/session/:sessionId/appium/device/activate_app")); commandRepository.put(REMOVE_APP, postC("/session/:sessionId/appium/device/remove_app")); commandRepository.put(TERMINATE_APP, postC("/session/:sessionId/appium/device/terminate_app")); - commandRepository.put(QUERY_APP_STATE, getC("/session/:sessionId/appium/device/app_state")); + commandRepository.put(QUERY_APP_STATE, postC("/session/:sessionId/appium/device/app_state")); //endregion //iOS From fe63a94b10abf0e33e4b343f0f1949f962fd5740 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 8 Feb 2018 11:03:58 +0100 Subject: [PATCH 5/6] Move android specific classes into the corresponding parent package --- .../AndroidInstallApplicationOptions.java | 3 ++- .../AndroidRemoveApplicationOptions.java | 3 ++- .../AndroidTerminateApplicationOptions.java | 3 ++- .../java_client/appmanagement/ApplicationState.java | 12 +++--------- 4 files changed, 9 insertions(+), 12 deletions(-) rename src/main/java/io/appium/java_client/{ => android}/appmanagement/AndroidInstallApplicationOptions.java (97%) rename src/main/java/io/appium/java_client/{ => android}/appmanagement/AndroidRemoveApplicationOptions.java (95%) rename src/main/java/io/appium/java_client/{ => android}/appmanagement/AndroidTerminateApplicationOptions.java (93%) diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java similarity index 97% rename from src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java rename to src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java index d0b8e980f..0a895a6c4 100644 --- a/src/main/java/io/appium/java_client/appmanagement/AndroidInstallApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.appium.java_client.appmanagement; +package io.appium.java_client.android.appmanagement; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; import java.time.Duration; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java similarity index 95% rename from src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java rename to src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java index e5a261e58..606728303 100644 --- a/src/main/java/io/appium/java_client/appmanagement/AndroidRemoveApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.appium.java_client.appmanagement; +package io.appium.java_client.android.appmanagement; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; import java.time.Duration; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java similarity index 93% rename from src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java rename to src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java index 8bae3b525..fbc89f254 100644 --- a/src/main/java/io/appium/java_client/appmanagement/AndroidTerminateApplicationOptions.java +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package io.appium.java_client.appmanagement; +package io.appium.java_client.android.appmanagement; import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; import java.time.Duration; import java.util.Map; diff --git a/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java index 1adb7e6e7..a613c6ce2 100644 --- a/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java +++ b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java @@ -19,14 +19,8 @@ import java.util.Arrays; public enum ApplicationState { - NOT_INSTALLED(0), NOT_RUNNING(1), RUNNING_IN_BACKGROUND_SUSPENDED(2), - RUNNING_IN_BACKGROUND(3), RUNNING_IN_FOREGROUND(4); - - private int code; - - ApplicationState(int code) { - this.code = code; - } + NOT_INSTALLED, NOT_RUNNING, RUNNING_IN_BACKGROUND_SUSPENDED, + RUNNING_IN_BACKGROUND, RUNNING_IN_FOREGROUND; /** * Creates {@link ApplicationState} instance based on the code. @@ -36,7 +30,7 @@ public enum ApplicationState { */ public static ApplicationState ofCode(int code) { return Arrays.stream(ApplicationState.values()) - .filter(x -> code == x.code) + .filter(x -> code == x.ordinal()) .findFirst() .orElseThrow(() -> new IllegalArgumentException( String.format("Application state %s is unknown", code)) From 61d196210b23720c212e60314dabc0d2c2bc3b46 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Sat, 10 Feb 2018 08:46:44 +0100 Subject: [PATCH 6/6] Fix argument type --- .../io/appium/java_client/appmanagement/ApplicationState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java index a613c6ce2..8cc9005a3 100644 --- a/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java +++ b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java @@ -28,7 +28,7 @@ public enum ApplicationState { * @param code the code received from state querying endpoint. * @return {@link ApplicationState} instance. */ - public static ApplicationState ofCode(int code) { + public static ApplicationState ofCode(long code) { return Arrays.stream(ApplicationState.values()) .filter(x -> code == x.ordinal()) .findFirst()