Protractor drag-and-drop: Angular vs. Angular with HTML5

I had the same issue with various dnd simulate libaries not seeming to work with angular-drag-and-drop-lists.

In the end I forked html-dnd simpling just adding a dragover event, something angular-drag-and-drop-lists requires as it calculates the index of the element being dropped upon via the dragover event. It also sticks in a temp li element which the code uses as the actual drop point before removing it. This is what the user sees as the shifting list points on the screen.

The fork is at forked html-dnd. I include it via a git pull in the dependancies in my package.json file

"html-dnd": "git://github.com/PloughingAByteField/html-dnd.git"

For usage in protractor

// at the top of the spec
var dragAndDrop = require('html-dnd').code;

<snip>

it('should dragover and drop', function() {
  var draggable = browser.findElement(by.id('id1'));
  var droppable = browser.findElement(by.id('id2'));
  browser.driver.executeScript(dragAndDrop, draggable, droppable);
);

Update: The html-dnd project has merged in the dragover event so you can use that instead of my not maintained fork.

Note sure if it's any different but I've used an ActionSequence call instead of just Action to drag and drop items which seemed to work just fine for me in our Angular app. It wasn't dealing with HTML5 though not sure if that makes a difference.

Also this was a while ago so again this may not be valid anymore but you could try something like:

//1. Obtain the list of all the measurement method draggables on the screen baItems.listOfMeasurementMethods.then(function (elements) {

            //Only perform drag and drops if there is more than one measurement method
            if (elements.length > 1) {
                var val1 = baItems.getLists();      //get the current text order

                console.log(elements.length);
                baItems.dragAndDrop(elements[0], elements[elements.length - 1]); //drag element 0 to last element and drop it
                helpers.sleepX(15000);

                var val2 = baItems.getLists();  //get the new text order

                expect(val2).not.toBe(val1);    //expect that they are not the same (i.e. they have been dragged and dropped)
            }
            else {
                console.log('Only one measurement method - cant change order');
            }

and then the drag and drop method for me looks like

this.dragAndDrop = function (dragFromElement, dropToElement) {
    //console.log('inside dragAndDrop()');
    new protractor.ActionSequence(browser).
            click(dragFromElement).
            dragAndDrop(dragFromElement.getLocation(), dropToElement.getLocation()).
            perform();
}

I was also going through this face few days before, native_js_drag_and_drop_helper.js didn't worked for me as well... Then I got to know about simulate.js api I have used its code and that fortunately worked for me...

code for dragdrop.js

module.exports = function (dragEleSelector, dropEleSelector) {
    (function ($, undefined) {
        var rkeyEvent = /^key/,
            rmouseEvent = /^(?:mouse|contextmenu)|click/;

        $.fn.simulate = function (type, options) {
            return this.each(function () {
                new $.simulate(this, type, options);
            });
        };

        $.simulate = function (elem, type, options) {
            var method = $.camelCase("simulate-" + type);

            this.target = elem;
            this.options = options;

            if (this[ method ]) {
                this[ method ]();
            } else {
                this.simulateEvent(elem, type, options);
            }
        };

        $.extend($.simulate, {
            keyCode: {
                BACKSPACE: 8,
                COMMA: 188,
                DELETE: 46,
                DOWN: 40,
                END: 35,
                ENTER: 13,
                ESCAPE: 27,
                HOME: 36,
                LEFT: 37,
                NUMPAD_ADD: 107,
                NUMPAD_DECIMAL: 110,
                NUMPAD_DIVIDE: 111,
                NUMPAD_ENTER: 108,
                NUMPAD_MULTIPLY: 106,
                NUMPAD_SUBTRACT: 109,
                PAGE_DOWN: 34,
                PAGE_UP: 33,
                PERIOD: 190,
                RIGHT: 39,
                SPACE: 32,
                TAB: 9,
                UP: 38
            },

            buttonCode: {
                LEFT: 0,
                MIDDLE: 1,
                RIGHT: 2
            }
        });

        $.extend($.simulate.prototype, {

            simulateEvent: function (elem, type, options) {
                var event = this.createEvent(type, options);
                this.dispatchEvent(elem, type, event, options);
            },

            createEvent: function (type, options) {
                if (rkeyEvent.test(type)) {
                    return this.keyEvent(type, options);
                }

                if (rmouseEvent.test(type)) {
                    return this.mouseEvent(type, options);
                }
            },

            mouseEvent: function (type, options) {
                var event, eventDoc, doc, body;
                options = $.extend({
                    bubbles: true,
                    cancelable: (type !== "mousemove"),
                    view: window,
                    detail: 0,
                    screenX: 0,
                    screenY: 0,
                    clientX: 1,
                    clientY: 1,
                    ctrlKey: false,
                    altKey: false,
                    shiftKey: false,
                    metaKey: false,
                    button: 0,
                    relatedTarget: undefined
                }, options);

                if (document.createEvent) {
                    event = document.createEvent("MouseEvents");
                    event.initMouseEvent(type, options.bubbles, options.cancelable,
                        options.view, options.detail,
                        options.screenX, options.screenY, options.clientX, options.clientY,
                        options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
                        options.button, options.relatedTarget || document.body.parentNode);

                    // IE 9+ creates events with pageX and pageY set to 0.
                    // Trying to modify the properties throws an error,
                    // so we define getters to return the correct values.
                    if (event.pageX === 0 && event.pageY === 0 && Object.defineProperty) {
                        eventDoc = event.relatedTarget.ownerDocument || document;
                        doc = eventDoc.documentElement;
                        body = eventDoc.body;

                        Object.defineProperty(event, "pageX", {
                            get: function () {
                                return options.clientX +
                                    ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
                                    ( doc && doc.clientLeft || body && body.clientLeft || 0 );
                            }
                        });
                        Object.defineProperty(event, "pageY", {
                            get: function () {
                                return options.clientY +
                                    ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
                                    ( doc && doc.clientTop || body && body.clientTop || 0 );
                            }
                        });
                    }
                } else if (document.createEventObject) {
                    event = document.createEventObject();
                    $.extend(event, options);
                    // standards event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ff974877(v=vs.85).aspx
                    // old IE event.button uses constants defined here: http://msdn.microsoft.com/en-us/library/ie/ms533544(v=vs.85).aspx
                    // so we actually need to map the standard back to oldIE
                    event.button = {
                        0: 1,
                        1: 4,
                        2: 2
                    }[ event.button ] || ( event.button === -1 ? 0 : event.button );
                }

                return event;
            },

            keyEvent: function (type, options) {
                var event;
                options = $.extend({
                    bubbles: true,
                    cancelable: true,
                    view: window,
                    ctrlKey: false,
                    altKey: false,
                    shiftKey: false,
                    metaKey: false,
                    keyCode: 0,
                    charCode: undefined
                }, options);

                if (document.createEvent) {
                    try {
                        event = document.createEvent("KeyEvents");
                        event.initKeyEvent(type, options.bubbles, options.cancelable, options.view,
                            options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
                            options.keyCode, options.charCode);
                        // initKeyEvent throws an exception in WebKit
                        // see: http://stackoverflow.com/questions/6406784/initkeyevent-keypress-only-works-in-firefox-need-a-cross-browser-solution
                        // and also https://bugs.webkit.org/show_bug.cgi?id=13368
                        // fall back to a generic event until we decide to implement initKeyboardEvent
                    } catch (err) {
                        event = document.createEvent("Events");
                        event.initEvent(type, options.bubbles, options.cancelable);
                        $.extend(event, {
                            view: options.view,
                            ctrlKey: options.ctrlKey,
                            altKey: options.altKey,
                            shiftKey: options.shiftKey,
                            metaKey: options.metaKey,
                            keyCode: options.keyCode,
                            charCode: options.charCode
                        });
                    }
                } else if (document.createEventObject) {
                    event = document.createEventObject();
                    $.extend(event, options);
                }

                if (!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()) || (({}).toString.call(window.opera) === "[object Opera]")) {
                    event.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
                    event.charCode = undefined;
                }

                return event;
            },

            dispatchEvent: function (elem, type, event) {
                if (elem[ type ]) {
                    elem[ type ]();
                } else if (elem.dispatchEvent) {
                    elem.dispatchEvent(event);
                } else if (elem.fireEvent) {
                    elem.fireEvent("on" + type, event);
                }
            },

            simulateFocus: function () {
                var focusinEvent,
                    triggered = false,
                    element = $(this.target);

                function trigger() {
                    triggered = true;
                }

                element.bind("focus", trigger);
                element[ 0 ].focus();

                if (!triggered) {
                    focusinEvent = $.Event("focusin");
                    focusinEvent.preventDefault();
                    element.trigger(focusinEvent);
                    element.triggerHandler("focus");
                }
                element.unbind("focus", trigger);
            },

            simulateBlur: function () {
                var focusoutEvent,
                    triggered = false,
                    element = $(this.target);

                function trigger() {
                    triggered = true;
                }

                element.bind("blur", trigger);
                element[ 0 ].blur();

                // blur events are async in IE
                setTimeout(function () {
                    // IE won't let the blur occur if the window is inactive
                    if (element[ 0 ].ownerDocument.activeElement === element[ 0 ]) {
                        element[ 0 ].ownerDocument.body.focus();
                    }

                    // Firefox won't trigger events if the window is inactive
                    // IE doesn't trigger events if we had to manually focus the body
                    if (!triggered) {
                        focusoutEvent = $.Event("focusout");
                        focusoutEvent.preventDefault();
                        element.trigger(focusoutEvent);
                        element.triggerHandler("blur");
                    }
                    element.unbind("blur", trigger);
                }, 1);
            }
        });

        /** complex events **/
        function findCenter(elem) {
            var offset,
                document = $(elem.ownerDocument);
            elem = $(elem);
            offset = elem.offset();

            return {
                x: offset.left + elem.outerWidth() / 2 - document.scrollLeft(),
                y: offset.top + elem.outerHeight() / 2 - document.scrollTop()
            };
        }

        function findCorner(elem) {
            var offset,
                document = $(elem.ownerDocument);
            elem = $(elem);
            offset = elem.offset();

            return {
                x: offset.left - document.scrollLeft(),
                y: offset.top - document.scrollTop()
            };
        }

        $.extend($.simulate.prototype, {
            simulateDrag: function () {
                var i = 0,
                    target = this.target,
                    options = this.options,
                    center = options.handle === "corner" ? findCorner(target) : findCenter(target),
                    x = Math.floor(center.x),
                    y = Math.floor(center.y),
                    coord = { clientX: x, clientY: y },
                    dx = options.dx || ( options.x !== undefined ? options.x - x : 0 ),
                    dy = options.dy || ( options.y !== undefined ? options.y - y : 0 ),
                    moves = options.moves || 3;

                this.simulateEvent(target, "mousedown", coord);

                for (; i < moves; i++) {
                    x += dx / moves;
                    y += dy / moves;

                    coord = {
                        clientX: Math.round(x),
                        clientY: Math.round(y)
                    };

                    this.simulateEvent(target.ownerDocument, "mousemove", coord);
                }

                if ($.contains(document, target)) {
                    this.simulateEvent(target, "mouseup", coord);
                    this.simulateEvent(target, "click", coord);
                } else {
                    this.simulateEvent(document, "mouseup", coord);
                }
            }
        });

    })($);

    try {
        var dragEle = $(dragEleSelector);
        var dropEle = $(dropEleSelector);
        if (dragEle.length == 0 || dropEle.length == 0) {
            console.error("Unable to perform drag n drop operation: Selectors are incorrect.");
            return false;
        }
        var droppableOffset = dropEle.offset(),
            draggableOffset = dragEle.offset(),
            dx = droppableOffset.left - draggableOffset.left,
            dy = droppableOffset.top - draggableOffset.top;

        dragEle.simulate("drag", {
            dx: dx,
            dy: dy
        });
        return true;
    } catch (err) {
        console.error("Unable to perform drag n drop operation.");
        return false;
    }
};

You will have to execute this script against the DOM elements specified by the CSS selectors.

var dragDrop = require('../../common/javascript/dragDrop.js');

//keep this in mind selectors are CSS selectors.
function dragDropElement(dragEleSelector, dropEleSelector) {
    var deferred = protractor.promise.defer();
    browser.executeScript(dragDrop, dragEleSelector, dropEleSelector).then(function (dropSuccessful) {
        expect(dropSuccessful).toBe(true);
        if (dropSuccessful) {
            console.log("Element dropped successfully on target.");
            deferred.fulfill(dropSuccessful);
        } else {
            console.log("Fail: Not able to drop element on target.");
            deferred.reject(dropSuccessful);
        }
    });
    return deferred.promise;
};

Though you will not be able to see the drag drop animation but it will work.