如何防止在foreach循环中出现“陈旧元素”?

时间:2018-08-08 07:20:45

标签: c# selenium

我正在使用Selenium从此site,检索数据,当我尝试单击foreach中的元素时遇到了一个小问题。

我要做什么

我正在尝试将表格与特定赔率类别相关联,在上面的链接中,我们有不同的类别:

enter image description here

从图像中可以看到,我单击了Asian handicap -1.75,该站点已通过javascript生成了一个表,因此在我的代码中,我试图获取该表以查找相应的元素并单击它。 / p>

代码

实际上,我有两种方法,第一种称为GetAsianHandicap,它会遍历所有类别的赔率:

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
    }
}

然后是方法GetSelector,它单击了搜索到的元素,这就是设计:

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;
    }

问题和异常详细信息

foreach到达以下行:

var listItems = driver.FindElement(By.Id("odds-data-table")).FindElements(By.TagName("a"));

我收到此异常:

  WebDriver.dll中的

'OpenQA.Selenium.StaleElementReferenceException'   旧元素参考:元素未附加到页面文档

搜索错误意味着html页面源已更改,但是在这种情况下,我将元素存储为要单击的变量,而将html本身存储为另一个变量,因此我无法摆脱这一问题。 / p>

有人可以帮助我吗?

谢谢。

3 个答案:

答案 0 :(得分:2)

正如您在相关Post中所提到的,此问题是因为网站执行了自动刷新。

  

解决方案1:

我建议是否有明确的刷新方法,定期执行刷新,或者(如果确定,何时需要刷新)。

  

解决方案2:

FindElementFindElements创建一个扩展方法,以便它尝试获取给定超时的元素。

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);
}

因此您的代码将使用以下代码:

var listItems = driver.FindElement(By.Id("odds-data-table"), 30).FindElements(By.TagName("a"),30);
  

解决方案3:

使用扩展方法处理StaleElementException:

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)
       {
       }
    }
}

您的代码将使用以下代码:

var listItems = driver.FindElement(By.Id("odds-data-table"), 2).FindElements(By.TagName("a"),2);

答案 1 :(得分:2)

我查看了您的代码,我认为您正在使它变得比所需的更加复杂。我假设您要刮擦单击残障链接之一时显示的表。这是一些简单的代码可以做到这一点。它会转储元素的文本,这些文本最终会以无格式显示,但是您可以以此为起点并根据需要添加功能。运行此代码时,我没有遇到任何StaleElementExceptions,并且我从未看到页面刷新过,所以我不确定其他人在看什么。

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("====================================");
}

我建议您花一些时间来学习定位器。定位器功能非常强大,可以省去堆叠嵌套循环以查找一件事……然后是该事物的子项……然后是该事物的子项……等等的麻烦。正确的定位器可以在页面的一部分中找到所有内容,从而节省了大量代码和时间。

答案 2 :(得分:-1)

使用此:

string description = header.FindElement(By.XPath("strong/a")).Text;

代替您的

string description = header.FindElement(By.TagName("a")).Text;