WebDriver click() vs JavaScript click()

故事:

在StackOverflow上,我看到用户报告说他们无法通过selenium WebDriver“click”命令单击元素,并且可以通过执行脚本来使用JavaScript单击来解决此问题。

Python中的示例:

element = driver.find_element_by_id("myid")
driver.execute_script("arguments[0].click();", element)

WebDriverJS/Protractor 中的示例:

var elm = $("#myid");
browser.executeScript("arguments[0].click();", elm.getWebElement());

问题:

为什么单击“通过 JavaScript”在常规 WebDriver 单击不起作用时不起作用?这种情况究竟发生在什么时候,这种解决方法的缺点是什么(如果有的话)?

我个人使用了这种解决方法,但没有完全理解为什么我必须这样做以及它可能导致什么问题。


答案 1

目前接受的答案所暗示的相反,当涉及到让WebDriver执行单击和在JavaScript中执行单击之间的区别时,PhantomJS没有什么特定的。

不同之处

这两种方法之间的本质区别是所有浏览器通用的,可以非常简单地解释:

  • WebDriver:当WebDriver进行单击时,它会尽可能地模拟真实用户使用浏览器时发生的情况。假设您有一个元素A,它是一个显示“单击我”的按钮,而元素B是一个透明的元素,但其尺寸和设置使其完全覆盖A。然后,您告诉 WebDriver 单击 A。WebDriver 将模拟单击,以便 B 首先收到单击。为什么?因为 B 覆盖 A,如果用户尝试单击 A,则 B 将首先获取事件。A 最终是否会获得点击事件取决于 B 如何处理该事件。无论如何,在这种情况下,WebDriver的行为与真实用户尝试单击A时的行为相同。divzIndex

  • JavaScript:现在,假设你使用JavaScript来做.这种单击方法不会重现当用户尝试单击 A 时实际发生的情况。JavaScript 将事件直接发送到 A,B 不会得到任何事件。A.click()click

为什么 JavaScript 单击在 WebDriver 单击不起作用时起作用?

正如我上面提到的,WebDriver将尝试尽可能地模拟真实用户使用浏览器时发生的事情。事实是,DOM可以包含用户无法与之交互的元素,而WebDriver不允许您单击这些元素。除了我提到的重叠情况外,这还意味着无法单击不可见的元素。我在Stack Overflow问题中看到的一个常见情况是,有人试图与DOM中已经存在的GUI元素进行交互,但只有在操纵其他元素时才变得可见。这有时会发生在下拉菜单中:您必须首先单击下拉菜单以显示下拉列表的按钮,然后才能选择菜单项。如果有人尝试在菜单可见之前单击菜单项,WebDriver 将犹豫不决,并说无法操作该元素。如果这个人然后尝试使用JavaScript来做这件事,它将起作用,因为无论可见性如何,事件都直接传递到元素。

什么时候应该使用JavaScript进行点击?

如果你使用Selenium来测试一个应用程序,我对这个问题的回答是“几乎从不”。总的来说,你的Selenium测试应该重现用户对浏览器的处理方式。以下拉菜单为例:测试应单击首先显示下拉列表的按钮,然后单击菜单项。如果由于按钮不可见而导致 GUI 出现问题,或者按钮无法显示菜单项或类似内容,则测试将失败,并且您将检测到该 Bug。如果您使用JavaScript点击,您将无法通过自动测试来检测这些错误。

我说“几乎从不”,因为可能有例外,使用JavaScript是有意义的。不过,它们应该非常罕见。

如果您使用Selenium来抓取网站,那么尝试重现用户行为并不那么重要。因此,使用JavaScript绕过GUI并不是一个问题。


答案 2

驱动程序执行的单击尝试在 JavaScript 对事件执行默认操作时尽可能接近地模拟真实用户的行为,即使该元素不可交互。HTMLElement.click()click

