【问题标题】:Selenium: Stale Element Reference (works fine when debugging)Selenium:过时的元素参考(调试时工作正常)
【发布时间】:2023-04-06 07:33:01
【问题描述】:

我正在尝试使用 C# 中的 Selenium 抓取一个页面,该页面有几个页面,我可以通过单击页面上的“下一步”按钮来浏览这些页面。我通常会收到一个错误,即存在 stale element reference,只有当我在没有断点的情况下运行它时才会发生这种情况。如果我一步一步完成程序,它工作得很好。我假设 Selenium 在不等待的情况下跳过重要的东西(即使我实现了等待方法)。

对代码来说,这是问题的主要逻辑:

foundVacancies.AddRange(FindVacanciesOnPage());
const string nextBtnXPath = "//*[@id=\"ContainerResultList\"]/div/div[3]/nav/ul/li[8]/a";
if (Driver.FindElements(By.XPath(nextBtnXPath)).Count != 0)
{
    while (TryClickingNextButton(nextBtnXPath))
    {
        foundVacancies.AddRange(FindVacanciesOnPage());
    }
}

此方法首先获取第一页上的所有项目并将它们添加到foundVacancies 列表中。之后,它将尝试寻找“下一步”按钮,如果没有足够的项目,该按钮并不总是存在。如果是,它会尝试单击它,刮掉页面,然后再次单击它,直到没有剩余页面为止。这在调试的时候效果很好,但是正常运行就很不对劲了。

获取页面所有item的方法,以及出错的位置:

private IEnumerable<string> FindVacanciesOnPage()
{
    var vacancies = new List<string>();

    var tableContainingAllVacancies = Driver.FindElement(By.XPath("//*[@id=\"ContainerResultList\"]/div/div[2]/div/ul"));
    var listOfVacancies = tableContainingAllVacancies.FindElements(By.XPath(".//li/article/div[1]/a"));

    foreach (var vacancy in listOfVacancies)
    {
        vacancies.Add(vacancy.FindElement(By.XPath(".//h2")).Text);
    }

    return vacancies;
}

这些项目在一个&lt;ul&gt; HTML 标记中,并有&lt;li&gt; 孩子,我将一一进行,并获取它们的内部文本。陈旧元素错误发生在foreach 循环中。我假设网络驱动程序没有时间重新加载 DOM,因为它在断点时工作。但是,我确实有一种方法可以等待页面完全加载,这是我在转到下一页时使用的方法。

private bool TryClickingNextButton(string nextButtonXPath)
{
    var nextButton = Driver.FindElement(By.XPath(nextButtonXPath));

    var currentUrl = Driver.Url;
    ScrollElementIntoView(nextButton);
    nextButton.Click();
    WaitUntilLoaded();
    var newUrl = Driver.Url;

    return !currentUrl.Equals(newUrl);
}

我正在比较新旧 URL 以确定这是否是最后一页。 WaitUntilLoaded 方法如下所示:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(30));
wait.Until(x => ((IJavaScriptExecutor) Driver).ExecuteScript("return document.readyState").Equals("complete"));

奇怪的是,有时 Web 驱动程序在加载第一页后立即关闭,没有任何错误或任何结果。我花了很多时间调试和搜索 SO,但似乎找不到任何信息,因为代码在通过断点时运行良好。

我只尝试过 Chrome,有无无头模式,但我不认为这可能是 Chrome 问题。

“下一步”按钮具有以下 HTML:

<a href="" data-jn-click="nextPage()" data-ng-class="{'disabled-element':currentPage === totalPages}" tabindex="0">
    <span class="hidden-md hidden-sm hidden-xs">Next <span class="icon icon-pagination-single-forward"></span></span>
    <span class="hidden-lg icon icon-pagination-forward-enable"></span>
</a>

我找不到data-jn-click 是什么。我试图只执行 JavaScript nextPage();,但这并没有做任何事情。

