Selenium Selenium 中如何设置移动鼠标行为的耗时?

cmlanche · 2020年04月14日 · 最后由 cmlanche 回复于 2020年04月22日 · 2457 次阅读

场景说明

一般来说用户不太会碰到这个问题,但具备如下场景的,都需要设置移动鼠标的耗时,移动鼠标到某控件到这某位置过快,导致无法让想要的效果出现。

比较抽象,举个例子,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 毫秒,包括如下公开方法都收到影响:dragAndDropmoveToElement

被否决的不够好的解决方案

最开始我想到的办法是,继承 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();

完美解决!

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 2 条回复 时间 点赞

建议 private final static 顺序调整为 private static final

fdeferf 回复

有什么区别吗?另外这个代码是 selenium 的源码,不是我写的

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册