Drag and Drop is a very different interaction than simpler one step interactions like clicking on an element. Testing them on automation tools such as Selenium or puppeteer is an interesting problem to solve. I looked far and wide for a solution for almost 8 hours and had a hard time getting to work. Finally, I put together pieces that I found in two places and it works!
Here is how.
I found this code segment at the blog for ghostinspector to simulate a drag and drop. It takes two selectors as the drag element and the element to be dropped on to. I liked this code segment since it can be used independently by including it in your html file, creating a separate module etc.
Even after finding this code segment, I kept running into problems on how to find the correct query selector for this code segment to work. I found a reliable solution for that also which is under the drag and drop code.
var triggerDragAndDrop = function (selectorDrag, selectorDrop) {
// 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);
};
// fetch target elements
var elemDrag = document.querySelector(selectorDrag);
var elemDrop = document.querySelector(selectorDrop);
if (!elemDrag || !elemDrop) return false;
// 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;
};
Like I mentioned above , I was struggling to get the query selector to be just right and wasting a lot of time trying the right sequence and format. I found another code segment at this stack overflow answer
jQuery.fn.extend({
getPath: function() {
var pathes = [];
this.each(function(index, element) {
var path, $node = jQuery(element);
while ($node.length) {
var realNode = $node.get(0), name = realNode.localName;
if (!name) { break; }
name = name.toLowerCase();
var parent = $node.parent();
var sameTagSiblings = parent.children(name);
if (sameTagSiblings.length > 1)
{
var allSiblings = parent.children();
var index = allSiblings.index(realNode) + 1;
if (index > 0) {
name += ':nth-child(' + index + ')';
}
}
path = name + (path ? ' > ' + path : '');
$node = parent;
}
pathes.push(path);
});
return pathes.join(',');
}
});
Combining the two, the drag drop can be activated in using jquery to help you find the exact selector and not have to keep guessing.
triggerDragAndDrop(jquerySelectorDrag.first().getPath(), jquerySelectorDrop.first().getPath())
This is especially useful when you are using complex components. I was using jstree and wanted to test the drag and drop code that gets triggered when we move one branch to another. This component, like most others, create a complex element hierarchy which are hard to decode into a query selector. In the specific case of jstree, the nodes are created with id as {node_id}_anchor which is in a deep hierarchy. Using the above code, I could execute it as
triggerDragAndDrop($("#id1_anchor").first().getPath(), $("#id2_anchor").first().getPath())
where id1 is the id of the node you want to move and id2 is the id of the node you want to move it. This segment then triggers all events and functions that would be executed as part of a manual drag and drop.