Selenium 读取 DOM 时极其缓慢
Selenium extremely slow on reading the DOM
Selenium 与 DOM 的交互似乎非常慢,同时在每个页面实例化中做几件事。在整个站点中,我们都有可见的微调器,指示任何未解决的 API 调用是否已解决。总之,我有三种方法可以在执行任何操作之前确保页面的稳定性。
- 检查DOM就绪状态
- 检查是否有任何未完成的 JQuery 呼叫
- 检查加载微调器
所有这三个都是通过以下方法作为页面对象实例化的一部分完成的。
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver){
final WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(waitForDomReadyState());
wait.until(waitForjQueryToBeInactive());
List<WebElement> elements = wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(spinnersLoacator));
for(WebElement element: elements){
wait.until(invisibilityOfElementLocated(element));
}
}
private static ExpectedCondition<Boolean> waitForDomReadyState(){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d){
return ( ((JavascriptExecutor) d).executeScript("return document.readyState;").equals("complete"));
}
};
}
private static ExpectedCondition<Boolean> waitForjQueryToBeInactive(){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d){
return (Boolean) ( ((JavascriptExecutor) d).executeScript("return jQuery.active == 0;"));
}
};
}
public static ExpectedCondition<Boolean> invisibilityOfElementLocated(final WebElement element){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver driver){
try{
return !element.isDisplayed();
} catch (NoSuchElementException | StaleElementReferenceException e){
// Returns true because the element is not present in DOM.
// The
// try block checks if the element is present but is
// invisible or stale
return true;
}
}
};
}
以一个页面(比如患者页面)为例,该页面有大量 API 调用并获取大量数据。对于初始 class 实例化,大约需要 17 秒(记录如下)。我的 Selenium 知识表明,后续页面实例化不应花费相同或更多时间来检查 DOM 就绪状态,或 JQuery 调用或微调器等待,因为根本没有任何变化。但是,每次实例化新页面时,我都会看到检查所有这三个页面所花费的时间相同。那里发生了什么?每次我执行这些操作时,Selenium 是否真的会尝试与服务器交互,或者只是由于某种原因与客户端的交互很慢?如果是这样,可能的答案是什么?
Console log
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== Browser on [[[Patient]]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
环境:
- 硒 2.48
- 火狐 38
我也尝试使用 Selenium 2.52 和 firefox 44,结果相同
您的测试似乎都是 non-native
调用,因此 Firefox 应该适合您,但令我惊讶的是 Firefox 本机调用 driver.navigate() 甚至可以让您到达初始值如果您使用的是 44 和 48,请查看页面。众所周知,31.6.0 是最后一个受支持的本机 Firefox 版本。所以,我会说你应该使用 Chrome 直到你弄明白。
但是,要回答你关于速度慢的问题。您编写代码的方式高度依赖于 jQuery,我想您对 jQuery 代码的调用有问题被延迟,这会传播到您的 Selenium 测试,并进一步受到影响事实上,你通过多个微调器循环。我之前注意到的一件事是,如果页面忙于 运行 ajax 调用,那么您使用 JavascriptExecutor 的 Selenium 调用可能必须排队等候那些放弃处理器时间的位。
我会做些什么不同的事情?
好吧,我会写我的微调器等待在 DOM 上运行,而不是将 JavascriptExecutors 调用到 jQuery。也许在您的情况下,这不是一个选项,但我认为经过深思熟虑的计划可以提高页面就绪工作流程的效率。
Selenium 处理客户端的所有等待,每次评估都会向服务器发送请求,直到满足条件。
在高延迟的情况下,它会迅速退化,尤其是在有很多调用的情况下。此外,一些评估需要脚本注入,这也无济于事。
因此,在您的情况下提高性能的最佳方法是使用单个异步 JavaScript 调用:
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver) {
const String JS_WAIT_SPINNERS =
"var callback = arguments[0]; " +
"(function fn(){ " +
" if (document.readyState == 'complete' && jQuery.active == 0) { " +
" var elts = $('.spinners'); " +
" if (elts.length == 8 && !elts.is(':visible')) " +
" return callback(); " +
" } " +
" setTimeout(fn, 60); " +
"})();";
((JavascriptExecutor)driver).executeAsyncScript(JS_WAIT_SPINNERS);
}
初始化超时:
driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);
Selenium 与 DOM 的交互似乎非常慢,同时在每个页面实例化中做几件事。在整个站点中,我们都有可见的微调器,指示任何未解决的 API 调用是否已解决。总之,我有三种方法可以在执行任何操作之前确保页面的稳定性。
- 检查DOM就绪状态
- 检查是否有任何未完成的 JQuery 呼叫
- 检查加载微调器
所有这三个都是通过以下方法作为页面对象实例化的一部分完成的。
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver){
final WebDriverWait wait = new WebDriverWait(driver, timeout);
wait.until(waitForDomReadyState());
wait.until(waitForjQueryToBeInactive());
List<WebElement> elements = wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(spinnersLoacator));
for(WebElement element: elements){
wait.until(invisibilityOfElementLocated(element));
}
}
private static ExpectedCondition<Boolean> waitForDomReadyState(){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d){
return ( ((JavascriptExecutor) d).executeScript("return document.readyState;").equals("complete"));
}
};
}
private static ExpectedCondition<Boolean> waitForjQueryToBeInactive(){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver d){
return (Boolean) ( ((JavascriptExecutor) d).executeScript("return jQuery.active == 0;"));
}
};
}
public static ExpectedCondition<Boolean> invisibilityOfElementLocated(final WebElement element){
return new ExpectedCondition<Boolean>() {
@Override
public Boolean apply(WebDriver driver){
try{
return !element.isDisplayed();
} catch (NoSuchElementException | StaleElementReferenceException e){
// Returns true because the element is not present in DOM.
// The
// try block checks if the element is present but is
// invisible or stale
return true;
}
}
};
}
以一个页面(比如患者页面)为例,该页面有大量 API 调用并获取大量数据。对于初始 class 实例化,大约需要 17 秒(记录如下)。我的 Selenium 知识表明,后续页面实例化不应花费相同或更多时间来检查 DOM 就绪状态,或 JQuery 调用或微调器等待,因为根本没有任何变化。但是,每次实例化新页面时,我都会看到检查所有这三个页面所花费的时间相同。那里发生了什么?每次我执行这些操作时,Selenium 是否真的会尝试与服务器交互,或者只是由于某种原因与客户端的交互很慢?如果是这样,可能的答案是什么?
Console log
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
==== Browser on [[[Patient]]]
==== [[Start waiting for 8 spinner elements found on widget [Patient] ]]
==== [[Finished waiting for 8 spinner elements found on widget [Patient] after [17] s]]
环境:
- 硒 2.48
- 火狐 38
我也尝试使用 Selenium 2.52 和 firefox 44,结果相同
您的测试似乎都是 non-native
调用,因此 Firefox 应该适合您,但令我惊讶的是 Firefox 本机调用 driver.navigate() 甚至可以让您到达初始值如果您使用的是 44 和 48,请查看页面。众所周知,31.6.0 是最后一个受支持的本机 Firefox 版本。所以,我会说你应该使用 Chrome 直到你弄明白。
但是,要回答你关于速度慢的问题。您编写代码的方式高度依赖于 jQuery,我想您对 jQuery 代码的调用有问题被延迟,这会传播到您的 Selenium 测试,并进一步受到影响事实上,你通过多个微调器循环。我之前注意到的一件事是,如果页面忙于 运行 ajax 调用,那么您使用 JavascriptExecutor 的 Selenium 调用可能必须排队等候那些放弃处理器时间的位。
我会做些什么不同的事情? 好吧,我会写我的微调器等待在 DOM 上运行,而不是将 JavascriptExecutors 调用到 jQuery。也许在您的情况下,这不是一个选项,但我认为经过深思熟虑的计划可以提高页面就绪工作流程的效率。
Selenium 处理客户端的所有等待,每次评估都会向服务器发送请求,直到满足条件。 在高延迟的情况下,它会迅速退化,尤其是在有很多调用的情况下。此外,一些评估需要脚本注入,这也无济于事。
因此,在您的情况下提高性能的最佳方法是使用单个异步 JavaScript 调用:
public static void waitForLoadingAllSpinnersAnywhere(final WebDriver driver) {
const String JS_WAIT_SPINNERS =
"var callback = arguments[0]; " +
"(function fn(){ " +
" if (document.readyState == 'complete' && jQuery.active == 0) { " +
" var elts = $('.spinners'); " +
" if (elts.length == 8 && !elts.is(':visible')) " +
" return callback(); " +
" } " +
" setTimeout(fn, 60); " +
"})();";
((JavascriptExecutor)driver).executeAsyncScript(JS_WAIT_SPINNERS);
}
初始化超时:
driver.manage().timeouts().setScriptTimeout(30, TimeUnit.SECONDS);