【问题讨论】:

  • 当您尝试查找下一个 (,next...) .//h2's Text 时,我感觉到网页是动态更新的。或者,当您开始查找第一个文本时,页面没有完全加载。我建议给予一定的等待时间并使用 FindElements 而不是多个 FindElements 并将找到的每个元素添加到“空缺”列表中,这应该会给您带来更好的性能和可靠性。
  • 我认为 Selenium 能够等待特定类被加载,这有助于您的测试更加健壮。检查此链接seleniumhq.github.io/selenium/docs/api/dotnet/html/…
  • @kurakura88 我认为你是对的,我编辑了我的问题并添加了按钮/链接 HTML。我不知道“data-jn-click”到底是什么。我会尝试实现一些东西,等待一两秒钟,看看它做了什么。
  • @sjmarsh 我尝试在我的问题中使用 JavaScript 方法等待,但我也会尝试等待我需要的元素,看看它是否有效!会让你知道的。
  • 您是否必须更改页面的状态才能定位过时的元素?如果您必须翻页或单击任何链接/按钮,那么以前未显示的元素将变得陈旧,因为它们的 css 将发生变化

标签: c# selenium selenium-webdriver web-scraping selenium-chromedriver


【解决方案1】:

我没有任何 c# 经验,所以如果有错请不要介意。 您正在使用 findElements 并将其存储到 var listOfVacancies。我推荐了一些网站。你为什么不使用ReadOnlyCollection&lt;IWebElement&gt;。最好将所有元素存储为 List 并遍历它。 于是代码变成了,

ReadOnlyCollection<IWebElement> listOfVacancies = tableContainingAllVacancies.FindElements(By.XPath(".//li/article/div[1]/a"));

【讨论】:

  • FindElements 实际上返回一个 IWebElement 的列表。如果清楚变量具有哪种类型,则在 C# 中使用“var”只会提高可读性。不过感谢您的回答!
  • @JohannesMols..请查看以下链接。可能有一些参考可以帮助您解决问题。 Refer this link
【解决方案2】:

如果进入 listOfVacancies 的元素是通过 ajax 调用填充的,那么 document.readystate 将无法捕捉到这一点。尝试使用:

wait.Until(x => ((IJavaScriptExecutor) Driver).ExecuteScript("return jQuery.active").Equals("0"));

【讨论】:

  • 感谢您的回答。不幸的是,这个事件没有被触发并且 WebDriverWait 将超时,所以我猜该网站没有使用 Ajax。
【解决方案3】:

我终于找到了解决这个问题的方法。它很脏,但它有效。我尝试了许多不同的方法来等待页面完全加载,但都没有奏效。于是我走上了Thread.Sleep的黑暗之路,但它并没有听起来那么糟糕:

private IEnumerable<string> FindVacanciesOnPage()
{
    return FindVacanciesOnPage(new List<string>(), 0, 50, 15000);
}

private IEnumerable<string> FindVacanciesOnPage(ICollection<string> foundVacancies, long waitedTime, int interval, long maxWaitTime)
{
    try
    {
        var list = Driver.FindElements(By.XPath("//*[@data-ng-bind=\"item.JobHeadline\"]"));
        foreach (var vacancy in list)
        {
            foundVacancies.Add(vacancy.Text);
        }
    }
    catch (Exception)
    {
        if (waitedTime >= maxWaitTime) throw;

        Thread.Sleep(interval);
        waitedTime += interval;

        return FindVacanciesOnPage(foundVacancies, waitedTime, interval, maxWaitTime);

    }

    return foundVacancies;
}

这将尝试获取项目,如果抛出异常,则等待一定时间,直到再次尝试。当等待指定的最长时间时,最终抛出异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-13
    • 2019-04-27
    • 1970-01-01
    • 2016-10-13
    • 1970-01-01
    • 1970-01-01
    • 2016-07-16
    相关资源
    最近更新 更多