Selenium等待文档准备就绪

151

有人能告诉我如何使Selenium等待页面完全加载吗? 我想要一些通用的东西,我知道我可以配置WebDriverWait并调用类似于“find”的内容来等待,但我不需要那么多。 我只需要测试页面成功加载并继续下一页进行测试。

我在.net中找到了一些东西,但无法在java中使其工作...

IWait<IWebDriver> wait = new OpenQA.Selenium.Support.UI.WebDriverWait(driver, TimeSpan.FromSeconds(30.00));
wait.Until(driver1 => ((IJavaScriptExecutor)driver).ExecuteScript("return document.readyState").Equals("complete"));

有任何想法吗?


为什么你不想使用wait? - Amey
8
你是指显式等待吗?这需要花费时间,我正在测试大约10,000个页面。 - Girish
2
我的意思是,如果我正在测试大量的链接,添加一个固定的等待时间可能不是个好主意,对吧? - Girish
14
等待固定秒数是没有用的。那就是瞎猜。 - Anders Lindén
鉴于页面的JavaScript可以运行任何通用代码,编写一个程序等待其完成是不可能的。这是停机问题(https://en.wikipedia.org/wiki/Halting_problem)的一种形式。这里的任何解决方案都将需要做出妥协或基于底层网页的假设。 - speedplane
如下面的答案所述,大多数事情已经等待(driver.get())。一个不等待的特殊情况是表单提交点击按钮,请参见Python Selenium - Wait until next page has loaded after form submit - Stack Overflow - user202729
27个回答

104
你的建议解决方案只等待 DOM readyState 信号发出 complete。但是 Selenium 默认情况下尝试通过 driver.get()element.click() 方法在页面加载时等待这些(还有更多)。它们已经是阻塞的,它们等待页面完全加载,应该可以正常工作。问题显然是 AJAX 请求重定向和运行脚本 - 这些无法被 Selenium 捕获,它不会等待它们完成。此外,您无法可靠地通过 readyState 捕获它们 - 它会等待一段时间,这可能很有用,但是在下载所有 AJAX 内容之前,它将很快发出 complete 信号。没有通用的解决方案适用于每个人和每个地方,这就是为什么它很难,每个人都使用略微不同的东西的原因。
一般而言,应该依赖WebDriver来完成其任务,然后使用隐式等待,再对页面上想要断言的元素使用显式等待,但是还有许多其他技术可以使用。你应该选择最适合你的情况和被测试页面的一个或多个组合。
查看我的两个答案以获取更多信息:
- 如何检查Web Driver是否已完全加载页面 - Selenium Webdriver:等待带有JavaScript的复杂页面加载

25
不准确,Selenium在element.click()调用上不会等待或阻塞。 - hwjp
3
@hwjp 能否详细说明一下?JavaDocs的说法与此不同:_"如果这导致加载一个新页面,该方法将尝试阻塞直到页面加载完毕。"_ - Petr Janeček
5
根据我在邮件列表上进行的一些对话,似乎这是不准确的。Selenium可能会在您明确请求URL时阻止.get调用,但在点击调用上它并不执行任何特殊操作,因为它无法确定您是否已单击了“真实”的超链接还是被JavaScript拦截的超链接... - hwjp
4
我在邮件列表讨论的开头链接了一个错误。即使文档含糊其辞:“如果 click() 是通过发送本地事件来完成的,则该方法将不会等待”。 - hwjp
4
关键在于浏览器是否使用“本机事件(native events)”。似乎大多数浏览器默认会使用本机事件:https://code.google.com/p/selenium/wiki/AdvancedUserInteractions#Native_events_versus_synthetic_events(因此我会说这些文档至少是误导性的,会在邮件列表中提出疑问)。 - hwjp
显示剩余9条评论

84

尝试这段代码:

  driver.manage().timeouts().pageLoadTimeout(10, TimeUnit.SECONDS);

以上代码将等待页面加载最多10秒钟。如果页面加载超过时间限制,它将抛出TimeoutException异常。您可以捕获该异常并进行处理。我不确定在抛出异常后是否会停止页面加载。我还没有尝试过这段代码。希望您可以尝试一下。
这是一个隐式等待。如果您设置了一次,它将在Web Driver实例销毁之前一直有效。
有关更多信息,请参阅WebDriver.Timeouts的文档

3
好的,如果页面在10秒钟之前加载完成,那么它会等待10秒钟后再执行下一行代码吗? - Girish
如果您的页面在10秒内加载完成,则会终止等待条件并执行前面的代码行。 - Manigandan
3
这是用于当您预计页面加载时间过长时,避免超时并抛出异常,它不会立即等待页面加载或设置更好的加载策略。默认情况下,它的超时时间为无限制,因此您的页面加载永远不会抛出异常,并且Selenium始终尝试完全加载它们。 - Petr Janeček
23
这种方法的问题在于,即使隐式等待先前成功返回了一个WebElement对象,DOM也可能不是完全可访问的。然后,如果您尝试点击该元素,您将会收到一个“过时的元素”异常。因此,这个答案并不完全安全。 - djangofan
3
这个超时时间和等待文档加载有什么关系? - Anders Lindén
显示剩余3条评论

73
这是一个可工作的 Java 版本,与您提供的示例相同:
void waitForLoad(WebDriver driver) {
    new WebDriverWait(driver, 30).until((ExpectedCondition<Boolean>) wd ->
            ((JavascriptExecutor) wd).executeScript("return document.readyState").equals("complete"));
}

示例:对于c#:

public static void WaitForLoad(IWebDriver driver, int timeoutSec = 15)
{
    IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
    WebDriverWait wait = new WebDriverWait(driver, new TimeSpan(0, 0, timeoutSec));
    wait.Until(wd => js.ExecuteScript("return document.readyState").ToString() == "complete");
}

PHP的例子:

final public function waitUntilDomReadyState(RemoteWebDriver $webDriver): void
{
    $webDriver->wait()->until(function () {
        return $webDriver->executeScript('return document.readyState') === 'complete';
    });
}

2
你能把它放到Java 1.7版本兼容的环境中吗?因为Lambda表达式不被支持。 - vkrams
3
Java 1.7版本:wait.until( new Predicate<WebDriver>() { public boolean apply(WebDriver driver) { return ((JavascriptExecutor)driver).executeScript("return document.readyState").equals("complete"); } } ); 翻译:等待直到页面加载完成的代码,适用于Java 1.7版本。 - luQ
1
如果有人想使用@IuQ的解决方案,那么Predicate需要导入import com.google.common.base.Predicate - Knu8
我在VB测试应用程序中尝试了这个想法。大部分时间都有效。有时会出现以下错误:System.InvalidOperationException: JavaScript error (UnexpectedJavaScriptError) at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse) 测试应用程序单击2级菜单链接,然后调用WaitObj.Until(Function(D) DirectCast(D, InternetExplorerDriver).ExecuteScript("return document.readyState") = "complete")。我认为错误是由于浏览器卸载当前页面并且没有文档对象所致。也许在执行“return document.readystate”之前等待1/2秒钟?有什么想法吗? - CoolBreeze
WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(30)); wait.Until(wd => { try { return (wd as IJavaScriptExecutor).ExecuteScript("return (document.readyState == 'complete' && jQuery.active == 0)"); } catch { return false; } } ); - Roger Perkins
显示剩余2条评论

