Bob's Blog

Web开发、测试框架、自动化平台、APP开发、机器学习等

返回上页首页

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))

 

下一篇:  解决python在Mac上tkinter导入错误
上一篇:  国内设置招商银行为Google Adsense电汇收款

共有1条评论

添加评论

Linnea
2021年10月3日 20:44
请问一下,使用这个方法是不是网页必需已经被注入jquery? 如果网页并没有使用jquery, 应该怎么办?