[英]SpinWait.SpinUntil taking MUCH longer than timeout to exit while waiting for Selnium element to exist
我有一个相对简单的方法来等待元素存在并显示。 该方法处理为给定 By 返回多个元素的情况(通常我们只希望显示其中一个元素,但无论如何该方法将返回找到的第一个显示元素)。
我遇到的问题是,当页面上(根本)没有匹配的元素时,它花费的时间比指定的 TimeSpan 多*,我不知道为什么。
*我刚刚测试了 30 秒的超时时间,花了 5m 多一点
代码:
/// <summary>
/// Returns the (first) element that is displayed when multiple elements are found on page for the same by
/// </summary>
public static IWebElement FindDisplayedElement(By by, int secondsToWait = 30)
{
WebDriver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(secondsToWait);
// Wait for an element to exist and also displayed
IWebElement element = null;
bool success = SpinWait.SpinUntil(() =>
{
var collection = WebDriver.FindElements(by);
if (collection.Count <= 0)
return false;
element = collection.ToList().FirstOrDefault(x => x.Displayed == true);
return element != null;
}
, TimeSpan.FromSeconds(secondsToWait));
if (success)
return element;
// if element still not found
throw new NoSuchElementException("Could not find visible element with by: " + by.ToString());
}
你会用这样的东西来调用它:
[Test]
public void FindDisplayedElement()
{
webDriver.Navigate().GoToUrl("https://stackoverflow.com/questions");
var nonExistenetElementBy = By.CssSelector("#custom-header99");
FindDisplayedElement(nonExistenetElementBy , 10);
}
如果您运行测试(超时 10 秒),您会发现实际退出大约需要 100 秒。
看起来它可能与包含在 SpinWait.WaitUntil() 中的 WebDriver.FindElements() 中内置的继承等待的混合有关。
想听听你们对这个难题的看法。
干杯!
那是因为SpinWait.WaitUntil
的实现方式如下:
public static bool SpinUntil(Func<bool> condition, TimeSpan timeout) {
int millisecondsTimeout = (int) timeout.TotalMilliseconds;
long num = 0;
if (millisecondsTimeout != 0 && millisecondsTimeout != -1)
num = Environment.TickCount;
SpinWait spinWait = new SpinWait();
while (!condition())
{
if (millisecondsTimeout == 0)
return false;
spinWait.SpinOnce();
// HERE
if (millisecondsTimeout != -1 && spinWait.NextSpinWillYield && millisecondsTimeout <= (Environment.TickCount - num))
return false;
}
return true;
}
注意上面“HERE”注释下的条件。 它只检查超时是否已过期,如果spinWait.NextSpinWillYield
返回 true。 这意味着:如果下一次旋转将导致上下文切换并且超时到期 - 放弃并返回。 但除此之外 - 甚至不检查超时就继续旋转。
NextSpinWillYield
结果取决于之前的旋转次数。 基本上这个构造旋转 X 次(我相信是 10 次),然后开始屈服(将当前线程时间片交给其他线程)。
在您的情况下, SpinUntil
内的SpinUntil
需要很长时间来评估,这完全违反 SpinWait 的设计 - 它期望条件评估根本不需要时间(并且 SpinWait 实际上适用 - 这是真的)。 假设对您的情况进行一次条件评估需要 5 秒。 然后,即使超时是 1 秒 - 它会在检查超时之前先旋转 10 次(总共 50 秒)。 那是因为 SpinWait 不是为您尝试使用它的目的而设计的。 从文档:
System.Threading.SpinWait 是一种轻量级同步类型,您可以在低级场景中使用它来避免内核事件所需的昂贵的上下文切换和内核转换。 在多核计算机上,当资源预计不会被长时间持有时,等待线程在用户模式下旋转几十或几百个周期,然后重新尝试获取资源会更有效率. 如果旋转后资源可用,那么您就节省了数千个周期。 如果资源仍然不可用,那么您只花了几个周期,仍然可以进入基于内核的等待。 这种旋转然后等待的组合有时被称为两阶段等待操作。
在我看来,这些都不适用于您的情况。 文档的另一部分指出“SpinWait 通常对普通应用程序没有用”。
在这种情况下,有这么长的条件评估时间 - 您可以在循环中运行它而无需额外等待或旋转,并手动检查每次迭代是否超时。
做一些进一步的测试,我发现将 WebDriver 隐式等待超时减少到一个较低的数字(例如 100 毫秒)可以解决这个问题。 这对应于@Evk提供的解释为什么使用 SpinUntil 不起作用。
我已将函数更改为使用 WebDriverWait(如对另一个问题的回答所示),现在它可以正常工作。 这完全不需要使用隐式等待超时。
/// <summary>
/// Returns the (first) element that is displayed when multiple elements are found on page for the same by
/// </summary>
/// <exception cref="NoSuchElementException">Thrown when either an element is not found or none of the found elements is displayed</exception>
public static IWebElement FindDisplayedElement(By by, int secondsToWait = DEFAULT_WAIT)
{
var wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(secondsToWait));
try
{
return wait.Until(condition =>
{
return WebDriver.FindElements(by).ToList().FirstOrDefault(x => x.Displayed == true);
});
}
catch (WebDriverTimeoutException ex)
{
throw new NoSuchElementException("Could not find visible element with by: " + by.ToString(), ex);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.