Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document and change the syntax on the steps for Drag and Drop #4580

Merged
merged 2 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/modules/plugins/pages/plugin-web-app.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,48 @@ 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`
----

=== Drag and drop steps

This set of steps facilitates the dragging and dropping of elements on a web page.

==== Drag and drop using mouse

Performs drag-and-drop operation for the 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` - The target position relative to the target 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 `id(item-number)` and drop it at right bottom of element located by `id(item-number)`
----

==== Drag and drop simulation

Simulates a drag-and-drop operation for the specified elements,
replicating the behavior without actual mouse actions performed.

valfirst marked this conversation as resolved.
Show resolved Hide resolved
[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 `id(item-number)` and drop at element located by `id(item-number)`
----

=== 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
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;
import org.jbehave.core.annotations.When;
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;
Expand All @@ -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;
Expand All @@ -59,9 +66,47 @@ public DragAndDropSteps(IWebDriverProvider webDriverProvider, WebJavascriptActio
* @param location location relatively to the <b>target</b> element (<b>TOP</b>,<b>BOTTOM</b>,<b>LEFT</b>,
* <b>RIGHT</b>,<b>CENTER</b>,<b>LEFT_TOP</b>,<b>RIGHT_TOP</b>,<b>LEFT_BOTTOM</b>,<b>RIGHT_BOTTOM</b>)
* @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.5", 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 <b>draggable</b> element and moves it relatively to the <b>target</b> element in
* accordance to provided <b>location</b>.
* <br>
* <i>Example</i>
* <br>
* <code>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'])`</code>
* If this step doesn't work, try to use step simulating drag&amp;drop
* @param draggable draggable element
* @param location location relatively to the <b>target</b> element (<b>TOP</b>,<b>BOTTOM</b>,<b>LEFT</b>,
* <b>RIGHT</b>,<b>CENTER</b>,<b>LEFT_TOP</b>,<b>RIGHT_TOP</b>,<b>LEFT_BOTTOM</b>,<b>RIGHT_BOTTOM</b>)
* @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) ->
Expand All @@ -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
Expand Down Expand Up @@ -106,25 +151,79 @@ public void dragAndDropToTargetAtLocation(Locator draggable, Location location,
* </p>
* @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.5", 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 <b>draggable</b> element and its drop at the <b>target</b> element via JavaScript.
* <br>
* <i>Example</i>
* <br>
* <code>When I simulate drag of element located `By.xpath(//div[@class='draggable'])` and drop at element located
* `By.xpath(//div[@class='target'])`</code>
* <p>
* The reason of having this step is that Selenium WebDriver doesn't support HTML5 drag&amp;drop:
* </p>
* <ul>
* <li><a href="https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/3604">
* Issue 3604: HTML5 Drag and Drop with Selenium WebDriver
* </a>
* </li>
* <li><a href="https://github.com/SeleniumHQ/selenium/issues/1365">
* Issue 1365: Actions drag and drop method
* </a>
* </li>
* </ul>
* <p>
* As workaround for these issue the step simulates HTML5 drag&amp;drop via JavaScript. There is no difference in
* actual drag&amp;drop and its simulation via JavaScript from the functional side.
* </p>
* @param draggable draggable element
* @param target target element
*/
@When("I simulate drag of element located by `$draggable` and drop at element located by `$target`")
valfirst marked this conversation as resolved.
Show resolved Hide resolved
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,
private void performDragAndDropDeprecated(Locator draggable, Locator target,
BiConsumer<WebElement, WebElement> 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<WebElement, WebElement> dragAndDropExecutor)
{
Optional<WebElement> draggableElement = baseValidations.assertElementExists(DRAGGABLE_ELEMENT, draggable);
Optional<WebElement> targetElement = baseValidations.assertElementExists(TARGET_ELEMENT, target);

if (draggableElement.isPresent() && targetElement.isPresent())
{
dragAndDropExecutor.accept(draggableElement.get(), targetElement.get());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -60,23 +74,15 @@ class DragAndDropStepsTests
private DragAndDropSteps dragAndDropSteps;

@Test
void shouldDragAndDropToTargetAtLocation()
void shouldDragAndDropToTargetAtLocationDeprecated()
{
WebElement draggable = mockWebElementWithRect(100, 100, 50, 50);
WebElement target = mockWebElementWithRect(200, 200, 50, 50);
WebDriver driver = mock(WebDriver.class, withSettings().extraInterfaces(Interactive.class));
when(webDriverProvider.get()).thenReturn(driver);
testDragAndDropToTargetAtLocation(draggable, Location.TOP, target);
testDragAndDropToTargetAtLocationDeprecated(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)));
}
Expand All @@ -97,6 +103,35 @@ static Stream<Arguments> getElements()
);
}

@Test
void shouldDragAndDropToTargetAtLocation()
{
WebElement draggable = mockWebElementWithRect(100, 100, 50, 50);
WebElement target = mockWebElementWithRect(200, 200, 50, 50);
WebDriver driver = mock(WebDriver.class, withSettings().extraInterfaces(Interactive.class));
when(webDriverProvider.get()).thenReturn(driver);
testDragAndDropToTargetAtLocation(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)));
}

@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)
Expand All @@ -112,33 +147,63 @@ private void testDragAndDropToTargetAtLocation(WebElement draggable, Location lo
dragAndDropSteps.dragAndDropToTargetAtLocation(draggableLocator, location, targetLocator);
}

@Test
void shouldSimulateDragAndDropDeprecated()
{
WebElement draggable = mock();
WebElement target = mock();
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()
{
WebElement draggable = mock(WebElement.class);
WebElement target = mock(WebElement.class);
WebElement draggable = mock();
WebElement target = mock();
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*`
Loading