JS解决Selenium无法拖拽HTML5控件的问题
平时模拟拖拽操作时,可以用到selenium中ActionChains的drag_and_drop,但最近遇到了HTML5 Drag无效的情况。
接下来我做了如下尝试:
1)浏览器窗口放大并滑动到对应的元素的位置,以避免遮挡而操作不了。----无效
2)将操作拆分为click_and_hold、move_to、release。----无效
3)用selenium执行JS代码来完成拖拽动作。----有效
目前selenium是有个issue关于HTML5 Drag Drop的,至今仍为Open状态:https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/3604
可用上的JS来源于https://gist.github.com/rcorreia/2362544,感谢rcorreia。代码我也附在下方。
(function( $ ) {
$.fn.simulateDragDrop = function(options) {
return this.each(function() {
new $.simulateDragDrop(this, options);
});
};
$.simulateDragDrop = function(elem, options) {
this.options = options;
this.simulateEvent(elem, options);
};
$.extend($.simulateDragDrop.prototype, {
simulateEvent: function(elem, options) {
/*Simulating drag start*/
var type = 'dragstart';
var event = this.createEvent(type);
this.dispatchEvent(elem, type, event);
/*Simulating drop*/
type = 'drop';
var dropEvent = this.createEvent(type, {});
dropEvent.dataTransfer = event.dataTransfer;
this.dispatchEvent($(options.dropTarget)[0], type, dropEvent);
/*Simulating drag end*/
type = 'dragend';
var dragEndEvent = this.createEvent(type, {});
dragEndEvent.dataTransfer = event.dataTransfer;
this.dispatchEvent(elem, type, dragEndEvent);
},
createEvent: function(type) {
var event = document.createEvent("CustomEvent");
event.initCustomEvent(type, true, true, null);
event.dataTransfer = {
data: {
},
setData: function(type, val){
this.data[type] = val;
},
getData: function(type){
return this.data[type];
}
};
return event;
},
dispatchEvent: function(elem, type, event) {
if(elem.dispatchEvent) {
elem.dispatchEvent(event);
}else if( elem.fireEvent ) {
elem.fireEvent("on"+type, event);
}
}
});
})(jQuery);
将该段代码存入文件,比如drag_and_drop.js,用selenium webdriver来执行js代码,将左边元素拖拽到右边元素处。接下来这里用一段简单的python代码来展示一个例子。
from selenium import webdriver
driver = webdriver.Chrome()
with open("./drag_and_drop.js") as f:
drag_js = f.read()
driver.execute_script(drag_js + "$('#column-a').simulateDragDrop({ dropTarget: '#column-b'});")
driver.quit()
便完成了模拟的拖拽的行为效果。
在这个代码片段里,'#column-a'和'#column-b'分别代表两个元素的定位,但实际的页面上并不会这么简单,需要用JQuery的定位来代替平时常用的xpath定位的写法。
比如一个简单的例子:
<div class="template-container">
<div class="empty-template">
<div class="template-name">Temp template</div>
<div class="template-desc">Drag and drop fields from left panel.</div>
</div>
</div>
用xpath可以是"//div[@class='template-desc' and contains(text(),'Drag')]";
用JQuery则是"div.template-desc:contains(Drag)".
相应的语句就可以换做:driver.execute_script(drag_js + "$('div.field-dropdown:contains(Relation)').simulateDragDrop({ dropTarget: 'div.template-desc:contains(Drag)'});")
contains可以用来部分匹配,我记得是没有对比值完全相等的方法,但可以自己加一个,如下:
driver.execute_script(drag_js + "$('div.field-dropdown').filter(function() {return $(this).text().trim() == 'Date';}).simulateDragDrop({ dropTarget: 'div.template-desc:contains(Drag)'});")
-------------------
另外如果在用drag_and_drop的js时遇到了selenium报javascriptException,错误里包含jQuery is not defined的情况,则意味着当前页面并没有引用jquery的资源,所以我们需要主动指定一次。
记得下载jQuery.min.js的文件,保存到本地,然后用下面的样例来加载jquery,然后再执行一次即可。
with open(str(Path(__file__).absolute().parent.parent.parent.joinpath("utils", "drag_and_drop.js")), "r") as f:
drag_js = f.read()
try:
self._driver.execute_script(drag_js + "$('%s').simulateDragDrop({ dropTarget: '%s'});" % (css_from, css_to))
except Exception as e:
if str(e).find("jQuery is not defined"):
with open(str(Path(__file__).absolute().parent.parent.parent.joinpath("utils", "jquery.min.js")), "r") as f:
jquery = f.read()
self._driver.execute_script(jquery)
self._driver.execute_script(drag_js + "$('%s').simulateDragDrop({ dropTarget: '%s'});" % (css_from, css_to))
共有1条评论
添加评论
Linnea
2021年10月3日 20:44请问一下,使用这个方法是不是网页必需已经被注入jquery? 如果网页并没有使用jquery, 应该怎么办?