【发布时间】:2011-02-19 13:57:01
【问题描述】:
如何让 selenium 等待日历小部件之类的加载?现在我只是在将测试用例导出到 junit 程序后做一个Thread.sleep(2500)。
【问题讨论】:
标签: ajax unit-testing selenium
如何让 selenium 等待日历小部件之类的加载?现在我只是在将测试用例导出到 junit 程序后做一个Thread.sleep(2500)。
【问题讨论】:
标签: ajax unit-testing selenium
在我的情况下,问题似乎是由于 ajax 延迟,但与主页内的内部 iframe 有关。 在 seleminum 中,可以通过以下方式切换到内部框架:
driver.switchTo().frame("body");
driver.switchTo().frame("bodytab");
我使用java。之后,我能够找到元素
driver.findElement(By.id("e_46")).click();
【讨论】:
使用 webdriver aka selenium2,您可以使用前面提到的隐式等待配置 https://www.selenium.dev/documentation/en/webdriver/waits/#implicit-wait
使用 Java:
WebDriver driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.findElement(By.id("myDynamicElement"));
或者使用python:
from selenium import webdriver
ff = webdriver.Firefox()
ff.implicitly_wait(10) # seconds
ff.get("http://somedomain/url_that_delays_loading")
myDynamicElement = ff.find_element_by_id("myDynamicElement")
【讨论】:
以下是我的获取代码。带我去研究,因为 jQuery.active 不适用于 fetch。这是帮助我代理获取的答案,但它仅适用于 ajax 不能获取 mock for selenium
public static void customPatchXMLHttpRequest(WebDriver driver) {
try {
if (driver instanceof JavascriptExecutor) {
JavascriptExecutor jsDriver = (JavascriptExecutor) driver;
Object numberOfAjaxConnections = jsDriver.executeScript("return window.openHTTPs");
if (numberOfAjaxConnections instanceof Long) {
return;
}
String script = " (function() {" + "var oldFetch = fetch;"
+ "window.openHTTPs = 0; console.log('starting xhttps');" + "fetch = function(input,init ){ "
+ "window.openHTTPs++; "
+ "return oldFetch(input,init).then( function (response) {"
+ " if (response.status >= 200 && response.status < 300) {"
+ " window.openHTTPs--; console.log('Call completed. Remaining active calls: '+ window.openHTTPs); return response;"
+ " } else {"
+ " window.openHTTPs--; console.log('Call fails. Remaining active calls: ' + window.openHTTPs); return response;"
+ " };})" + "};" + "var oldOpen = XMLHttpRequest.prototype.open;"
+ "XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {"
+ "window.openHTTPs++; console.log('xml ajax called');"
+ "this.addEventListener('readystatechange', function() {" + "if(this.readyState == 4) {"
+ "window.openHTTPs--; console.log('xml ajax complete');" + "}" + "}, false);"
+ "oldOpen.call(this, method, url, async, user, pass);" + "}" +
"})();";
jsDriver.executeScript(script);
} else {
System.out.println("Web driver: " + driver + " cannot execute javascript");
}
} catch (Exception e) {
System.out.println(e);
}
}
【讨论】:
这对我来说就像一个魅力:
public void waitForAjax() {
try {
WebDriverWait driverWait = new WebDriverWait(driver, 10);
ExpectedCondition<Boolean> expectation;
expectation = new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driverjs) {
JavascriptExecutor js = (JavascriptExecutor) driverjs;
return js.executeScript("return((window.jQuery != null) && (jQuery.active === 0))").equals("true");
}
};
driverWait.until(expectation);
}
catch (TimeoutException exTimeout) {
// fail code
}
catch (WebDriverException exWebDriverException) {
// fail code
}
return this;
}
【讨论】:
这对我有用
public void waitForAjax(WebDriver driver) {
new WebDriverWait(driver, 180).until(new ExpectedCondition<Boolean>(){
public Boolean apply(WebDriver driver) {
JavascriptExecutor js = (JavascriptExecutor) driver;
return (Boolean) js.executeScript("return jQuery.active == 0");
}
});
}
【讨论】:
如果您正在等待的控件是“Ajax”网页元素,则以下代码将等待它或任何其他 Ajax 网页元素完成加载或执行它需要执行的任何操作,以便您可以更安全地继续跟着你的脚步。
public static void waitForAjaxToFinish() {
WebDriverWait wait = new WebDriverWait(driver, 10);
wait.until(new ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver wdriver) {
return ((JavascriptExecutor) driver).executeScript(
"return jQuery.active == 0").equals(true);
}
});
}
【讨论】:
如上所述,您可以等待活动连接关闭:
private static void WaitForReady() {
WebDriverWait wait = new WebDriverWait(webDriver, waitForElement);
wait.Until(driver => (bool)((IJavaScriptExecutor)driver).ExecuteScript("return jQuery.active == 0"));
}
我的观察是这不可靠,因为数据传输发生得非常快。页面上的数据处理和呈现花费了更多时间,甚至jQuery.active == 0 数据可能还没有出现在页面上。
更明智的做法是使用explicit wait 作为页面上显示的元素,请参阅与此相关的一些答案。
最好的情况是,如果您的 Web 应用程序有一些自定义加载器或指示正在处理数据。在这种情况下,您可以等待此指示隐藏。
【讨论】:
我写了下一个方法作为我的解决方案(我没有任何负载指示器):
public static void waitForAjax(WebDriver driver, String action) {
driver.manage().timeouts().setScriptTimeout(5, TimeUnit.SECONDS);
((JavascriptExecutor) driver).executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"var xhr = new XMLHttpRequest();" +
"xhr.open('POST', '/" + action + "', true);" +
"xhr.onreadystatechange = function() {" +
" if (xhr.readyState == 4) {" +
" callback(xhr.responseText);" +
" }" +
"};" +
"xhr.send();");
}
然后我用实际的驱动程序调用了这个方法。 此post 中有更多描述。
【讨论】:
这是一个基于 Morten Christiansen 的回答的 groovy 版本。
void waitForAjaxCallsToComplete() {
repeatUntil(
{ return getJavaScriptFunction(driver, "return (window.jQuery || {active : false}).active") },
"Ajax calls did not complete before timeout."
)
}
static void repeatUntil(Closure runUntilTrue, String errorMessage, int pollFrequencyMS = 250, int timeOutSeconds = 10) {
def today = new Date()
def end = today.time + timeOutSeconds
def complete = false;
while (today.time < end) {
if (runUntilTrue()) {
complete = true;
break;
}
sleep(pollFrequencyMS);
}
if (!complete)
throw new TimeoutException(errorMessage);
}
static String getJavaScriptFunction(WebDriver driver, String jsFunction) {
def jsDriver = driver as JavascriptExecutor
jsDriver.executeScript(jsFunction)
}
【讨论】:
下面的代码 (C#) 确保显示目标元素:
internal static bool ElementIsDisplayed()
{
IWebDriver driver = new ChromeDriver();
driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
By locator = By.CssSelector("input[value='csharp']:first-child");
IWebElement myDynamicElement = wait.Until<IWebElement>((d) =>
{
return d.FindElement(locator);
});
return myDynamicElement.Displayed;
}
如果页面支持 jQuery,则可以使用jQuery.active function 确保在所有 ajax 调用完成后检索目标元素:
public static bool ElementIsDisplayed()
{
IWebDriver driver = new ChromeDriver();
driver.Url = "http://www.seleniumhq.org/docs/04_webdriver_advanced.jsp";
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
By locator = By.CssSelector("input[value='csharp']:first-child");
return wait.Until(d => ElementIsDisplayed(d, locator));
}
public static bool ElementIsDisplayed(IWebDriver driver, By by)
{
try
{
if (driver.FindElement(by).Displayed)
{
//jQuery is supported.
if ((bool)((IJavaScriptExecutor)driver).ExecuteScript("return window.$ != undefined"))
{
return (bool)((IJavaScriptExecutor)driver).ExecuteScript("return $.active == 0");
}
else
{
return true;
}
}
else
{
return false;
}
}
catch (Exception)
{
return false;
}
}
【讨论】:
如果使用python,你可以使用这个函数,点击按钮并等待DOM变化:
def click_n_wait(driver, button, timeout=5):
source = driver.page_source
button.click()
def compare_source(driver):
try:
return source != driver.page_source
except WebDriverException:
pass
WebDriverWait(driver, timeout).until(compare_source)
(信用:基于this stack overflow answer)
【讨论】:
jQuery.active == 0 是行不通的。
driver.page_source 行修改为driver.find_element_by_xpath(comparator_xpath).get_attribute('innerHTML')。这样一来,当加载元素处于动画状态时发生的事情就无关紧要了。
我也有类似的情况,我想等ajax请求让加载面板消失,我检查了请求前后的html,发现ajax加载面板有一个div,dix ajax请求时显示,请求结束后隐藏。我创建了一个函数来等待面板显示,然后等待它被隐藏
public void WaitForModalPanel()
{
string element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer' and not(contains(@style,'display: none'))]";
WebDriverWait wait = new WebDriverWait(driver, new TimeSpan(0, 2, 0));
wait.Until(ExpectedConditions.ElementIsVisible(By.XPath(element_xpath)));
element_xpath = ".//*[@id='ajaxLoadingModalPanelContainer' and contains(@style,'DISPLAY: none')]";
wait.Until(ExpectedConditions.ElementExists(By.XPath(element_xpath)));
}
查看this了解更多详情
【讨论】:
对于那些使用 primefaces 的人,只需这样做:
selenium.waitForCondition("selenium.browserbot.getCurrentWindow().$.active==0", defaultWaitingPeriod);
【讨论】:
比等待元素更通用的解决方案是等待与服务器的所有连接关闭。这将允许您等待所有 ajax 调用完成,即使它们没有任何回调因此不会影响页面。更多详情请见here。
使用 C# 和 jQuery,我创建了以下方法来等待所有 AJax 调用完成(如果有人有更直接的方法从 C# 访问 JS 变量,请发表评论):
internal void WaitForAjax(int timeOut = 15)
{
var value = "";
RepeatUntil(
() => value = GetJavascriptValue("jQuery.active"),
() => value == "0",
"Ajax calls did not complete before timeout"
);
}
internal void RepeatUntil(Action repeat, Func<bool> until, string errorMessage, int timeout = 15)
{
var end = DateTime.Now + TimeSpan.FromSeconds(timeout);
var complete = false;
while (DateTime.Now < end)
{
repeat();
try
{
if (until())
{
complete = true;
break;
}
}
catch (Exception)
{ }
Thread.Sleep(500);
}
if (!complete)
throw new TimeoutException(errorMessage);
}
internal string GetJavascriptValue(string variableName)
{
var id = Guid.NewGuid().ToString();
_selenium.RunScript(String.Format(@"window.$('body').append(""<input type='text' value='""+{0}+""' id='{1}'/>"");", variableName, id));
return _selenium.GetValue(id);
}
【讨论】:
我会用
waitForElementPresent(locator)
这将等到元素出现在 DOM 中。
如果您需要检查元素是否可见,您可能会更好地使用
waitForElementHeight(locator)
【讨论】: