Skip to content

Commit

Permalink
[plugin-mobile-app] Add possibility to swipe the context element (#1939)
Browse files Browse the repository at this point in the history
  • Loading branch information
ikalinin1 committed Sep 8, 2021
1 parent d498139 commit c6ab617
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 74 deletions.
13 changes: 13 additions & 0 deletions docs/modules/plugins/pages/plugin-mobile-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,9 @@ When I clear field located `accessibilityId(username)`

Swipes to an element in either UP, DOWN, LEFT, or RIGHT direction with the specified swipe duration

[INFO]
The step takes into account current context. If you need to perform swipe on the element, you need to switch the context to this element.

==== *_Wording_*

[source,gherkin]
Expand Down Expand Up @@ -467,6 +470,16 @@ When I swipe UP to element located `accessibilityId(end-of-screen)` with duratio
Then number of elements found by `accessibilityId(end-of-screen)` is equal to `1`
----

.Swipe context element.story
[source,gherkin]
----
Scenario: Switch slides
When I change context to element located `accessibilityId(carousel)`
Then number of elements found by `accessibilityId(slide 2)` is equal to `0`
When I swipe LEFT to element located `accessibilityId(slide 2)` with duration PT1S
Then number of elements found by `accessibilityId(slide 2)` is equal to `1`
----

=== Upload a file to a device

==== *_Info_*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,67 +17,77 @@
package org.vividus.bdd.mobileapp.model;

import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.vividus.bdd.mobileapp.configuration.MobileApplicationConfiguration;

public enum SwipeDirection
{
UP
{
@Override
public SwipeCoordinates calculateCoordinates(Dimension dimension,
public SwipeCoordinates calculateCoordinates(Rectangle swipeArea,
MobileApplicationConfiguration mobileApplicationConfiguration)
{
int indent = dimension.getHeight() / VERTICAL_INDENT_COEFFICIENT;
return createCoordinates(dimension.getHeight() - indent, indent, dimension.getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition());
int indent = swipeArea.getHeight() / VERTICAL_INDENT_COEFFICIENT;
return createCoordinates(swipeArea.getHeight() - indent, indent, swipeArea.getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition(), swipeArea.getPoint());
}
},
DOWN
{
@Override
public SwipeCoordinates calculateCoordinates(Dimension dimension,
public SwipeCoordinates calculateCoordinates(Rectangle swipeArea,
MobileApplicationConfiguration mobileApplicationConfiguration)
{
int indent = dimension.getHeight() / VERTICAL_INDENT_COEFFICIENT;
return createCoordinates(indent, dimension.getHeight() - indent, dimension.getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition());
int indent = swipeArea.getHeight() / VERTICAL_INDENT_COEFFICIENT;
return createCoordinates(indent, swipeArea.getHeight() - indent, swipeArea.getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition(), swipeArea.getPoint());
}
},
LEFT
{
@Override
public SwipeCoordinates calculateCoordinates(Dimension dimension,
public SwipeCoordinates calculateCoordinates(Rectangle swipeArea,
MobileApplicationConfiguration mobileApplicationConfiguration)
{
int indent = dimension.getWidth() / HORIZONTAL_INDENT_COEFFICIENT;
int y = calculateCoordinate(dimension.getHeight(),
int indent = swipeArea.getWidth() / HORIZONTAL_INDENT_COEFFICIENT;
int y = calculateCoordinate(swipeArea.getHeight(),
mobileApplicationConfiguration.getSwipeHorizontalYPosition());
return new SwipeCoordinates(dimension.getWidth() - indent, y, indent, y);
return createAdjustedCoordinates(swipeArea.getWidth() - indent, y, indent, y, swipeArea.getPoint());
}
},
RIGHT
{
@Override
public SwipeCoordinates calculateCoordinates(Dimension dimension,
public SwipeCoordinates calculateCoordinates(Rectangle swipeArea,
MobileApplicationConfiguration mobileApplicationConfiguration)
{
Dimension dimension = swipeArea.getDimension();
Point point = swipeArea.getPoint();
int indent = dimension.getWidth() / HORIZONTAL_INDENT_COEFFICIENT;
int y = calculateCoordinate(dimension.getHeight(),
mobileApplicationConfiguration.getSwipeHorizontalYPosition());
return new SwipeCoordinates(indent, y, dimension.getWidth() - indent, y);
return createAdjustedCoordinates(indent, y, dimension.getWidth() - indent, y, point);
}
};

private static final int VERTICAL_INDENT_COEFFICIENT = 5;
private static final int HORIZONTAL_INDENT_COEFFICIENT = 8;

public abstract SwipeCoordinates calculateCoordinates(Dimension dimension,
public abstract SwipeCoordinates calculateCoordinates(Rectangle swipeArea,
MobileApplicationConfiguration mobileApplicationConfiguration);

public static SwipeCoordinates createCoordinates(int startY, int endY, int width, int xOffsetPercentage)
public static SwipeCoordinates createCoordinates(int startY, int endY, int width, int xOffsetPercentage,
Point point)
{
int x = calculateCoordinate(width, xOffsetPercentage);
return new SwipeCoordinates(x, startY, x, endY);
return createAdjustedCoordinates(x, startY, x, endY, point);
}

private static SwipeCoordinates createAdjustedCoordinates(int startX, int startY, int endX, int endY, Point point)
{
return new SwipeCoordinates(startX + point.x, startY + point.y, endX + point.getX(), endY + point.getY());
}

@SuppressWarnings("MagicNumber")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;

import org.jbehave.core.annotations.When;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.vividus.bdd.mobileapp.model.SwipeDirection;
import org.vividus.bdd.monitor.TakeScreenshotOnFailure;
Expand All @@ -31,6 +35,7 @@
import org.vividus.selenium.manager.GenericWebDriverManager;
import org.vividus.ui.action.ISearchActions;
import org.vividus.ui.action.search.Locator;
import org.vividus.ui.context.IUiContext;

@TakeScreenshotOnFailure
public class TouchSteps
Expand All @@ -42,14 +47,16 @@ public class TouchSteps
private final IBaseValidations baseValidations;
private final ISearchActions searchActions;
private final GenericWebDriverManager genericWebDriverManager;
private final IUiContext uiContext;

public TouchSteps(TouchActions touchActions, IBaseValidations baseValidations,
ISearchActions searchActions, GenericWebDriverManager genericWebDriverManager)
ISearchActions searchActions, GenericWebDriverManager genericWebDriverManager, IUiContext uiContext)
{
this.touchActions = touchActions;
this.baseValidations = baseValidations;
this.searchActions = searchActions;
this.genericWebDriverManager = genericWebDriverManager;
this.uiContext = uiContext;
}

/**
Expand Down Expand Up @@ -89,6 +96,8 @@ public void tapByLocator(Locator locator)

/**
* Swipes to element in <b>direction</b> direction with duration <b>duration</b>
* The step takes into account current context. If you need to perform swipe on the element,
* you need to switch the context to this element.
* @param direction direction to swipe, either <b>UP</b> or <b>DOWN</b>
* @param locator locator to find an element
* @param swipeDuration swipe duration in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format
Expand All @@ -97,44 +106,54 @@ public void tapByLocator(Locator locator)
public void swipeToElement(SwipeDirection direction, Locator locator, Duration swipeDuration)
{
locator.getSearchParameters().setWaitForElement(false);

Supplier<Rectangle> swipeArea = createSwipeArea();
List<WebElement> elements = new ArrayList<>(searchActions.findElements(locator));
if (elements.isEmpty())
{
touchActions.swipeUntil(direction, swipeDuration, () ->
touchActions.swipeUntil(direction, swipeDuration, swipeArea.get(), () ->
{
elements.addAll(searchActions.findElements(locator));
return !elements.isEmpty();
});
}

if (baseValidations.assertElementsNumber(String.format("The element by locator %s exists", locator), elements,
ComparisonRule.EQUAL_TO, 1) && (SwipeDirection.UP == direction || SwipeDirection.DOWN == direction))
{
adjustVerticalPosition(elements.get(0), swipeDuration);
adjustVerticalPosition(elements.get(0), swipeArea.get(), swipeDuration);
}
}

private Supplier<Rectangle> createSwipeArea()
{
SearchContext searchContext = uiContext.getSearchContext();
if (searchContext instanceof WebElement)
{
WebElement contextElement = (WebElement) searchContext;
return contextElement::getRect;
}
return () -> new Rectangle(new Point(0, 0), genericWebDriverManager.getSize());
}

private void adjustVerticalPosition(WebElement element, Duration swipeDuration)
private void adjustVerticalPosition(WebElement element, Rectangle swipeArea, Duration swipeDuration)
{
int windowSizeHeight = genericWebDriverManager.getSize().getHeight();
int windowCenterY = windowSizeHeight / 2;
int swipeAreaSizeHeight = swipeArea.getHeight();
int swipeAreaCenterY = swipeAreaSizeHeight / 2;
int elementTopCoordinateY = element.getLocation().getY();

int bottomVisibilityIndent = (int) (VISIBILITY_BOTTOM_INDENT_COEFFICIENT * windowSizeHeight);
int visibilityY = windowSizeHeight - bottomVisibilityIndent;
int bottomVisibilityIndent = (int) (VISIBILITY_BOTTOM_INDENT_COEFFICIENT * swipeAreaSizeHeight);
int visibilityY = swipeAreaSizeHeight - bottomVisibilityIndent;
if (elementTopCoordinateY > visibilityY)
{
touchActions.performVerticalSwipe(windowCenterY, windowCenterY - (elementTopCoordinateY - visibilityY),
swipeDuration);
touchActions.performVerticalSwipe(swipeAreaCenterY,
swipeAreaCenterY - (elementTopCoordinateY - visibilityY), swipeArea, swipeDuration);
return;
}

int topVisibilityIndent = (int) (VISIBILITY_TOP_INDENT_COEFFICIENT * windowSizeHeight);
int topVisibilityIndent = (int) (VISIBILITY_TOP_INDENT_COEFFICIENT * swipeAreaSizeHeight);
if (elementTopCoordinateY < topVisibilityIndent)
{
touchActions.performVerticalSwipe(windowCenterY,
windowCenterY + topVisibilityIndent - elementTopCoordinateY, swipeDuration);
touchActions.performVerticalSwipe(swipeAreaCenterY,
swipeAreaCenterY + topVisibilityIndent - elementTopCoordinateY, swipeArea, swipeDuration);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.vividus.bdd.mobileapp.configuration.MobileApplicationConfiguration;
Expand Down Expand Up @@ -131,11 +132,13 @@ private TouchAction<?> buildTapAction(WebElement element, BiConsumer<TouchAction
* <li>the end of mobile scroll view is reached</li>
* <li>the swipe limit is exceeded</li>
* </ul>
* @param direction direction to swipe, either <b>UP</b> or <b>DOWN</b>
* @param direction direction to swipe, either <b>UP</b> or <b>DOWN</b>
* @param swipeDuration duration between a pointer moves from the start to the end of the swipe coordinates
* @param swipeArea the area to execute the swipe
* @param stopCondition condition to stop swiping
*/
public void swipeUntil(SwipeDirection direction, Duration swipeDuration, BooleanSupplier stopCondition)
public void swipeUntil(SwipeDirection direction, Duration swipeDuration, Rectangle swipeArea,
BooleanSupplier stopCondition)
{
/*
* mobile:scroll
Expand All @@ -156,8 +159,7 @@ public void swipeUntil(SwipeDirection direction, Duration swipeDuration, Boolean
Duration stabilizationDuration = mobileApplicationConfiguration.getSwipeStabilizationDuration();
int swipeLimit = mobileApplicationConfiguration.getSwipeLimit();
BufferedImage previousFrame = null;
SwipeCoordinates swipeCoordinates = direction.calculateCoordinates(genericWebDriverManager.getSize(),
mobileApplicationConfiguration);
SwipeCoordinates swipeCoordinates = direction.calculateCoordinates(swipeArea, mobileApplicationConfiguration);
for (int count = 0; count <= swipeLimit; count++)
{
swipe(swipeCoordinates, swipeDuration);
Expand Down Expand Up @@ -185,14 +187,15 @@ public void swipeUntil(SwipeDirection direction, Duration swipeDuration, Boolean
/**
* Performs vertical swipe from <b>startY</b> to <b>endY</b> with <b>swipeDuration</b>
*
* @param startY start Y coordinate
* @param endY end Y coordinate
* @param startY start Y coordinate
* @param endY end Y coordinate
* @param swipeArea the area to execute the swipe
* @param swipeDuration swipe duration in <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a> format
*/
public void performVerticalSwipe(int startY, int endY, Duration swipeDuration)
public void performVerticalSwipe(int startY, int endY, Rectangle swipeArea, Duration swipeDuration)
{
swipe(SwipeDirection.createCoordinates(startY, endY, genericWebDriverManager.getSize().getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition()), swipeDuration);
swipe(SwipeDirection.createCoordinates(startY, endY, swipeArea.getWidth(),
mobileApplicationConfiguration.getSwipeVerticalXPosition(), swipeArea.getPoint()), swipeDuration);
}

private BufferedImage takeScreenshot()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.remote.RemoteWebElement;
import org.vividus.bdd.mobileapp.configuration.MobileApplicationConfiguration;
import org.vividus.bdd.mobileapp.model.SwipeDirection;
Expand Down Expand Up @@ -78,6 +79,7 @@ class TouchActionsTests
private static final String WHITE_IMAGE = "white.png";
private static final String ELEMENT_ID = "elementId";
private static final Dimension DIMENSION = new Dimension(600, 800);
private static final Rectangle SWIPE_AREA = new Rectangle(new Point(0, 0), DIMENSION);

@Spy private final MobileApplicationConfiguration mobileApplicationConfiguration =
new MobileApplicationConfiguration(Duration.ZERO, 5, 50, 0);
Expand Down Expand Up @@ -181,7 +183,7 @@ void shouldSwipeUntilConditionIsTrue() throws IOException
when(screenshotTaker.takeViewportScreenshot()).thenReturn(getImage(BLACK_IMAGE))
.thenReturn(getImage(WHITE_IMAGE));

touchActions.swipeUntil(SwipeDirection.UP, DURATION, stopCondition);
touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition);

verifySwipe(3);
verifyConfiguration();
Expand All @@ -200,7 +202,7 @@ void shouldNotExceedSwipeLimit() throws IOException
.thenReturn(getImage(WHITE_IMAGE));

IllegalStateException exception = assertThrows(IllegalStateException.class,
() -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, stopCondition));
() -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition));

assertEquals("Swiping is stopped due to exceeded swipe limit '5'", exception.getMessage());
verifySwipe(6);
Expand All @@ -217,7 +219,7 @@ void shouldStopSwipeOnceEndOfPageIsReached() throws IOException
.thenReturn(getImage(BLACK_IMAGE))
.thenReturn(getImage(BLACK_IMAGE));

touchActions.swipeUntil(SwipeDirection.UP, DURATION, stopCondition);
touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition);

verifySwipe(4);
verifyConfiguration();
Expand All @@ -233,7 +235,7 @@ void shouldWrapIOException() throws IOException
doThrow(exception).when(screenshotTaker).takeViewportScreenshot();

UncheckedIOException wrapper = assertThrows(UncheckedIOException.class,
() -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, stopCondition));
() -> touchActions.swipeUntil(SwipeDirection.UP, DURATION, SWIPE_AREA, stopCondition));

assertEquals(exception, wrapper.getCause());
verifySwipe(1);
Expand All @@ -243,8 +245,7 @@ void shouldWrapIOException() throws IOException
@Test
void shouldPerformVerticalSwipe()
{
when(genericWebDriverManager.getSize()).thenReturn(DIMENSION);
touchActions.performVerticalSwipe(640, 160, DURATION);
touchActions.performVerticalSwipe(640, 160, SWIPE_AREA, DURATION);
verifySwipe(1);
}

Expand Down
Loading

0 comments on commit c6ab617

Please sign in to comment.