Skip to content

Commit

Permalink
Add support for executing tests on multiple devices simultaneously us…
Browse files Browse the repository at this point in the history
…ing custom capabilities (#245)

* cleanup caps

* Added DevicePool

* Fixed minor issue

* Code cleanup

* Added IOS APP support
  • Loading branch information
RameshBabuPrudhvi authored Apr 11, 2023
1 parent 07380dd commit b21f20f
Show file tree
Hide file tree
Showing 24 changed files with 617 additions and 259 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@

package io.github.selcukes.commons.http;

import io.github.selcukes.databind.utils.Resources;
import io.github.selcukes.databind.utils.StringHelper;
import lombok.SneakyThrows;

import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
Expand All @@ -32,7 +30,6 @@
import java.util.ArrayList;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import static java.net.http.HttpRequest.BodyPublisher;
Expand All @@ -44,11 +41,10 @@ public class WebClient {
private HttpRequest.Builder requestBuilder;
private BodyPublisher bodyPublisher;

@SneakyThrows
public WebClient(final String url) {
public WebClient(final String uri) {
clientBuilder = HttpClient.newBuilder();
requestBuilder = HttpRequest.newBuilder()
.uri(new URI(url));
.uri(Resources.toURI(uri));
}

/**
Expand All @@ -64,7 +60,7 @@ public WebClient(final String url) {
@SneakyThrows
public Response post(final Object payload) {
contentType("application/json");
HttpRequest request = requestBuilder.POST(bodyPublisher(payload)).build();
var request = requestBuilder.POST(bodyPublisher(payload)).build();
return execute(request);
}

Expand All @@ -75,7 +71,7 @@ public Response post(final Object payload) {
*/
@SneakyThrows
public Response post() {
HttpRequest request = requestBuilder.POST(bodyPublisher).build();
var request = requestBuilder.POST(bodyPublisher).build();
return execute(request);
}

Expand All @@ -85,7 +81,7 @@ public Response post() {
* @return A Response object.
*/
public Response delete() {
HttpRequest request = requestBuilder.DELETE().build();
var request = requestBuilder.DELETE().build();
return execute(request);
}

Expand All @@ -101,7 +97,7 @@ public Response delete() {
* @return A Response object
*/
public Response put(final Object payload) {
HttpRequest request = requestBuilder.PUT(bodyPublisher(payload)).build();
var request = requestBuilder.PUT(bodyPublisher(payload)).build();
return execute(request);
}

Expand Down Expand Up @@ -155,18 +151,10 @@ private Response execute(final HttpRequest request) {
* @return A Response object.
*/
public Response get() {
HttpRequest request = requestBuilder.GET().build();
var request = requestBuilder.GET().build();
return execute(request);
}

private Optional<URL> getProxyUrl(final String proxy) {
try {
return Optional.of(new URL(proxy));
} catch (MalformedURLException e) {
return Optional.empty();
}
}

/**
* If the proxy parameter is a valid URL, then set the proxy host and port
* to the host and port of the URL
Expand All @@ -175,7 +163,7 @@ private Optional<URL> getProxyUrl(final String proxy) {
* @return A WebClient object
*/
public WebClient proxy(final String proxy) {
Optional<URL> url = getProxyUrl(proxy);
var url = Resources.tryURL(proxy);
url.ifPresent(u -> clientBuilder = clientBuilder
.proxy(ProxySelector.of(new InetSocketAddress(u.getHost(),
u.getPort() == -1 ? 80 : u.getPort()))));
Expand Down
19 changes: 14 additions & 5 deletions selcukes-commons/src/test/resources/selcukes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ env: Dev
proxy: false
baseUrl:
excel:
runner:
filePath: ""
suiteName: "smoke"
runner: fase
suiteFile: ""
dataFile: ""
suiteName: "Smoke"
cucumber:
module: google
features: src/test/resources/features/${module}
Expand All @@ -16,13 +17,21 @@ cucumber:
plugin:
web:
remote: false
cloud:
browser: CHROME
headLess: true
serviceUrl: "http://127.0.0.1:8080"
serviceUrl: "http://127.0.0.1:4444"
windows:
serviceUrl: "http://127.0.0.1:4723"
app: "C:\\Windows\\System32\\notepad.exe"
mobile:
remote: false
cloud: BROWSER_STACK
platform: Android
browser: CHROME
headLess: true
serviceUrl: "http://127.0.0.1:4723"
app: "src/test/resources/android-app.apk"
reports:
emailReport: true
htmlReport: true
Expand All @@ -34,7 +43,7 @@ video:
watermark: false
notifier:
notification: false
type: slack
type: Teams
webhookToken: WEBHOOKXXXX
apiToken: APIXXXX
channel: selcukes
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,17 @@
import io.appium.java_client.service.local.AppiumServiceBuilder;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
import io.github.selcukes.commons.exception.DriverSetupException;
import io.github.selcukes.commons.helper.Singleton;
import lombok.CustomLog;

import java.net.URL;

@CustomLog
class AppiumEngine {
private static AppiumEngine appiumEngine;
private AppiumDriverLocalService service;

public static AppiumEngine getInstance() {
if (appiumEngine == null) {
appiumEngine = new AppiumEngine();
}
return appiumEngine;
return Singleton.instanceOf(AppiumEngine.class);
}

URL getServiceUrl() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package io.github.selcukes.core.driver;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.ios.IOSDriver;
import io.github.selcukes.commons.config.ConfigFactory;
import io.github.selcukes.databind.utils.Resources;
import lombok.CustomLog;
import lombok.SneakyThrows;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.remote.RemoteWebDriver;

Expand All @@ -31,56 +33,68 @@
import static java.util.Optional.ofNullable;

@CustomLog
public class AppiumManager implements RemoteManager {
class AppiumManager implements RemoteManager {

@Override
public WebDriver createDriver() {
public WebDriver createDriver(Capabilities customCapabilities) {
String target = ConfigFactory.getConfig().getMobile().getBrowser().toUpperCase();
return target.equals("APP") ? createAppDriver() : createBrowserDriver(target);
return target.equals("APP") ? createAppDriver(customCapabilities)
: createBrowserDriver(customCapabilities, target);
}

@SneakyThrows
public URL getServiceUrl() {
URL serviceUrl;

String serviceUrl;
if (isLocalAppium()) {
serviceUrl = AppiumEngine.getInstance().getServiceUrl();
serviceUrl = AppiumEngine.getInstance().getServiceUrl().toString();
} else if (isCloudAppium()) {
serviceUrl = new URL(CloudOptions.browserStackUrl());
serviceUrl = CloudOptions.browserStackUrl();
} else {
serviceUrl = new URL(ConfigFactory.getConfig().getMobile().getServiceUrl());
serviceUrl = ConfigFactory.getConfig().getMobile().getServiceUrl();
}
logger.debug(() -> String.format("Using ServiceUrl[%s://%s:%s]", serviceUrl.getProtocol(), serviceUrl.getHost(),
serviceUrl.getPort()));
return serviceUrl;
var url = Resources.toURL(serviceUrl);
logger.debug(() -> String.format("Using ServiceUrl[%s://%s:%s]", url.getProtocol(), url.getHost(),
url.getPort()));
return url;
}

public WebDriver createBrowserDriver(String browser) {
logger.debug(() -> "Initiating New Mobile Browser Session...");
var capabilities = ofNullable(AppiumOptions.getUserOptions())
public WebDriver createBrowserDriver(Capabilities capabilities, String browser) {
logger.debug(() -> "Creating New Mobile Browser Session...");
var options = ofNullable(capabilities)
.orElseGet(() -> {
String platform = ConfigFactory.getConfig().getMobile().getPlatform();
var driverOptions = BrowserOptions.getBrowserOptions(BrowserOptions.valueOf(browser), platform);
return isCloudAppium() ? driverOptions.merge(CloudOptions.getBrowserStackOptions(false))
: driverOptions;
});
return new RemoteWebDriver(getServiceUrl(), capabilities);
return new RemoteWebDriver(getServiceUrl(), options);
}

public WebDriver createAppDriver(Capabilities capabilities) {
var platform = ConfigFactory.getConfig().getMobile().getPlatform();
if (platform.equalsIgnoreCase("IOS")) {
logger.debug(() -> "Creating New IOS App Session...");
var options = getAppiumAppOptions(capabilities, true);
return new IOSDriver(getServiceUrl(), options);
} else {
logger.debug(() -> "Creating New ANDROID App Session...");
var options = getAppiumAppOptions(capabilities, false);
return new AndroidDriver(getServiceUrl(), options);
}
}

public WebDriver createAppDriver() {
logger.debug(() -> "Initiating New Mobile App Session...");
var capabilities = ofNullable(AppiumOptions.getUserOptions())
private Capabilities getAppiumAppOptions(Capabilities capabilities, boolean isIOS) {
return ofNullable(capabilities)
.orElseGet(() -> {
if (isCloudAppium()) {
return CloudOptions.getBrowserStackOptions(true);
} else {
var app = ConfigFactory.getConfig().getMobile().getApp();
String appPath = Path.of(app).toAbsolutePath().toString();
logger.info(() -> "Using APP: " + appPath);
return AppiumOptions.getAndroidOptions(appPath);
logger.debug(() -> "Using APP: " + appPath);
return isIOS ? AppiumOptions.getIOSOptions(appPath)
: AppiumOptions.getAndroidOptions(appPath);
}
});
return new AndroidDriver(getServiceUrl(), capabilities);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
package io.github.selcukes.core.driver;

import io.appium.java_client.android.options.UiAutomator2Options;
import io.appium.java_client.ios.options.XCUITestOptions;
import io.appium.java_client.windows.options.WindowsOptions;
import lombok.experimental.UtilityClass;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.MutableCapabilities;

@UtilityClass
public class AppiumOptions {
Capabilities caps;

public Capabilities setAppTopLevelWindow(String windowId) {
return setCapability("appTopLevelWindow", windowId);
}
Expand All @@ -35,33 +34,34 @@ public Capabilities appRoot() {
}

public MutableCapabilities getWinAppOptions(String app) {
WindowsOptions windowsOptions = new WindowsOptions();
windowsOptions.setApp(app);
return merge(windowsOptions);
var options = new WindowsOptions();
options.setApp(app);
return merge(options);
}

public MutableCapabilities getAndroidOptions(String app) {
UiAutomator2Options uiAutomator2Options = new UiAutomator2Options();
uiAutomator2Options.setApp(app);
return merge(uiAutomator2Options);
var options = new UiAutomator2Options();
options.setApp(app);
return merge(options);
}

public MutableCapabilities setCapability(String capabilityName, String value) {
MutableCapabilities capabilities = new MutableCapabilities();
capabilities.setCapability(capabilityName, value);
var capabilities = newCapabilities();
newCapabilities().setCapability(capabilityName, value);
return capabilities;
}

private MutableCapabilities merge(Capabilities capabilities) {
return new MutableCapabilities().merge(capabilities);
return newCapabilities().merge(capabilities);
}

public Capabilities getUserOptions() {
return caps;
private MutableCapabilities newCapabilities() {
return new MutableCapabilities();
}

public void setUserOptions(Capabilities capabilities) {
caps = capabilities;
public MutableCapabilities getIOSOptions(final String app) {
var options = new XCUITestOptions();
options.setApp(app);
return merge(options);
}

}
Loading

0 comments on commit b21f20f

Please sign in to comment.