SpinWait.Spin - пока не потребуется НАМНОГО больше времени, чем таймаут, для выхода в ожидании существования элемента селния
У меня есть относительно простой способ дождаться, пока элемент не появится и не отобразится. Метод обрабатывает ситуацию, когда для данного By возвращается более одного элемента (обычно мы ожидаем отображения только одного из них, но в любом случае метод вернет первый найденный отображаемый элемент).
Проблема, с которой я сталкиваюсь, заключается в том, что, когда на странице нет соответствующего элемента (вообще), он занимает больше времени *, чем указано в 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://stackru.com/questions");
var nonExistenetElementBy = By.CssSelector("#custom-header99");
FindDisplayedElement(nonExistenetElementBy , 10);
}
Если вы запустите тест (с таймаутом 10 секунд), вы обнаружите, что для фактического выхода требуется около 100 секунд.
Похоже, это может иметь какое-то отношение к сочетанию ожидания наследования, встроенного в WebDriver.FindElements(), заключенного внутри SpinWait.WaitUntil().
Хотел бы услышать, что вы думаете об этой загадке.
Ура!
2 ответа
Проведя дополнительное тестирование, я обнаружил, что уменьшение времени ожидания неявного ожидания 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);
}
}
Это потому что
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
возвращает истину. Это означает следующее: если следующее вращение приведет к переключению контекста и истечет время ожидания - сдавайтесь и возвращайтесь. А в противном случае - продолжайте крутить, даже не проверив таймаут.
NextSpinWillYield
результат зависит от количества предыдущих вращений. В основном эта конструкция вращает X количество раз (я полагаю, 10), затем начинает уступать (отдавать текущий временной отрезок потока другим потокам).
В вашем случае условие внутри
SpinUntil
требуется ОЧЕНЬ много времени для оценки, что полностью противоречит дизайну SpinWait - он ожидает, что оценка состояния вообще не займет времени (и там, где SpinWait действительно применим, это правда). Допустим, в вашем случае одна оценка состояния занимает 5 секунд. Затем, даже если тайм-аут равен 1 секунде, он сначала будет вращаться 10 раз (всего 50 секунд), прежде чем даже проверять тайм-аут. Это потому, что SpinWait не предназначен для того, для чего вы пытаетесь его использовать. Из документации:
System.Threading.SpinWait - это облегченный тип синхронизации, который можно использовать в низкоуровневых сценариях, чтобы избежать дорогостоящих переключений контекста и переходов ядра, которые требуются для событий ядра. На многоядерных компьютерах, когда ожидается, что ресурс не будет удерживаться в течение длительного периода времени, может быть более эффективным, чтобы ожидающий поток вращался в пользовательском режиме в течение нескольких десятков или нескольких сотен циклов, а затем повторил попытку получить ресурс.. Если ресурс доступен после отжима, то вы сэкономили несколько тысяч циклов. Если ресурс по-прежнему недоступен, значит, вы потратили всего несколько циклов и все еще можете войти в режим ожидания на основе ядра. Эту комбинацию вращения и ожидания иногда называют двухфазной операцией ожидания.
На мой взгляд, все это не применимо к вашей ситуации. В другой части документации говорится, что "SpinWait обычно не используется для обычных приложений".
В этом случае с таким большим временем оценки условия - вы можете просто запустить его в цикле без дополнительного ожидания или вращения и вручную проверять, истек ли тайм-аут на каждой итерации.