WebDriver click()与JavaScript click()的区别

160

故事:

在StackOverflow上,我看到有用户报告他们无法通过selenium WebDriver的“click”命令点击一个元素,并通过执行脚本来使用JavaScript click进行解决。

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点击不行?这是在什么时候发生的,如果有的话,这种解决方法的缺点是什么?

我个人在没有完全理解为什么要这样做以及可能引起什么问题的情况下使用了这种解决方法。

5个回答

197

目前接受的答案所说的相反,无论是WebDriver点击还是JavaScript点击,在PhantomJS上并没有什么特别之处。

区别

两种方法之间的基本区别适用于所有浏览器,并且可以很简单地解释:

  • WebDriver: 当WebDriver执行点击操作时,它会尽力模拟真实用户使用浏览器的情况。 假设您有一个元素A,它是一个按钮,上面写着“点击我”,还有一个元素B,它是一个透明的

    元素,但已设置其尺寸和zIndex,以完全覆盖A。然后您告诉WebDriver点击A。WebDriver将模拟点击,以便B首先接收点击事件。为什么?因为B覆盖了A,如果用户试图点击A,那么B将首先接收事件。无论A是否最终接收到点击事件取决于B如何处理事件。在任何情况下,WebDriver在这种情况下的行为与真实用户尝试单击A时相同。

  • JavaScript: 现在,假设您使用JavaScript执行A.click()。此点击方法不会重现用户尝试单击A时发生的真实情况。 JavaScript直接将click事件发送给A,B将不会接收任何事件。

为什么JavaScript点击有效而WebDriver点击无效?

如我之前提到的,WebDriver会尽力模拟真实用户在浏览器中的操作。事实上,DOM可能包含一些用户无法交互的元素,而WebDriver不允许您单击这些元素。除了我提到的重叠情况外,这还意味着不可见元素也无法被单击。我在Stack Overflow问题中经常看到的一个普遍情况是,有人试图与已存在于DOM中但只有在其他元素被操作后才变得可见的GUI元素进行交互。这种情况有时会发生在下拉菜单中:您必须先单击打开下拉菜单的按钮,然后才能选择菜单项。如果有人在菜单可见之前尝试单击菜单项,WebDriver将会拒绝并说该元素无法被操作。如果此时使用JavaScript来操作,它将起作用,因为事件直接传递给元素,而与其是否可见无关。

何时应该使用JavaScript进行点击?

如果您正在使用Selenium进行应用程序测试,我的答案是“几乎从不”。大体上说,您的Selenium测试应该重现用户在浏览器中的操作。以下拉菜单为例:测试应该先点击带出下拉框的按钮,然后再点击菜单项。如果GUI出现问题,因为按钮不可见,或者按钮无法显示菜单项等类似情况,那么您的测试将失败,并且您会检测到错误。如果您使用JavaScript来点击周围,则无法通过自动化测试检测到这些错误。
我说“几乎从不”,因为可能有一些例外情况,使用JavaScript是有道理的。但是这些情况应该非常少见。
如果您正在使用Selenium进行网站抓取,则尝试重现用户行为并不那么重要。因此,使用JavaScript绕过GUI问题就不那么重要了。

1
更好的答案,应该接受这个答案。上面那个答案是针对PhantomJS的特殊情况,我认为这个更准确。 - Ardesco
@Ardesco 绝对没错。已标记为接受答案。完美而且非常详细的回答。 - alecxe
按计划将赏金授予Linh,但我会开始一个新的以感谢你另一个令人惊叹的答案。谢谢。 - alecxe
1
非常好而且易懂的答案。根据我的经验,WebDriver在处理AngularJS页面时会遇到很多问题:对于某些元素,WebDriver方法(如clicksendKeys)可以正常工作,但并非总是如此。没有定位问题或其他异常,测试用例只是无法继续执行。日志显示操作已执行。有时使用Action可以帮助解决问题。如果我手动点击元素(在测试用例停止后),一切都能正常工作。因此,在这种情况下我们转而使用原始JS。我已经有2年没有使用AngularJS了,所以现在可能会更好。 - Würgspaß
@Louis:你说“当WebDriver进行点击时,它会尽其所能地模拟真实用户使用浏览器时发生的情况。”这是你的意见还是你有相关的参考资料呢? - Alex
1
@Alex 我没有方便的链接。我的答案是由我在Selenium中特别是模拟用户事件方面的经验得出的。我在5年前开始使用Selenium。在我使用Selenium的时间里,我不得不阅读Selenium的代码来解决一些问题,并且我已经提交了相当多的关于事件分派的bug报告,并与Selenium开发人员讨论了这些bug。"尽可能好"是目标。我肯定遇到了(现在已经修复)阻止它达到该目标的错误。可能会有一些错误仍然存在。 - Louis

36

通过驱动程序执行的点击操作尽可能地模拟真实用户的行为,而JavaScript HTMLElement.click()则执行click事件的默认操作,即使该元素不可交互。

