故事:
在StackOverflow上,我看到用户报告说他们无法通过selenium WebDriver“点击”命令点击一个元素,并且可以通过执行脚本点击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());
问题:
为什么在常规WebDriver点击时没有点击“通过JavaScript”?究竟是什么时候发生这种变通方法的缺点(如果有的话)?
我个人使用这种解决方法而没有完全理解为什么我必须这样做以及它可能导致什么问题。
答案 0 :(得分:126)
与currently accepted answer建议的内容相反,当涉及到WebDriver执行单击并在JavaScript中执行此操作之间的区别时,PhantomJS没有任何特定内容。
这两种方法的本质区别在于所有浏览器都很常见,可以简单地解释一下:
WebDriver:当WebDriver执行点击时,它会尽可能地模拟真实用户使用浏览器时会发生什么。假设您有一个元素A是一个按钮表示“点击我”和一个元素B,它是div
元素,它是透明的,但其尺寸和zIndex
设置为完全覆盖A.然后你告诉WebDriver点击A. WebDriver将模拟点击,以便B接收点击第一个。为什么?因为B覆盖A,并且如果用户试图点击A,那么B将首先获得该事件。 A是否最终会获得click事件取决于B如何处理事件。无论如何,在这种情况下,WebDriver的行为与真实用户尝试点击A时的行为相同。
JavaScript:现在,假设您使用JavaScript执行A.click()
。 这种点击方法无法重现用户尝试点击A时实际发生的情况。 JavaScript会将click
事件直接发送给A,B不会收到任何事件。
正如我上面提到的,WebDriver将尽力模拟当真实用户使用浏览器时会发生什么。事实上,DOM可以包含用户无法与之交互的元素,而WebDriver将不允许您单击这些元素。除了我提到的重叠案例,这也需要不能点击隐形元素。我在Stack Overflow问题中看到的一个常见情况是尝试与已经存在于DOM中的GUI元素进行交互,但只有在操作了某些其他元素时才会显示。这有时会发生在下拉菜单中:您必须首先单击按钮,然后在选择菜单项之前调出下拉列表。如果有人在菜单可见之前尝试单击菜单项,WebDriver将拒绝并说该元素无法操作。 如果此人尝试使用JavaScript,那么它将起作用,因为事件直接传递给元素,而不考虑可见性。
如果您使用Selenium进行测试应用程序,我对这个问题的回答是“几乎从不”。大体上,您的Selenium测试应该重现用户的内容会对浏览器有所帮助。以下拉菜单为例:测试应该首先点击下拉按钮,然后单击菜单项。如果GUI出现问题,因为按钮不可见,或者按钮无法显示菜单项或类似内容,那么您的测试将失败并且您将检测到该错误。 如果您使用JavaScript进行点击,则无法通过自动化测试检测到这些错误。
我说“几乎从不”,因为可能存在使用JavaScript有意义的例外情况。但它们应该是非常罕见的。
如果您使用Selenium来抓取网站,那么尝试重现用户行为并不重要。因此,使用JavaScript绕过GUI不是一个问题。
答案 1 :(得分:27)
当JavaScript HTMLElement.click()
执行click
事件的默认操作时,驱动程序执行的单击尝试尽可能接近地模拟真实用户的行为,即使该元素不可交互
区别在于:
驱动程序通过将元素滚动到视图中来确保元素可见,并检查该元素是可交互。
驱动程序将引发错误:
disabled
为true
)pointer-events
为none
)
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/en/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(下拉侧栏)
<强> Workarrounds:强>
添加服务员以等待可见度,最小尺寸或稳定位置:
// 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);
答案 2 :(得分:16)
注意:让我们拨打电话&#39;点击&#39;是最终用户点击。 &#39; js点击&#39;点击通过JS
为什么点击&#34;通过JavaScript&#34;在常规WebDriver点击时不起作用吗?
有两种情况发生:
然后,这是PhantomJS
最常见的已知行为。有些元素有时无法点击,例如<div>
。这是因为PhantomJS
是用于模拟浏览器引擎的原始版本(如初始HTML + CSS - &gt;计算CSS - &gt;渲染)。但这并不意味着以最终用户的方式(查看,点击,拖动)进行交互。因此,PhamtomJS
仅部分支持最终用户互动。
为什么JS点击工作?至于点击,它们都是平均点击。它就像一支 1桶和 2触发器的枪。一个来自视口,一个来自JS。由于PhamtomJS
非常适合模拟浏览器的引擎,因此JS点击应该可以完美运行。
例如,我们得到了<div>
- &GT;我们做了一些计算
- &GT;然后我们将点击事件绑定到<div>
。
- &GT;加上一些不良的角度编码(例如,没有正确处理范围&#39;
我们可能会得到相同的结果。单击“赢了”,因为WebdriverJS在没有单击事件处理程序时尝试单击该元素。
为什么JS点击工作? Js点击就像将js直接注入浏览器一样。可能有两种方式,
Fist 是通过devtools控制台(是的,WebdriverJS确实与devtools&#39;控制台进行通信)。
第二次会将<script>
标记直接注入HTML。
对于每个浏览器,行为都会有所不同。但无论如何,这些方法比点击按钮更复杂。单击是使用已存在的内容(最终用户单击),js单击正在通过后门。
对于js,click似乎是一个异步任务。这与“浏览器异步任务和CPU任务调度”等有点复杂的主题有关。 (读一会儿就不能再找到这篇文章)。简而言之,这主要是因为js click需要等待任务调度CPU 的循环,并且在绑定click事件后它会运行得慢一些。 (当你发现元素有时可点击时,你可能知道这种情况,有时不会。 )
究竟是什么时候发生这种情况,这是什么缺点 解决方法(如果有的话)?
=&GT;如上所述,两者都意味着一个目的,但关于使用哪个入口:
=&GT;对于性能,很难说因为它依赖于浏览器。但一般来说:
=&GT;缺点:
browser.wait()
(check this for more information)(我希望简短,但结果很糟糕。任何与理论相关的内容都很难解释......)
答案 3 :(得分:2)
谢谢你的好解释,我遇到了同样的问题,你的解释帮助解决了我的问题。
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)
答案 4 :(得分:1)
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()
上方,我们将检查 Radio 按钮,但随后弹出异常,如上图所示。
其实这不是点击后的页面加载动作,点击只是为了选择Radio按钮,不知道为什么WebDriver Click()
会导致这个异常,谁能解释一下为什么这个异常发生了。
我使用 Webdriver 3.141.59 和 IE 11 以及 selenium-server-standalone-3.141.59.jar 进行远程测试。