Solve the bug that Selenium element dragging does not work

A few days ago, when I was using Selenium to perform element dragging operations, I found that Selenium's own element dragging method (dragAndDrop()) did not work. The answers online were various and confusing. I tried the following methods but could not solve it.

Option 1: Drag and drop elements to a specific area through the dragAndDrop() method - invalid

// 要拖拽的元素
WebElement draggable = driver.findElement(By.xpath(""));
// 目标元素/区域
WebElement droppable = driver.findElement(By.xpath(""));
new Actions(driver).dragAndDrop(draggable, droppable).build().perform();

Option 2: Use the dragAndDropBy() method to shift the element to a specified pixel, so as to drag and drop it into a specific area. This method needs to find the pixel of the element first - invalid

new Actions(driver).dragAndDropBy(draggable,135, 40).build().perform();

Option 3: First click and hold the element through the clickAndHold() method, then use the moveByOffset() method to drag the element to the target area, and then use the release() method to release the held element - invalid

new Actions(driver).clickAndHold(draggable).moveByOffset(400, 0).release().build().perform();

Option 4: First click and hold the element through the clickAndHold() method, then use the moveToElement() method to drag the element to the specified element, and then use the release() method to release the element - invalid

new Actions(driver).clickAndHold(draggable).moveToElement(droppable).release(droppable).build().perform();

Solution 5: Use the Robot class to implement drag and drop - invalid

Point coordinates1 = draggable.getLocation();
Point coordinates2 = droppable.getLocation();
Robot robot = new Robot();
robot.mouseMove(coordinates1.getX(), coordinates1.getY());
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.mouseMove(coordinates2.getX(), coordinates2.getY());
robot.mouseRelease(InputEvent.BUTTON1_MASK);
……

None of the above solutions took effect. The specific performance is that there are no errors when running, but no drag and drop occurs in the application.

After some operations, I finally found the cause and solution on "Selenium Drag and Drop Bug Workaround: https://www.softwaretestingmagazine.com/knowledge/selenium-drag-and-drop-bug-workaround".

It is understood that a bug has existed for many years in Selenium where drag and drop functionality does not work in certain situations.

The reason is that the drag-and-drop function consists of three actions: click and hold, move mouse to other element/location, and release mouse. The problem lies in the last step. The operation of releasing the mouse. When the Webdriver API sends a request to release the mouse, in some cases it will keep pressing it, so the drag and drop function is invalid.

The solution is to send JavaScript code to the browser through the Webdriver API and use JavaScript to simulate drag-and-drop operations instead of using Webdriver's own drag-and-drop method.

It works by taking the browser instance and the two web elements found by the CSS selector as parameters, and then executing the JavaScript code on the browser side.

If you are using the Python+Selenium technology stack to implement Web UI automation, you can directly download the seletools (Selenium Tools, author: Dmitrii Bormotov) package, import it to where you need to perform drag and drop, and then simply call its drag_and_drop( ) method.

pip install seletools
from seletools.actions import drag_and_drop

source = driver.find_element(By.CSS_SELECTOR, "#column-a")
target = browser.find_element(By.CSS_SELECTOR, "#column-b")
drag_and_drop(driver, source, target)

If you are using the Java+Selenium technology stack, you can use the following code to achieve it:

// 要拖拽的元素
WebElement draggable = driver.findElement(By.xpath(""));
// 目标元素
WebElement droppable = driver.findElement(By.xpath(""));

// 拖动前先点击并按住要拖拽的元素,避免在elementui,拖放前draggable属性才会变成true,目的是让draggable变成true,如果一开始就是true也可不加这句
new Actions(driver).clickAndHold(draggable).perform();

final String java_script = "var args = arguments," + "callback = args[args.length - 1]," + "source = args[0]," + "target = args[1]," + "offsetX = (args.length > 2 && args[2]) || 0," + "offsetY = (args.length > 3 && args[3]) || 0," + "delay = (args.length > 4 && args[4]) || 1;" + "if (!source.draggable) throw new Error('Source element is not draggable.');" + "var doc = source.ownerDocument," + "win = doc.defaultView," + "rect1 = source.getBoundingClientRect()," + "rect2 = target ? target.getBoundingClientRect() : rect1," + "x = rect1.left + (rect1.width >> 1)," + "y = rect1.top + (rect1.height >> 1)," + "x2 = rect2.left + (rect2.width >> 1) + offsetX," + "y2 = rect2.top + (rect2.height >> 1) + offsetY," + "dataTransfer = Object.create(Object.prototype, {" + "  _items: { value: { } }," + "  effectAllowed: { value: 'all', writable: true }," + "  dropEffect: { value: 'move', writable: true }," + "  files: { get: function () { return undefined } }," + "  types: { get: function () { return Object.keys(this._items) } }," + "  setData: { value: function (format, data) { this._items[format] = data } }," + "  getData: { value: function (format) { return this._items[format] } }," + "  clearData: { value: function (format) { delete this._items[format] } }," + "  setDragImage: { value: function () { } }" + "});" + "target = doc.elementFromPoint(x2, y2);" + "if(!target) throw new Error('The target element is not interactable and need to be scrolled into the view.');" + "rect2 = target.getBoundingClientRect();" + "emit(source, 'dragstart', delay, function () {" + "var rect3 = target.getBoundingClientRect();" + "x = rect3.left + x2 - rect2.left;" + "y = rect3.top + y2 - rect2.top;" + "emit(target, 'dragenter', 1, function () {" + "  emit(target, 'dragover', delay, function () {" + "\ttarget = doc.elementFromPoint(x, y);" + "\temit(target, 'drop', 1, function () {" + "\t  emit(source, 'dragend', 1, callback);" + "});});});});" + "function emit(element, type, delay, callback) {" + "var event = doc.createEvent('DragEvent');" + "event.initMouseEvent(type, true, true, win, 0, 0, 0, x, y, false, false, false, false, 0, null);" + "Object.defineProperty(event, 'dataTransfer', { get: function () { return dataTransfer } });" + "element.dispatchEvent(event);" + "win.setTimeout(callback, delay);" + "}";

// 默认拖拽到中心点位置,第3个参数是X坐标偏移量(左负右正),第4个参数为Y坐标偏移量(上负下正),第5个参数是延迟时间(单位为毫秒,表示当鼠标点下后,延迟指定时间后才开始激活拖拽动作,用来防止误点击)
((JavascriptExecutor) driver).executeScript(java_script, draggable, droppable, -200, -300, 500);

The above is the solution in Python and Java. As for why not to modify the program directly in Selenium, but to create a separate package to handle it, the following is what Dmitrii Bormotov said:

The drag and drop bug is a webdriver issue, so all you can do on the Selenium side is to simply perform the same workaround that I did. I spoke with David Burnes (core Selenium committer) about pushing that workaround into Selenium, but he said that it is not a good idea to have any workarounds in Selenium itself. That is why I had to create a separate package to help the test automation community with this problem.

The general meaning is that the drag and drop error is a webdriver network driver problem, and David Burnes (core Selenium committer) believes that it is not a good idea to provide any way to temporarily avoid the network in Selenium.

Finally, I would like to thank everyone who reads my article carefully. Reciprocity is always necessary. Although it is not a very valuable thing, if you can use it, you can take it directly:

Insert image description here

This information should be the most comprehensive and complete preparation warehouse for [software testing] friends. This warehouse has also accompanied tens of thousands of test engineers through the most difficult journey. I hope it can also help you!  

Guess you like

Origin blog.csdn.net/nhb687095/article/details/132831024