如何让webDriver等待页面加载(C# Selenium项目)

32

我在C#中开始了一个Selenium项目。尝试等待页面完成加载,之后才进行下一步操作。

我的代码看起来像这样:

 loginPage.GoToLoginPage();
        loginPage.LoginAs(TestCase.Username, TestCase.Password);
        loginPage.SelectRole(TestCase.Orgunit);
        loginPage.AcceptRole();

在loginPage内,选择角色(TestCase.Orgunit):

 RoleHierachyLabel = CommonsBasePage.Driver.FindElement(By.XPath("//span[contains(text(), " + role + ")]"));
 RoleHierachyLabel.Click();
 RoleLoginButton.Click();

我正在寻找元素RoleHierachyLabel。 我一直在尝试使用多种方法等待页面加载或搜索一些允许超时的元素属性:

1. _browserInstance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(5));

2. public static bool WaitUntilElementIsPresent(RemoteWebDriver driver, By by, int timeout = 5)
    {
        for (var i = 0; i < timeout; i++)
        {
            if (driver.ElementExists(by)) return true;
        }
        return false;
    }

你如何应对这个障碍?


1
可能是等待Selenium中的页面加载的重复问题。 - serv-inc
10个回答

47

我一直在寻找替代方案,并已选择以下版本。第一个版本使用明确的等待和定义的超时时间,基于元素属性;第二个版本则基于元素失效。

首选是检查元素属性,直到达到超时时间。我已确认以下属性可证实该元素在页面上存在:

存在性 - 检查页面 DOM 中是否存在该元素的期望值。但这并不一定意味着元素可见。

//this will not wait for page to load
Assert.True(Driver.FindElement(By elementLocator).Enabled)

//this will search for the element until a timeout is reached
public static IWebElement WaitUntilElementExists(By elementLocator, int timeout = 10)
    {
        try
        {
            var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeout));
            return wait.Until(ExpectedConditions.ElementExists(elementLocator));
        }
        catch (NoSuchElementException)
        {
            Console.WriteLine("Element with locator: '" + elementLocator + "' was not found in current context page.");
            throw;
        }
    }

可见性 - 检查页面的DOM上是否存在元素并且可见的期望。可见性意味着该元素不仅被显示,而且其高度和宽度大于0。

//this will not wait for page to load
Assert.True(Driver.FindElement(By elementLocator).Displayed)

//this will search for the element until a timeout is reached
public static IWebElement WaitUntilElementVisible(By elementLocator, int timeout = 10)
    {
        try
        {
            var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeout));
            return wait.Until(ExpectedConditions.ElementIsVisible(elementLocator));
        }
        catch (NoSuchElementException)
        {
            Console.WriteLine("Element with locator: '" + elementLocator + "' was not found.");
            throw;
        }
    }

可点击的 - 检查元素是否可见和启用,以便您可以单击它的期望。

//this will not wait for page to load
//both properties need to be true in order for element to be clickable
Assert.True(Driver.FindElement(By elementLocator).Enabled)
Assert.True(Driver.FindElement(By elementLocator).Displayed)

//this will search for the element until a timeout is reached
public static IWebElement WaitUntilElementClickable(By elementLocator, int timeout = 10)
    {
        try
        {
            var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeout));
            return wait.Until(ExpectedConditions.ElementToBeClickable(elementLocator));
        }
        catch (NoSuchElementException)
        {
            Console.WriteLine("Element with locator: '" + elementLocator + "' was not found in current context page.");
            throw;
        }
    }

第二选择适用于触发对象(例如菜单项)在被点击后从DOM中分离的情况下。当元素上的点击动作将触发重定向到另一个页面时,通常会出现这种情况。在这种情况下,使用检查StalenessOf(element)很有用,其中element是触发重定向到新页面的项目。

public static void ClickAndWaitForPageToLoad(By elementLocator, int timeout = 10)
    {
        try
        {
            var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(timeout));
            var element = Driver.FindElement(elementLocator);
            element.Click();
            wait.Until(ExpectedConditions.StalenessOf(element));
        }
        catch (NoSuchElementException)
        {
            Console.WriteLine("Element with locator: '" + elementLocator + "' was not found in current context page.");
            throw;
        }
    }

非常出色和详尽的回答! 有没有一种方法可以等待弹出窗口加载? - Lars
谢谢@Lars!弹出窗口交互可以有不同的处理方式:首先考虑使用WindowHandles查找弹出窗口,然后使用driver.SwitchTo()与之交互。 - clau84
不再希望在我的 C# Selenium WebDriver 测试中依赖于 ExpectedConditions。https://www.ontestautomation.com/on-no-longer-wanting-to-depend-on-expectedconditions-in-my-c-selenium-webdriver-tests/ - arman
3
"ExpectedConditions已过时。可以使用lambda代替:Wait.Until(x => x.FindElement(By.Id("xxx"))); Wait.Until()默认忽略NoSuchElementException。" - Rohim Chou
1
ExpectedConditions being obsolete, you can use lambda as suggested above, or use DotNetSeleniumExtras as described in this post: https://dev59.com/AlUL5IYBdhLWcg3wvqTAtldr; Install-Package DotNetSeleniumExtras.WaitHelpers -Version 3.11.0 - Machtyn

8

driver.Manage().Timeouts().PageLoad = TimeSpan.FromSeconds(5);

另外,查看此答案


3
请说明这个准确的作用。例如:这与“ImplicitWait”有何不同? - Kellen Stuart
并不总是有用的;您可能不希望用户期望页面停止完全加载,仅因为设置了超时间隔。此外,您将没有时间研究不同计算机、浏览器和互联网连接限制下的预期负载时间。 - Rekkon

