Skip to content

Commit

Permalink
Application management functionality (#42) +semver: feature
Browse files Browse the repository at this point in the history
* Application management functionality +semver: feature

* Add missed logging and retry appManagement test

* Fix naming issue
  • Loading branch information
mialeska committed May 10, 2024
1 parent 07b837e commit 3d6cc65
Show file tree
Hide file tree
Showing 17 changed files with 408 additions and 71 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@
<dependency>
<groupId>com.github.aquality-automation</groupId>
<artifactId>aquality-selenium-core</artifactId>
<version>4.0.1</version>
<version>4.0.2</version>
</dependency>

<dependency>
Expand Down
123 changes: 100 additions & 23 deletions src/main/java/aquality/appium/mobile/application/Application.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
package aquality.appium.mobile.application;

import aquality.appium.mobile.configuration.IApplicationProfile;
import aquality.selenium.core.applications.IApplication;
import aquality.selenium.core.configurations.ITimeoutConfiguration;
import aquality.selenium.core.localization.ILocalizedLogger;
import aquality.selenium.core.utilities.IActionRetrier;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.InteractsWithApps;
import io.appium.java_client.CommandExecutionHelper;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.appmanagement.ApplicationState;
import io.appium.java_client.appmanagement.BaseActivateApplicationOptions;
import io.appium.java_client.appmanagement.BaseTerminateApplicationOptions;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.service.DriverService;

import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

public class Application implements IApplication {
public class Application implements IMobileApplication {

private final ILocalizedLogger localizedLogger;
private final IApplicationProfile applicationProfile;
Expand All @@ -36,10 +45,7 @@ private void setImplicitlyWaitToDriver(Duration duration) {
this.timeoutImpl = duration;
}

/**
* Provides AppiumDriver instance for current application session.
* @return driver instance.
*/
@Override
public AppiumDriver getDriver() {
return appiumDriver;
}
Expand All @@ -49,18 +55,12 @@ public boolean isStarted() {
return appiumDriver.getSessionId() != null;
}

/**
* Provides current AppiumDriver service instance (would be null if driver is not local)
* @return driver service instance
*/
@Override
public DriverService getDriverService() {
return driverService;
}

/**
* Returns name of current platform
* @return name
*/
@Override
public final PlatformName getPlatformName() {
return applicationProfile.getPlatformName();
}
Expand All @@ -73,9 +73,7 @@ public void setImplicitWaitTimeout(Duration timeout) {
}
}

/**
* Executes appium driver quit, then stops the driver service
*/
@Override
public void quit() {
localizedLogger.info("loc.application.quit");
if (getDriver() != null) {
Expand All @@ -88,11 +86,90 @@ public void quit() {
}

/**
* 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.
* Retries application actions.
* @param function result supplier.
* @return result of the function executed.
* @param <T> type of the result.
*/
protected <T> T doWithRetry(Supplier<T> function) {
return AqualityServices.get(IActionRetrier.class)
.doWithRetry(function, Collections.singletonList(WebDriverException.class));
}

/**
* Retries application actions.
* @param function runnable function.
*/
public boolean terminateApp(String bundleId) {
return ((InteractsWithApps)getDriver()).terminateApp(bundleId);
protected void doWithRetry(Runnable function) {
AqualityServices.get(IActionRetrier.class)
.doWithRetry(function, Collections.singletonList(WebDriverException.class));
}

@Override
public String getId() {
final String iosExtName = "mobile: activeAppInfo";
return doWithRetry(() -> {
if (PlatformName.ANDROID == getPlatformName()) {
return ((AndroidDriver) getDriver()).getCurrentPackage();
}
Map<String, Object> result = CommandExecutionHelper.executeScript(getDriver().assertExtensionExists(iosExtName), iosExtName);
return String.valueOf(Objects.requireNonNull(result).get("bundleId"));
});
}

@Override
public ApplicationState getState(String appId) {
localizedLogger.info("loc.application.get.state", appId);
ApplicationState state = doWithRetry(() -> appManagement().queryAppState(appId));
localizedLogger.info("loc.application.state", state);
return state;
}

@Override
public void install(String appPath) {
localizedLogger.info("loc.application.install", appPath);
doWithRetry(() -> appManagement().installApp(appPath));
}

@Override
public void background(Duration timeout) {
localizedLogger.info("loc.application.background");
doWithRetry(() -> appManagement().runAppInBackground(timeout));
}

@Override
public boolean remove(String appId) {
localizedLogger.info("loc.application.remove", appId);
return doWithRetry(() -> appManagement().removeApp(appId));
}

@Override
public void activate(String appId) {
localizedLogger.info("loc.application.activate", appId);
doWithRetry(() -> appManagement().activateApp(appId));
}

@Override
public void activate(String appId, Duration timeout) {
localizedLogger.info("loc.application.activate", appId);
class Options extends BaseActivateApplicationOptions<Options> {
@Override
public Map<String, Object> build() {
return Collections.singletonMap("timeout", timeout.toMillis());
}
}
doWithRetry(() -> appManagement().activateApp(appId, new Options()));
}

@Override
public boolean terminate(String appId, Duration timeout) {
localizedLogger.info("loc.application.terminate", appId);
class Options extends BaseTerminateApplicationOptions<Options> {
@Override
public Map<String, Object> build() {
return Collections.singletonMap("timeout", timeout.toMillis());
}
}
return doWithRetry(() -> appManagement().terminateApp(appId, new Options()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import aquality.selenium.core.waitings.IConditionalWait;
import com.google.inject.Injector;

public class AqualityServices extends aquality.selenium.core.applications.AqualityServices<Application> {
public class AqualityServices extends aquality.selenium.core.applications.AqualityServices<IMobileApplication> {

private static final ThreadLocal<AqualityServices> INSTANCE_CONTAINER = ThreadLocal.withInitial(AqualityServices::new);

Expand All @@ -34,7 +34,7 @@ private static AqualityServices getInstance() {
*
* @return Instance of application.
*/
public static Application getApplication() {
public static IMobileApplication getApplication() {
return getInstance().getApp(injector -> AqualityServices.startApplication());
}

Expand Down Expand Up @@ -95,7 +95,7 @@ public static void setApplicationFactory(IApplicationFactory applicationFactory)
getInstance().applicationFactory = applicationFactory;
}

private static Application startApplication() {
private static IMobileApplication startApplication() {
return getApplicationFactory().getApplication();
}

Expand All @@ -104,7 +104,7 @@ private static Application startApplication() {
*
* @param application Instance of desired application.
*/
public static void setApplication(Application application) {
public static void setApplication(IMobileApplication application) {
getInstance().setApp(application);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public interface IApplicationFactory {

Application getApplication();
IMobileApplication getApplication();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package aquality.appium.mobile.application;

import aquality.selenium.core.applications.IApplication;
import aquality.selenium.core.configurations.ITimeoutConfiguration;
import io.appium.java_client.AppiumDriver;
import io.appium.java_client.InteractsWithApps;
import io.appium.java_client.appmanagement.ApplicationState;
import org.openqa.selenium.remote.service.DriverService;

import java.time.Duration;

public interface IMobileApplication extends IApplication {
/**
* Provides default timeout for terminate methods.
* @return default timeout for waiting until the application is terminated.
*/
static Duration getDefaultTerminateTimeout() {
return AqualityServices.get(ITimeoutConfiguration.class).getCondition();
}

/**
* Provides AppiumDriver instance for current application session.
*
* @return driver instance.
*/
AppiumDriver getDriver();

/**
* Provides current AppiumDriver service instance (would be null if driver is not local)
*
* @return driver service instance
*/
DriverService getDriverService();

/**
* Returns name of current platform
*
* @return name
*/
PlatformName getPlatformName();

/**
* Executes appium driver quit, then stops the driver service
*/
void quit();

/**
* Gets the bundle identifier (or appId) of the currently running application.
*
* @return id of the application in the foreground.
*/
String getId();

default InteractsWithApps appManagement() {
return (InteractsWithApps) getDriver();
}

/**
* Gets the state of the application.
*
* @param appId the bundle identifier (or appId) of the application.
* @return an enumeration of the application state
*/
ApplicationState getState(String appId);

/**
* Installs an application.
*
* @param appPath file path or url of the application.
*/
void install(String appPath);

/**
* Installs an application defined in settings.
* Note that path to the application must be defined as 'app' capability.
*/
default void install() {
install(AqualityServices.getApplicationProfile().getDriverSettings().getApplicationPath());
}

/**
* Send the currently active application to the background,
* and either return after a certain amount of time.
*
* @param timeout How long to background the application for.
*/
void background(Duration timeout);

/**
* Send the currently active application to the background,
* and leave the application deactivated (as "Home" button does).
*/
default void background() {
background(Duration.ofSeconds(-1));
}

/**
* Removes an application.
*
* @param appId the bundle identifier (or appId) of the application to be removed.
* @return true if the uninstallation was successful.
*/
boolean remove(String appId);

/**
* Removes currently running application.
*
* @return true if the uninstallation was successful.
*/
default boolean remove() {
return remove(getId());
}

/**
* Activates the given application by moving to the foreground if it is running in the background
* or starting it if it is not running yet.
*
* @param appId the bundle identifier (or appId) of the application.
*/
void activate(String appId);

/**
* Activates the given application by moving to the foreground if it is running in the background
* or starting it if it is not running yet.
*
* @param appId the bundle identifier (or appId) of the application.
* @param timeout command timeout.
*/
void activate(String appId, Duration timeout);

/**
* Terminate the particular application if it is running.
*
* @param appId the bundle identifier (or appId) of the application to be terminated.
* @param timeout defines for how long to wait until the application is terminated.
* @return true if the application was running before and has been successfully stopped.
*/
boolean terminate(String appId, Duration timeout);

/**
* Terminate the particular application if it is running.
*
* @param appId the bundle identifier (or appId) of the application to be terminated.
* @return true if the application was running before and has been successfully stopped.
*/
default boolean terminate(String appId) {
return terminate(appId, getDefaultTerminateTimeout());
}

/**
* Terminates currently running application.
*
* @param timeout defines for how long to wait until the application is terminated.
* @return true if the application was running before and has been successfully stopped.
*/
default boolean terminate(Duration timeout) {
return terminate(getId(), timeout);
}

/**
* Terminates currently running application.
*
* @return true if the application was running before and has been successfully stopped.
*/
default boolean terminate() {
return terminate(getDefaultTerminateTimeout());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
public class LocalApplicationFactory extends ApplicationFactory {

@Override
public Application getApplication() {
public IMobileApplication getApplication() {
ILocalServiceSettings settings = AqualityServices.getLocalServiceSettings();
AppiumServiceBuilder builder = new AppiumServiceBuilder()
.withCapabilities(settings.getCapabilities());
Expand Down
Loading

0 comments on commit 3d6cc65

Please sign in to comment.