diff --git a/src/main/java/io/appium/java_client/InteractsWithApps.java b/src/main/java/io/appium/java_client/InteractsWithApps.java index 12a497131..c1e7a1a3b 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,100 @@ 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. + * @return true if the uninstall was successful. + */ + 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..19bf9d10f 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, postC("/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/android/appmanagement/AndroidInstallApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java new file mode 100644 index 000000000..0a895a6c4 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidInstallApplicationOptions.java @@ -0,0 +1,150 @@ +/* + * 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.android.appmanagement; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseInstallApplicationOptions; + +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/android/appmanagement/AndroidRemoveApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java new file mode 100644 index 000000000..606728303 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidRemoveApplicationOptions.java @@ -0,0 +1,77 @@ +/* + * 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.android.appmanagement; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseRemoveApplicationOptions; + +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/android/appmanagement/AndroidTerminateApplicationOptions.java b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java new file mode 100644 index 000000000..fbc89f254 --- /dev/null +++ b/src/main/java/io/appium/java_client/android/appmanagement/AndroidTerminateApplicationOptions.java @@ -0,0 +1,53 @@ +/* + * 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.android.appmanagement; + +import com.google.common.collect.ImmutableMap; +import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions; + +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..8cc9005a3 --- /dev/null +++ b/src/main/java/io/appium/java_client/appmanagement/ApplicationState.java @@ -0,0 +1,39 @@ +/* + * 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, NOT_RUNNING, RUNNING_IN_BACKGROUND_SUSPENDED, + RUNNING_IN_BACKGROUND, RUNNING_IN_FOREGROUND; + + /** + * 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(long code) { + return Arrays.stream(ApplicationState.values()) + .filter(x -> code == x.ordinal()) + .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..1c1327a86 --- /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. + */ + public abstract 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 { + +} 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));