最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - JavaScript workaround for drag and drop in Selenium WebDriver - Stack Overflow

programmeradmin1浏览0评论

clickAndHold is not working for me in my test environment setup. I keep getting this error when trying to execute it using the Advanced User Interactions:

"Cannot press more than one button or an already pressed button.' when calling method: [wdIMouse::down]"

I've tested numerous versions of Firefox with selenium versions 2.31.0-2.35.0 and Firefox 21 with selenium 2.35 has the fewest issues. Other combinations have problems with click() failing silently and visible elements being treated as invisible.

I want to use a JavaScript workaround for drag and drop of one element to another, but I can't find any decent examples anywhere after googling extensively.

clickAndHold is not working for me in my test environment setup. I keep getting this error when trying to execute it using the Advanced User Interactions:

"Cannot press more than one button or an already pressed button.' when calling method: [wdIMouse::down]"

I've tested numerous versions of Firefox with selenium versions 2.31.0-2.35.0 and Firefox 21 with selenium 2.35 has the fewest issues. Other combinations have problems with click() failing silently and visible elements being treated as invisible.

I want to use a JavaScript workaround for drag and drop of one element to another, but I can't find any decent examples anywhere after googling extensively.

Share Improve this question edited Apr 6, 2017 at 13:24 Ripon Al Wasim 37.8k42 gold badges158 silver badges178 bronze badges asked Oct 15, 2013 at 15:07 SelenaSelena 2,2689 gold badges33 silver badges49 bronze badges 8
  • where are you getting clickAndHold from? I'm not seeing it in Selenium2, or Selenium1 – ddavison Commented Oct 15, 2013 at 15:12
  • 1 From the Actions package: Actions actions = new Actions(driver); actions.clickAndHold(element).moveToElement(targetElement).release(element).perform(); – Selena Commented Oct 15, 2013 at 15:17
  • There is a chance that there are two buttons with same locator or you did not release the element in previous drag and drop. – HemChe Commented Oct 15, 2013 at 15:41
  • I split up the code to do each action individually. The error is thrown when the clickAndHold action is invoked. There aren't two elements with the same id -- it's definitely unique. – Selena Commented Oct 15, 2013 at 15:53
  • 2 Have you tried actions.dragAndDrop(element, targetElement).perform();? – Richard Commented Oct 15, 2013 at 23:14
 |  Show 3 more comments

5 Answers 5

Reset to default 12

Since I posted this question, I've found a few different solutions to this problem. I'm posting them here for reference so that this question is useful to others:

When drag and drop in Selenium failed to work for HTML5, a defect was entered against Selenium and some people commented on the defect with suggested Javascript work around solutions:

JQuery/HTML5 Solution

http://code.google.com/p/selenium/issues/detail?id=3604#c9

This is a ruby-based solution, which could be translated into Java, C#, or any other language supported by Selenium. It requires JQuery to be on the page, as is clear from many of the comments from other users trying to use this solution, and it seems this only works for HTML5 pages:

The workaround we put together is working for us. It's been a life saver for testing our Ember.js app.

attached is the latest version of what we are using...

this is what we have in our test_helper:

  def drag_and_drop(source,target)

    js_filepath=File.dirname(__FILE__)+"/drag_and_drop_helper.js"
    js_file= File.new(js_filepath,"r")
    java_script=""

    while (line=js_file.gets)
      java_script+=line
    end

    js_file.close

    @driver.execute_script(java_script+"$('#{source}').simulateDragDrop({ dropTarget: '#{target}'});")

    rescue Exception => e
      puts "ERROR :" + e.to_s

  end

The source code for the referenced Javascript drag and drop simulation is posted here:

https://gist.github.com/rcorreia/2362544

