https://gist.github.com/florentbr/60ef7cb8d9b1ae690cafc82aad52da73
from selenium import webdriver
import time
# JavaScript: HTML5 Drag and drop script
# param1 (WebElement): Source element to drag
# param2 (WebElement): Optional - target element for the drop
# param3 (int): Optional - Drop offset x relative to the target if any or source element
# param4 (int): Optional - Drop offset y relative to the target if any or source element
# param4 (int): Optional - Delay in milliseconds (default = 1ms) for dragging and dropping
# param5 (string): Optional - Key pressed (alt or ctrl or shilf)
JS_DRAG_AND_DROP = "var t=arguments,e=t[0],n=t[1],i=t[2]||0,o=t[3]||0,r=t[4]||1,a=t[5]||'',s='alt'===a||'\ue00a'===a,l='ctrl'===a||'\ue009'===a,c='shift'===a||'\ue008'===a,u=e.ownerDocument,f=e.getBoundingClientRect(),g=n?n.getBoundingClientRect():f,p=f.left+f.width/2,d=f.top+f.height/2,h=g.left+(i||g.width/2),m=g.top+(o||g.height/2),v=u.elementFromPoint(p,d),y=u.elementFromPoint(h,m);if(!v||!y){var E=new Error('source or target element is not interactable');throw E.code=15,E}var _={constructor:DataTransfer,effectAllowed:null,dropEffect:null,types:[],files:Object.setPrototypeOf([],null),_items:Object.setPrototypeOf([],{add:function(t,e){this[this.length]={_data:''+t,kind:'string',type:e,getAsFile:function(){},getAsString:function(t){t(this._data)}},_.types.push(e)},remove:function(t){Array.prototype.splice.call(this,65535&t,1),_.types.splice(65535&t,1)},clear:function(t,e){this.length=0,_.types.length=0}}),setData:function(t,e){this.clearData(t),this._items.add(e,t)},getData:function(t){for(var e=this._items.length;e--&&this._items[e].type!==t;);return e>=0?this._items[e]._data:null},clearData:function(t){for(var e=this._items.length;e--&&this._items[e].type!==t;);this._items.remove(e)},setDragImage:function(t){}};function w(t,e,n,i){for(var o=0;o<e.length;++o){var r=u.createEvent('MouseEvent');r.initMouseEvent(e[o],!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),t.dispatchEvent(r)}i&&setTimeout(i,n)}function D(t,e,n,i){var o=u.createEvent('DragEvent');o.initMouseEvent(e,!0,!0,u.defaultView,0,0,0,p,d,l,s,c,!1,0,null),Object.setPrototypeOf(o,null),o.dataTransfer=_,Object.setPrototypeOf(o,DragEvent.prototype),t.dispatchEvent(o),i&&setTimeout(i,n)}'items'in DataTransfer.prototype&&(_.items=_._items),w(v,['pointerdown','mousedown'],1,function(){for(var t=v;t&&!t.draggable;)t=t.parentElement;if(t&&t.contains(v)){var e=y.getBoundingClientRect();D(v,'dragstart',r,function(){var t=y.getBoundingClientRect();p=t.left+h-e.left,d=t.top+m-e.top,D(y,'dragenter',1,function(){D(y,'dragover',r,function(){D(u.elementFromPoint(p,d),'drop',1,function(){D(v,'dragend',1,function(){w(u.elementFromPoint(p,d),['mouseup','pointerup'])})})})})})}})"
def drag_and_drop(driver, source, target=None, offsetX=0, offsetY=0, delay=25, key=None) :
driver.execute_script(JS_DRAG_AND_DROP, source, target, offsetX, offsetY, delay, key)
time.sleep(delay * 2 / 1000)
driver = webdriver.Chrome()
driver.get("http://xxxx.xxxxl")
# drag and drop Glass
source = driver.find_element_by_xpath("//*[not(./*)][normalize-space()='Glass']")
target = driver.find_element_by_xpath("//*[text()[contains(.,'Works with any drop effect')]]")
drag_and_drop(driver, source, target)
# drag and drop Banana with alt key pressed
source = driver.find_element_by_xpath("//*[not(./*)][normalize-space()='Banana']")
target = driver.find_element_by_xpath("//*[text()[contains(.,'Works with copy drop effect')]]")
drag_and_drop(driver, source, target, key='alt')
# drag and drop Paper by offset
source = driver.find_element_by_xpath("//*[not(./*)][normalize-space()='Paper']")
drag_and_drop(driver, source, offsetX=250, offsetY=-100)
目前找到的有jquery的、无需jquery的,其中基本都是不支持xpath,只有这一个既不需要jquery也不需要css selector,并且封装了延迟、附加键的方法
原始JS如下:可以看到语法还是比较老的。
var args = arguments,
elemSrc = args[0],
elemDst = args[1],
offsetX = args[2] || 0,
offsetY = args[3] || 0,
delay = args[4] || 1,
key = args[5] || '',
alt = key === 'alt' || key === '\uE00A',
ctrl = key === 'ctrl' || key === '\uE009',
shift = key === 'shift' || key === '\uE008',
doc = elemSrc.ownerDocument,
box1 = elemSrc.getBoundingClientRect(),
box2 = elemDst ? elemDst.getBoundingClientRect() : box1,
x = box1.left + (box1.width / 2),
y = box1.top + (box1.height / 2),
x2 = box2.left + (offsetX ? offsetX : box2.width / 2),
y2 = box2.top + (offsetY ? offsetY : box2.height / 2),
source = doc.elementFromPoint(x, y),
target = doc.elementFromPoint(x2, y2);
if (!source || !target) {
var ex = new Error('source or target element is not interactable');
ex.code = 15;
throw ex;
}
var dataTransfer = {
constructor : DataTransfer,
effectAllowed : null,
dropEffect : null,
types : [ ],
files : Object.setPrototypeOf([], null),
_items : Object.setPrototypeOf([], {
add : function add(data, type) {
this[this.length] = {
_data : '' + data,
kind : 'string',
type : type,
getAsFile : function () { },
getAsString : function (callback) { callback(this._data) }
};
dataTransfer.types.push(type);
},
remove : function remove(i) {
Array.prototype.splice.call(this, i & 65535, 1);
dataTransfer.types.splice(i & 65535, 1);
},
clear : function clear(data, type) {
this.length = 0;
dataTransfer.types.length = 0;
}
}),
setData : function setData(format, data) {
this.clearData(format);
this._items.add(data, format);
},
getData : function getData(format) {
for (var i = this._items.length; i-- && this._items[i].type !== format;);
return i >= 0 ? this._items[i]._data : null;
},
clearData : function clearData(format) {
for (var i = this._items.length; i-- && this._items[i].type !== format;);
this._items.remove(i);
},
setDragImage : function setDragImage(format) { }
};
if ('items' in DataTransfer.prototype)
dataTransfer.items = dataTransfer._items;
emit_mouse(source, [ 'pointerdown', 'mousedown' ], 1, function () {
var elem = source;
while (elem && !elem.draggable)
elem = elem.parentElement;
if (!elem || !elem.contains(source))
return;
var box2 = target.getBoundingClientRect();
emit_drag(source, 'dragstart', delay, function () {
var box3 = target.getBoundingClientRect();
x = box3.left + x2 - box2.left;
y = box3.top + y2 - box2.top;
emit_drag(target, 'dragenter', 1, function () {
emit_drag(target, 'dragover', delay, function () {
emit_drag(doc.elementFromPoint(x, y), 'drop', 1, function () {
emit_drag(source, 'dragend', 1, function () {
emit_mouse(doc.elementFromPoint(x, y), [ 'mouseup', 'pointerup' ]);
})})})})})});
function emit_mouse(element, types, delay, callback) {
for (var i=0; i < types.length; ++i) {
var event = doc.createEvent('MouseEvent');
event.initMouseEvent(types[i], true, true, doc.defaultView, 0, 0, 0, x, y, ctrl, alt, shift, false, 0, null);
element.dispatchEvent(event);
}
callback && setTimeout(callback, delay);
}
function emit_drag(element, type, delay, callback) {
var event = doc.createEvent('DragEvent');
event.initMouseEvent(type, true, true, doc.defaultView, 0, 0, 0, x, y, ctrl, alt, shift, false, 0, null);
Object.setPrototypeOf(event, null);
event.dataTransfer = dataTransfer;
Object.setPrototypeOf(event, DragEvent.prototype);
element.dispatchEvent(event);
callback && setTimeout(callback, delay);
}
不过我目前使用pyautogui,虽然看上去有点傻...