我有一个相对简单的方法来等待元素存在并显示出来。该方法处理的情况是,为指定的对象返回一个以上的元素(通常我们只希望显示其中的一个元素,但无论如何,该方法将返回找到的第一个显示的元素)。
我遇到的问题是,当页面上没有匹配的元素时,所花费的时间要比指定的TimeSpan多*,我不知道为什么。
*我刚测试了30分钟的超时时间,花费了5米多一点
代码:
/// <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秒钟。
看起来,这可能与封装在WebDriver.FindElements()中的SpinWait.WaitUntil()中内置的继承等待的混合有关。
想听听你们对这个难题的看法。
干杯!
发布于 2020-11-10 08:11:28
通过进一步的测试,我发现将WebDriver隐式等待超时减少到一个较低的数目(例如100 to )可以解决这个问题。这与@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);
}
}发布于 2020-11-10 07:34:17
这是因为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;
}请注意上面“这里”注释下面的条件。它只检查spinWait.NextSpinWillYield返回true时超时是否已过期。这意味着:如果下一个旋转将导致上下文切换和超时过期-放弃并返回。但否则-继续旋转甚至不检查超时。
NextSpinWillYield结果取决于以前的自旋数。基本上,这个构造旋转X次(我相信10次),然后开始产生(将当前线程时间切片让给其他线程)。
在您的例子中,SpinUntil内部的条件需要很长的时间来评估,这完全违背了SpinWait的设计--它期望条件评估完全不需要时间(而SpinWait实际上是适用的--这是真的)。假设在你的情况下,一次状态评估需要5秒。然后,即使超时是1秒-它将旋转10倍的第一个(50秒总数),甚至检查超时。这是因为SpinWait不是为您试图使用它的东西而设计的。来自文档
System.Threading.SpinWait是一种轻量级同步类型,可以在低级别场景中使用,以避免内核事件所需的昂贵上下文切换和内核转换。在多核计算机上,当资源不被期望长时间保持时,等待线程在用户模式下旋转几十个或几百个周期,然后重试获取资源,效率会更高。如果资源在旋转后可用,那么您已经保存了几千个循环。如果资源仍然不可用,那么您只花了几个周期,仍然可以输入一个基于内核的等待。这种旋转-然后等待的组合有时被称为两阶段等待操作.
在我看来,这些都不适用于你的情况。文档的另一部分声明"SpinWait对于普通应用程序通常并不有用“。
在这种情况下,使用如此长的条件评估时间-您可以只在一个循环中运行它而不需要额外的等待或旋转,并且手动检查每次迭代是否超时。
https://stackoverflow.com/questions/64763173
复制相似问题