13

这是我用Python尝试的一种完全通用的解决方案:

首先是一个通用的“等待”函数(如果您喜欢,可以使用WebDriverWait,但我认为它们很丑):

def wait_for(condition_function):
    start_time = time.time()
    while time.time() < start_time + 3:
        if condition_function():
            return True
        else:
            time.sleep(0.1)
    raise Exception('Timeout waiting for {}'.format(condition_function.__name__))

接下来,解决方案依赖于Selenium记录页面上所有元素(包括顶级<html>元素)的(内部)ID号。当页面刷新或加载时,它会获得一个新的带有新ID的html元素。

因此,假设您想单击一个名为“my link”的链接:

old_page = browser.find_element_by_tag_name('html')

browser.find_element_by_link_text('my link').click()

def page_has_loaded():
    new_page = browser.find_element_by_tag_name('html')
    return new_page.id != old_page.id

wait_for(page_has_loaded)

为了使代码更Python化、可重用和通用化,您可以创建一个上下文管理器:

from contextlib import contextmanager

@contextmanager
def wait_for_page_load(browser):
    old_page = browser.find_element_by_tag_name('html')

    yield

    def page_has_loaded():
        new_page = browser.find_element_by_tag_name('html')
        return new_page.id != old_page.id

    wait_for(page_has_loaded)

然后您几乎可以在任何Selenium交互中使用它:

with wait_for_page_load(browser):
    browser.find_element_by_link_text('my link').click()

我认为那是防弹的!你觉得呢?

这里有一篇关于它的博客文章提供更多信息。


非常有趣的方法。一个挑战是这是否可以在浏览器首次启动后的初始页面加载时工作。无法保证浏览器的初始状态是否已经加载了任何页面。此外,在Java中,我没有看到我们在对象上有一个“id” - 我假设您不是指selenium插入html id属性。一旦我更深入地探索了这个选项,我会在这个回复中添加更多内容。感谢您的帖子! - Lukus
@hwjp 我多次使用此解决方案并取得了出色的结果,但似乎在某些情况下它不起作用。问题的完整说明请参考 http://stackoverflow.com/q/31985739/4249707 - El Ruso

