I am trying to check all links on a page. Some questions already were asked on this topic, but for some reason none are working when I tried. One particular issue I'm having is that after going to a page and after getting all links into a list variable, when looping through them, error message shows the link to be a stale reference. Here is the code snippet:
var driver = new FirefoxDriver();
driver.Navigate().GoToUrl(URLPROD);
driver.Manage().Window.Maximize();
ICollection<IWebElement> links = driver.FindElements(By.TagName("a"));
foreach (var link in links)
{
if (!(link.Text.Contains("Email")) || !(link.Text == "") || !(link.Text == null) || !(link.Text.Contains("Element")))
{
((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", link);
Console.WriteLine(link);
driver.ExecuteScript("arguments[0].click();", link);
driver.Navigate().Back();
}
}
Error message: OpenQA.Selenium.StaleElementReferenceException: 'The element reference of is stale; either the element is no longer attached to the DOM, it is not in the current frame context, or the document has been refreshed'
What should I be doing to correct this error so that I can check each link on a page?
You could just re-find the links.
So 1. get number of links 2. loop that number getting the links fresh each time (to avoid stale errors).
var links = driver.FindElements(By.TagName("a"));
for (int i=0; i < links.Count(); i++)
{
var newLinks = driver.FindElements(By.TagName("a"));
newLinks[i].Click();
driver.Navigate().Back();
}
I'm using Selenium for retrieve data from this site, and I encountered a little problem when I try to click an element within a foreach.
What I'm trying to do
I'm trying to get the table associated to a specific category of odds, in the link above we have different categories:
As you can see from the image, I clicked on Asian handicap -1.75 and the site has generated a table through javascript, so inside my code I'm trying to get that table finding the corresponding element and clicking it.
Code
Actually I have two methods, the first called GetAsianHandicap which iterate over all categories of odds:
public List<T> GetAsianHandicap(Uri fixtureLink)
{
//Contains all the categories displayed on the page
string[] categories = new string[] { "-1.75", "-1.5", "-1.25", "-1", "-0.75", "-0.5", "-0.25", "0", "+0.25", "+0.5", "+0.75", "+1", "+1.25", "+1.5", "+1.75" };
foreach(string cat in categories)
{
//Get the html of the table for the current category
string html = GetSelector("Asian handicap " + asian);
if(html == string.Empty)
continue;
//other code
}
}
and then the method GetSelector which click on the searched element, this is the design:
public string GetSelector(string selector)
{
//Get the available table container (the category).
var containers = driver.FindElements(By.XPath("//div[#class='table-container']"));
//Store the html to return.
string html = string.Empty;
foreach (IWebElement container in containers)
{
//Container not available for click.
if (container.GetAttribute("style") == "display: none;")
continue;
//Get container header (contains the description).
IWebElement header = container.FindElement(By.XPath(".//div[starts-with(#class, 'table-header')]"));
//Store the table description.
string description = header.FindElement(By.TagName("a")).Text;
//The container contains the searched category
if (description.Trim() == selector)
{
//Get the available links.
var listItems = driver.FindElement(By.Id("odds-data-table")).FindElements(By.TagName("a"));
//Get the element to click.
IWebElement element = listItems.Where(li => li.Text == selector).FirstOrDefault();
//The element exist
if (element != null)
{
//Click on the container for load the table.
element.Click();
//Wait few seconds on ChromeDriver for table loading.
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);
//Get the new html of the page
html = driver.PageSource;
}
return html;
}
return string.Empty;
}
Problem and exception details
When the foreach reach this line:
var listItems = driver.FindElement(By.Id("odds-data-table")).FindElements(By.TagName("a"));
I get this exception:
'OpenQA.Selenium.StaleElementReferenceException' in WebDriver.dll
stale element reference: element is not attached to the page document
Searching for the error means that the html page source was changed, but in this case I store the element to click in a variable and the html itself in another variable, so I can't get rid to patch this issue.
Someone could help me?
Thanks in advance.
I looked at your code and I think you're making it more complicated than it needs to be. I'm assuming you want to scrape the table that is exposed when you click one of the handicap links. Here's some simple code to do this. It dumps the text of the elements which ends up unformatted but you can use this as a starting point and add functionality if you want. I didn't run into any StaleElementExceptions when running this code and I never saw the page refresh so I'm not sure what other people were seeing.
string url = "http://www.oddsportal.com/soccer/europe/champions-league/paok-spartak-moscow-pIXFEt8o/#ah;2";
driver.Url = url;
// get all the (visible) handicap links and click them to open the page and display the table with odds
IReadOnlyCollection<IWebElement> links = driver.FindElements(By.XPath("//a[contains(.,'Asian handicap')]")).Where(e => e.Displayed).ToList();
foreach (var link in links)
{
link.Click();
}
// print all the odds tables
foreach (var item in driver.FindElements(By.XPath("//div[#class='table-container']")))
{
Console.WriteLine(item.Text);
Console.WriteLine("====================================");
}
I would suggest that you spend some more time learning locators. Locators are very powerful and can save you having to stack nested loops looking for one thing... and then children of that thing... and then children of that thing... and so on. The right locator can find all that in one scrape of the page which saves a lot of code and time.
As you mentioned in related Post, this issue is because site executes an auto refresh.
Solution 1:
I would suggest if there is an explicit way to do refresh, perform that refresh on a periodic basis, or (if you are sure, when you need to do refresh).
Solution 2:
Create a Extension method for FindElement and FindElements, so that it try to get element for a given timeout.
public static void FindElement(this IWebDriver driver, By by, int timeout)
{
if(timeout >0)
{
return new WebDriverWait(driver, TimeSpan.FromSeconds(timeout)).Until(ExpectedConditions.ElementToBeClickable(by));
}
return driver.FindElement(by);
}
public static IReadOnlyCollection<IWebElement> FindElements(this IWebDriver driver, By by, int timeout)
{
if(timeout >0)
{
return new WebDriverWait(driver, TimeSpan.FromSeconds(timeout)).Until(ExpectedConditions.PresenceOfAllElementsLocatedBy(by));
}
return driver.FindElements(by);
}
so your code will use these like this:
var listItems = driver.FindElement(By.Id("odds-data-table"), 30).FindElements(By.TagName("a"),30);
Solution 3:
Handle StaleElementException using an Extension Method:
public static void FindElement(this IWebDriver driver, By by, int maxAttempt)
{
for(int attempt =0; attempt <maxAttempt; attempt++)
{
try
{
driver.FindElement(by);
break;
}
catch(StaleElementException)
{
}
}
}
public static IReadOnlyCollection<IWebElement> FindElements(this IWebDriver driver, By by, int maxAttempt)
{
for(int attempt =0; attempt <maxAttempt; attempt++)
{
try
{
driver.FindElements(by);
break;
}
catch(StaleElementException)
{
}
}
}
Your code will use these like this:
var listItems = driver.FindElement(By.Id("odds-data-table"), 2).FindElements(By.TagName("a"),2);
Use this:
string description = header.FindElement(By.XPath("strong/a")).Text;
instead of your:
string description = header.FindElement(By.TagName("a")).Text;
So my issue is as follows, I am attempting to check all elements on the page and verify element.GetAttribute("class") == expectedClass. Here is the code
var feedback = Driver.FindElements(AuctivaSalesPageModel.ViewFeedbackSelector);
var attempts = 0;
foreach (IWebElement element in feedback)
{
while (attempts < 3)
{
try
{
Assert.AreEqual("leaveFeed actionTaken", element.GetAttribute("class"));
attempts = 0;
break;
}
catch (StaleElementReferenceException)
{
Assert.AreEqual("leaveFeed actionTaken", element.GetAttribute("class"));
attempts = 0;
break;
}
catch (AssertionException)
{
System.Threading.Thread.Sleep(3000);
Driver.Navigate().Refresh();
AuctivaSalesPage.WaitForElementVisible(Driver, AuctivaSalesPageModel.TotalNumberOfSalesSelector);
AuctivaSalesPage.ScrollToTop();
AuctivaSalesPage.SelectNoFolder();
attempts++;
}
}
}
Now I have been reading up on the StaleElementException and I think that my catch and retry approach is useless as if the DOM has refreshed then the element within the list will always be stale. I believe what I need to do here is refind the element with a Driver.FindElement() but being that I am encountering this issue within a foreach loop of IWebElements I am not sure how to get the selector for the specific element that is failing to retry?
Should I catch the exception rebuild the list and then retry the whole foreach loop? or is there a way to extract the selector specific to the element within the loop so I can do something along the lines of
Assert.AreEqual("leaveFeed actionTaken", Driver.FindElement(By.someSelector(element.GetSelector)).GetAttribute("class"));
I hope this helps, but i had a similar problem and was able to get around it using the following logic, granted its not the best approach but it works:
var feedbackCount = Driver.FindElements(AuctivaSalesPageModel.ViewFeedbackSelector).Count();
var attempts = 0;
for(var i = 0; i < feedbackCount; i++)
{
while (attempts < 3)
{
var element = Driver.FindElements(AuctivaSalesPageModel.ViewFeedbackSelector).ElementAt(i);
//Continue you logic here
}
}
Hope this help
I am trying to verify if the added text in a list box has been successfully removed or not. What is the best way to handle this type of scenario in Selenium with C#?
Given below is the code I am using currently.
//Verify that the subject is added and then deleted
public static void VerifySubjectDel()
{
string subjectAddValue = GenerateRandomAlphaCode(200);
productPage.subjectAddTxtBx.SendKeys(subjectAddValue);
productPage.subjectAddBtn.Click();
IWebElement elem = WebDriver.FindElement(By.Id("Subjects_ListBox"));
SelectElement selectList = new SelectElement(elem);
IList<IWebElement> options = selectList.Options;
if (options.ToList().Any(tagname => tagname.Text.Contains(subjectAddValue)))
{
Assert.IsTrue(true);
selectList.SelectByText(subjectAddValue);
productPage.subjectDelBtn.Click();
WebDriver.SwitchTo().Alert().Accept();
bool subjectDel = WebDriver.FindElements(By.XPath(".//*[#id='Subjects_ListBox']//option[contains(text(),'" + subjectAddValue + "')]")).Count == 0;
if (subjectDel)
{
Assert.IsTrue(subjectDel);
}
else
Assert.IsTrue(subjectDel, "Subject not deleted successfully");
}
else
Assert.IsTrue(false, "The Subject added is not present in the Subject-ListBox");
}
I would call FindElements on the IWebElement you captured above and return all the elements within the list box. Then using LINQ you could do something like
bool success = !listBoxItems.Any(x => string.Compare(x.Text, subjectAddValue) == 0):
First off.. I am new to asp.net and EF.
I have an EntityDatsource on my page I would like to loop through each row in the result set.
My goal is to dynamically build a page based on the values in the result set. Then to post the information back after it is edited by the user. My plan was to iterate each row on the page_load event. Currently I just have p-code in the area I would like to make this happen. The p-code is as follows
// foreach (DataRow row in AvailableDeviceConfigDataSource.enti Rows)
// {
// if sectionHeading <> lastSectionHeading
// {
// lastSectionHeading = sectionHeading
// AddSettingsSection(sectionHeading)
// }
// AddRowObjects
// }
Any guidance would be much appreciated.
In case anybody comes across this and is interested, I did solve my issue a while ago and figured I should post my answer for the benefit of others....
using (var context = new MyEntities())
{
string lastSectionHeading = "";
bool isFirstHeading = true;
var dynamicPageItems = context.view_dynamicPageItems;
foreach (var item in dynamicPageItems)
{
if (item.IsActive == 1)
{
if (!lastSectionHeading.Equals(item.CategoryId))
{
if (!isFirstHeading)
CloseSection();
lastSectionHeading = item.CategoryId;
AddSettingsSection(item.CategoryDescription);
isFirstHeading = false;
}
AddControl( item.DataType );
}
}
}