Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application management functionality +semver: feature #42

Merged
merged 3 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading