Reading paged datagrids in Selenium Webdriver

Viewed 206

I keep getting this exception in Selenium ...

OpenQA.Selenium.StaleElementReferenceException: 'stale element reference: element is not attached to the page document (Session info: chrome=60.0.3112.113) (Driver info: chromedriver=2.32.498550 (9dec58e66c31bcc53a9ce3c7226f0c1c5810906a),platform=Windows NT 10.0.15063 x86_64)'

This is insanely frustrating and I can't understand why I get the exception thrown in the return statement inside the BuildOrg method below.

Here's the code that triggers the problem ...

for (int i = 0; i <= lastPage; i++)
{
    orgs.AddRange( 
        browser
            .FindElement(By.Id("all_organizations"))
            .FindElement(By.TagName("tbody"))
            .FindElements(By.TagName("tr"))
            .Select(e => BuildOrg(e))
    );

    browser
        .FindElement(By.Id("all_organizations_next"))
        .FindElement(By.TagName("a"))
        .Click();
}

 .....


Organisation BuildOrg(IWebElement orgElement)
{
    var cells = orgElement.FindElements(By.TagName("td"));
    var avatarUrl = cells[0].FindElement(By.TagName("img")).GetAttribute("src");
    var c = DateTimeOffset.Parse(cells[2].Text);
    return new Organisation
    {
        Id = int.Parse(avatarUrl.Split('_')[1].Split('/')[0]),
        Avatar = avatarUrl,
        Name = cells[1].Text,
        Link = cells[1].FindElement(By.TagName("a")).GetAttribute("href"),
        CreatedOn = c
    };
}

Can any lend any light on this?

3 Answers

One of the row is probably being updated between two .FindElement(s). Web elements are no longer usable if the page was updated or if the DOM element was replaced.

It's probably because the second iteration following the .Click starts even though the rows are not yet refreshed.

To overcome this issue, try to use a single .FindElement(s) and wait for one element to become stale after the Click:

for (int i = 0; i <= lastPage; i++)
{
    var rows = browser
      .FindElements(By.CssSelector("#all_organizations tbody tr"));

    orgs.AddRange(rows.Select(e => BuildOrg(e)));

    var link = browser
      .FindElement(By.CssSelector("a"))

    link.Click();

    new WebDriverWait(browser, TimeSpan.FromSeconds(3))
      .Until(ExpectedConditions.StalenessOf(rows[0]));
}

StaleElementReferenceException usually dealt with refresh of the entire DOM or only the component which is changed.

I am not able to understand the flow between for loop and buildOrg method that is returning Organization. Assuming the exception happening in one of the findElements method in buildOrg method:

try { 
  // do action that refresh DOM element
  // add wait condition if required 
  var cells = orgElement.findElements(By.TagName("td"));
} catch(StaleElementReferenceException ex) {
  // do action that refresh DOM element
  // add wait condition if required 
  var cells = orgElement.findElements(By.TagName("td"));
}

You may need to re-design your code in order to accommodate the above mentioned hack. Hopefully it helps!!!

It turns out that no amount of using Seleniums tools to "wait" click events results in the expected behaviour so I simply gave up and put in a "configurable delay" that I just pull from config to handle this.

To my mind Selenium is buggy as hell and simple things like browsing a to a url, clicking some buttons then browsing to another url and repeating the process often results in unexpected exceptions (like a socket exception because it crashed the browser instance) for seemingly no reason.

I would highly recommend that anyone with a Selenium issue simply go find another solution but if that fails do what I did ...

DoSeleniumStuff();
Thread.Sleep(configuredDelay);
DoSeleniumStuff();

It's ugly but it solves the problem for me. I guess I can always thread multiple instances and do pieces on each thread to speed things up but that's ugly as hell too.

I also found the edge driver to be a ton more reliable than the chrome one.

Related