解决Selenium元素拖拽不生效Bug

前几天在使用Selenium进行元素拖拽操作时,发现Selenium自带的元素拖拽方法(dragAndDrop())不生效,网上的回答也是五花八门,比较混乱,尝试了以下几种方法均无法解决。

方案1:通过dragAndDrop()方法将元素拖放到特定区域上------无效。

  1.   // 要拖拽的元素

  2.   WebElement draggable = driver.findElement(By.xpath(""));

  3.   // 目标元素/区域

  4.   WebElement droppable = driver.findElement(By.xpath(""));

  5.   new Actions(driver).dragAndDrop(draggable, droppable).build().perform();

方案2:通过dragAndDropBy()方法将元素进行指定像素位移,从而实现拖放到特定区域,该方法需要先找到元素的像素------无效。

复制代码
new Actions(driver).dragAndDropBy(draggable,135, 40).build().perform();

方案3:先通过clickAndHold()方法点击并按住元素,然后使用moveByOffset()方法将元素拖拽到目标区域,再使用release()方法将按住的元素释放------无效。

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

方案4:先通过clickAndHold()方法点击并按住元素,然后使用moveToElement()方法将元素拖拽到指定元素上,再使用release()方法将元素释放------无效。

  1.   new

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

方案5:借助Robot类实现拖拽------无效。

  1.   Point coordinates1 = draggable.getLocation();

  2.   Point coordinates2 = droppable.getLocation();

  3.   Robot robot = new Robot();

  4.   robot.mouseMove(coordinates1.getX(), coordinates1.getY());

  5.   robot.mousePress(InputEvent.BUTTON1_MASK);

  6.   robot.mouseMove(coordinates2.getX(), coordinates2.getY());

  7.   robot.mouseRelease(InputEvent.BUTTON1_MASK);

  8.   ......

以上方案均未生效,具体表现为运行均无任何报错,但在应用程序中未发生拖放。

经过一顿操作,最终在「Selenium Drag and Drop Bug Workaround」上找到了问题原因及解决方案。

经了解,Selenium的拖放功能在某些情况下无效的错误已经存在多年。

原因是拖放功能包含三个动作:单击并按住(click and hold)、将鼠标移动到其他元素或位置(move mouse to other element/location)、释放鼠标(release mouse),问题在于最后一步释放鼠标的操作,当Webdriver API发送释放鼠标的请求时,在某些情况下它会一直按住它,所以导致拖放功能无效。

解决方法就是通过Webdriver API将JavaScript代码发送到浏览器,利用JavaScript模拟拖放操作,而不使用Webdriver自带的拖放方法。

其工作原理是将浏览器实例和CSS选择器找到的两个Web元素作为参数,然后在浏览器端执行JavaScript代码。

如果你是使用Python+Selenium技术栈实现的Web UI自动化,可以直接下载seletools(Selenium Tools,作者:Dmitrii Bormotov)包,并将它导入到需要执行拖放的地方,然后简单地调用它的drag_and_drop()方法即可。

  1.   pip install seletools

  2.   from seletools.actions import drag_and_drop

  3.   source = driver.find_element(By.CSS_SELECTOR, "#column-a")

  4.   target = browser.find_element(By.CSS_SELECTOR, "#column-b")

  5.   drag_and_drop(driver, source, target)

如果使用的是Java+Selenium技术栈,则可以使用以下代码实现:

  1.   // 要拖拽的元素

  2.   WebElement draggable = driver.findElement(By.xpath(""));

  3.   // 目标元素

  4.   WebElement droppable = driver.findElement(By.xpath(""));

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

  6.   new Actions(driver).clickAndHold(draggable).perform();

  7.   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);" + "}";

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

  9.   ((JavascriptExecutor) driver).executeScript(java_script, draggable, droppable, -200, -300, 500);

以上就是在Python和Java中的解决方案,至于为什么不在Selenium中直接修改程序,而是创建单独的包来处理,以下是Dmitrii Bormotov的说法:

复制代码
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.

大概的意思就是拖放错误是一个webdriver网络驱动问题,David Burnes(核心 Selenium 提交者)认为在Selenium中提供任何暂时避开网络的方法并不是一个好主意。

相关推荐
弹简特15 小时前
【精通Postman接口测试】03-结果断言实战 + 数据驱动保姆级教程
测试工具·接口测试·postman·断言
测试员周周18 小时前
【免费福利】AI测试:测试技能包进阶:造数、压测、视觉回归、CI 全流程串联
开发语言·人工智能·python·功能测试·测试工具·ci/cd·测试用例
PhotonixBay21 小时前
表面粗糙度怎么测才准?ISO 25178标准三维面积测量标准解析
人工智能·测试工具
KK溜了溜了1 天前
wireshark and tcpdump抓包
测试工具·wireshark·tcpdump
隔窗听雨眠1 天前
Git二分法精准定位Bug
git·bug·git bisect
老神在在0012 天前
JSON Schema
测试工具·json
小杍随笔2 天前
【FlyEnv v4.15.0 重磅更新!新增 FrankenPHP、CliProxyAPI、Numa、Rnacos 模块,开发效率再升级】
测试工具·开发环境管理工具
软件测试慧姐2 天前
软件测试常见面试题汇总(2026版)
软件测试·测试工具·面试
技术人生黄勇3 天前
GitNexus 把代码库变成知识图谱|审核 AI 产出更清晰,改 Bug 更精准
人工智能·bug
weixin_419658313 天前
基于 Trae 和 Postman-MCP-Server 接口自动化测试
测试工具·postman