Reputation: 1397
I have a list defined as a propterty on a selenium page, e.g:
public IEnumerable<MyItem> MyList =>
webDriverInstance.FindListElementsWithWait<MyItem>(By.ClassName("my-item"));
Where FindListElementsWithWait is an extension method:
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;
}
This is making use of WebDriverWait rather than a Thread.Sleep. As this is a list, there is no unique locator for each MyItem. This approach works but there are a few quirks.
for example:
public class MyItem
{
public string Name { get; private set; }
public MyItem(IWebElement webElement)
{
Name = webElement.FindElement(By.TagName("H4")).Text;
}
}
This works, but the issue I have is if I had additional properties on MyItem which aren't initially on the element. For example, if I change some state and now a cancel button appears. If I have the cancel button initialisation in the constructor then it would throw a NoSuchElementException and fail on page load. I can get around this by providing a getter body for the new Cancel button property. But this is producing/promoting fragile tests.
Has anyone found a clean way to update/refresh a stale WebElement within a list where there is no unique locator for the stale WebElement?
Upvotes: 2
Views: 1690
Reputation: 1397
Ok, so this was a race condition in the javascript where the webdriver was doing things before an AJAX request had completed. @JeffC made a good point about using the driver to pull web elements before doing something with them, especially with a list.
In my particular case I was deleting an element from a list. So if you are getting a StaleElementReferenceException and are deleting an element from the view then this maybe useful to you:
I found the following article quite helpful when solving this problem: https://bocoup.com/weblog/a-day-at-the-races
Upvotes: 1
Reputation: 25644
In my opinion you are over engineering the problem. I like to keep things simple.
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
I would suggest you also look into the page object model. Define all your locators for a given page in the page object and then provide methods to perform actions on the page utilizing those locators.
Upvotes: 0