3

我遇到了相同的问题。 使用以下方法,我等待页面完全加载,而不依赖于JavaScript引起页面加载,点击或操作输入元素。

private void WaitForPageToLoad(Action doing)
{
    IWebElement oldPage = _driver.FindElement(By.TagName("html"));
    doing();
    WebDriverWait wait = new WebDriverWait(_driver, new TimeSpan(0, 0, Timeout));
    try
    {
        wait.Until(driver => ExpectedConditions.StalenessOf(oldPage)(_driver) &&
            ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));
    }
    catch (Exception pageLoadWaitError)
    {
        throw new TimeoutException("Timeout during page load", pageLoadWaitError);
    }
}

称为以下内容

WaitForPageToLoad(() => _driver.FindElement(By.Id("button1")).Click());

3

我通常使用显式等待,等待元素可见后再执行下一步操作。代码应该如下所示:

WebDriverWait waitForElement = new WebDriverWait(driver, TimeSpan.FromSeconds(5));
waitForElement.Until(ExpectedConditions.ElementIsVisible(By.Id("yourIDHere")));

更多关于显式等待的内容,请参见:Selenium C#中的显式等待WebDriver显式等待


10
.NET 绑定中的 ExpectedConditions 实现已被弃用,并将在未来版本中删除。 - Martin Chinome

1
我为了解决这种问题而做了这件事。这是一种定时器和循环的组合,它们会寻找特定的元素,直到经过一定数量的毫秒后超时。
private IWebElement FindElementById(string id, int timeout = 1000)
{
    IWebElement element = null;

    var s = new Stopwatch();
    s.Start();

    while (s.Elapsed < TimeSpan.FromMilliseconds(timeout))
    {
        try
        {
            element = _driver.FindElementById(id);
            break;
        }
        catch (NoSuchElementException)
        {
        }
    }

    s.Stop();
    return element;
}

我也为元素启用创建了一个。

private IWebElement ElementEnabled(IWebElement element, int timeout = 1000)
{
    var s = new Stopwatch();
    s.Start();

    while (s.Elapsed < TimeSpan.FromMilliseconds(timeout))
    {
        if (element.Enabled)
        {
            return element;
        }
    }

    s.Stop();
    return null;
}

根据您的系统设置,您可能需要增加可用端口或减少超时时间。为了防止端口饱和,我所做的是在内部循环结束处添加Thread.Sleep(200);,以便它不会尽可能快地进行太多调用并使用所有端口。这意味着您将每秒检查您的元素5次,而不是像每秒30次这样的频率。 - Ben Williams
@StephenRauch:看起来不错!IWebElement将返回一个对象,您如何获取其路径?为了断言目的,element.getAbsoluteXpath()和element.getRelativeXpath()可能是一组不错的候选项。 - clau84
@clau84,我不确定你的意思,因为当你调用element.getAbsoluteXpath()时,你已经有了这个元素,且它不为空。这意味着你已经通过某种方式(如Id、类名、xpath等)获取了该IWebElement。 - Ben Williams

1

Selenium中等待页面加载所述:

通常情况下,使用Selenium 2.0时,web driver 应该只在确定页面已经加载完成后才将控制权返回给调用代码。如果没有,您可以调用waitforelemement,它会循环调用findelement直到找到元素或超时(超时时间可以设置)。


0

您只需要通过NuGet导入SeleniumExtras包,然后像下面这样使用它:

var e = new WebDriverWait(driver, new TimeSpan(0, 0, 60)).Until(SeleniumExtras.WaitHelpers.ExpectedConditions.ElementIsVisible(By.Id("--id")));

0

这个答案适用于那些想要简单易懂的解决方案的人。它对我很有效。

private void WaitForLoad(IWebDriver driver, string findBy, string value)
{
    bool load=false;
    while (!load)
    {
        try
        {
            Thread.Sleep(1000);
            if(findBy == "ID")
            {
                driver.FindElement(By.Id(value));
            }
            else
            {
                driver.FindElement(By.Name(value));
            }
            load = true;
        }
        catch (Exception ex)
        {
        }
    }
}

注意:您可以根据需要更改睡眠时间。
您可以像这样调用此函数:
WaitForLoad(driver, "NAME", "emailOrUsername");

IWebElement uname = driver.FindElement(By.Name("emailOrUsername"));

0
由于它的简单性,我喜欢这个解决方案。此外,它避免了过度等待,并消除了可能是上限等待时间的猜测。
    public bool WaitToLoad(By by)
    {
        int i = 0;
        while (i < 600)
        {
            i++;
            Thread.Sleep(100); // sleep 100 ms
            try
            {
                driver.FindElement(by);
                break;
            }
            catch { }
        }
        if (i == 600) return false; // page load failed in 1 min
        else return true;
    }

如果需要监控页面加载延迟,可以进行修改以包括“计时器”:

    public int WaitToLoad(By by)
    {
        int i = 0;
        while (i < 600)
        {
            i++;
            Thread.Sleep(100); // sleep 100 ms
            try
            {
                driver.FindElement(by);
                break;
            }
            catch { }
        }
        return i; // page load latency in 1/10 secs
    }

为什么会被踩?尽管Thread.Sleep和轮询不是完成任务的最佳方式,但这似乎是一个合理的选择... - Shawn Eary

-3

所有的回答都没有意义。 页面可以包含所有控件。但是你更改了控件中的数据,页面就会重新加载。因此,如果您继续使用此页面,您将得到一堆错误。 我真的看不到比Thread.Sleep()更好的解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接