atlassian / react-beautiful-dnd

Beautiful and accessible drag and drop for lists with React
https://react-beautiful-dnd.netlify.app
Other
32.75k stars 2.52k forks source link

How to test / automate drag and drop of an element using Selenium in Java? #1090

Closed pdubuc closed 5 years ago

pdubuc commented 5 years ago

I have tried various options, but have been unable to simulate a mouse click to drag an element from one position to another in a browser using Selenium. When the test runs, I see the element get selected, but it does not move to the specified drop point. Any advice on how to test drag & drop using Selenium is immensely appreciated!

React: v16.4.2 react-beautiful-dnd: v10.0.0 Issue observed across multiple browsers: Chrome (v71.0), Firefox (v64.0.2), Edge (v42)

Here's how I defined the function in my latest attempt (variations on this theme also tried and failed):

private void dragAndDrop(WebElement dragPoint, WebElement dropPoint, WebDriver driver) {
    Actions builder = new Actions(driver);
    builder.clickAndHold(dragPoint).perform();
    builder.pause(Duration.ofSeconds(1));
    builder.moveByOffset(10,0).perform(); 
    builder.moveToElement(dropPoint).perform();
    builder.moveByOffset(10,0).perform(); 
    builder.pause(Duration.ofSeconds(1));
    builder.release();
    builder.build();
    builder.perform();
}

Also tried the following (same result):

private void dragAndDrop(WebElement dragPoint, WebElement dropPoint, WebDriver driver) {
    Actions builder = new Actions(driver);
    Action dragAndDrop = builder.dragAndDrop(dragPoint, dropPoint).build();
    dragAndDrop.perform();
}

In the test, the 2 elements are identified uniquely using xpath and the function is called:

WebElement dragPoint = driver.findElement(By.xpath(".../div[3]/...(etc.)/div[@class='rst__moveHandle']"));
WebElement dropPoint = driver.findElement(By.xpath(".../div[5]/...(etc.)/div[@class='rst__moveHandle']"));

dragAndDrop(dragPoint, dropPoint, driver);

Relevant libraries:

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Action;
import org.openqa.selenium.interactions.Actions;
MikaelCarpenter commented 5 years ago

So I just implemented this in our Ruby framework that sits on top of Selenium.

Here's what it looks like:

    def drag_and_drop_to(destination)
      target = self

      # target and destination are Selenium::WebDriver::Elements in this case
      selenium_actions(target, destination) do |target, destination|
        # react-beautiful-dnd looks for a click, hold, and small movement to initiate a drag event
        click_and_hold(target)
        move_by(0, -5)

        move_to(destination) # now that the drag has started, move to your destination

        release # drop the target at it's destination
      end
      sleep 1 # allow for a rerender
    end

The main learnings from trying to get this to work was:

pdubuc commented 5 years ago

@MikaelCarpenter Yes, thank you! That worked for one aspect of the app that renders tables where rows can be dragged & dropped. Here's how the Java method ended up:

private void dragAndDrop(WebElement dragPoint, WebElement dropPoint, WebDriver driver) { new Actions(driver) .clickAndHold(dragPoint) .moveByOffset(-10, 0) .moveToElement(dropPoint) .release() .perform(); }

But, another section of the app renders nodes in a resource tree structure where each node is draggable. In this case, the above method does NOT work. I have not yet found a workable solution for this problem using Selenium, but I did find an alternative workaround using the UiPath Studio tool.

cpandit201 commented 5 years ago

@MikaelCarpenter thanks for the solution, I was planning to raise a ticket for the same, Thank god I found you 👍 @OP - Even I had the same issue - Selenium was not able to drag drop objects,

Moving element slightly and then performing a drag operation worked.

Actions actions = new Actions(driver);

actions
    .clickAndHold(iataCard)
    .moveByOffset(0, 10)
    .moveToElement(cityCard, 0, -10)
    .release().build().perform();

Below is a code which I performed on their template


System.setProperty("webdriver.chrome.driver", "chromedriver");
WebDriver driver = new ChromeDriver();
driver.get("https://react-beautiful-dnd.netlify.com/iframe.html?selectedKind=single%20vertical%20list&selectedStory=basic");
TimeUnit.SECONDS.sleep(3);

WebElement source = driver.findElement(By.xpath("//*[text()='Sometimes life is scary and dark']"));
WebElement target = driver.findElement(By.xpath("//*[text()='Sucking at something is the first step towards being sorta good at something.']"));

Actions actions = new Actions(driver);
actions.clickAndHold(source).moveByOffset(0, 100).moveToElement(target, 0, 100).release().build().perform();
alexreardon commented 5 years ago

For those looking in the future, here is how we do it using cypress.io:

https://github.com/atlassian/react-beautiful-dnd/blob/master/cypress/integration/reorder.spec.js

alexreardon commented 5 years ago

Please reopen if you think there is more to answer on this one

jaymuk commented 4 years ago

@MikaelCarpenter thanks! Your solution worked for me with a modification. My scenario was:

Adding this second item was the only way I could get the second list to update and actually include the first item. Sleeps and pauses didn't work

Not ideal at all, but was a temporary workaround for me... I'm hoping there's a better way to resolve this

keoghpe commented 3 years ago

@MikaelCarpenter you're a hero! For anyone looking to implement this in capybara, here's the version of Mikael's snippet I used:

      target = draggable.native
      destination = droppable.native

      page.driver.browser.action.
        click_and_hold(target).
        move_by(0, -5).
        move_to(destination).
        release.perform