7

我有一个类似的问题。我需要等待文档准备好,同时也需要等待所有Ajax调用完成。第二个条件很难检测到。最后,我检查了活动的Ajax调用,这样就解决了问题。

Javascript:

return (document.readyState == 'complete' && jQuery.active == 0)

完整的C#方法:

private void WaitUntilDocumentIsReady(TimeSpan timeout)
{
    var javaScriptExecutor = WebDriver as IJavaScriptExecutor;
    var wait = new WebDriverWait(WebDriver, timeout);            

    // Check if document is ready
    Func<IWebDriver, bool> readyCondition = webDriver => javaScriptExecutor
        .ExecuteScript("return (document.readyState == 'complete' && jQuery.active == 0)");
    wait.Until(readyCondition);
}

1
在 React 中是否有类似于 document.readyState == 'complete' && jQuery.active == 0 的东西? - Rain9333

7
WebDriverWait wait = new WebDriverWait(dr, 30);
wait.until(ExpectedConditions.jsReturnsValue("return document.readyState==\"complete\";"));

4

对于 C# NUnit,您需要将 WebDriver 转换为 JSExecuter,然后执行脚本以检查文档是否已完全准备好。请参考以下代码:

 public static void WaitForLoad(IWebDriver driver)
    {
        IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
        int timeoutSec = 15;
        WebDriverWait wait = new WebDriverWait(driver, new TimeSpan(0, 0, timeoutSec));
        wait.Until(wd => js.ExecuteScript("return document.readyState").ToString() == "complete");
    }

这将等待条件被满足或超时。

3
我注意到在初始页面加载时,“最大化”浏览器窗口实际上会等待页面加载完成(包括资源)。
替换为:
AppDriver.Navigate().GoToUrl(url);

随着:

public void OpenURL(IWebDriver AppDriver, string Url)
            {
                try
                {
                    AppDriver.Navigate().GoToUrl(Url);
                    AppDriver.Manage().Window.Maximize();
                    AppDriver.SwitchTo().ActiveElement();
                }
                catch (Exception e)
                {
                    Console.WriteLine("ERR: {0}; {1}", e.TargetSite, e.Message);
                    throw;
                }
            }

然后使用:

OpenURL(myDriver, myUrl);

这将加载页面,等待完成后最大化并聚焦于它。我不知道为什么会这样,但它起作用。

如果您想在点击下一页或其他页面导航触发器后等待页面加载,而不是使用“Navigate()”,Ben Dyer的回答(在此线程中)将起到作用。


2

在Nodejs中,您可以通过Promises获取它...

如果您编写此代码,则可以确保在到达then时页面已完全加载...

driver.get('www.sidanmor.com').then(()=> {
    // here the page is fully loaded!!!
    // do your stuff...
}).catch(console.log.bind(console));

如果您编写此代码,您将导航,而Selenium将等待3秒钟...
driver.get('www.sidanmor.com');
driver.sleep(3000);
// you can't be sure that the page is fully loaded!!!
// do your stuff... hope it will be OK...

来自Selenium文档:

this.get( url ) → Thenable

安排一个命令以导航到给定的URL。

返回一个承诺,在文档加载完成时将被解决。

Selenium文档(Nodejs)


除了这通常无法正常工作,尽管Selenium尽最大努力。 - Mike Godin

1
通常情况下,当Selenium从单击、提交或获取方法打开新页面时,它会等待页面加载完成,但问题是如果页面有一个XHR调用(Ajax),它将永远不会等待XHR加载完成。因此,创建一种新的方法来监视XHR并等待它们将是正确的做法。
public boolean waitForJSandJQueryToLoad() {
    WebDriverWait wait = new WebDriverWait(webDriver, 30);
    // wait for jQuery to load
    ExpectedCondition<Boolean> jQueryLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        try {
            Long r = (Long)((JavascriptExecutor)driver).executeScript("return $.active");
            return r == 0;
        } catch (Exception e) {
            LOG.info("no jquery present");
            return true;
        }
      }
    };

    // wait for Javascript to load
    ExpectedCondition<Boolean> jsLoad = new ExpectedCondition<Boolean>() {
      @Override
      public Boolean apply(WebDriver driver) {
        return ((JavascriptExecutor)driver).executeScript("return document.readyState")
        .toString().equals("complete");
      }
    };

  return wait.until(jQueryLoad) && wait.until(jsLoad);
}

如果 $.active == 0,那么就没有正在进行的xhr调用(这仅适用于jQuery)。对于JavaScript ajax调用,您需要在项目中创建一个变量并模拟它。

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