解决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中提供任何暂时避开网络的方法并不是一个好主意。

相关推荐
蒲公英内测分发10 分钟前
App 更新一次就换一个二维码?智能硬件配套 App 的版本和下载入口怎么管理
人工智能·测试工具·智能家居·智能硬件
测试老哥4 小时前
接口测试的测试用例应该怎么写?
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
是Leo刘啊7 小时前
别再手敲 JSON 造数据了!Mock 数据生成器实战与避坑指南
测试工具
Luminbox紫创测控7 小时前
航天热平衡试验用太阳模拟器:光学积分器设计与均匀性提升方法
测试工具·安全性测试·太阳光模拟器·测试标准·航天热平衡实验
程序员杰哥12 小时前
UI自动化测试:Jenkins配置
自动化测试·软件测试·python·测试工具·职场和发展·jenkins·测试用例
TomEval13 小时前
【性能测试】17 - 性能测试入门
测试工具·jmeter
wuhuhuan15 小时前
Case Generator 测试自动化平台
python·测试工具·自动化
iNeuOS工业互联网1 天前
iNeuOS工业互联网操作系统,重要更新:性能优化、安全漏洞与BUG修复,远程控制响应效率大幅提升
性能优化·bug
星核0penstarry2 天前
ToolGrad:把数据生成倒过来,工具调用样本通过率提到 99.8%
人工智能·测试工具·llm·函数调用·数据合成·文本调用
测试19982 天前
Jmeter接口自动化测试:Jmeter变量的使用
自动化测试·软件测试·测试工具·jmeter·职场和发展·测试用例·接口测试