diff --git a/pom.xml b/pom.xml index 3dd0a26..58203a1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.github.aquality-automation aquality-appium-mobile - 2.0.0 + 2.1.0 jar Aquality Appium Mobile diff --git a/src/main/java/aquality/appium/mobile/actions/IActionsModule.java b/src/main/java/aquality/appium/mobile/actions/IActionsModule.java new file mode 100644 index 0000000..12ac05a --- /dev/null +++ b/src/main/java/aquality/appium/mobile/actions/IActionsModule.java @@ -0,0 +1,16 @@ +package aquality.appium.mobile.actions; + +import aquality.appium.mobile.configuration.IConfigurationsModule; + +/** + * Describes implementations of actions services to be registered in DI container. + */ +public interface IActionsModule extends IConfigurationsModule { + + /** + * @return class which implements {@link ITouchActions} + */ + default Class getTouchActionsImplementation() { + return TouchActions.class; + } +} diff --git a/src/main/java/aquality/appium/mobile/actions/ITouchActions.java b/src/main/java/aquality/appium/mobile/actions/ITouchActions.java new file mode 100644 index 0000000..6f4c624 --- /dev/null +++ b/src/main/java/aquality/appium/mobile/actions/ITouchActions.java @@ -0,0 +1,25 @@ +package aquality.appium.mobile.actions; + +import org.openqa.selenium.Point; + +/** + * Describes general Touch Actions. + */ +public interface ITouchActions { + + /** + * Swipes from start point to end point using TouchAction. + * + * @param startPoint point on screen to swipe from. + * @param endPoint point on screen to swipe to. + */ + void swipe(Point startPoint, Point endPoint); + + /** + * Performs long press action and moves cursor from a start point to an end point imitating the swipe action. + * + * @param startPoint point on screen to swipe from. + * @param endPoint point on screen to swipe to. + */ + void swipeWithLongPress(Point startPoint, Point endPoint); +} diff --git a/src/main/java/aquality/appium/mobile/actions/SwipeDirection.java b/src/main/java/aquality/appium/mobile/actions/SwipeDirection.java new file mode 100644 index 0000000..888610b --- /dev/null +++ b/src/main/java/aquality/appium/mobile/actions/SwipeDirection.java @@ -0,0 +1,11 @@ +package aquality.appium.mobile.actions; + +/** + * The enum describing the possible swipe directions. + */ +public enum SwipeDirection { + UP, + DOWN, + LEFT, + RIGHT; +} diff --git a/src/main/java/aquality/appium/mobile/actions/TouchActions.java b/src/main/java/aquality/appium/mobile/actions/TouchActions.java new file mode 100644 index 0000000..848b30c --- /dev/null +++ b/src/main/java/aquality/appium/mobile/actions/TouchActions.java @@ -0,0 +1,44 @@ +package aquality.appium.mobile.actions; + +import aquality.appium.mobile.application.AqualityServices; +import aquality.appium.mobile.configuration.ITouchActionsConfiguration; +import aquality.selenium.core.utilities.IElementActionRetrier; +import io.appium.java_client.TouchAction; +import io.appium.java_client.touch.offset.PointOption; +import org.openqa.selenium.Point; +import java.util.function.UnaryOperator; +import static io.appium.java_client.touch.WaitOptions.waitOptions; + +public class TouchActions implements ITouchActions { + + @Override + public void swipe(Point startPoint, Point endPoint) { + AqualityServices.getLocalizedLogger().info( + "loc.action.swipe", + startPoint.getX(), + startPoint.getY(), + endPoint.getX(), + endPoint.getY()); + performTouchAction(touchAction -> touchAction + .press(PointOption.point(startPoint)) + .waitAction(waitOptions(AqualityServices.get(ITouchActionsConfiguration.class).getSwipeDuration())), + endPoint); + } + + @Override + public void swipeWithLongPress(Point startPoint, Point endPoint) { + AqualityServices.getLocalizedLogger().info( + "loc.action.swipeLongPress", + startPoint.getX(), + startPoint.getY(), + endPoint.getX(), + endPoint.getY()); + performTouchAction(touchAction -> touchAction.longPress(PointOption.point(startPoint)), endPoint); + } + + protected void performTouchAction(UnaryOperator> function, Point endPoint) { + TouchAction touchAction = new TouchAction<>(AqualityServices.getApplication().getDriver()); + AqualityServices.get(IElementActionRetrier.class).doWithRetry(() -> + function.apply(touchAction).moveTo(PointOption.point(endPoint)).release().perform()); + } +} diff --git a/src/main/java/aquality/appium/mobile/application/AqualityServices.java b/src/main/java/aquality/appium/mobile/application/AqualityServices.java index b36be72..6c7c3d9 100644 --- a/src/main/java/aquality/appium/mobile/application/AqualityServices.java +++ b/src/main/java/aquality/appium/mobile/application/AqualityServices.java @@ -1,5 +1,6 @@ package aquality.appium.mobile.application; +import aquality.appium.mobile.actions.ITouchActions; import aquality.appium.mobile.configuration.IApplicationProfile; import aquality.appium.mobile.configuration.IConfiguration; import aquality.appium.mobile.configuration.ILocalServiceSettings; @@ -190,4 +191,13 @@ public static ILocalServiceSettings getLocalServiceSettings() { public static IConfiguration getConfiguration() { return get(IConfiguration.class); } + + /** + * Gets the the utility used to perform touch actions. + * + * @return instance of touch actions. + */ + public static ITouchActions getTouchActions() { + return get(ITouchActions.class); + } } diff --git a/src/main/java/aquality/appium/mobile/application/MobileModule.java b/src/main/java/aquality/appium/mobile/application/MobileModule.java index 74c342c..6932974 100644 --- a/src/main/java/aquality/appium/mobile/application/MobileModule.java +++ b/src/main/java/aquality/appium/mobile/application/MobileModule.java @@ -1,9 +1,12 @@ package aquality.appium.mobile.application; +import aquality.appium.mobile.actions.IActionsModule; +import aquality.appium.mobile.actions.ITouchActions; import aquality.appium.mobile.configuration.IApplicationProfile; import aquality.appium.mobile.configuration.IConfiguration; import aquality.appium.mobile.configuration.IConfigurationsModule; import aquality.appium.mobile.configuration.ILocalServiceSettings; +import aquality.appium.mobile.configuration.ITouchActionsConfiguration; import aquality.appium.mobile.elements.IElementsModule; import aquality.appium.mobile.elements.interfaces.IElementFactory; import aquality.appium.mobile.screens.screenfactory.IScreenFactory; @@ -12,7 +15,7 @@ import com.google.inject.Provider; import com.google.inject.Singleton; -public class MobileModule extends AqualityModule implements IConfigurationsModule, IElementsModule, IScreensModule { +public class MobileModule extends AqualityModule implements IConfigurationsModule, IElementsModule, IScreensModule, IActionsModule { public MobileModule(Provider applicationProvider) { super(applicationProvider); @@ -26,5 +29,7 @@ protected void configure() { bind(IConfiguration.class).to(getConfigurationImplementation()); bind(IElementFactory.class).to(getElementFactoryImplementation()); bind(IScreenFactory.class).to(getScreenFactoryImplementation()); + bind(ITouchActionsConfiguration.class).to(getTouchActionsConfigurationImplementation()); + bind(ITouchActions.class).to(getTouchActionsImplementation()); } } diff --git a/src/main/java/aquality/appium/mobile/configuration/Configuration.java b/src/main/java/aquality/appium/mobile/configuration/Configuration.java index 8ce2ce7..4a78722 100644 --- a/src/main/java/aquality/appium/mobile/configuration/Configuration.java +++ b/src/main/java/aquality/appium/mobile/configuration/Configuration.java @@ -13,16 +13,19 @@ public class Configuration implements IConfiguration { private final IApplicationProfile applicationProfile; private final ILoggerConfiguration loggerConfiguration; private final IElementCacheConfiguration elementCacheConfiguration; + private final ITouchActionsConfiguration touchActionsConfiguration; @Inject public Configuration(ITimeoutConfiguration timeoutConfiguration, IRetryConfiguration retryConfiguration, IApplicationProfile applicationProfile, ILoggerConfiguration loggerConfiguration, - IElementCacheConfiguration elementCacheConfiguration) { + IElementCacheConfiguration elementCacheConfiguration, + ITouchActionsConfiguration touchActionsConfiguration) { this.timeoutConfiguration = timeoutConfiguration; this.retryConfiguration = retryConfiguration; this.applicationProfile = applicationProfile; this.loggerConfiguration = loggerConfiguration; this.elementCacheConfiguration = elementCacheConfiguration; + this.touchActionsConfiguration = touchActionsConfiguration; } @Override @@ -49,4 +52,9 @@ public ILoggerConfiguration getLoggerConfiguration() { public IElementCacheConfiguration getElementCacheConfiguration() { return elementCacheConfiguration; } + + @Override + public ITouchActionsConfiguration getTouchActionsConfiguration() { + return touchActionsConfiguration; + } } \ No newline at end of file diff --git a/src/main/java/aquality/appium/mobile/configuration/IConfiguration.java b/src/main/java/aquality/appium/mobile/configuration/IConfiguration.java index 5e997f1..10369e1 100644 --- a/src/main/java/aquality/appium/mobile/configuration/IConfiguration.java +++ b/src/main/java/aquality/appium/mobile/configuration/IConfiguration.java @@ -41,4 +41,11 @@ public interface IConfiguration { * @return Configuration of element caching. */ IElementCacheConfiguration getElementCacheConfiguration(); + + /** + * Gets configuration of touch actions. + * + * @return Configuration of touch actions. + */ + ITouchActionsConfiguration getTouchActionsConfiguration(); } \ No newline at end of file diff --git a/src/main/java/aquality/appium/mobile/configuration/IConfigurationsModule.java b/src/main/java/aquality/appium/mobile/configuration/IConfigurationsModule.java index 0cf797b..2b1a6eb 100644 --- a/src/main/java/aquality/appium/mobile/configuration/IConfigurationsModule.java +++ b/src/main/java/aquality/appium/mobile/configuration/IConfigurationsModule.java @@ -26,4 +26,10 @@ default Class getConfigurationImplementation() { return Configuration.class; } + /** + * @return class which implements {@link ITouchActionsConfiguration} + */ + default Class getTouchActionsConfigurationImplementation() { + return TouchActionsConfiguration.class; + } } diff --git a/src/main/java/aquality/appium/mobile/configuration/ITouchActionsConfiguration.java b/src/main/java/aquality/appium/mobile/configuration/ITouchActionsConfiguration.java new file mode 100644 index 0000000..e9c6a66 --- /dev/null +++ b/src/main/java/aquality/appium/mobile/configuration/ITouchActionsConfiguration.java @@ -0,0 +1,56 @@ +package aquality.appium.mobile.configuration; + +import java.time.Duration; + +/** + * Describes Touch Actions settings. + */ +public interface ITouchActionsConfiguration { + /** + * Gets number of retries to perform swipe. + * + * @return arguments map. + */ + int getSwipeRetries(); + + /** + * Gets the number of seconds required to perform a swipe action. + * + * @return number of seconds required to perform a swipe action. + */ + Duration getSwipeDuration(); + + /** + * Gets the offset coefficient to adjust the start/end point for swipe action relatively to the parallel screen edge. + * Example for swipe down action: + * The offset coefficient is used to calculate start/end point's Y coordinate. + * If offset coefficient == 0.2, then the start point's Y coordinate == screen's length * (1 - 0.2). + * If offset coefficient == 0.2, then the end point's Y coordinate == screen's length * 0.2. + * The vice versa for swipe up action. + * Example for swipe left action: + * The offset coefficient is used to calculate start/end point's X coordinate. + * If offset coefficient == 0.2, then the start point's X coordinate == screen's width * (1 - 0.2). + * If offset coefficient == 0.2, then the end point's X coordinate == screen's width * 0.2. + * The vice versa for swipe right action. + * + * @return offset coefficient to adjust the start/end point for swipe action relatively to the parallel screen edge + */ + double getSwipeVerticalOffset(); + + /** + * Gets the offset coefficient to adjust the start/end point for swipe action relatively to the perpendicular screen edge. + * Example for swipe down action: + * The offset coefficient is used to calculate start/end point's X coordinate. + * If offset coefficient == 0.5, then the start point's X coordinate == screen's width * (1 - 0.5). + * If offset coefficient == 0.5, then the end point's X coordinate == screen's width * 0.5. + * The vice versa for swipe up action. + * Example for swipe left action: + * The offset coefficient is used to calculate start/end point's X coordinate. + * If offset coefficient == 0.5, then the start point's Y coordinate == screen's length * (1 - 0.5). + * If offset coefficient == 0.5, then the end point's Y coordinate == screen's length * 0.5. + * The vice versa for swipe right action. + * + * @return offset coefficient to adjust the start/end point for swipe action relatively to the perpendicular screen edge + */ + double getSwipeHorizontalOffset(); +} diff --git a/src/main/java/aquality/appium/mobile/configuration/TouchActionsConfiguration.java b/src/main/java/aquality/appium/mobile/configuration/TouchActionsConfiguration.java new file mode 100644 index 0000000..57827db --- /dev/null +++ b/src/main/java/aquality/appium/mobile/configuration/TouchActionsConfiguration.java @@ -0,0 +1,41 @@ +package aquality.appium.mobile.configuration; + +import aquality.selenium.core.utilities.ISettingsFile; +import com.google.inject.Inject; + +import java.time.Duration; + +public class TouchActionsConfiguration implements ITouchActionsConfiguration { + private final Duration swipeDuration; + private final int swipeRetries; + private final double swipeVerticalOffset; + private final double swipeHorizontalOffset; + + @Inject + public TouchActionsConfiguration(ISettingsFile settingsFile) { + this.swipeDuration = Duration.ofSeconds(Long.parseLong(settingsFile.getValue("/touchActions/swipe/duration").toString())); + this.swipeRetries = (int) settingsFile.getValue("/touchActions/swipe/retries"); + this.swipeVerticalOffset = (double) settingsFile.getValue("/touchActions/swipe/verticalOffset"); + this.swipeHorizontalOffset = (double) settingsFile.getValue("/touchActions/swipe/horizontalOffset"); + } + + @Override + public int getSwipeRetries() { + return this.swipeRetries; + } + + @Override + public Duration getSwipeDuration() { + return this.swipeDuration; + } + + @Override + public double getSwipeVerticalOffset() { + return this.swipeVerticalOffset; + } + + @Override + public double getSwipeHorizontalOffset() { + return this.swipeHorizontalOffset; + } +} diff --git a/src/main/java/aquality/appium/mobile/elements/Element.java b/src/main/java/aquality/appium/mobile/elements/Element.java index d4462fb..96a48a6 100644 --- a/src/main/java/aquality/appium/mobile/elements/Element.java +++ b/src/main/java/aquality/appium/mobile/elements/Element.java @@ -2,6 +2,8 @@ import aquality.appium.mobile.application.Application; import aquality.appium.mobile.application.AqualityServices; +import aquality.appium.mobile.elements.actions.ElementTouchActions; +import aquality.appium.mobile.elements.actions.IElementTouchActions; import aquality.appium.mobile.elements.interfaces.IElement; import aquality.appium.mobile.elements.interfaces.IElementFactory; import aquality.selenium.core.configurations.IElementCacheConfiguration; @@ -86,4 +88,9 @@ public void sendKeys(Keys key) { logElementAction("loc.text.sending.keys", Keys.class.getSimpleName().concat(".").concat(key.name())); doWithRetry(() -> getElement().sendKeys(key)); } + + @Override + public IElementTouchActions getTouchActions() { + return new ElementTouchActions(this); + } } diff --git a/src/main/java/aquality/appium/mobile/elements/actions/ElementTouchActions.java b/src/main/java/aquality/appium/mobile/elements/actions/ElementTouchActions.java new file mode 100644 index 0000000..9f9b6fe --- /dev/null +++ b/src/main/java/aquality/appium/mobile/elements/actions/ElementTouchActions.java @@ -0,0 +1,114 @@ +package aquality.appium.mobile.elements.actions; + +import aquality.appium.mobile.actions.ITouchActions; +import aquality.appium.mobile.actions.SwipeDirection; +import aquality.appium.mobile.application.AqualityServices; +import aquality.appium.mobile.configuration.ITouchActionsConfiguration; +import aquality.appium.mobile.elements.interfaces.IElement; +import org.openqa.selenium.Dimension; +import org.openqa.selenium.Point; + +public class ElementTouchActions implements IElementTouchActions { + private IElement element; + private Point scrollDownStartPoint; + private Point scrollDownEndPoint; + private Point swipeLeftStartPoint; + private Point swipeLeftEndPoint; + private Point scrollUpStartPoint; + private Point scrollUpEndPoint; + private Point swipeRightStartPoint; + private Point swipeRightEndPoint; + + public ElementTouchActions(IElement element) { + this.element = element; + this.scrollDownStartPoint = recalculatePointCoordinates( + getBottomRightCornerPoint(), + (1 - getConfiguration().getSwipeHorizontalOffset()), + (1 - getConfiguration().getSwipeVerticalOffset())); + this.scrollDownEndPoint = recalculatePointCoordinates( + getBottomRightCornerPoint(), + getConfiguration().getSwipeHorizontalOffset(), + getConfiguration().getSwipeVerticalOffset()); + this.swipeLeftStartPoint = recalculatePointCoordinates( + getBottomRightCornerPoint(), + (1 - getConfiguration().getSwipeVerticalOffset()), + (1 - getConfiguration().getSwipeHorizontalOffset())); + this.swipeLeftEndPoint = recalculatePointCoordinates( + getBottomRightCornerPoint(), + getConfiguration().getSwipeVerticalOffset(), + getConfiguration().getSwipeHorizontalOffset()); + this.scrollUpStartPoint = this.scrollDownEndPoint; + this.scrollUpEndPoint = this.scrollDownStartPoint; + this.swipeRightStartPoint = this.swipeLeftEndPoint; + this.swipeRightEndPoint = this.swipeLeftStartPoint; + } + + @Override + public void swipe(Point endPoint) { + AqualityServices.getTouchActions().swipe(element.getElement().getCenter(), endPoint); + } + + @Override + public void swipeWithLongPress(Point endPoint) { + AqualityServices.getTouchActions().swipeWithLongPress(element.getElement().getCenter(), endPoint); + } + + @Override + public void scrollToElement(SwipeDirection direction) { + int numberOfRetries = AqualityServices.get(ITouchActionsConfiguration.class).getSwipeRetries(); + ITouchActions touchActions = AqualityServices.getTouchActions(); + while (numberOfRetries > 0) { + if (!element.state().isDisplayed()) { + switch (direction) { + case DOWN: + touchActions.swipe(scrollDownStartPoint, scrollDownEndPoint); + break; + case UP: + touchActions.swipe(scrollUpStartPoint, scrollUpEndPoint); + break; + case LEFT: + touchActions.swipe(swipeLeftStartPoint, swipeLeftEndPoint); + break; + case RIGHT: + touchActions.swipe(swipeRightStartPoint, swipeRightEndPoint); + break; + default: + throw new IllegalArgumentException( + String.format("'%s' direction does not exist", direction.toString())); + } + } + numberOfRetries--; + } + } + + /** + * Returns Point in the bottom right corner of the screen. + */ + private Point getBottomRightCornerPoint() { + Dimension screenSize = AqualityServices.getApplication().getDriver().manage().window().getSize(); + return new Point(screenSize.width, screenSize.height); + } + + /** + * Returns the point with recalculated coordinates. + * + * @param point point to recalculate coordinates + * @param horizontalOffset coefficient to recalculate the point with horizontal offset. + * @param verticalOffset coefficient to recalculate the point with vertical offset. + * @return point with recalculated coordinates with horizontal and vertical offset. + */ + private Point recalculatePointCoordinates(Point point, double horizontalOffset, double verticalOffset) { + return new Point( + (int) (point.getX() * horizontalOffset), + (int) (point.getY() * verticalOffset)); + } + + /** + * Returns ITouchActionsConfiguration class. + * + * @return ITouchActionsConfiguration class + */ + private ITouchActionsConfiguration getConfiguration() { + return AqualityServices.get(ITouchActionsConfiguration.class); + } +} diff --git a/src/main/java/aquality/appium/mobile/elements/actions/IElementTouchActions.java b/src/main/java/aquality/appium/mobile/elements/actions/IElementTouchActions.java new file mode 100644 index 0000000..6727a9a --- /dev/null +++ b/src/main/java/aquality/appium/mobile/elements/actions/IElementTouchActions.java @@ -0,0 +1,31 @@ +package aquality.appium.mobile.elements.actions; + +import aquality.appium.mobile.actions.SwipeDirection; +import org.openqa.selenium.Point; + +/** + * Describes Touch Actions for elements. + */ +public interface IElementTouchActions { + + /** + * Swipes from element to end point using TouchAction. + * + * @param endPoint point on screen to swipe to. + */ + void swipe(final Point endPoint); + + /** + * Swipes from element to end point using LongPress. + * + * @param endPoint point on screen to swipe to. + */ + void swipeWithLongPress(final Point endPoint); + + /** + * Scrolls current screen in provided direction until element is displayed. + * + * @param direction direction to swipe. + */ + void scrollToElement(final SwipeDirection direction); +} diff --git a/src/main/java/aquality/appium/mobile/elements/interfaces/IElement.java b/src/main/java/aquality/appium/mobile/elements/interfaces/IElement.java index 5d7ab6f..7069fa0 100644 --- a/src/main/java/aquality/appium/mobile/elements/interfaces/IElement.java +++ b/src/main/java/aquality/appium/mobile/elements/interfaces/IElement.java @@ -2,6 +2,7 @@ import aquality.appium.mobile.elements.Attributes; import aquality.appium.mobile.elements.ElementType; +import aquality.appium.mobile.elements.actions.IElementTouchActions; import aquality.selenium.core.elements.ElementState; import io.appium.java_client.MobileElement; import org.openqa.selenium.By; @@ -99,4 +100,11 @@ default MobileElement getElement() { default String getAttribute(Attributes attribute) { return getAttribute(attribute.toString()); } + + /** + * Gets the the utility used to perform touch actions for element. + * + * @return instance of element touch actions. + */ + IElementTouchActions getTouchActions(); } diff --git a/src/main/resources/localization/be.json b/src/main/resources/localization/be.json index df07dab..5956638 100644 --- a/src/main/resources/localization/be.json +++ b/src/main/resources/localization/be.json @@ -37,5 +37,7 @@ "loc.text.typing" : "Уводзім '%s'", "loc.text.focusing": "Факусуемся на полі", "loc.text.unfocusing": "Адводзім фокус з поля", - "loc.text.masked_value": "********" + "loc.text.masked_value": "********", + "loc.action.swipe": "Праводзім па экране з каардынатаў (x:%1$s; y:%2$s) у (x:%3$s; y:%4$s)", + "loc.action.swipeLongPress": "Праводзім па экране доўгім націскам з каардынатаў (x:%1$s; y:%2$s) у (x:%3$s; y:%4$s)" } diff --git a/src/main/resources/localization/en.json b/src/main/resources/localization/en.json index ccead49..3cbea6d 100644 --- a/src/main/resources/localization/en.json +++ b/src/main/resources/localization/en.json @@ -37,5 +37,7 @@ "loc.text.typing": "Typing '%s'", "loc.text.focusing": "Focusing on field", "loc.text.unfocusing": "Unfocusing from field", - "loc.text.masked_value": "********" + "loc.text.masked_value": "********", + "loc.action.swipe": "Swiping from coordinates (x:%1$s; y:%2$s) to (x:%3$s; y:%4$s)", + "loc.action.swipeLongPress": "Swiping using long press from coordinates (x:%1$s; y:%2$s) to (x:%3$s; y:%4$s)" } diff --git a/src/main/resources/localization/ru.json b/src/main/resources/localization/ru.json index 4686ab9..6ce8c03 100644 --- a/src/main/resources/localization/ru.json +++ b/src/main/resources/localization/ru.json @@ -37,5 +37,7 @@ "loc.text.typing": "Ввод текста '%s'", "loc.text.focusing": "Наведение курсора на поле", "loc.text.unfocusing": "Сдвиг курсора с поля", - "loc.text.masked_value": "********" + "loc.text.masked_value": "********", + "loc.action.swipe": "Проводим по экрану из координат (x:%1$s; y:%2$s) в (x:%3$s; y:%4$s)", + "loc.action.swipeLongPress": "Проводим по экрану долгим нажатием из координат (x:%1$s; y:%2$s) в (x:%3$s; y:%4$s)" } diff --git a/src/main/resources/settings.json b/src/main/resources/settings.json index bae900f..f427ceb 100644 --- a/src/main/resources/settings.json +++ b/src/main/resources/settings.json @@ -1,9 +1,8 @@ { - "platformName" : "android", + "platformName": "android", "isRemote": false, "remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub", "screensLocation": "", - "driverSettings": { "android": { "deviceKey": "", @@ -44,5 +43,13 @@ }, "elementCache": { "isEnabled": true + }, + "touchActions": { + "swipe": { + "duration": 1, + "retries": 5, + "verticalOffset": 0.2, + "horizontalOffset": 0.5 + } } } \ No newline at end of file diff --git a/src/test/java/samples/android/nativeapp/AndroidBasicInteractionsTest.java b/src/test/java/samples/android/nativeapp/AndroidBasicInteractionsTest.java index c9c9de5..2fcf8dd 100644 --- a/src/test/java/samples/android/nativeapp/AndroidBasicInteractionsTest.java +++ b/src/test/java/samples/android/nativeapp/AndroidBasicInteractionsTest.java @@ -12,10 +12,7 @@ import samples.android.ITestCheckBox; import samples.android.ITestRadioButton; import samples.android.nativeapp.apidemos.ApplicationActivity; -import samples.android.nativeapp.apidemos.screens.AlertsMenuScreen; -import samples.android.nativeapp.apidemos.screens.InvokeSearchScreen; -import samples.android.nativeapp.apidemos.screens.TwoButtonsAlert; -import samples.android.nativeapp.apidemos.screens.ViewControlsScreen; +import samples.android.nativeapp.apidemos.screens.*; import testreport.ScreenshotListener; @Listeners(ScreenshotListener.class) @@ -88,6 +85,38 @@ public void testOpensAlert() { alertDialog.close(); } + @Test + public void testVerticalSwipeToElement() { + ViewControlsScreen viewControlsScreen = new ViewControlsScreen(); + openRadioButtonsScreen(); + viewControlsScreen.scrollToAllInsideScrollViewLabel(); + Assert.assertEquals( + viewControlsScreen.getAllInsideScrollViewLabelText(), + "(And all inside of a ScrollView!)", + "Label text does not match expected"); + viewControlsScreen.scrollToDisabledButton(); + Assert.assertFalse(viewControlsScreen.isDisabledButtonClickable()); + } + + @Test + public void testHorizontalSwipeToElement() { + ViewTabsScrollableScreen viewTabsScrollableScreen = ApplicationActivity.VIEW_TABS_SCROLLABLE.open(); + Assert.assertTrue(viewTabsScrollableScreen.state().isDisplayed(), + String.format("%s screen should be opened", viewTabsScrollableScreen.getName())); + viewTabsScrollableScreen.swipeTab(4, 1); + viewTabsScrollableScreen.selectTab(7); + Assert.assertEquals( + viewTabsScrollableScreen.getTabContentText(7), + "Content for tab with tag Tab 7", + "Label text does not match expected"); + viewTabsScrollableScreen.swipeTab(5, 7); + viewTabsScrollableScreen.selectTab(4); + Assert.assertEquals( + viewTabsScrollableScreen.getTabContentText(4), + "Content for tab with tag Tab 4", + "Label text does not match expected"); + } + private void logStep(String step) { AqualityServices.getLogger().info(step); } diff --git a/src/test/java/samples/android/nativeapp/apidemos/ApplicationActivity.java b/src/test/java/samples/android/nativeapp/apidemos/ApplicationActivity.java index 6f0dd96..9b2f289 100644 --- a/src/test/java/samples/android/nativeapp/apidemos/ApplicationActivity.java +++ b/src/test/java/samples/android/nativeapp/apidemos/ApplicationActivity.java @@ -8,6 +8,7 @@ import samples.android.nativeapp.apidemos.screens.AndroidScreen; import samples.android.nativeapp.apidemos.screens.InvokeSearchScreen; import samples.android.nativeapp.apidemos.screens.ViewControlsScreen; +import samples.android.nativeapp.apidemos.screens.ViewTabsScrollableScreen; import java.lang.reflect.InvocationTargetException; @@ -15,7 +16,8 @@ public enum ApplicationActivity { SEARCH(".app.SearchInvoke", InvokeSearchScreen.class), ALERT_DIALOGS(".app.AlertDialogSamples", AlertsMenuScreen.class), - VIEW_CONTROLS(".view.Controls1", ViewControlsScreen.class); + VIEW_CONTROLS(".view.Controls1", ViewControlsScreen.class), + VIEW_TABS_SCROLLABLE(".view.Tabs5", ViewTabsScrollableScreen.class); private static final String PACKAGE = "io.appium.android.apis"; diff --git a/src/test/java/samples/android/nativeapp/apidemos/screens/ViewControlsScreen.java b/src/test/java/samples/android/nativeapp/apidemos/screens/ViewControlsScreen.java index de41f45..590c55d 100644 --- a/src/test/java/samples/android/nativeapp/apidemos/screens/ViewControlsScreen.java +++ b/src/test/java/samples/android/nativeapp/apidemos/screens/ViewControlsScreen.java @@ -1,24 +1,50 @@ package samples.android.nativeapp.apidemos.screens; +import aquality.appium.mobile.actions.SwipeDirection; +import aquality.appium.mobile.elements.interfaces.IButton; import aquality.appium.mobile.elements.interfaces.ICheckBox; +import aquality.appium.mobile.elements.interfaces.ILabel; import aquality.appium.mobile.elements.interfaces.IRadioButton; import io.appium.java_client.MobileBy; import org.openqa.selenium.By; public class ViewControlsScreen extends AndroidScreen { - public ViewControlsScreen() { - super(By.id("android:id/content"), "View/Controls"); - } - public IRadioButton getRadioButton(int number){ + public IRadioButton getRadioButton(int number) { return getElementFactory().getRadioButton( MobileBy.AccessibilityId(String.format("RadioButton %d", number)), String.valueOf(number)); } - public ICheckBox getCheckBox(int number){ + public ICheckBox getCheckBox(int number) { return getElementFactory().getCheckBox( MobileBy.AccessibilityId(String.format("Checkbox %d", number)), String.valueOf(number)); } + + private final IButton btnDisabled = getElementFactory().getButton(MobileBy.id("button_disabled"), "Disabled"); + + public ViewControlsScreen() { + super(By.id("android:id/content"), "View/Controls"); + } + + public void scrollToAllInsideScrollViewLabel() { + lblAllInsideScrollView.getTouchActions().scrollToElement(SwipeDirection.DOWN); + } + + public String getAllInsideScrollViewLabelText() { + return lblAllInsideScrollView.getText(); + } + + public void scrollToDisabledButton() { + btnDisabled.getTouchActions().scrollToElement(SwipeDirection.UP); + } + + public boolean isDisabledButtonClickable() { + return btnDisabled.state().isClickable(); + } + + private final ILabel lblAllInsideScrollView = getElementFactory().getLabel( + MobileBy.AccessibilityId("(And all inside of a ScrollView!)"), + "All inside of Scroll View"); } diff --git a/src/test/java/samples/android/nativeapp/apidemos/screens/ViewTabsScrollableScreen.java b/src/test/java/samples/android/nativeapp/apidemos/screens/ViewTabsScrollableScreen.java new file mode 100644 index 0000000..9b24b76 --- /dev/null +++ b/src/test/java/samples/android/nativeapp/apidemos/screens/ViewTabsScrollableScreen.java @@ -0,0 +1,45 @@ +package samples.android.nativeapp.apidemos.screens; + +import aquality.appium.mobile.elements.interfaces.IButton; +import aquality.appium.mobile.elements.interfaces.ILabel; +import io.appium.java_client.MobileBy; +import org.openqa.selenium.By; +import org.openqa.selenium.Point; + +public class ViewTabsScrollableScreen extends AndroidScreen { + + private static final String TAB_TEXT = "Content for tab with tag Tab %s"; + + public IButton getTab(int tabNumber) { + return getElementFactory().getButton( + MobileBy.xpath(String.format("//*[@text='TAB %s' and @resource-id = 'android:id/title']", tabNumber)), + String.valueOf(tabNumber)); + } + + public ILabel getTabContent(int tabNumber) { + return getElementFactory().getLabel( + MobileBy.xpath(String.format("//*[@text='%s']", generateTabText(tabNumber))), + String.valueOf(tabNumber)); + } + + public ViewTabsScrollableScreen() { + super(By.id("android:id/content"), "View/Tabs/Scrollable"); + } + + public void swipeTab(int startTabNumber, int endTabNumber) { + Point endTabPoint = getTab(endTabNumber).getElement().getCenter(); + getTab(startTabNumber).getTouchActions().swipe(endTabPoint); + } + + public String getTabContentText(int tabNumber) { + return getTabContent(tabNumber).getText(); + } + + public void selectTab(int tabNumber) { + getTab(tabNumber).click(); + } + + private String generateTabText(int tabNumber) { + return String.format(TAB_TEXT, tabNumber); + } +} diff --git a/src/test/resources/settings.androidwebsession.json b/src/test/resources/settings.androidwebsession.json index af2dc16..1c9af04 100644 --- a/src/test/resources/settings.androidwebsession.json +++ b/src/test/resources/settings.androidwebsession.json @@ -1,9 +1,8 @@ { - "platformName" : "android", + "platformName": "android", "isRemote": true, "remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub", "screensLocation": "integration", - "driverSettings": { "android": { "capabilities": { @@ -22,10 +21,10 @@ } }, "timeouts": { - "timeoutImplicit" : 0, - "timeoutCondition" : 15, + "timeoutImplicit": 0, + "timeoutCondition": 15, "timeoutPollingInterval": 300, - "timeoutCommand":120 + "timeoutCommand": 120 }, "retry": { "number": 2, @@ -36,5 +35,13 @@ }, "elementCache": { "isEnabled": true + }, + "touchActions": { + "swipe": { + "duration": 1, + "retries": 5, + "verticalOffset": 0.2, + "horizontalOffset": 0.5 + } } } \ No newline at end of file diff --git a/src/test/resources/settings.json b/src/test/resources/settings.json index bd61397..f04bb75 100644 --- a/src/test/resources/settings.json +++ b/src/test/resources/settings.json @@ -1,9 +1,8 @@ { - "platformName" : "android", + "platformName": "android", "isRemote": true, "remoteConnectionUrl": "http://127.0.0.1:4723/wd/hub", "screensLocation": "integration", - "driverSettings": { "android": { "deviceKey": "Android_Emulator", @@ -32,10 +31,10 @@ } }, "timeouts": { - "timeoutImplicit" : 0, - "timeoutCondition" : 15, + "timeoutImplicit": 0, + "timeoutCondition": 15, "timeoutPollingInterval": 300, - "timeoutCommand":120 + "timeoutCommand": 120 }, "retry": { "number": 2, @@ -46,5 +45,13 @@ }, "elementCache": { "isEnabled": true + }, + "touchActions": { + "swipe": { + "duration": 1, + "retries": 5, + "verticalOffset": 0.2, + "horizontalOffset": 0.5 + } } } \ No newline at end of file