WebElements列表中的Selenium Web Driver Stale Reference异常

时间:2015-11-18 16:40:47

标签: java c# selenium selenium-webdriver

我在selenium页面上有一个列为propterty的列表,例如:

public IEnumerable<MyItem> MyList =>
        webDriverInstance.FindListElementsWithWait<MyItem>(By.ClassName("my-item"));

其中FindListElementsWithWait是一个扩展方法:

public static IEnumerable<T> FindListElementsWithWait<T>(this IWebDriver driver, By locator, int timeOutMilliseconds = 10000)
{
    var elements = new List<T>();
    var wait = new WebDriverWait(driver, TimeSpan.FromMilliseconds(timeOutMilliseconds));
    wait.Until(d =>
        {
            try
            {  
                elements = d.FindElements(locator).Select(x => (T)Activator.CreateInstance(typeof(T), x)).ToList();
                return true;
            }
            catch (TargetInvocationException)
            {
                return false;
            }
        }
    );

    return elements;
} 

这是使用WebDriverWait而不是Thread.Sleep。由于这是一个列表,因此每个MyItem都没有唯一的定位器。这种方法有效,但有一些怪癖。

  • 依赖于MyItem的构造函数(扩展方法示例中的T)抛出异常(StaleElementReferenceException)
  • 这意味着将属性初始化放在MyItem的构造函数中

例如:

public class MyItem
{
    public string Name { get; private set; }

    public MyItem(IWebElement webElement)
    {
        Name = webElement.FindElement(By.TagName("H4")).Text;
    }
}

这是有效的,但我遇到的问题是,如果我在MyItem上有其他属性,这些属性最初不在元素上。例如,如果我更改某个状态,现在会出现取消按钮。如果我在构造函数中初始化取消按钮,那么它将抛出NoSuchElementException并在页面加载时失败。我可以通过为新的Cancel按钮属性提供一个getter主体来解决这个问题。但这是产生/促进脆弱的测试。

有没有人找到一种干净的方法来更新/刷新列表中的陈旧WebElement,其中没有唯一的定位器用于陈旧的WebElement?

2 个答案:

答案 0 :(得分:1)

好的,所以这是javascript中的竞争条件,其中webdriver在AJAX请求完成之前正在做事情。 @JeffC在使用驱动程序拉取web元素之前提出了一个很好的观点,特别是使用列表。

在我的特定情况下,我正在删除列表中的元素。因此,如果您收到StaleElementReferenceException并从视图中删除元素,那么这可能对您有用:

  • 确保您使用webdriver获取页面上最新的元素。因此,如果您要更改页面状态,请确保通过使用webdriver再次找到它们来刷新元素
  • 如果您仍然遇到异常,那么javascript中可能存在竞争条件。因此,在执行webelement引用之前,请确保您正在等待任何动画/ ajax请求完成。

我在解决此问题时发现以下文章非常有用:https://bocoup.com/weblog/a-day-at-the-races

答案 1 :(得分:0)

在我看来,你是在设计这个问题。我喜欢简单。

By MyItemsBeforeLocator = By.TagName("h4"); // locator for the element BEFORE the page state change
By MyItemsAfterLocator = By.CssSelector("h4.my-item"); // locator for the element AFTER the page state change
WebDriverWait wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
ReadOnlyCollection<IWebElement> Items = wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(MyItemsBeforeLocator));
// do stuff with Items

// do stuff that changes the state of the page

Items = wait.Until(ExpectedConditions.VisibilityOfAllElementsLocatedBy(MyItemsAfterLocator));
// do stuff with Items

我建议您也查看页面对象模型。在页面对象中定义给定页面的所有定位器,然后提供使用这些定位器在页面上执行操作的方法。