diff --git a/README.md b/README.md index 2258737..c87ff80 100644 --- a/README.md +++ b/README.md @@ -28,10 +28,16 @@ It will register with Testerra Hooking system and uses the event bus to react on ### Requirements -| Appium connector | Testerra | -|------------------|-------------| -| `>=1.1` | `>=1.3` | -| `2.0 - 2.2` | `2.0 - 2.3` | +| Appium connector | Testerra | Notes | +|------------------|-------------|--------------------------------------| +| `>=1.1` | `>=1.3` | +| `2.0 - 2.2` | `2.0 - 2.3` | | +| `2.3` | `2.4` | Support of Appium Client 8.5.1/8.6.0 | +| `2.4` | `>=2.6` | (not released) | + +Important notes: +* Please check the matrix at https://github.com/appium/java-client?tab=readme-ov-file#compatibility-matrix if Appium Java Client matches to Selenium version +* Please check the Testerra releaes at https://github.com/telekom/testerra/releases which Selenium vesion is used ### Usage @@ -41,10 +47,10 @@ Gradle: ```groovy // Version from this module -implementation 'io.testerra:appium:2.2' +implementation 'io.testerra:appium:2.3' // Used Testerra version -implementation 'io.testerra:driver-ui:2.2' -implementation 'io.appium:java-client:7.3.0' +implementation 'io.testerra:driver-ui:2.4' +implementation 'io.appium:java-client:8.6.0' ``` Maven: @@ -54,18 +60,18 @@ Maven: io.testerra appium - 2.2 + 2.3 io.testerra driver-ui - 2.2 + 2.4 io.appium java-client - 7.3.0 + 8.6.0 @@ -86,15 +92,15 @@ This module is deployed and published to Maven Central. All JAR files are signed The following properties have to be set via command line or ``~/.gradle/gradle.properties`` -| Property | Description | -| ----------------------------- | --------------------------------------------------- | -| `moduleVersion` | Version of deployed module, default is `1-SNAPSHOT` | -| `deployUrl` | Maven repository URL | -| `deployUsername` | Maven repository username | -| `deployPassword` | Maven repository password | -| `signing.keyId` | GPG private key ID (short form) | -| `signing.password` | GPG private key password | -| `signing.secretKeyRingFile` | Path to GPG private key | +| Property | Description | +|-----------------------------|-----------------------------------------------------| +| `moduleVersion` | Version of deployed module, default is `1-SNAPSHOT` | +| `deployUrl` | Maven repository URL | +| `deployUsername` | Maven repository username | +| `deployPassword` | Maven repository password | +| `signing.keyId` | GPG private key ID (short form) | +| `signing.password` | GPG private key password | +| `signing.secretKeyRingFile` | Path to GPG private key | If all properties are set, call the following to build, deploy and release this module: ```shell diff --git a/appium-seetest/build.gradle b/appium-seetest/build.gradle index 3273036..cb76555 100644 --- a/appium-seetest/build.gradle +++ b/appium-seetest/build.gradle @@ -1,6 +1,6 @@ dependencies { compileOnly 'io.testerra:driver-ui:' + testerraCompileVersion - compileOnly 'io.appium:java-client:7.3.0' + compileOnly 'io.appium:java-client:' + appiumJavaClientVersion compileOnly project(':appium') // Rest client @@ -11,7 +11,7 @@ dependencies { testImplementation 'io.testerra:driver-ui-desktop:' + testerraTestVersion testImplementation 'io.testerra:report-ng:' + testerraTestVersion testImplementation project(':appium') - testImplementation 'io.appium:java-client:7.3.0' + testImplementation 'io.appium:java-client:' + appiumJavaClientVersion } test { diff --git a/appium-seetest/src/main/java/eu/tsystems/mms/tic/testerra/plugins/appium/seetest/DriverUi_AppiumSeeTest.java b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/ioc/DriverUi_AppiumSeeTest.java similarity index 97% rename from appium-seetest/src/main/java/eu/tsystems/mms/tic/testerra/plugins/appium/seetest/DriverUi_AppiumSeeTest.java rename to appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/ioc/DriverUi_AppiumSeeTest.java index cc843c9..0302514 100644 --- a/appium-seetest/src/main/java/eu/tsystems/mms/tic/testerra/plugins/appium/seetest/DriverUi_AppiumSeeTest.java +++ b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/ioc/DriverUi_AppiumSeeTest.java @@ -19,7 +19,7 @@ * under the License. * */ -package eu.tsystems.mms.tic.testerra.plugins.appium.seetest; +package io.testerra.plugins.appium.seetest.ioc; import com.google.inject.AbstractModule; import com.google.inject.Scopes; diff --git a/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/utils/VideoLoader.java b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/utils/VideoLoader.java index 60ee8c6..6f1e383 100644 --- a/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/utils/VideoLoader.java +++ b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/utils/VideoLoader.java @@ -28,7 +28,6 @@ import eu.tsystems.mms.tic.testframework.utils.AppiumProperties; import eu.tsystems.mms.tic.testframework.utils.Sequence; import io.testerra.plugins.appium.seetest.request.VideoRequest; -import org.apache.http.HttpStatus; import java.io.File; import java.io.IOException; @@ -81,7 +80,7 @@ private Optional downloadVideo(VideoRequest videoRequest) { .header("Authorization", "Bearer " + AppiumProperties.MOBILE_GRID_ACCESS_KEY.asString()) .build(); HttpResponse httpResponse = client.send(request, HttpResponse.BodyHandlers.ofFile(videoFile.toPath())); - if (httpResponse.statusCode() != HttpStatus.SC_OK) { + if (httpResponse.statusCode() != 200) { log().info("Download status code: {}", httpResponse.statusCode()); log().info("Wait for video is ready for download..."); } else { diff --git a/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/webdriver/SeeTestAppiumDriverFactory.java b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/webdriver/SeeTestAppiumDriverFactory.java index d90e4f0..2208706 100644 --- a/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/webdriver/SeeTestAppiumDriverFactory.java +++ b/appium-seetest/src/main/java/io/testerra/plugins/appium/seetest/webdriver/SeeTestAppiumDriverFactory.java @@ -5,6 +5,7 @@ import eu.tsystems.mms.tic.testframework.webdriver.WebDriverFactory; import eu.tsystems.mms.tic.testframework.webdrivermanager.AppiumDriverRequest; import eu.tsystems.mms.tic.testframework.webdrivermanager.WebDriverRequest; +import io.appium.java_client.remote.options.BaseOptions; import io.testerra.plugins.appium.seetest.utils.SeeTestProperties; import org.apache.commons.lang3.StringUtils; @@ -19,11 +20,16 @@ public class SeeTestAppiumDriverFactory extends AppiumDriverFactory implements W public WebDriverRequest prepareWebDriverRequest(WebDriverRequest webDriverRequest) { AppiumDriverRequest request = (AppiumDriverRequest) super.prepareWebDriverRequest(webDriverRequest); + BaseOptions options = new BaseOptions(); + final String appiumServer = SeeTestProperties.SEETEST_APPIUM_VERSION.asString(); if (StringUtils.isNotBlank(appiumServer)) { - request.getDesiredCapabilities().setCapability("appiumVersion", appiumServer); + options.setCapability("appiumVersion", appiumServer); +// request.getDesiredCapabilities().setCapability("appiumVersion", appiumServer); } + request.setCapabilities(request.getCapabilities().merge(options)); + return request; } diff --git a/appium/build.gradle b/appium/build.gradle index 3569e6d..9b2b3d2 100644 --- a/appium/build.gradle +++ b/appium/build.gradle @@ -1,6 +1,6 @@ dependencies { compileOnly 'io.testerra:driver-ui:' + testerraCompileVersion - compileOnly 'io.appium:java-client:7.3.0' + compileOnly 'io.appium:java-client:' + appiumJavaClientVersion implementation 'com.google.code.gson:gson:2.9.0' implementation 'org.apache.commons:commons-lang3:3.12.0' @@ -11,7 +11,7 @@ dependencies { testImplementation 'io.testerra:driver-ui:' + testerraTestVersion // testImplementation 'io.testerra:driver-ui' testImplementation 'io.testerra:report-ng:' + testerraTestVersion - testImplementation 'io.appium:java-client:7.3.0' + testImplementation 'io.appium:java-client:' + appiumJavaClientVersion } test() { diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/AppiumCapabilityHelper.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/AppiumCapabilityHelper.java new file mode 100644 index 0000000..ed3d995 --- /dev/null +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/AppiumCapabilityHelper.java @@ -0,0 +1,35 @@ +/* + * Testerra + * + * (C) 2024, Martin Großmann, Deutsche Telekom MMS GmbH, Deutsche Telekom AG + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * 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 eu.tsystems.mms.tic.testframework.appium; + +/** + * Created on 2024-06-11 + * + * The Appium client convert all Appium specific capabilities and adds the prefix 'appium:'. + * This should help to read a capability. + */ +public interface AppiumCapabilityHelper { + + default String getAppiumCap(final String capabilityName) { + return "appium:" + capabilityName; + } + +} diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/WinAppDriverFactory.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/WinAppDriverFactory.java index 24e4442..6cfa741 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/WinAppDriverFactory.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/appium/WinAppDriverFactory.java @@ -21,8 +21,8 @@ package eu.tsystems.mms.tic.testframework.appium; -import eu.tsystems.mms.tic.testframework.core.WinAppDriverCoreAdapter; import eu.tsystems.mms.tic.testframework.common.IProperties; +import eu.tsystems.mms.tic.testframework.core.WinAppDriverCoreAdapter; import eu.tsystems.mms.tic.testframework.logging.Loggable; import eu.tsystems.mms.tic.testframework.pageobjects.internal.core.GuiElementCore; import eu.tsystems.mms.tic.testframework.pageobjects.internal.core.GuiElementData; @@ -33,16 +33,15 @@ import eu.tsystems.mms.tic.testframework.webdrivermanager.WebDriverRequest; import eu.tsystems.mms.tic.testframework.webdrivermanager.WinAppDriverRequest; import io.appium.java_client.windows.WindowsDriver; -import io.appium.java_client.windows.WindowsElement; -import java.net.URL; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; +import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.remote.DesiredCapabilities; +import java.net.URL; +import java.util.Arrays; +import java.util.List; + public class WinAppDriverFactory implements WebDriverFactory, Loggable, TestControllerProvider, Sleepy { public enum Properties implements IProperties { @@ -68,7 +67,7 @@ public Object getDefault() { } } - private WindowsDriver startNewWindowsDriver(WinAppDriverRequest appDriverRequest, SessionContext sessionContext) { + private WindowsDriver startNewWindowsDriver(WinAppDriverRequest appDriverRequest, SessionContext sessionContext) { final URL finalWinAppServerUrl = appDriverRequest.getServerUrl().get(); sessionContext.setNodeUrl(finalWinAppServerUrl); @@ -80,7 +79,7 @@ private WindowsDriver startNewWindowsDriver(WinAppDriverRequest desiredCapabilities.setCapability(WinAppDriverRequest.APP_ID, appId); }); - WindowsDriver windowsDriver = new WindowsDriver<>(finalWinAppServerUrl, desiredCapabilities); + WindowsDriver windowsDriver = new WindowsDriver(finalWinAppServerUrl, desiredCapabilities); //CONTROL.retryFor(appDriverRequest.getStartupTimeoutSeconds(), windowsDriver::getTitle, this::sleep); return windowsDriver; } @@ -94,8 +93,8 @@ public WebDriverRequest prepareWebDriverRequest(WebDriverRequest webDriverReques } @Override - public WebDriver createWebDriver(WebDriverRequest webDriverRequest, SessionContext sessionContext) { - WinAppDriverRequest appDriverRequest = (WinAppDriverRequest)webDriverRequest; + public WebDriver createWebDriver(WebDriverRequest webDriverRequest, SessionContext sessionContext) { + WinAppDriverRequest appDriverRequest = (WinAppDriverRequest) webDriverRequest; /** * Try to reuse an already opened application @@ -113,11 +112,13 @@ public WebDriver createWebDriver(WebDriverRequest webDriverRequest, SessionConte appDriverRequest.getServerUrl().ifPresent(desktopDriverRequest::setServerUrl); } - WindowsDriver desktopDriver = startNewWindowsDriver(desktopDriverRequest, sessionContext); + WindowsDriver desktopDriver = startNewWindowsDriver(desktopDriverRequest, sessionContext); log().info(String.format("Try to create driver on running application by window title \"%s\"", reuseableWindowTitle)); CONTROL.waitFor(appDriverRequest.getReuseTimeoutSeconds(), () -> { - WebElement elementByName = desktopDriver.findElementByName(reuseableWindowTitle); + // TODO: Verify migration + // WebElement elementByName = desktopDriver.findElementByName(reuseableWindowTitle); --> migrate to Appium 8.x + WebElement elementByName = desktopDriver.findElement(By.name(reuseableWindowTitle)); String nativeWindowHandle = elementByName.getAttribute("NativeWindowHandle"); int nativeWindowHandleId = Integer.parseInt(nativeWindowHandle); if (nativeWindowHandleId > 0) { diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/ioc/DriverUi_Appium.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/ioc/DriverUi_Appium.java index 4735a69..780fca9 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/ioc/DriverUi_Appium.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/ioc/DriverUi_Appium.java @@ -26,6 +26,10 @@ import com.google.inject.multibindings.Multibinder; import eu.tsystems.mms.tic.testframework.appium.WinAppDriverFactory; import eu.tsystems.mms.tic.testframework.mobile.driver.AppiumDriverFactory; +import eu.tsystems.mms.tic.testframework.mobile.guielement.AppiumUiElementHighlighter; +import eu.tsystems.mms.tic.testframework.mobile.pageobject.AppiumPageFactory; +import eu.tsystems.mms.tic.testframework.pageobjects.UiElementHighlighter; +import eu.tsystems.mms.tic.testframework.pageobjects.internal.PageFactory; import eu.tsystems.mms.tic.testframework.utils.AppiumExecutionUtils; import eu.tsystems.mms.tic.testframework.utils.ExecutionUtils; import eu.tsystems.mms.tic.testframework.webdriver.WebDriverFactory; @@ -46,5 +50,9 @@ protected void configure() { webDriverFactoryBinder.addBinding().to(AppiumDriverFactory.class).in(Scopes.SINGLETON); webDriverFactoryBinder.addBinding().to(WinAppDriverFactory.class).in(Scopes.SINGLETON); bind(ExecutionUtils.class).to(AppiumExecutionUtils.class).in(Scopes.SINGLETON); + // Support for device specific pages + bind(PageFactory.class).to(AppiumPageFactory.class).in(Scopes.SINGLETON); + // Prevent error while trying element highlighting in apps + bind(UiElementHighlighter.class).to(AppiumUiElementHighlighter.class).in(Scopes.SINGLETON); } } diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDeviceQuery.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDeviceQuery.java index 3687d88..ca4ae64 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDeviceQuery.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDeviceQuery.java @@ -45,7 +45,8 @@ public AppiumDeviceQuery() { } public AppiumDeviceQuery(Capabilities capabilities) { - this.setOs((String) capabilities.getCapability("platformName")); + this.setOs(capabilities.getPlatformName().toString()); +// this.setOs((String) capabilities.getCapability("platformName")); this.setVersion((String) capabilities.getCapability("platformVersion")); this.setManufacture((String) capabilities.getCapability("deviceManufacture")); this.setModel((String) capabilities.getCapability("deviceModel")); diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDriverFactory.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDriverFactory.java index dfa46b8..1ade3db 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDriverFactory.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/AppiumDriverFactory.java @@ -31,7 +31,6 @@ import eu.tsystems.mms.tic.testframework.report.model.context.SessionContext; import eu.tsystems.mms.tic.testframework.report.utils.IExecutionContextController; import eu.tsystems.mms.tic.testframework.utils.AppiumProperties; -import eu.tsystems.mms.tic.testframework.utils.DefaultCapabilityUtils; import eu.tsystems.mms.tic.testframework.utils.TimerUtils; import eu.tsystems.mms.tic.testframework.webdriver.WebDriverFactory; import eu.tsystems.mms.tic.testframework.webdrivermanager.AppiumDriverRequest; @@ -41,10 +40,13 @@ import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.remote.MobileBrowserType; +import io.appium.java_client.remote.options.BaseOptions; import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.WebDriver; -import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.support.events.EventFiringWebDriver; import java.net.URL; @@ -71,24 +73,23 @@ public WebDriverRequest prepareWebDriverRequest(WebDriverRequest webDriverReques finalRequest = new AppiumDriverRequest(); finalRequest.setSessionKey(webDriverRequest.getSessionKey()); finalRequest.setBrowser(webDriverRequest.getBrowser()); - finalRequest.setBrowserVersion(webDriverRequest.getBrowserVersion()); } - DesiredCapabilities requestCapabilities = finalRequest.getDesiredCapabilities(); + MutableCapabilities requestCapabilities = finalRequest.getMutableCapabilities(); + BaseOptions baseOptions = new BaseOptions<>(); - // general caps IExecutionContextController executionContext = Testerra.getInjector().getInstance(IExecutionContextController.class); - requestCapabilities.setCapability(AppiumDriverRequest.CAPABILITY_NAME_TEST_NAME, executionContext.getExecutionContext().getRunConfig().getReportName()); + baseOptions.setCapability(AppiumDriverRequest.CAPABILITY_NAME_TEST_NAME, executionContext.getExecutionContext().getRunConfig().getReportName()); if (requestCapabilities.getCapability(AppiumDriverRequest.DEVICE_QUERY) == null || StringUtils.isBlank(requestCapabilities.getCapability(AppiumDriverRequest.DEVICE_QUERY).toString())) { switch (webDriverRequest.getBrowser()) { case Browsers.mobile_safari: { - finalRequest.setDeviceQuery(AppiumProperties.MOBILE_APPIUM_DEVICE_QUERY_IOS.asString()); + baseOptions.setCapability(AppiumDriverRequest.DEVICE_QUERY, AppiumProperties.MOBILE_APPIUM_DEVICE_QUERY_IOS.asString()); break; } case Browsers.mobile_chrome: { - finalRequest.setDeviceQuery(AppiumProperties.MOBILE_APPIUM_DEVICE_QUERY_ANDROID.asString()); + baseOptions.setCapability(AppiumDriverRequest.DEVICE_QUERY, AppiumProperties.MOBILE_APPIUM_DEVICE_QUERY_ANDROID.asString()); break; } } @@ -96,15 +97,20 @@ public WebDriverRequest prepareWebDriverRequest(WebDriverRequest webDriverReques switch (webDriverRequest.getBrowser()) { case Browsers.mobile_chrome: - requestCapabilities.setBrowserName(MobileBrowserType.CHROME); + baseOptions.setCapability(CapabilityType.BROWSER_NAME, MobileBrowserType.CHROME); break; case Browsers.mobile_safari: - requestCapabilities.setBrowserName(MobileBrowserType.SAFARI); + baseOptions.setCapability(CapabilityType.BROWSER_NAME, MobileBrowserType.SAFARI); break; default: log().info("No mobile browser requested."); } + // Any additional defined desired capabilities are merged into base options + baseOptions = baseOptions.merge(finalRequest.getDesiredCapabilities()); + baseOptions = baseOptions.merge(finalRequest.getMutableCapabilities()); + finalRequest.setCapabilities(baseOptions); + return finalRequest; } @@ -123,44 +129,26 @@ public WebDriver createWebDriver(WebDriverRequest webDriverRequest, SessionConte private WebDriver startNewAppiumSession(WebDriverRequest webDriverRequest, SessionContext sessionContext) { AppiumDriverRequest appiumDriverRequest = (AppiumDriverRequest) webDriverRequest; - DesiredCapabilities requestCapabilities = appiumDriverRequest.getDesiredCapabilities(); + Capabilities requestCapabilities = appiumDriverRequest.getCapabilities(); URL appiumUrl = appiumDriverRequest.getServerUrl().get(); - DesiredCapabilities finalCapabilities = new DesiredCapabilities(requestCapabilities); - - IExecutionContextController executionContextController = Testerra.getInjector().getInstance(IExecutionContextController.class); - DefaultCapabilityUtils utils = new DefaultCapabilityUtils(); - // TODO: Move to prepareWebDriverRequest - utils.putIfAbsent(finalCapabilities, AppiumDriverRequest.CAPABILITY_NAME_TEST_NAME, executionContextController.getExecutionContext().getRunConfig().getReportName()); AppiumDriver appiumDriver = null; Platform mobilePlatform = new MobileOsChecker().getPlatform(webDriverRequest); switch (mobilePlatform) { case IOS: - appiumDriver = new IOSDriver<>(appiumUrl, finalCapabilities); + appiumDriver = new IOSDriver(appiumUrl, requestCapabilities); break; case ANDROID: - appiumDriver = new AndroidDriver<>(appiumUrl, finalCapabilities); + appiumDriver = new AndroidDriver(appiumUrl, requestCapabilities); break; } -// switch (webDriverRequest.getBrowser()) { -// case Browsers.mobile_safari: { -// finalCapabilities.setBrowserName(MobileBrowserType.SAFARI); -// appiumDriver = new IOSDriver<>(appiumUrl, finalCapabilities); -// break; -// } -// case Browsers.mobile_chrome: { -// finalCapabilities.setBrowserName(MobileBrowserType.CHROME); -// appiumDriver = new AndroidDriver<>(appiumUrl, finalCapabilities); -// break; -// } -// } if (appiumDriver != null) { AppiumDeviceQuery appiumDeviceQuery = new AppiumDeviceQuery(appiumDriver.getCapabilities()); - sessionContext.setActualBrowserName(appiumDeviceQuery.toString()); + sessionContext.setUserAgent(appiumDeviceQuery.toString()); + sessionContext.setActualBrowserName(appiumDeviceQuery.getBrowserName()); } else { - throw new RuntimeException("Cannot create new Appium session - ambiguous capabilities found:\n " + finalCapabilities.toString()); -// throw new RuntimeException("Mobile Browser not supported: " + webDriverRequest.getBrowser()); + throw new RuntimeException("Cannot create new Appium session - ambiguous capabilities found:\n " + requestCapabilities.toString()); } return appiumDriver; } @@ -174,7 +162,7 @@ public void setupNewWebDriverSession(EventFiringWebDriver webDriver, SessionCont .ifPresent(driver -> driverString.set(driver.getClass().toString())); // In case of app automation it es not possible to call a URL - if (StringUtils.isNotBlank(appiumDriverRequest.getDesiredCapabilities().getBrowserName())) { + if (StringUtils.isNotBlank(appiumDriverRequest.getBrowser())) { appiumDriverRequest.getBaseUrl().ifPresent(url -> { log().info("Open {} on {}", url, driverString.get()); webDriver.get(url.toString()); diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/MobileOsChecker.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/MobileOsChecker.java index 52e3746..d1a90d2 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/MobileOsChecker.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/driver/MobileOsChecker.java @@ -20,6 +20,7 @@ */ package eu.tsystems.mms.tic.testframework.mobile.driver; +import eu.tsystems.mms.tic.testframework.appium.AppiumCapabilityHelper; import eu.tsystems.mms.tic.testframework.appium.Browsers; import eu.tsystems.mms.tic.testframework.common.Testerra; import eu.tsystems.mms.tic.testframework.report.model.context.SessionContext; @@ -28,10 +29,10 @@ import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.IOSMobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType; +import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.WebDriver; -import java.util.Map; import java.util.Optional; /** @@ -39,27 +40,15 @@ * * @author mgn */ -public class MobileOsChecker { +public class MobileOsChecker implements AppiumCapabilityHelper { public Platform getPlatform(WebDriverRequest webDriverRequest) { - Map capabilities = webDriverRequest.getCapabilities(); - if (webDriverRequest.getBrowser().equals(Browsers.mobile_chrome) - || "Espresso".equals(capabilities.get(MobileCapabilityType.AUTOMATION_NAME)) - || "UiAutomator2".equals(capabilities.get(MobileCapabilityType.AUTOMATION_NAME)) - || "UiAutomator".equals(capabilities.get(MobileCapabilityType.AUTOMATION_NAME)) - || capabilities.containsKey(AndroidMobileCapabilityType.APP_PACKAGE) - || capabilities.containsKey(AndroidMobileCapabilityType.APP_ACTIVITY) - ) { + if (Browsers.mobile_chrome.equals(webDriverRequest.getBrowser()) || isAppTest(webDriverRequest, Platform.ANDROID)) { return Platform.ANDROID; } - if (webDriverRequest.getBrowser().equals(Browsers.mobile_safari) - || "XCUITest".equals(capabilities.get(MobileCapabilityType.AUTOMATION_NAME)) - || "UIAutomation".equals(capabilities.get(MobileCapabilityType.AUTOMATION_NAME)) - || capabilities.containsKey(IOSMobileCapabilityType.BUNDLE_ID) - ) { + if (Browsers.mobile_safari.equals(webDriverRequest.getBrowser()) || isAppTest(webDriverRequest, Platform.IOS)) { return Platform.IOS; } - return Platform.ANY; } @@ -73,4 +62,23 @@ public Platform getPlatform(WebDriver driver) { } } + // Returns true if WebDriverRequest contains typical app capabilities + public boolean isAppTest(WebDriverRequest webDriverRequest, Platform platform) { + Capabilities capabilities = webDriverRequest.getCapabilities(); + switch (platform) { + case ANDROID: + return "Espresso".equals(capabilities.getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME))) + || "UiAutomator2".equals(capabilities.getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME))) + || "UiAutomator".equals(capabilities.getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME))) + || capabilities.getCapability(getAppiumCap(AndroidMobileCapabilityType.APP_PACKAGE)) != null + || capabilities.getCapability(getAppiumCap(AndroidMobileCapabilityType.APP_ACTIVITY)) != null; + case IOS: + return "XCUITest".equals(capabilities.getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME))) + || "UIAutomation".equals(capabilities.getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME))) + || capabilities.getCapability(getAppiumCap(IOSMobileCapabilityType.BUNDLE_ID)) != null; + default: + return false; + } + } + } diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumGuiElementCoreAdapter.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumGuiElementCoreAdapter.java index f35b61e..87dc6cf 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumGuiElementCoreAdapter.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumGuiElementCoreAdapter.java @@ -29,21 +29,21 @@ import eu.tsystems.mms.tic.testframework.pageobjects.internal.core.GuiElementCore; import eu.tsystems.mms.tic.testframework.pageobjects.internal.core.GuiElementData; import eu.tsystems.mms.tic.testframework.testing.WebDriverManagerProvider; +import eu.tsystems.mms.tic.testframework.utils.AppiumUtils; import eu.tsystems.mms.tic.testframework.utils.ExecutionUtils; import io.appium.java_client.AppiumDriver; -import io.appium.java_client.TouchAction; -import io.appium.java_client.touch.LongPressOptions; -import io.appium.java_client.touch.TapOptions; -import io.appium.java_client.touch.WaitOptions; -import io.appium.java_client.touch.offset.ElementOption; -import io.appium.java_client.touch.offset.PointOption; +import org.apache.commons.lang3.NotImplementedException; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Pause; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; import java.time.Duration; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; -import java.util.function.Supplier; /** * Implements {@link GuiElementCore} to fullfill Testerra {@link GuiElement} functionality. @@ -75,46 +75,48 @@ protected void switchToFrame(WebDriver webDriver, WebElement webElement) { @Override public void hover() { - // Caused by: org.openqa.selenium.WebDriverException: Not Implemented (Method 'mouseMoveTo' is not implemented) - throw new MobileActionNotSupportedException("hover() is not supported on mobile element.s"); + throw new MobileActionNotSupportedException("hover() is not supported on mobile elements."); } @Override public void contextClick() { this.findWebElement(webElement -> { - final ElementOption elementOption = new ElementOption().withElement(webElement); - final TouchAction action = new TouchAction<>(appiumDriver); - - action.longPress(new LongPressOptions().withElement(elementOption)); - action.perform(); + Point sourceLocation = webElement.getLocation(); + Dimension sourceSize = webElement.getSize(); + int centerX = sourceLocation.getX() + sourceSize.getWidth() / 2; + int centerY = sourceLocation.getY() + sourceSize.getHeight() / 2; + + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence tap = new Sequence(finger, 1); + tap.addAction(finger.createPointerMove(Duration.ofMillis(0), PointerInput.Origin.viewport(), centerX, centerY)); + tap.addAction(finger.createPointerDown(PointerInput.MouseButton.LEFT.asArg())); + + // Default in iOS: 500ms + // https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/handling_uikit_gestures/handling_long-press_gestures + // Default in Android: 1_000ms + // https://developer.android.com/develop/ui/views/touch-and-input/input-events + tap.addAction(new Pause(finger, Duration.ofMillis(1_000))); + tap.addAction(finger.createPointerUp(PointerInput.MouseButton.LEFT.asArg())); + this.appiumDriver.perform(List.of(tap)); }); } - @Override - public void doubleClick() { - this.findWebElement(webElement -> { - final ElementOption elementOption = new ElementOption().withElement(webElement); - final TouchAction action = new TouchAction<>(appiumDriver); - - final TapOptions tapOptions = new TapOptions().withTapsCount(2).withElement(elementOption); - action.tap(tapOptions).perform(); - }); - } + // TODO: Migrate to W3C actions +// @Override +// public void doubleClick() { +// this.findWebElement(webElement -> { +// final ElementOption elementOption = new ElementOption().withElement(webElement); +// final TouchAction action = new TouchAction<>(appiumDriver); +// +// final TapOptions tapOptions = new TapOptions().withTapsCount(2).withElement(elementOption); +// action.tap(tapOptions).perform(); +// }); +// } @Override public void swipe(int offsetX, int offsetY) { - this.findWebElement(webElement -> { - TouchAction touchAction = new TouchAction(appiumDriver); - - final TapOptions tapOption = new TapOptions().withElement(new ElementOption().withElement(webElement)); - touchAction.tap(tapOption); - touchAction.waitAction(new WaitOptions().withDuration(Duration.ofMillis(1500))); - touchAction.moveTo(new PointOption().withCoordinates(offsetX, offsetY)); - touchAction.waitAction(new WaitOptions().withDuration(Duration.ofMillis(1500))); - touchAction.release(); - touchAction.perform(); - }); + new AppiumUtils().swipe(this.guiElementData.getGuiElement(), new Point(offsetX, offsetY)); } @Override diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumUiElementHighlighter.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumUiElementHighlighter.java new file mode 100644 index 0000000..eb2481b --- /dev/null +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/AppiumUiElementHighlighter.java @@ -0,0 +1,53 @@ +/* + * Testerra + * + * (C) 2024, Martin Großmann, Deutsche Telekom MMS GmbH, Deutsche Telekom AG + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * 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 eu.tsystems.mms.tic.testframework.mobile.guielement; + +import eu.tsystems.mms.tic.testframework.common.Testerra; +import eu.tsystems.mms.tic.testframework.mobile.driver.MobileOsChecker; +import eu.tsystems.mms.tic.testframework.pageobjects.DefaultUiElementHighlighter; +import eu.tsystems.mms.tic.testframework.report.utils.IExecutionContextController; +import org.openqa.selenium.Platform; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; + +import java.awt.Color; + +/** + * Created on 2024-06-11 + * + * @author mgn + */ +public class AppiumUiElementHighlighter extends DefaultUiElementHighlighter { + + @Override + public void highlight(WebDriver driver, WebElement webElement, Color color) { + IExecutionContextController instance = Testerra.getInjector().getInstance(IExecutionContextController.class); + instance.getCurrentSessionContext().ifPresent(sessionContext -> { + // Highlighting is only working in browsers but not in apps + MobileOsChecker mobileOsChecker = new MobileOsChecker(); + Platform platform = mobileOsChecker.getPlatform(driver); + if (!mobileOsChecker.isAppTest(sessionContext.getWebDriverRequest(), platform)) { + super.highlight(driver, webElement, color); + } + }); + } + +} diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/CreateAppiumGuiElementAction.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/CreateAppiumGuiElementAction.java index cb794f3..e179fd3 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/CreateAppiumGuiElementAction.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/guielement/CreateAppiumGuiElementAction.java @@ -20,6 +20,7 @@ */ package eu.tsystems.mms.tic.testframework.mobile.guielement; +import eu.tsystems.mms.tic.testframework.appium.AppiumCapabilityHelper; import eu.tsystems.mms.tic.testframework.common.Testerra; import eu.tsystems.mms.tic.testframework.internal.NameableChild; import eu.tsystems.mms.tic.testframework.logging.Loggable; @@ -47,7 +48,10 @@ * * @author mgn */ -public class CreateAppiumGuiElementAction extends AbstractFieldAction implements UiElementFinderFactoryProvider, Loggable { +public class CreateAppiumGuiElementAction extends AbstractFieldAction implements + UiElementFinderFactoryProvider, + Loggable, + AppiumCapabilityHelper { public CreateAppiumGuiElementAction(Field field, AbstractPage declaringPage) { super(field, declaringPage); @@ -100,9 +104,9 @@ private String getAutomationEngine(WebDriver driver, Platform platform) { IWebDriverManager instance = Testerra.getInjector().getInstance(IWebDriverManager.class); Optional optional = instance.getSessionContext(driver).map(SessionContext::getWebDriverRequest); if (optional.isPresent()) { - String automationEngine = optional.get().getCapabilities().get(MobileCapabilityType.AUTOMATION_NAME).toString(); - if (StringUtils.isNotBlank(automationEngine)) { - return automationEngine; + Object automationEngine = optional.get().getCapabilities().getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME)); + if (automationEngine != null && StringUtils.isNotBlank(automationEngine.toString())) { + return automationEngine.toString(); } else { // Use default values for automation engine switch (platform) { diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumClassFinder.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumClassFinder.java index 3814e37..5964a73 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumClassFinder.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumClassFinder.java @@ -1,3 +1,23 @@ +/* + * Testerra + * + * (C) 2023, Martin Großmann, Deutsche Telekom MMS GmbH, Deutsche Telekom AG + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * 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 eu.tsystems.mms.tic.testframework.mobile.pageobject; import eu.tsystems.mms.tic.testframework.logging.Loggable; diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumPageFactory.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumPageFactory.java index 706f8b0..1c6e0fa 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumPageFactory.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/mobile/pageobject/AppiumPageFactory.java @@ -1,3 +1,23 @@ +/* + * Testerra + * + * (C) 2023, Martin Großmann, Deutsche Telekom MMS GmbH, Deutsche Telekom AG + * + * Deutsche Telekom AG and all other contributors / + * copyright owners license this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this + * file except in compliance with the License. + * 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 eu.tsystems.mms.tic.testframework.mobile.pageobject; import eu.tsystems.mms.tic.testframework.enums.CheckRule; diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/utils/AppiumUtils.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/utils/AppiumUtils.java index 6673048..d6153db 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/utils/AppiumUtils.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/utils/AppiumUtils.java @@ -25,13 +25,22 @@ import eu.tsystems.mms.tic.testframework.appium.AppiumContext; import eu.tsystems.mms.tic.testframework.logging.Loggable; import eu.tsystems.mms.tic.testframework.mobile.driver.MobileOsChecker; +import eu.tsystems.mms.tic.testframework.pageobjects.UiElement; import eu.tsystems.mms.tic.testframework.testing.WebDriverManagerProvider; import io.appium.java_client.AppiumDriver; +import io.appium.java_client.remote.SupportsContextSwitching; +import io.appium.java_client.remote.SupportsRotation; +import org.openqa.selenium.Dimension; import org.openqa.selenium.Platform; +import org.openqa.selenium.Point; +import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.interactions.PointerInput; +import org.openqa.selenium.interactions.Sequence; +import java.time.Duration; import java.util.Arrays; -import java.util.Optional; +import java.util.List; import java.util.Set; /** @@ -41,11 +50,45 @@ */ public class AppiumUtils implements WebDriverManagerProvider, Loggable { + public enum Swipe { + LEFT, + RIGHT, + UP, + DOWN; + } + + // Relative start and end positions to the screen dimension of standard swipe actions + // for example scroll down: + // - 0.2 * screenHeight -> start y (SCREEN_SWIPE_START) + // - 0.5 * screenHeight -> end y (SCREEN_MULTIPLIER) + // - x is center of screen + // for example scroll left: + // - 0.8 * screenWidth -> start x (SCREEN_SWIPE_END) + // - 0.5 * screenWidth -> end x (SCREEN_MULTIPLIER) + // - y is center of screen + private static final double SCREEN_SWIPE_START = 0.2; + private static final double SCREEN_SWIPE_END = 0.8; + private static final double SCREEN_MULTIPLIER = 0.5; + + /** + * Run a shell command, especially at Android devices + * + * @param driver + * @param command + * @param args + * @return + */ public String runCommand(WebDriver driver, String command, String... args) { log().info("Shell command (native): {}, {}", command, Arrays.toString(args)); // ImmutableList does NOT work here... return String.valueOf(JSUtils.executeScript(driver, "mobile: shell", ImmutableMap.of("command", command, "args", Lists.newArrayList(args)))); } + /** + * Helper method to start a native app from iOS system. To start installed custom apps use the default way with AppiumDriverRequest + * + * @param driver Current webdriver instance + * @param bundleId Bundle ID of native app + */ public void launchIOSApp(WebDriver driver, String bundleId) { log().info("Start application '{}'", bundleId); MobileOsChecker checker = new MobileOsChecker(); @@ -58,15 +101,110 @@ public void launchIOSApp(WebDriver driver, String bundleId) { } } + public void rotate(WebDriver driver, ScreenOrientation screenOrientation) { + WEB_DRIVER_MANAGER.unwrapWebDriver(driver, AppiumDriver.class).ifPresent(appiumDriver -> { + ((SupportsRotation) appiumDriver).rotate(screenOrientation); + }); + } + + /** + * Perform a swipe action, start point is an element and end point is calculated based on the offset. + */ + public void swipe(UiElement uiElement, Point offset) { + AppiumDriver appiumDriver = this.getAppiumDriver(uiElement.getWebDriver()); + + uiElement.findWebElement(webElement -> { + Point sourceLocation = webElement.getLocation(); + Dimension sourceSize = webElement.getSize(); + int centerX = sourceLocation.getX() + sourceSize.getWidth() / 2; + int centerY = sourceLocation.getY() + sourceSize.getHeight() / 2; + int endX = centerX + offset.getX(); + int endY = centerY + offset.getY(); + + this.swipeAction(appiumDriver, Duration.ofMillis(200), centerX, centerY, endX, endY); + }); + } + + /** + * Perform a swipe action, start point is the center of an element. Possible directions are + * Swipe.LEFT, Swipe.RIGHT, Swipe.UP and Swipe.DOWN. + */ + public void swipe(UiElement startElement, Swipe direction) { + startElement.findWebElement(webElement -> { + Point sourceLocation = webElement.getLocation(); + Dimension sourceSize = webElement.getSize(); + int startX = sourceLocation.getX() + sourceSize.getWidth() / 2; + int startY = sourceLocation.getY() + sourceSize.getHeight() / 2; + Point startPoint = new Point(startX, startY); + + this.swipeAction(startElement.getWebDriver(), direction, startPoint); + }); + } + + /** + * Perform a swipe action. Start point is the center of the screen. Possible directions are + * Swipe.LEFT, Swipe.RIGHT, Swipe.UP and Swipe.DOWN. + */ + public void swipe(WebDriver driver, Swipe direction) { + Dimension screenDim = driver.manage().window().getSize(); + int startX = screenDim.getWidth() / 2; + int startY = screenDim.getHeight() / 2; + Point startPoint = new Point(startX, startY); + this.swipeAction(driver, direction, startPoint); + } + + private void swipeAction(WebDriver driver, Swipe direction, Point startPoint) { + AppiumDriver appiumDriver = this.getAppiumDriver(driver); + final Duration intensity = Duration.ofMillis(500); + // Length of swipe action according screen resolution + + + Dimension screenDim = appiumDriver.manage().window().getSize(); + final int diffX = (int) (screenDim.getWidth() * SCREEN_MULTIPLIER); + final int diffY = (int) (screenDim.getHeight() * SCREEN_MULTIPLIER); + + int startX = startPoint.getX(); + int startY = startPoint.getY(); + int endX = startX; + int endY = startY; + switch (direction) { + case LEFT: + startX = (int) (screenDim.getWidth() * SCREEN_SWIPE_END); + endX = (int) (screenDim.getWidth() * SCREEN_SWIPE_START); +// endX = Math.max(startX - diffX, 1); + break; + case RIGHT: + endX = Math.max(startX + diffX, screenDim.getWidth() - 1); + break; + case UP: + endY = Math.max(startY - diffY, 1); + break; + case DOWN: + endY = Math.max(startY + diffY, screenDim.getHeight() - 1); + } + + this.swipeAction(appiumDriver, intensity, startX, startY, endX, endY); + } + + private void swipeAction(AppiumDriver driver, Duration intensity, int startX, int startY, int endX, int endY) { + PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); + Sequence swipe = new Sequence(finger, 1); + swipe.addAction(finger.createPointerMove(intensity, PointerInput.Origin.viewport(), startX, startY)); + swipe.addAction(finger.createPointerDown(0)); + swipe.addAction(finger.createPointerMove(intensity, PointerInput.Origin.viewport(), endX, endY)); + swipe.addAction(finger.createPointerUp(0)); + driver.perform(List.of(swipe)); + } + /** * Switch the Appium Context if available */ public void switchContext(WebDriver driver, AppiumContext desiredContext) { - Optional appiumDriver = WEB_DRIVER_MANAGER.unwrapWebDriver(driver, AppiumDriver.class); - if (appiumDriver.isEmpty()) { - throw new RuntimeException("Current Webdriver is not an Appium driver."); - } - String currentContext = appiumDriver.get().getContext(); + AppiumDriver appiumDriver = this.getAppiumDriver(driver); + + SupportsContextSwitching contextSwitchingDriver = (SupportsContextSwitching) appiumDriver; + + String currentContext = contextSwitchingDriver.getContext(); AppiumContext parsedInitialContext = AppiumContext.parse(currentContext); log().info("Current context: {} ({})", currentContext, parsedInitialContext); @@ -75,14 +213,14 @@ public void switchContext(WebDriver driver, AppiumContext desiredContext) { return; } - Set contextHandles = appiumDriver.get().getContextHandles(); + Set contextHandles = contextSwitchingDriver.getContextHandles(); log().info("Available contexts: {}", contextHandles); contextHandles.stream() .filter(contextHandle -> AppiumContext.parse(contextHandle).equals(desiredContext)) .findFirst() .ifPresentOrElse(handle -> { log().info("Switch to context {} ({})", handle, desiredContext); - appiumDriver.get().context(handle); + contextSwitchingDriver.context(handle); }, () -> { log().error("Couldn't find a {} context in {}", desiredContext, contextHandles); throw new RuntimeException(String.format("Cannot switch in %s, because it does not exist. (%s)", desiredContext, contextHandles)); @@ -90,4 +228,12 @@ public void switchContext(WebDriver driver, AppiumContext desiredContext) { } + /** + * Unwrap the current WebDriver instance and returns a native AppiumDriver instance. + */ + public AppiumDriver getAppiumDriver(WebDriver webDriver) { + return WEB_DRIVER_MANAGER.unwrapWebDriver(webDriver, AppiumDriver.class) + .orElseThrow(() -> new RuntimeException("Current Webdriver is not an Appium driver.")); + } + } diff --git a/appium/src/main/java/eu/tsystems/mms/tic/testframework/webdrivermanager/AppiumDriverRequest.java b/appium/src/main/java/eu/tsystems/mms/tic/testframework/webdrivermanager/AppiumDriverRequest.java index 4c5be9a..824f087 100644 --- a/appium/src/main/java/eu/tsystems/mms/tic/testframework/webdrivermanager/AppiumDriverRequest.java +++ b/appium/src/main/java/eu/tsystems/mms/tic/testframework/webdrivermanager/AppiumDriverRequest.java @@ -21,6 +21,7 @@ package eu.tsystems.mms.tic.testframework.webdrivermanager; +import eu.tsystems.mms.tic.testframework.appium.AppiumCapabilityHelper; import eu.tsystems.mms.tic.testframework.appium.Browsers; import eu.tsystems.mms.tic.testframework.utils.AppiumProperties; import io.appium.java_client.remote.MobileCapabilityType; @@ -30,15 +31,19 @@ import java.net.URL; import java.util.Optional; -public class AppiumDriverRequest extends SeleniumWebDriverRequest { +public class AppiumDriverRequest extends SeleniumWebDriverRequest implements AppiumCapabilityHelper { public static final String DEVICE_QUERY = "deviceQuery"; public static final String ACCESS_KEY = "accessKey"; public static final String CAPABILITY_NAME_TEST_NAME = "testName"; public AppiumDriverRequest() { - setAccessKey(AppiumProperties.MOBILE_GRID_ACCESS_KEY.asString()); - this.setBrowser(Browsers.mobile); + this.setAccessKey(AppiumProperties.MOBILE_GRID_ACCESS_KEY.asString()); + + if (StringUtils.isBlank(this.getBrowser())) { + this.setBrowser(Browsers.mobile); + } + } @Override @@ -55,43 +60,43 @@ public Optional getServerUrl() { public void setDeviceQuery(String deviceQuery) { if (StringUtils.isNotBlank(deviceQuery)) { - this.getDesiredCapabilities().setCapability(DEVICE_QUERY, deviceQuery); + this.getMutableCapabilities().setCapability(DEVICE_QUERY, deviceQuery); } } public void setAccessKey(String accessKey) { - this.getDesiredCapabilities().setCapability(ACCESS_KEY, accessKey); + this.getMutableCapabilities().setCapability(ACCESS_KEY, accessKey); } public void setAppiumEngine(String engine) { - this.getDesiredCapabilities().setCapability(MobileCapabilityType.AUTOMATION_NAME, engine); + this.getMutableCapabilities().setCapability(MobileCapabilityType.AUTOMATION_NAME, engine); } public String getAppiumEngine() { - return this.getDesiredCapabilities().getCapability(MobileCapabilityType.AUTOMATION_NAME).toString(); + return this.getMutableCapabilities().getCapability(getAppiumCap(MobileCapabilityType.AUTOMATION_NAME)).toString(); } public void setDeviceName(String deviceName) { - this.getDesiredCapabilities().setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); + this.getMutableCapabilities().setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); } public String getDeviceName() { - return this.getDesiredCapabilities().getCapability(MobileCapabilityType.DEVICE_NAME).toString(); + return this.getMutableCapabilities().getCapability(getAppiumCap(MobileCapabilityType.DEVICE_NAME)).toString(); } public void setPlatformVersion(String platformVersion) { - this.getDesiredCapabilities().setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion); + this.getMutableCapabilities().setCapability(MobileCapabilityType.PLATFORM_VERSION, platformVersion); } public String getPlatformVersion() { - return this.getDesiredCapabilities().getCapability(MobileCapabilityType.PLATFORM_VERSION).toString(); + return this.getMutableCapabilities().getCapability(getAppiumCap(MobileCapabilityType.PLATFORM_VERSION)).toString(); } public void setDeviceId(String id) { - this.getDesiredCapabilities().setCapability(MobileCapabilityType.UDID, id); + this.getMutableCapabilities().setCapability(MobileCapabilityType.UDID, id); } public String getDeviceId() { - return this.getDesiredCapabilities().getCapability(MobileCapabilityType.UDID).toString(); + return this.getMutableCapabilities().getCapability(getAppiumCap(MobileCapabilityType.UDID)).toString(); } } diff --git a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/TesterraMobileAppTest.java b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/TesterraMobileAppTest.java index 82cdf42..2fb8e5d 100644 --- a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/TesterraMobileAppTest.java +++ b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/TesterraMobileAppTest.java @@ -47,12 +47,13 @@ public class TesterraMobileAppTest extends AbstractAppiumTest { @Test public void testT01AndroidApp() { AppiumDriverRequest request = new AppiumDriverRequest(); + request.setBrowser("mobile"); request.setDeviceQuery("contains(@name, 'Galaxy S20')"); - request.getDesiredCapabilities().setCapability("appiumVersion", "1.22.3"); + request.getMutableCapabilities().setCapability("appiumVersion", "2.2.2"); - request.getDesiredCapabilities().setCapability(MobileCapabilityType.APP, "cloud:eu.tsystems.mms.tic.mdc.app.android/.HomeActivity"); - request.getDesiredCapabilities().setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "eu.tsystems.mms.tic.mdc.app.android"); - request.getDesiredCapabilities().setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".HomeActivity"); + request.getMutableCapabilities().setCapability(MobileCapabilityType.APP, "cloud:eu.tsystems.mms.tic.mdc.app.android/.HomeActivity"); + request.getMutableCapabilities().setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "eu.tsystems.mms.tic.mdc.app.android"); + request.getMutableCapabilities().setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".HomeActivity"); request.setAppiumEngine("UiAutomator2"); WebDriver webDriver = WEB_DRIVER_MANAGER.getWebDriver(request); diff --git a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/VanillaAppiumAppTest.java b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/VanillaAppiumAppTest.java index 39b5be0..3283f3d 100644 --- a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/VanillaAppiumAppTest.java +++ b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/apps/VanillaAppiumAppTest.java @@ -24,7 +24,6 @@ import eu.tsystems.mms.tic.testframework.utils.AppiumProperties; import eu.tsystems.mms.tic.testframework.utils.TimerUtils; import io.appium.java_client.android.AndroidDriver; -import io.appium.java_client.android.AndroidElement; import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType; import org.openqa.selenium.remote.DesiredCapabilities; @@ -43,7 +42,7 @@ */ public class VanillaAppiumAppTest extends AbstractAppiumTest { - protected AndroidDriver driver = null; + protected AndroidDriver driver = null; @BeforeMethod public void setUp() throws MalformedURLException { @@ -61,7 +60,7 @@ public void setUp() throws MalformedURLException { dc.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UIAutomator2"); URL url = new URL(AppiumProperties.MOBILE_GRID_URL.asString()); // driver = new IOSDriver<>(new URL(PropertyManager.getProperty("tt.mobile.grid.url")), dc); - driver = new AndroidDriver<>(url, dc); + driver = new AndroidDriver(url, dc); } @Test diff --git a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/TesterraAppiumDriverTest.java b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/TesterraAppiumDriverTest.java index e738f55..5cf89d9 100644 --- a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/TesterraAppiumDriverTest.java +++ b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/TesterraAppiumDriverTest.java @@ -27,11 +27,11 @@ import eu.tsystems.mms.tic.testframework.report.model.context.Screenshot; import eu.tsystems.mms.tic.testframework.report.model.context.SessionContext; import eu.tsystems.mms.tic.testframework.testing.WebDriverManagerProvider; +import eu.tsystems.mms.tic.testframework.utils.AppiumUtils; import eu.tsystems.mms.tic.testframework.utils.JSUtils; import eu.tsystems.mms.tic.testframework.utils.UITestUtils; import eu.tsystems.mms.tic.testframework.utils.WebDriverUtils; import eu.tsystems.mms.tic.testframework.webdrivermanager.AppiumDriverRequest; -import io.appium.java_client.AppiumDriver; import org.openqa.selenium.Rectangle; import org.openqa.selenium.ScreenOrientation; import org.openqa.selenium.WebDriver; @@ -48,28 +48,25 @@ public class TesterraAppiumDriverTest extends AbstractAppiumTest implements WebD @Test public void testT01_startDefaultSession() { + AppiumDriverRequest request = new AppiumDriverRequest(); + request.getMutableCapabilities().setCapability("appiumVersion", "2.2.2"); + final WebDriver driver = WEB_DRIVER_MANAGER.getWebDriver(request); + + new AppiumUtils().rotate(driver, ScreenOrientation.LANDSCAPE); - final WebDriver driver = WEB_DRIVER_MANAGER.getWebDriver(); - WEB_DRIVER_MANAGER.unwrapWebDriver(driver, AppiumDriver.class).ifPresent(appiumDriver -> { - appiumDriver.rotate(ScreenOrientation.LANDSCAPE); - }); driver.get("https://the-internet.herokuapp.com/dropdown"); + UITestUtils.takeScreenshots(); } @Test public void testT02_startMultipleSessions() { final WebDriver driver = WEB_DRIVER_MANAGER.getWebDriver(); - AppiumDriver appiumDriver = WEB_DRIVER_MANAGER.unwrapWebDriver(driver, AppiumDriver.class).get(); - - appiumDriver.rotate(ScreenOrientation.LANDSCAPE); + new AppiumUtils().rotate(driver, ScreenOrientation.LANDSCAPE); driver.get("https://the-internet.herokuapp.com/dropdown"); final WebDriver driver2 = WEB_DRIVER_MANAGER.getWebDriver("second"); - AppiumDriver appiumDriver2 = WEB_DRIVER_MANAGER.unwrapWebDriver(driver2, AppiumDriver.class).get(); - - appiumDriver2.rotate(ScreenOrientation.PORTRAIT); - driver2.get("https://the-internet.herokuapp.com/checkboxes"); + new AppiumUtils().rotate(driver2, ScreenOrientation.PORTRAIT); Assert.assertNotEquals(driver, driver2, "Driver equals"); } @@ -80,6 +77,7 @@ public void testT03_startRequestSession() { final String device = "Apple iPhone X"; request.setBrowser(Browsers.mobile_safari); request.setDeviceQuery(String.format("contains(@name, '%s')", device)); + request.getMutableCapabilities().setCapability("appiumVersion", "2.2.2"); final WebDriver driver = WEB_DRIVER_MANAGER.getWebDriver(request); @@ -103,15 +101,13 @@ public void testT04_startSessionTwice() { public void testT05_takeScreenshot() { final WebDriver driver = WEB_DRIVER_MANAGER.getWebDriver(); - AppiumDriver appiumDriver = WEB_DRIVER_MANAGER.unwrapWebDriver(driver, AppiumDriver.class).get(); - driver.get("https://the-internet.herokuapp.com/"); - appiumDriver.rotate(ScreenOrientation.LANDSCAPE); + new AppiumUtils().rotate(driver, ScreenOrientation.LANDSCAPE); final Screenshot screenshotLandScape = UITestUtils.takeScreenshot(driver, true); Assert.assertNotNull(screenshotLandScape, "Screenshot created."); - appiumDriver.rotate(ScreenOrientation.PORTRAIT); + new AppiumUtils().rotate(driver, ScreenOrientation.PORTRAIT); final Screenshot screenshotPortrait = UITestUtils.takeScreenshot(driver, true); Assert.assertNotNull(screenshotPortrait, "Screenshot created."); } diff --git a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/VanillaAppiumDriverTest.java b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/VanillaAppiumDriverTest.java index 27b54b1..8e7ec24 100644 --- a/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/VanillaAppiumDriverTest.java +++ b/appium/src/test/java/eu/tsystems/mms/tic/testframework/mobile/test/driver/VanillaAppiumDriverTest.java @@ -28,8 +28,8 @@ import eu.tsystems.mms.tic.testframework.report.Report; import eu.tsystems.mms.tic.testframework.report.TesterraListener; import eu.tsystems.mms.tic.testframework.utils.AppiumProperties; +import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.ios.IOSDriver; -import io.appium.java_client.ios.IOSElement; import io.appium.java_client.remote.MobileBrowserType; import org.openqa.selenium.By; import org.openqa.selenium.OutputType; @@ -47,6 +47,7 @@ import java.io.File; import java.net.MalformedURLException; import java.net.URL; +import java.time.Duration; /** * Date: 24.06.2020 @@ -56,30 +57,33 @@ */ public class VanillaAppiumDriverTest extends AbstractAppiumTest implements Loggable, PropertyManagerProvider { - protected IOSDriver driver = null; -// protected AndroidDriver driver = null; +// protected IOSDriver driver = null; + protected AndroidDriver driver = null; @BeforeMethod public void setUp() throws MalformedURLException { final String accessKey = AppiumProperties.MOBILE_GRID_ACCESS_KEY.asString(); Assert.assertNotNull(accessKey, "No access key loaded"); + + DesiredCapabilities dc = new DesiredCapabilities(); - dc.setCapability("testName", "Demo Tests"); - dc.setCapability("accessKey", accessKey); - dc.setCapability("appiumVersion", "1.22.3"); -// dc.setCapability("deviceQuery", "contains(@name, 'Samsung Galaxy S20')"); + dc.setCapability("appium:testName", "Demo Tests"); + dc.setCapability("appium:accessKey", accessKey); +// dc.setCapability("appiumVersion", "1.22.3"); + dc.setCapability("appium:appiumVersion", "2.2.2"); // dc.setCapability("deviceQuery", "contains(@name, 'Google Pixel 6')"); // dc.setCapability("deviceQuery", AppiumProperties.MOBILE_APPIUM_DEVICE_QUERY_ANDROID); - dc.setCapability("deviceQuery", "contains(@name, 'Apple iPhone X (')"); +// dc.setCapability("appium:deviceQuery", "contains(@name, 'Apple iPhone X (')"); + dc.setCapability("appium:deviceQuery", "contains(@name, 'Samsung Galaxy S20')"); // dc.setCapability(MobileCapabilityType.UDID, "..."); - dc.setBrowserName(MobileBrowserType.SAFARI); -// dc.setBrowserName(MobileBrowserType.CHROME); +// dc.setBrowserName(MobileBrowserType.SAFARI); + dc.setBrowserName(MobileBrowserType.CHROME); URL url = new URL(AppiumProperties.MOBILE_GRID_URL.asString()); log().info(dc.toString()); - driver = new IOSDriver<>(url, dc); -// driver = new AndroidDriver<>(url, dc); +// driver = new IOSDriver(url, dc); + driver = new AndroidDriver(url, dc); } @@ -87,7 +91,7 @@ public void setUp() throws MalformedURLException { public void testT01_DoGoogleSearch() { driver.rotate(ScreenOrientation.PORTRAIT); driver.get("https://www.google.com"); - new WebDriverWait(driver, 10).until(driver1 -> { + new WebDriverWait(driver, Duration.ofSeconds(10)).until(driver1 -> { ExpectedCondition q = ExpectedConditions.presenceOfElementLocated(By.xpath("//input[@name='q']")); return q; }); diff --git a/appium/src/test/resources/test.properties b/appium/src/test/resources/test.properties index c95bb1a..4a2af29 100644 --- a/appium/src/test/resources/test.properties +++ b/appium/src/test/resources/test.properties @@ -1,12 +1,13 @@ -#tt.browser=mobile_chrome +tt.browser=mobile_chrome #tt.browser=mobile_safari -tt.watchdog.enable=false -tt.baseurl=https://the-internet.herokuapp.com/ + +#tt.baseurl=https://the-internet.herokuapp.com/ + #tt.mobile.device.query.ios=@os='ios' and @category='PHONE' tt.mobile.device.query.ios=contains(@name, 'iPhone X') #tt.mobile.device.query.android=@os='android' and @category='PHONE' and @version='10' -tt.mobile.device.query.android=contains(@name, 'Samsung Galaxy A5') +tt.mobile.device.query.android=contains(@name, 'Samsung Galaxy S20') tt.winapp.server.url= diff --git a/build.gradle b/build.gradle index 1976dcd..3f46097 100644 --- a/build.gradle +++ b/build.gradle @@ -5,10 +5,15 @@ plugins { apply plugin: 'io.codearte.nexus-staging' ext { + // Tested with +// appiumJavaClientVersion = '9.0.0' + appiumJavaClientVersion = '8.6.0' + // Minimum required Testerra version - testerraCompileVersion = '2.0' + testerraCompileVersion = '2.4' // Unit tests use the latest Testerra version - testerraTestVersion = '[2,3-SNAPSHOT)' +// testerraTestVersion = '[2,3-SNAPSHOT)' + testerraTestVersion = '2.4' moduleVersion = '2-SNAPSHOT' if (System.properties.containsKey('moduleVersion')) { moduleVersion = System.getProperty('moduleVersion')