(function( $ ) {
    $.fn.simulateDragDrop = function(options) {
        return this.each(function() {
            new $.simulateDragDrop(this, options);
        });
    };
    $.simulateDragDrop = function(elem, options) {
        this.options = options;
        this.simulateEvent(elem, options);
    };
    $.extend($.simulateDragDrop.prototype, {
        simulateEvent: function(elem, options) {
            /*Simulating drag start*/
            var type = 'dragstart';
            var event = this.createEvent(type);
            this.dispatchEvent(elem, type, event);

            /*Simulating drop*/
            type = 'drop';
            var dropEvent = this.createEvent(type, {});
            dropEvent.dataTransfer = event.dataTransfer;
            this.dispatchEvent($(options.dropTarget)[0], type, dropEvent);

            /*Simulating drag end*/
            type = 'dragend';
            var dragEndEvent = this.createEvent(type, {});
            dragEndEvent.dataTransfer = event.dataTransfer;
            this.dispatchEvent(elem, type, dragEndEvent);
        },
        createEvent: function(type) {
            var event = document.createEvent("CustomEvent");
            event.initCustomEvent(type, true, true, null);
            event.dataTransfer = {
                data: {
                },
                setData: function(type, val){
                    this.data[type] = val;
                },
                getData: function(type){
                    return this.data[type];
                }
            };
            return event;
        },
        dispatchEvent: function(elem, type, event) {
            if(elem.dispatchEvent) {
                elem.dispatchEvent(event);
            }else if( elem.fireEvent ) {
                elem.fireEvent("on"+type, event);
            }
        }
    });
})(jQuery);

Another user posted additional code when it didn't work completely for him, along with some instructions for injecting JQuery into the page:

http://code.google.com/p/selenium/issues/detail?id=3604#c25

We had trouble with the original drag_and_drop_helper.js posted as a workaround for this issue. The workaround is 99% correct, but I needed to modify the workaround to include the dropTarget in the options propagated via the 'coord' object in simulateDrag.

i.e. I need to change:

  coord = { clientX: x, clientY: y }

to:

coord = { clientX: x, clientY: y , dropTarget: options.dropTarget || undefined }

Also, a note for those following the example usage, if the app under test does not already alias the jQuery function to $, you will need to spell-out jQuery:

i.e., after injecting the drag and drop helper onto the page, we have a method that accepts jQuery selectors to use the simulated drag and drop functions (Java):

/**
 * Drag and drop via the JQuery-based drag and drop helper -- the helper
 * must have been injected onto the page prior to calling this method.
 *
 * @param dragSourceJQuerySelector a JQuery-style selector that identifies the source element to drag;
 * <em>will be passed directly to jQuery(), perform all quoting yourself</em>
 * @param dropTargetJQuerySelector a JQuery-style selector that identifies the target element to drop the source onto;
 * <em>will be passed directly to jQuery(), perform all quoting yourself</em>
 */
protected void dragAndDropViaJQueryHelper(String dragSourceJQuerySelector, String dropTargetJQuerySelector) {
    String javascript =
        "var dropTarget = jQuery(" + dropTargetJQuerySelector + ");" +
        "\n" +
        "jQuery("+ dragSourceJQuerySelector + ").simulate('drag', { dropTarget: dropTarget });";

    getLogger().info("executing javascript:\n" + javascript);
    this.executeScript(javascript);
    getLogger().info("executed drag-n-drop action via javascript");
}

Another, non-jQuery solution

http://ynot408.wordpress.com/2011/09/22/drag-and-drop-using-selenium-webdriver/

Javascript based drag and drop which works across browsers.

Mouse move stopped working after version 2.3 while using RemoteWebDriver in selenium. The below function drags element 1 to element 2 position and releases the mouse down.

public void dragdrop(By ByFrom, By ByTo) {
    WebElement LocatorFrom = driver.findElement(ByFrom);
    WebElement LocatorTo = driver.findElement(ByTo);
    String xto=Integer.toString(LocatorTo.getLocation().x);
    String yto=Integer.toString(LocatorTo.getLocation().y);
    ((JavascriptExecutor)driver).executeScript("function simulate(f,c,d,e){var b,a=null;for(b in eventMatchers)if(eventMatchers[b].test(c)){a=b;break}if(!a)return!1;document.createEvent?(b=document.createEvent(a),a==\"HTMLEvents\"?b.initEvent(c,!0,!0):b.initMouseEvent(c,!0,!0,document.defaultView,0,d,e,d,e,!1,!1,!1,!1,0,null),f.dispatchEvent(b)):(a=document.createEventObject(),a.detail=0,a.screenX=d,a.screenY=e,a.clientX=d,a.clientY=e,a.ctrlKey=!1,a.altKey=!1,a.shiftKey=!1,a.metaKey=!1,a.button=1,f.fireEvent(\"on\"+c,a));return!0} var eventMatchers={HTMLEvents:/^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,MouseEvents:/^(?:click|dblclick|mouse(?:down|up|over|move|out))$/}; " +
    "simulate(arguments[0],\"mousedown\",0,0); simulate(arguments[0],\"mousemove\",arguments[1],arguments[2]); simulate(arguments[0],\"mouseup\",arguments[1],arguments[2]); ",
    LocatorFrom,xto,yto);
}

