一般来说用户不太会碰到这个问题,但具备如下场景的,都需要设置移动鼠标的耗时,移动鼠标到某控件到这某位置过快,导致无法让想要的效果出现。
比较抽象,举个例子,Selenium 中 Actions 有个方法叫 dragAndDrop,用来拖动一个控件到某个偏移位置或者到另外一个控件,默认情况下,拖拽会在 100 毫秒内完成,所有的移动鼠标的行为都会经过 Actions 下的这个方法:
private Actions moveInTicks(WebElement target, int xOffset, int yOffset) {
return tick(defaultMouse.createPointerMove(
Duration.ofMillis(100),
Origin.fromElement(target),
xOffset,
yOffset));
}
比较坑的是,Actions 的 API 并没有让用户自主去设定这个耗时,而是写死的 100 毫秒,包括如下公开方法都收到影响:dragAndDrop
,moveToElement
。
最开始我想到的办法是,继承 Actions,重写里面的部分方法,但我发现里面需要的鼠标等对象都是私有的,moveInTicks 也是私有的。
private final static Logger LOG = Logger.getLogger(Actions.class.getName());
private final WebDriver driver;
// W3C
private final Map<InputSource, Sequence> sequences = new HashMap<>();
private final PointerInput defaultMouse = new PointerInput(MOUSE, "default mouse");
private final KeyInput defaultKeyboard = new KeyInput("default keyboard");
// JSON-wire protocol
private final Keyboard jsonKeyboard;
private final Mouse jsonMouse;
protected CompositeAction action = new CompositeAction();
后来我看这个 Actions 的依赖都比较独立,干脆就全部拷贝过来,然鹅,类中有一个对Sequence
的依赖的 size 方法不是公开的,这意味着,我只能把 MyActions 这个类放到和Sequence
同包下,或者通过反射来调用 size 方法。
突然发现,解决这个耗时问题,改动过于笨重了。
在 SO 网站上发现这样一种说法,说actions.dragAndDrop
其实就等于:
actions.clickAndHold(element1).moveToElement(elemtent2).release()
而 moveToElement 这个方法的实现,比较简单:
/**
* Moves the mouse to the middle of the element. The element is scrolled into view and its
* location is calculated using getBoundingClientRect.
* @param target element to move to.
* @return A self reference.
*/
public Actions moveToElement(WebElement target) {
if (isBuildingActions()) {
action.addAction(new MoveMouseAction(jsonMouse, (Locatable) target));
}
return moveInTicks(target, 0, 0);
}
我们第一步调用了 clickAndHold,已经把鼠标移动到第一个元素了,所以我们就不需要这句话了:action.addAction(new MoveMouseAction(jsonMouse, (Locatable) target));
,有点重复。
剩下的我们只要自己实现 moveInTicks 就行,幸好,我们发现 tick 是 public 方法,我们这样去实现:
Actions actions = new Actions(getWebDriver());
PointerInput defaultMouse = new PointerInput(MOUSE, "default mouse");
// 执行拖拽
actions.clickAndHold(startElement)
.tick(defaultMouse.createPointerMove(
Duration.ofMillis(2000),
PointerInput.Origin.fromElement(endElement), 0, 0))
.release(endElement)
.perform();
然而执行起来,我们会发现一个错误:
invalid argument: 'id' already exists
思考了半天,发现 PointerInput 在创建的时候,这个 name 如果为空,会随机生成一个 uuid 的唯一名称:
public PointerInput(Kind kind, String name) {
this.kind = Objects.requireNonNull(kind, "Must set kind of pointer device");
this.name = Optional.ofNullable(name).orElse(UUID.randomUUID().toString());
}
那么我有理由猜测,这个名称是不允许重名的,我们知道 actions 创建后,会默认创建一个default mouse
名称的鼠标,所以我们这里不能再用这个名字了,代码改下:
Actions actions = new Actions(getWebDriver());
PointerInput defaultMouse = new PointerInput(MOUSE, "my mouse");
// 执行拖拽
actions.clickAndHold(startElement)
.tick(defaultMouse.createPointerMove(
Duration.ofMillis(2000),
PointerInput.Origin.fromElement(endElement), 0, 0))
.release(endElement)
.perform();
完美解决!