两者之间的区别在于:

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

    驱动程序将会报错:

    • 当鼠标点击坐标处的元素不是目标元素或其后代时
    • 当元素没有正面积或完全透明时
    • 当元素是禁用的输入或按钮(属性/属性disabledtrue)时
    • 当元素具有禁用鼠标指针(CSS pointer-eventsnone)时


    JavaScript HTMLElement.click()将始终执行默认操作,或者如果该元素被禁用,则最多会静默失败。

  • 如果该元素可以接受焦点,则期望驱动程序将其置于焦点上。

    JavaScript HTMLElement.click()不会这样做。

  • 期望驱动程序发出所有事件(mousemove、mousedown、mouseup、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/zh-CN/docs/Web/API/Event/isTrusted
    https://googlechrome.github.io/samples/event-istrusted/index.html

    请注意,一些驱动程序仍会生成不可信任的事件,如PhantomJS 2.1版本。

  • JavaScript的.click() 触发的事件没有点击的坐标信息

    clientX, clientY, screenX, screenY, layerX, layerY属性都设置为0。页面可能依赖这些属性,而导致行为不同。


  • 在某些情况下,使用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);
      

    这是一个非常有帮助的解决方案!谢谢。 - Carlo Nyte

    19

    注意:我们把“点击”称为用户的点击事件,“JS 点击”表示通过 JS 触发的点击事件。

    为什么“通过 JavaScript”触发点击会生效,而常规 WebDriver 点击则不行呢?

    有两种情况可能导致出现这种问题:

    I. 如果您正在使用 PhantomJS

    那么这是 PhantomJS 最常见的已知行为。某些元素有时无法进行点击,例如 <div> 标签。这是因为 PhantomJS 最初是用于模拟浏览器引擎(例如初始的 HTML + CSS -> 计算 CSS -> 渲染),但这并不意味着可以像最终用户一样与之交互(查看、点击、拖动)。因此,PhamtomJS 仅部分支持最终用户的交互。

    为什么 JS 点击会生效?对于任何一种点击方式来说,它们都意味着点击操作。这就像一把枪上有1 根枪管2 个扳机——一个是从视口触发的,一个是通过 JS 触发的。由于 PhamtomJS 在模拟浏览器引擎方面表现出色,因此通过 JS 触发的点击操作应该完全可以生效。

    II. “点击”事件处理程序绑定时机不当。

    例如我们有一个 <div> 标签:

    • -> 我们进行一些计算

    • -> 然后绑定点击事件到这个 <div> 标签上

    • -> 再加上 Angular 框架的一些错误编码(例如没有正确处理作用域周期)

    这可能会导致相同的结果。由于 WebdriverJS 尝试在元素没有“点击”事件处理程序的情况下进行点击操作,所以点击操作无法生效。

    为什么 JS 点击会生效?JS 点击就像直接将 JS 注入到浏览器中一样。有两种方式可行:

    首先是通过开发工具控制台(是的,WebdriverJS确实可以与开发工具控制台通信)。

    其次是将<script>标签直接注入HTML中。

    对于每个浏览器,行为都会不同。但无论如何,这些方法都比点击按钮更复杂。点击是使用已有功能(最终用户点击),js点击是通过后门进行的。

    而对于js点击,它将出现为异步任务。这与“浏览器异步任务和CPU任务调度”的某种复杂主题有关(之前读过一篇文章,现在找不到了)。简而言之,这将大多数情况下导致js点击需要等待“CPU任务调度周期”,并且在绑定点击事件后运行得稍慢一些。 (当您发现元素有时可单击,有时不可单击时,您可能会知道此情况。)

    具体是什么时候发生,这种解决方法有何缺点(如果有的话)?

    => 如上所述,两种方式都是为了一个目的,但使用哪个入口:

    • 点击:使用浏览器默认提供的内容。
    • JS点击:通过后门进行。

    => 对于性能来说,很难说因为它取决于浏览器。但通常:

    • 点击:并不意味着更快,而只是在CPU执行任务的调度列表中签名位置更高。
    • JS点击:并不意味着更慢,而只是将其签名到CPU任务调度的最后位置。

    => 缺点:

    • 点击:似乎没有任何缺点,除非您正在使用PhamtomJS。
    • JS点击:对健康非常不利。您可能会意外地点击某些不存在于视图中的东西。当您使用此功能时,请确保元素存在并且可以作为最终用户的视点查看和单击。

    P.S. 如果您正在寻找解决方案。

    • 使用PhantomJS?我建议改用Chrome headless。是的,你可以在Ubuntu上设置Chrome headless。它就像Chrome一样运行但没有视图,而且不像PhantomJS那样容易出错。
    • 如果没有使用PhamtomJS但仍然遇到问题,我建议使用Protractor的ExpectedCondition和browser.wait()方法(点击此链接获取更多信息

    5
    谢谢您清晰的解释,我遇到了同样的问题,您的解释帮助我解决了这个问题。
    button = driver.wait.until(EC.presence_of_element_located(
        (By.XPATH, "//div[@id='pagination-element']/nav[1]/span[3]/button[1]/span[1]/i[1]")
    ))
    driver.execute_script("arguments[0].click();", button)
    

    1

    enter image description here

    if (theElement.Enabled)
    {
        if (!theElement.Selected)
        {
          var driver = (IJavaScriptExecutor)Driver;
          driver.ExecuteScript("arguments[0].click();", theElement); //ok
    
          //theElement.Click();//action performed on theElement, then pops exception   
         }
     }
    

    我不同意我们将“几乎从不”使用JS来模拟点击操作。
    theElement.Click()上面,我们将检查单选按钮,但是异常如上图所示。
    实际上,在点击之后没有页面加载动作,点击只是选择单选按钮,我不知道为什么WebDriver的Click()会导致此异常,有人能解释一下为什么会出现这种异常吗?
    我使用Webdriver 3.141.59和IE 11以及selenium-server-standalone-3.141.59.jar进行远程测试。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接