2

I'm using jQuery to workaround the drag and drop issue in Selenium. However, I'm a jQuery beginner and I couldn't find an OOTB solution for dropping a JavaScript element at a specific location using jQuery.

The script I'm using is this:

(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);

And I'm calling simulateDragDrop with Selenium's JavascriptExecutor with 2 JS objects (object to be dragged and destination object). The problems are when I want to drop the first object at a location and not on an object and that it drops it on the upper left corner of the target object.

  1. How can I change the script (or perhaps use a different one) so it will accept a location to drop at (if it's possible...)?

  2. Can I change the script so it will drop at the center of the destination object?

A small note is that it's for automation testing purposes and that the AUT doesn't contain jQuery and I'm injecting it.

A possible solution I thought about is getting an element by it's location using document.elementFromPoint(x, y); but still it will drop on the upper left corner of this element...

Community
  • 1
  • 1
Moshisho
  • 2,781
  • 1
  • 23
  • 39

1 Answers1

3

No need to use JQuery. This piece of JavaScript simulates the drag and drop of an element with an offset:

var source = arguments[0], offsetX = arguments[1], offsetY = arguments[2];
var rect = source.getBoundingClientRect();
var dragPt = {x: rect.left + (rect.width >> 1), y: rect.top + (rect.height >> 1)};
var dropPt = {x: dragPt.x + offsetX, y: dragPt.y + offsetY};
var target = document.elementFromPoint(dropPt.x, dropPt.y);

var dataTransfer = {
  dropEffect: '',
  effectAllowed: 'all',
  files: [],
  items: {},
  types: [],
  setData: function (format, data) {
    this.items[format] = data;
    this.types.push(format);
  },
  getData: function (format) {
    return this.items[format];
  },
  clearData: function (format) { }
};

var emit = function (event, target, pt) {
  var evt = document.createEvent('MouseEvent');
  evt.initMouseEvent(event, true, true, window, 0, 0, 0, pt.x, pt.y, false, false, false, false, 0, null);
  evt.dataTransfer = dataTransfer;
  target.dispatchEvent(evt);
};

emit('mousedown', source, dragPt);
emit('mousemove', source, dragPt);
emit('dragstart', source, dragPt);
emit('mousemove', source, dropPt);
emit('dragenter', target, dropPt);
emit('dragover',  target, dropPt);
emit('drop',      target, dropPt);
emit('dragend',   source, dropPt);
emit('mouseup',   source, dropPt);

You can use it with executeScript by providing the element to drag as 1st argument and the offset to drop in the 2nd and 3rd arguments:

driver.executeScript("...", element, -100, 0);
Florent B.
  • 41,537
  • 7
  • 86
  • 101
  • Unfortunately, this dispatches the event to the target element which causes it to drop on the upper left corner of the element. If the element is sort of an editor (like in my case), it's not close to the offset I wanted... if I could drop in the middle of the target element, it would solve my issue. – Moshisho May 24 '16 at 10:49
  • The drop is probably using the mouse coordinates on the event. I would try with a mouse event instead. See the updated answer. Note that you should provide a reproducible example. Trying to guess how the drag and drop was implemented is not ideal. – Florent B. May 24 '16 at 13:10
  • Still no luck... the editor is implemented with REACT, probably by some third party libraries so I don't have an exact example but maybe [this](http://jqueryui.com/resources/demos/droppable/default.html) fits. I'm not sure this is even possible in my case, big thanks anyways. – Moshisho May 24 '16 at 15:01
  • @Moshisho, I've updated the code to simulate the DnD with the REACT library. It should now work as expected. – Florent B. May 24 '16 at 21:54
  • Will this work with Angular/Protractor. Tried the above in an attempt to test https://github.com/SortableJS/Sortable/issues/735 but no luck. – methuselah Apr 16 '20 at 17:42
  • 1
    @methuselah, I noticed in the samples that the element becomes draggable only once the mouse is down on it. My script checks/looks for this attribute first. It might work if you remove line 22 to 29. – Florent B. Apr 17 '20 at 09:24
  • @florent-b, great example. When simulation for SAP UI5, use slightly different value for data transfer property to prevent (non-breaking) console error: evt.dataTransfer = new DataTransfer(); – Michael Biermann Dec 21 '22 at 12:50