区别是:

  • 驱动程序通过将元素滚动到视图中来确保元素可见,并检查该元素是否可交互

    驱动程序将引发错误:

    • 当单击坐标处顶部的元素不是目标元素或后代时
    • 当元素没有正大小或完全透明时
    • 当元素是禁用的输入或按钮时(属性/属性为disabledtrue)
    • 当元素禁用鼠标指针时(CSS 是pointer-eventsnone)


    JavaScript 将始终执行默认操作,或者如果元素被禁用,则充其量只能静默失败。HTMLElement.click()

  • 如果元素可聚焦,则驱动程序应使元素成为焦点。

    JavaScript不会。HTMLElement.click()

  • 驱动程序应像真实用户一样发出所有事件(鼠标移动,鼠标下降,鼠标移动,鼠标移动,单击等)。

    JavaScript 只发出事件。页面可能依赖于这些额外的事件,如果不发出这些事件,则其行为可能会有所不同。HTMLElement.click()click

    以下是驱动程序发出的与 Chrome 一起点击的事件:

    mouseover {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousemove {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mousedown {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    mouseup {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    click {target:#topic, clientX:222, clientY:343, isTrusted:true, ... }
    

    这是通过 JavaScript 注入发出的事件:

    click {target:#topic, clientX:0, clientY:0, isTrusted:false, ... }
    
  • JavaScript 发出的事件不受信任,并且不能调用默认操作:.click()


    https://developer.mozilla.org/en/docs/Web/API/Event/isTrusted https://googlechrome.github.io/samples/event-istrusted/index.html

    请注意,某些驱动程序仍在生成不受信任的事件。从2.1版开始,PhantomJS就是这种情况。

  • JavaScript 发出的事件没有单击的坐标.click()

    属性设置为 。该页面可能依赖于它们,并且可能以不同的方式运行。clientX, clientY, screenX, screenY, layerX, layerY0


使用JavaScript来报废一些数据可能是可以的,但它不在测试上下文中。它违背了测试的目的,因为它不模拟用户的行为。因此,如果从驱动程序的单击失败,则真实用户很可能也无法在相同条件下执行相同的单击。.click()


是什么使驱动程序在预期某个元素成功时无法单击该元素?

  • 由于延迟或过渡效果,目标元素尚不可见/可交互。

    一些例子:

    https://developer.mozilla.org/fr/docs/Web(下拉导航菜单)http://materializecss.com/side-nav.html(下拉侧边栏)

    工作圈:

    添加一个服务员来等待能见度,最小尺寸或稳定位置:

    // wait visible
    browser.wait(ExpectedConditions.visibilityOf(elem), 5000);
    
    // wait visible and not disabled
    browser.wait(ExpectedConditions.elementToBeClickable(elem), 5000);
    
    // wait for minimum width
    browser.wait(function minimumWidth() {
        return elem.getSize().then(size => size.width > 50);
    }, 5000);
    

    重试以单击,直到成功:

    browser.wait(function clickSuccessful() {
        return elem.click().then(() => true, (ex) => false);
    }, 5000);
    

    添加与动画/过渡持续时间匹配的延迟:

    browser.sleep(250);
    


  • 一旦滚动到视图中,目标元素就会被浮动元素覆盖

    驱动程序会自动将元素滚动到视图中以使其可见。如果页面包含浮动/粘性元素(菜单、广告、页脚、通知、Cookie 政策等),则该元素最终可能会被覆盖,并且将不再可见/可交互。

    示例:https://twitter.com/?lang=en

    解决方法:

    将窗口的大小设置为较大的大小,以避免滚动或浮动元素。

    在具有负偏移的元素上移动,然后单击它:Y

      browser.actions()
         .mouseMove(elem, {x: 0, y: -250})
         .click()
         .perform();
    

    将元素滚动到窗口中心,然后单击:

    browser.executeScript(function scrollCenter(elem) {
      var win = elem.ownerDocument.defaultView || window,
        box = elem.getBoundingClientRect(),
        dy = box.top - (win.innerHeight - box.height) / 2;
      win.scrollTo(win.pageXOffset, win.pageYOffset + dy);
    }, element);
    
    element.click();
    

    如果无法避免,请隐藏浮动元素:

    browser.executeScript(function scrollCenter(elem) {
      elem.style.display = 'none';
    }, element);