I had exactly the same issue with Firefox which led me here. Playing around with the suggestions on Stack, finally I was able to resolve the issue using

actions.DragAndDropToOffset(element, x, y).Perform();

I know that's not the Javascript workaround you asked for but hopefully it will work for you, as it did for me. These quirky issues can be really frustrating.

On back of this post re Safari 14, I was testing a kendo application and tried the solutions in Selena's answer but it was a different solution that ended up working for me.

Test application.

Working drap and drop (small change to use WebElements rather than CSS strings):

var triggerDragAndDrop = function (elemDrag, elemDrop) {
  /* function for triggering mouse events */
  var fireMouseEvent = function (type, elem, centerX, centerY) {
    var evt = document.createEvent('MouseEvents');
    evt.initMouseEvent(
      type,
      true,
      true,
      window,
      1,
      1,
      1,
      centerX,
      centerY,
      false,
      false,
      false,
      false,
      0,
      elem
    );
    elem.dispatchEvent(evt);
  };

  /* calculate positions*/
  var pos = elemDrag.getBoundingClientRect();
  var center1X = Math.floor((pos.left + pos.right) / 2);
  var center1Y = Math.floor((pos.top + pos.bottom) / 2);
  pos = elemDrop.getBoundingClientRect();
  var center2X = Math.floor((pos.left + pos.right) / 2);
  var center2Y = Math.floor((pos.top + pos.bottom) / 2);

  /* mouse over dragged element and mousedown*/
  fireMouseEvent('mousemove', elemDrag, center1X, center1Y);
  fireMouseEvent('mouseenter', elemDrag, center1X, center1Y);
  fireMouseEvent('mouseover', elemDrag, center1X, center1Y);
  fireMouseEvent('mousedown', elemDrag, center1X, center1Y);

  /* start dragging process over to drop target*/
  fireMouseEvent('dragstart', elemDrag, center1X, center1Y);
  fireMouseEvent('drag', elemDrag, center1X, center1Y);
  fireMouseEvent('mousemove', elemDrag, center1X, center1Y);
  fireMouseEvent('drag', elemDrag, center2X, center2Y);
  fireMouseEvent('mousemove', elemDrop, center2X, center2Y);

  /* trigger dragging process on top of drop target*/
  fireMouseEvent('mouseenter', elemDrop, center2X, center2Y);
  fireMouseEvent('dragenter', elemDrop, center2X, center2Y);
  fireMouseEvent('mouseover', elemDrop, center2X, center2Y);
  fireMouseEvent('dragover', elemDrop, center2X, center2Y);

  /* release dragged element on top of drop target*/
  fireMouseEvent('drop', elemDrop, center2X, center2Y);
  fireMouseEvent('dragend', elemDrag, center2X, center2Y);
  fireMouseEvent('mouseup', elemDrag, center2X, center2Y);

  return true;
};

Injected javascript as per solutions in Selena's answers above (read file into variable java_script). Executed against existing WebElements (drag and drop) with:

    String js =  "var src = arguments[0];var dest = arguments[1];";
    js += "triggerDragAndDrop(src, dest);";
    JavascriptExecutor executor = (JavascriptExecutor) webdriver;
    executor.executeScript(java_script + js, drag, drop);

I think the way you should do it is the same as Richard suggested in the comment off your original question. Use the Action class. Just search this forum for "Action Selenium". If you get silent failures, then all you need to do is make sure your on the very latest Selenium (2.35 that you reference is not the latest). Also , if your using IE or Chrome, make sure the binary you are using also corresponds to the latest version.

IE11 and Chrome and Firefox all will auto-upgrade on you and so if you don't follow with a driver update, its possible you can get wierd behavior from the Actions class that does not throw any human readable error. For example, using the 2.30 driver on the latest Chrome 31 will open a browser and the .get() will hang on you.

Try this

Actions act = new Actions(driver); act.clickAndHold(scroll).moveByOffset( x, 200).build().perform(); seems to wort

发布评论

评论列表(0)

  1. 暂无评论