diff --git a/docs/modules/plugins/pages/plugin-web-app.adoc b/docs/modules/plugins/pages/plugin-web-app.adoc
index b96cd0819b..20cbe9bd6c 100644
--- a/docs/modules/plugins/pages/plugin-web-app.adoc
+++ b/docs/modules/plugins/pages/plugin-web-app.adoc
@@ -335,6 +335,73 @@ When I select element located `$locator` and upload file `$filePath`
When I select element located by `id(uploadfile)` and upload file `/folder/file_for_upload.png`
----
+=== Hover mouse over the element
+
+=== Drag and drop steps
+
+This set of steps facilitates the dragging and dropping of elements on a web page.
+Desired position relative to the target element (TOP, BOTTOM, LEFT, RIGHT, CENTER, LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM).
+
+==== The drag and drop operation
+
+This step for a drag-and-drop operation for specified elements.
+
+[source,gherkin]
+----
+When I drag element located by `$draggable` and drop it at $location of element located by `$target`
+----
+
+* $draggable: - The <<_locator,locator>> to identify the element to be dragged
+* $location: - Action to select the position state of the element (TOP, BOTTOM, LEFT, RIGHT, CENTER, LEFT_TOP, RIGHT_TOP, LEFT_BOTTOM, RIGHT_BOTTOM).
+* $target: - The <<_locator,locator>> to identify the target element.
+
+.Dragging the image on the page
+[source,gherkin]
+----
+When I drag element located by `$draggable` and drop it at $location of element located by `$target`
+----
+
+==== The drag and drop operation is simulated
+
+This step simulated a drag-and-drop operation for specified elements,
+replicating the behavior without actual user interaction.
+
+[source,gherkin]
+----
+When I simulate drag of element located by `$draggable` and drop at element located by `$target`
+----
+
+* $draggable: - The <<_locator,locator>> to identify the element to be dragged
+* $target: - The <<_locator,locator>> to identify the target element.
+
+.Simulation of dragging an image onto the page
+
+[source,gherkin]
+----
+When I simulate drag of element located by `$draggable` and drop at element located by `$target`
+----
+
+Moves a mouse cursor on the element.
+
+[source,gherkin]
+----
+When I hover mouse over element located by `$locator`
+----
+
+_Deprecated syntax (will be removed in VIVIDUS 0.7.0)_:
+[source,gherkin]
+----
+When I hover mouse over element located `$locator`
+----
+
+* `$locator` - The <<_locator,locator>> to an element to hover.
+
+.Step example
+[source,gherkin]
+----
+When I hover mouse over element located by `id(hoverme)`
+----
+
=== Check each element has a specific number of children elements
Checks that each element specified by the locator contains an exact number of visible child elements specified by another locator
diff --git a/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/DragAndDropSteps.java b/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/DragAndDropSteps.java
index 5b26c74064..fe2fe0f697 100644
--- a/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/DragAndDropSteps.java
+++ b/vividus-plugin-web-app/src/main/java/org/vividus/steps/ui/web/DragAndDropSteps.java
@@ -17,6 +17,7 @@
package org.vividus.steps.ui.web;
import java.time.Duration;
+import java.util.Optional;
import java.util.function.BiConsumer;
import org.apache.commons.lang3.ObjectUtils;
@@ -24,6 +25,7 @@
import org.openqa.selenium.Point;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
+import org.vividus.annotation.Replacement;
import org.vividus.selenium.IWebDriverProvider;
import org.vividus.selenium.locator.Locator;
import org.vividus.steps.ui.validation.IBaseValidations;
@@ -34,6 +36,11 @@
@TakeScreenshotOnFailure
public class DragAndDropSteps
{
+ private static final int RIGHT_OFFSET = 10;
+ private static final int LEFT_OFFSET = -10;
+ private static final String DRAGGABLE_ELEMENT = "Draggable element";
+ private static final String TARGET_ELEMENT = "Target element";
+ private static final String SIMULATE_DRAG_AND_DROP_JS = "simulate-drag-and-drop.js";
private final IWebDriverProvider webDriverProvider;
private final WebJavascriptActions javascriptActions;
private final IBaseValidations baseValidations;
@@ -59,9 +66,47 @@ public DragAndDropSteps(IWebDriverProvider webDriverProvider, WebJavascriptActio
* @param location location relatively to the target element (TOP,BOTTOM,LEFT,
* RIGHT,CENTER,LEFT_TOP,RIGHT_TOP,LEFT_BOTTOM,RIGHT_BOTTOM)
* @param target target element
+ * @deprecated Use step:
+ * "When I drag element located by `$draggable` and drop it at $location of element located by `$target`" instead
*/
+ @Deprecated(since = "0.6.1", forRemoval = true)
+ @Replacement(versionToRemoveStep = "0.7.0", replacementFormatPattern =
+ "When I drag element located by `%1$s` and drop it at %2$s of element located by `%3$s`")
@When("I drag element located `$draggable` and drop it at $location of element located `$target`")
@SuppressWarnings("checkstyle:MagicNumber")
+ public void dragAndDropToTargetAtLocationDeprecated(Locator draggable, Location location, Locator target)
+ {
+ performDragAndDropDeprecated(draggable, target, (draggableElement, targetElement) ->
+ {
+ Point offsetPoint = location.getPoint(draggableElement.getRect(), targetElement.getRect());
+ new Actions(webDriverProvider.get())
+ .clickAndHold(draggableElement)
+ // Selenium bug: https://github.com/SeleniumHQ/selenium/issues/1365#issuecomment-547786925
+ .moveByOffset(RIGHT_OFFSET, 0)
+ .moveByOffset(LEFT_OFFSET, 0)
+ .moveByOffset(offsetPoint.getX(), offsetPoint.getY())
+ .release()
+ // Wait for DOM stabilization
+ .pause(Duration.ofSeconds(1))
+ .perform();
+ });
+ }
+
+ /**
+ * Drags the draggable element and moves it relatively to the target element in
+ * accordance to provided location.
+ *
+ * Example
+ *
+ * When I drag element located by `By.xpath(//div[@class='draggable'])` and drop it at RIGHT_TOP of element
+ * located by `By.xpath(//div[@class='target'])`
+ * If this step doesn't work, try to use step simulating drag&drop
+ * @param draggable draggable element
+ * @param location location relatively to the target element (TOP,BOTTOM,LEFT,
+ * RIGHT,CENTER,LEFT_TOP,RIGHT_TOP,LEFT_BOTTOM,RIGHT_BOTTOM)
+ * @param target target element
+ */
+ @When("I drag element located by `$draggable` and drop it at $location of element located by `$target`")
public void dragAndDropToTargetAtLocation(Locator draggable, Location location, Locator target)
{
performDragAndDrop(draggable, target, (draggableElement, targetElement) ->
@@ -70,8 +115,8 @@ public void dragAndDropToTargetAtLocation(Locator draggable, Location location,
new Actions(webDriverProvider.get())
.clickAndHold(draggableElement)
// Selenium bug: https://github.com/SeleniumHQ/selenium/issues/1365#issuecomment-547786925
- .moveByOffset(10, 0)
- .moveByOffset(-10, 0)
+ .moveByOffset(RIGHT_OFFSET, 0)
+ .moveByOffset(LEFT_OFFSET, 0)
.moveByOffset(offsetPoint.getX(), offsetPoint.getY())
.release()
// Wait for DOM stabilization
@@ -106,25 +151,79 @@ public void dragAndDropToTargetAtLocation(Locator draggable, Location location,
*
* @param draggable draggable element
* @param target target element
+ * @deprecated Use step:
+ * "When I simulate drag of element located by `$draggable` and drop at element located by `$target`" instead
*/
+ @Deprecated(since = "0.6.1", forRemoval = true)
+ @Replacement(versionToRemoveStep = "0.7.0", replacementFormatPattern =
+ "When I simulate drag of element located by `%1$s` and drop at element located by `%2$s`")
@When("I simulate drag of element located `$draggable` and drop at element located `$target`")
+ public void simulateDragAndDropDeprecated(Locator draggable, Locator target)
+ {
+ performDragAndDropDeprecated(draggable, target, (draggableElement, targetElement) ->
+ // See gist for details: https://gist.github.com/valfirst/7f36c8755676cdf8943a8a8f08eab2e3
+ javascriptActions.executeScriptFromResource(getClass(),
+ SIMULATE_DRAG_AND_DROP_JS, draggableElement,
+ targetElement));
+ }
+
+ /**
+ * Simulates drag of the draggable element and its drop at the target element via JavaScript.
+ *
+ * Example
+ *
+ * When I simulate drag of element located `By.xpath(//div[@class='draggable'])` and drop at element located
+ * `By.xpath(//div[@class='target'])`
+ *
+ * The reason of having this step is that Selenium WebDriver doesn't support HTML5 drag&drop:
+ *
+ *
+ *
+ * As workaround for these issue the step simulates HTML5 drag&drop via JavaScript. There is no difference in
+ * actual drag&drop and its simulation via JavaScript from the functional side.
+ *
+ * @param draggable draggable element
+ * @param target target element
+ */
+ @When("I simulate drag of element located by `$draggable` and drop at element located by `$target`")
public void simulateDragAndDrop(Locator draggable, Locator target)
{
performDragAndDrop(draggable, target, (draggableElement, targetElement) ->
// See gist for details: https://gist.github.com/valfirst/7f36c8755676cdf8943a8a8f08eab2e3
- javascriptActions.executeScriptFromResource(getClass(), "simulate-drag-and-drop.js", draggableElement,
+ javascriptActions.executeScriptFromResource(getClass(), SIMULATE_DRAG_AND_DROP_JS, draggableElement,
targetElement));
}
- private void performDragAndDrop(Locator draggable, Locator target,
- BiConsumer dragAndDropExecutor)
+ private void performDragAndDropDeprecated(Locator draggable, Locator target,
+ BiConsumer dragAndDropExecutor)
{
- WebElement draggableElement = baseValidations.assertIfElementExists("Draggable element", draggable);
- WebElement targetElement = baseValidations.assertIfElementExists("Target element", target);
+ WebElement draggableElement = baseValidations.assertIfElementExists(DRAGGABLE_ELEMENT, draggable);
+ WebElement targetElement = baseValidations.assertIfElementExists(TARGET_ELEMENT, target);
if (ObjectUtils.allNotNull(draggableElement, targetElement))
{
dragAndDropExecutor.accept(draggableElement, targetElement);
}
}
+
+ private void performDragAndDrop(Locator draggable, Locator target,
+ BiConsumer dragAndDropExecutor)
+ {
+ Optional draggableElement = baseValidations.assertElementExists(DRAGGABLE_ELEMENT, draggable);
+ Optional targetElement = baseValidations.assertElementExists(TARGET_ELEMENT, target);
+
+ if (draggableElement.isPresent() && targetElement.isPresent())
+ {
+ dragAndDropExecutor.accept(draggableElement.get(), targetElement.get());
+ }
+ }
}
diff --git a/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/DragAndDropStepsTests.java b/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/DragAndDropStepsTests.java
index 098e12f184..9a5d35c180 100644
--- a/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/DragAndDropStepsTests.java
+++ b/vividus-plugin-web-app/src/test/java/org/vividus/steps/ui/web/DragAndDropStepsTests.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.withSettings;
+import java.util.Optional;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -47,6 +48,19 @@
@ExtendWith(MockitoExtension.class)
class DragAndDropStepsTests
{
+ private static final String DRAGGABLE_ELEMENT = "Draggable element";
+ private static final String TARGET_ELEMENT = "Target element";
+ private static final String SIMULATE_DRAG_AND_DROP_JS = "simulate-drag-and-drop.js";
+ private static final String DRAG_AND_DROP_ACTIONS = "{id=default mouse, "
+ + "type=pointer, parameters={pointerType=mouse}, "
+ + "actions=["
+ + "{duration=100, x=0, y=0, type=pointerMove, origin=Mock for WebElement, hashCode: %s}, "
+ + "{button=0, type=pointerDown}, "
+ + "{duration=200, x=10, y=0, type=pointerMove, origin=pointer}, "
+ + "{duration=200, x=-10, y=0, type=pointerMove, origin=pointer}, "
+ + "{duration=200, x=100, y=50, type=pointerMove, origin=pointer}, "
+ + "{button=0, type=pointerUp}, "
+ + "{duration=1000, type=pause}]}";
@Mock
private IWebDriverProvider webDriverProvider;
@@ -59,6 +73,37 @@ class DragAndDropStepsTests
@InjectMocks
private DragAndDropSteps dragAndDropSteps;
+ @Test
+ void shouldDragAndDropToTargetAtLocationDeprecated()
+ {
+ WebElement draggable = mockWebElementWithRectDeprecated(100, 100, 50, 50);
+ WebElement target = mockWebElementWithRectDeprecated(200, 200, 50, 50);
+ WebDriver driver = mock(WebDriver.class, withSettings().extraInterfaces(Interactive.class));
+ when(webDriverProvider.get()).thenReturn(driver);
+ testDragAndDropToTargetAtLocationDeprecated(draggable, Location.TOP, target);
+ String actionsSequence = String.format(
+ DRAG_AND_DROP_ACTIONS, draggable.hashCode());
+ verify((Interactive) driver).perform(
+
+ argThat(arg -> arg.iterator().next().encode().toString().equals(actionsSequence)));
+ }
+
+ private static WebElement mockWebElementWithRectDeprecated(int x, int y, int height, int width)
+ {
+ WebElement element = mock(WebElement.class);
+ when(element.getRect()).thenReturn(new Rectangle(x, y, height, width));
+ return element;
+ }
+
+ static Stream getElementsDeprecated()
+ {
+ return Stream.of(
+ Arguments.of(null, null),
+ Arguments.of(null, mock(WebElement.class)),
+ Arguments.of(mock(WebElement.class), null)
+ );
+ }
+
@Test
void shouldDragAndDropToTargetAtLocation()
{
@@ -68,15 +113,7 @@ void shouldDragAndDropToTargetAtLocation()
when(webDriverProvider.get()).thenReturn(driver);
testDragAndDropToTargetAtLocation(draggable, Location.TOP, target);
String actionsSequence = String.format(
- "{id=default mouse, type=pointer, parameters={pointerType=mouse}, "
- + "actions=["
- + "{duration=100, x=0, y=0, type=pointerMove, origin=Mock for WebElement, hashCode: %s}, "
- + "{button=0, type=pointerDown}, "
- + "{duration=200, x=10, y=0, type=pointerMove, origin=pointer}, "
- + "{duration=200, x=-10, y=0, type=pointerMove, origin=pointer}, "
- + "{duration=200, x=100, y=50, type=pointerMove, origin=pointer}, "
- + "{button=0, type=pointerUp}, "
- + "{duration=1000, type=pause}]}", draggable.hashCode());
+ DRAG_AND_DROP_ACTIONS, draggable.hashCode());
verify((Interactive) driver).perform(
argThat(arg -> arg.iterator().next().encode().toString().equals(actionsSequence)));
}
@@ -97,6 +134,21 @@ static Stream getElements()
);
}
+ @MethodSource("getElements")
+ @ParameterizedTest
+ void shouldNotDragAndDropWhenAnyElementIsNotFoundDeprecated(WebElement draggable, WebElement target)
+ {
+ testDragAndDropToTargetAtLocationDeprecated(draggable, Location.TOP, target);
+ verifyNoInteractions(webDriverProvider);
+ }
+
+ private void testDragAndDropToTargetAtLocationDeprecated(WebElement draggable, Location location, WebElement target)
+ {
+ Locator draggableLocator = mockDraggableElementSearchDeprecated(draggable);
+ Locator targetLocator = mockTargetElementSearchDeprecated(target);
+ dragAndDropSteps.dragAndDropToTargetAtLocationDeprecated(draggableLocator, location, targetLocator);
+ }
+
@MethodSource("getElements")
@ParameterizedTest
void shouldNotDragAndDropWhenAnyElementIsNotFound(WebElement draggable, WebElement target)
@@ -112,6 +164,18 @@ private void testDragAndDropToTargetAtLocation(WebElement draggable, Location lo
dragAndDropSteps.dragAndDropToTargetAtLocation(draggableLocator, location, targetLocator);
}
+ @Test
+ void shouldSimulateDragAndDropDeprecated()
+ {
+ WebElement draggable = mock(WebElement.class);
+ WebElement target = mock(WebElement.class);
+ Locator draggableLocator = mockDraggableElementSearchDeprecated(draggable);
+ Locator targetLocator = mockTargetElementSearchDeprecated(target);
+ dragAndDropSteps.simulateDragAndDropDeprecated(draggableLocator, targetLocator);
+ verify(javascriptActions)
+ .executeScriptFromResource(DragAndDropSteps.class, SIMULATE_DRAG_AND_DROP_JS, draggable, target);
+ }
+
@Test
void shouldSimulateDragAndDrop()
{
@@ -120,25 +184,43 @@ void shouldSimulateDragAndDrop()
Locator draggableLocator = mockDraggableElementSearch(draggable);
Locator targetLocator = mockTargetElementSearch(target);
dragAndDropSteps.simulateDragAndDrop(draggableLocator, targetLocator);
- verify(javascriptActions).executeScriptFromResource(DragAndDropSteps.class, "simulate-drag-and-drop.js",
+ verify(javascriptActions).executeScriptFromResource(DragAndDropSteps.class, SIMULATE_DRAG_AND_DROP_JS,
draggable, target);
}
+ private Locator mockDraggableElementSearchDeprecated(WebElement draggable)
+ {
+ return mockElementSearchDeprecated(draggable, DRAGGABLE_ELEMENT);
+ }
+
+ private Locator mockTargetElementSearchDeprecated(WebElement target)
+ {
+ return mockElementSearchDeprecated(target, TARGET_ELEMENT);
+ }
+
private Locator mockDraggableElementSearch(WebElement draggable)
{
- return mockElementSearch(draggable, "Draggable element");
+ return mockElementSearch(draggable, DRAGGABLE_ELEMENT);
}
private Locator mockTargetElementSearch(WebElement target)
{
- return mockElementSearch(target, "Target element");
+ return mockElementSearch(target, TARGET_ELEMENT);
}
- private Locator mockElementSearch(WebElement element, String assertionDescription)
+ private Locator mockElementSearchDeprecated(WebElement element, String assertionDescription)
{
Locator locator = mock(Locator.class);
lenient().when(baseValidations.assertIfElementExists(assertionDescription, locator)).thenReturn(
element);
return locator;
}
+
+ private Locator mockElementSearch(WebElement element, String assertionDescription)
+ {
+ Locator locator = mock(Locator.class);
+ lenient().when(baseValidations.assertElementExists(assertionDescription, locator))
+ .thenReturn(Optional.ofNullable(element));
+ return locator;
+ }
}
diff --git a/vividus-tests/src/main/resources/story/integration/DragAndDropSteps.story b/vividus-tests/src/main/resources/story/integration/DragAndDropSteps.story
index 5bb87095b8..ac38e52c9c 100644
--- a/vividus-tests/src/main/resources/story/integration/DragAndDropSteps.story
+++ b/vividus-tests/src/main/resources/story/integration/DragAndDropSteps.story
@@ -5,6 +5,9 @@ Scenario: Step verification 'When I drag element located `$origin` and drop it a
Given I am on page with URL `https://4qp6vjp319.codesandbox.io/`
When I wait until element located by `xpath(//div[@id='root']/ul)` appears
When I change context to element located by `xpath(//div[@id='root']/ul)`
-Then text matches `item 0.*item 1.*item 2.*item 3.*`
-When I drag element located `By.xpath(//li[contains(., 'item 0')])` and drop it at top of element located `By.xpath(//li[contains(., 'item 3')])`
-Then text matches `item 1.*item 2.*item 0.*item 3.*`
+Then text matches `item 0.*item 1.*item 2.*item 3.*item 4.*item 5.*item 6.*`
+When I drag element located by `By.xpath(//li[contains(., 'item 0')])` and drop it at top of element located by `By.xpath(//li[contains(., 'item 3')])`
+Then text matches `item 1.*item 2.*item 0.*item 3.*item 4.*item 5.*item 6.*`
+When I wait until element located by `xpath(//div[@id='root']/ul)` appears
+When I drag element located `By.xpath(//li[contains(., 'item 2')])` and drop it at top of element located `By.xpath(//li[contains(., 'item 5')])`
+Then text matches `item 1.*item 0.*item 3.*item 4.*item 2.*item 5.*item 6.*`