29

我在堆栈 ASP MVC + AngularJS 上有一个 SPA 应用程序,我想测试 UI。现在我正在使用 PhantomJS 和 WebKit 驱动程序尝试 Selenium。

这是一个示例测试页面 - 具有单个元素的视图。列表项<li>从服务器动态加载并受 Angular 限制。

<div id="items">
    <li>text</li>
    <li>text2</li>
</div>

我正在尝试通过测试,但此行有错误:

_driver.FindElements(By.TagName('li'))

此时没有加载元素并且 _driver.PageSource 不包含元素。

如何等待项目加载?请不要建议Thread.Sleep()

4

12 回答 12

41

这将等待页面加载/jquery.ajax(如果存在)和 $http 调用,以及任何随附的摘要/渲染周期,将其放入实用程序函数中并等待。

/* C# Example
 var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
            pageLoadWait.Until<bool>(
                (driver) =>
                {
                    return (bool)JS.ExecuteScript(
@"*/
try {
  if (document.readyState !== 'complete') {
    return false; // Page not loaded yet
  }
  if (window.jQuery) {
    if (window.jQuery.active) {
      return false;
    } else if (window.jQuery.ajax && window.jQuery.ajax.active) {
      return false;
    }
  }
  if (window.angular) {
    if (!window.qa) {
      // Used to track the render cycle finish after loading is complete
      window.qa = {
        doneRendering: false
      };
    }
    // Get the angular injector for this app (change element if necessary)
    var injector = window.angular.element('body').injector();
    // Store providers to use for these checks
    var $rootScope = injector.get('$rootScope');
    var $http = injector.get('$http');
    var $timeout = injector.get('$timeout');
    // Check if digest
    if ($rootScope.$$phase === '$apply' || $rootScope.$$phase === '$digest' || $http.pendingRequests.length !== 0) {
      window.qa.doneRendering = false;
      return false; // Angular digesting or loading data
    }
    if (!window.qa.doneRendering) {
      // Set timeout to mark angular rendering as finished
      $timeout(function() {
        window.qa.doneRendering = true;
      }, 0);
      return false;
    }
  }
  return true;
} catch (ex) {
  return false;
}
/*");
});*/
于 2015-05-29T23:38:29.157 回答
28

创建一个新类,让您确定使用 AngularJS 的网站是否完成了 AJAX 调用,如下所示:

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;

public class AdditionalConditions {
    public static ExpectedCondition<Boolean> angularHasFinishedProcessing() {
        return new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                return Boolean.valueOf(((JavascriptExecutor) driver).executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)").toString());
            }
        };
    }
}

您可以使用以下代码在代码中的任何位置使用它:

WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);
wait.until(AdditionalConditions.angularHasFinishedProcessing()));
于 2015-11-19T20:39:46.530 回答
6

我们有一个类似的问题,我们的内部框架被用于测试多个站点,其中一些使用 JQuery,一些使用 AngularJS(其中 1 甚至混合使用!)。我们的框架是用 C# 编写的,因此任何正在执行的 JScript 都必须以最小的块完成(出于调试目的),这一点很重要。实际上,它需要很多上述答案并将它们混合在一起(因此应归功于@npjohns)。下面是我们所做的解释:

如果 HTML DOM 已加载,则以下返回 true / false:

        public bool DomHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
    {

        var hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
        while (hasThePageLoaded == null || ((string)hasThePageLoaded != "complete" && timeout > 0))
        {
            Thread.Sleep(100);
            timeout--;
            hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
            if (timeout != 0) continue;
            Console.WriteLine("The page has not loaded successfully in the time provided.");
            return false;
        }
        return true;
    }

然后我们检查是否正在使用 JQuery:

public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined");
        return (bool)isTheSiteUsingJQuery;
    }

如果正在使用 JQuery,我们将检查它是否已加载:

public bool JqueryHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
                var hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                while (hasTheJQueryLoaded == null || (!(bool) hasTheJQueryLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                timeout--;
                    hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "JQuery is being used by the site but has failed to successfully load.");
                    return false;
                }
                return (bool) hasTheJQueryLoaded;
        }

然后我们对 AngularJS 做同样的事情:

    public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        string UsingAngular = @"if (window.angular){
        return true;
        }";            
        var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular);
        return (bool) isTheSiteUsingAngular;
    }

如果它正在被使用,那么我们检查它是否已经加载:

public bool AngularHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
    string HasAngularLoaded =
        @"return (window.angular !== undefined) && (angular.element(document.body).injector() !== undefined) && (angular.element(document.body).injector().get('$http').pendingRequests.length === 0)";            
    var hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                while (hasTheAngularLoaded == null || (!(bool)hasTheAngularLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                    timeout--;
                    hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "Angular is being used by the site but has failed to successfully load.");
                    return false;

                }
                return (bool)hasTheAngularLoaded;
        }

在我们检查 DOM 是否已成功加载后,您可以使用这些 bool 值进行自定义等待:

    var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript));
    var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));
于 2017-01-12T15:50:42.730 回答
3

如果您使用的是 AngularJS,那么使用 Protractor 是个好主意。

如果您使用量角器,则可以使用它的 waitForAngular() 方法,该方法将等待 http 请求完成。在对元素进行操作之前等待元素显示仍然是一种好习惯,具体取决于您的语言和实现,它可能看起来像同步语言

WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));

或者在 JS 中,您可以使用 wait 方法执行一个函数,直到它返回 true

browser.wait(function () {
    return browser.driver.isElementPresent(elementToFind);
});
于 2014-07-31T16:00:12.887 回答
3

您可以只挖掘量角器以获取有用的代码片段。这个函数会一直阻塞,直到 Angular 完成页面渲染。这是 Shahzaib Salim 答案的变体,除了他正在轮询它并且我正在设置回调。

def wait_for_angular(self, selenium):
    self.selenium.set_script_timeout(10)
    self.selenium.execute_async_script("""
        callback = arguments[arguments.length - 1];
        angular.element('html').injector().get('$browser').notifyWhenNoOutstandingRequests(callback);""")

将 'html' 替换为您的ng-app.

它来自https://github.com/angular/protractor/blob/71532f055c720b533fbf9dab2b3100b657966da6/lib/clientsidescripts.js#L51

于 2016-07-29T11:18:27.027 回答
2

我做了以下代码,它帮助我解决了异步竞争条件失败。

$window._docReady = function () {
        var phase = $scope.$root.$$phase;
        return $http.pendingRequests.length === 0 && phase !== '$apply' && phase !== '$digest';
    }

现在在 selenium PageObject 模型中,你可以等待

Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();");
                    return result == null ? false : (Boolean) result;
于 2015-08-27T11:46:49.660 回答
2

如果您的 Web 应用程序确实是使用 Angular 创建的,那么进行端到端测试的最佳方法是使用Protractor

在内部,Protractor 使用自己的waitForAngular方法,以确保 Protractor自动等待,直到 Angular 完成对 DOM 的修改。

因此,在正常情况下,您永远wait不需要在测试用例中编写显式代码:Protractor 会为您执行此操作。

您可以查看Angular Phonecat教程以了解如何设置量角器。

如果你想认真使用量角器,你会想要采用。如果您想要一个示例,请查看我的 Angular Phonecat页面对象测试套件

使用 Protractor,您可以使用 Javascript(Protractor 确实基于 Node)而不是 C# 编写测试——但作为回报,Protractor 会处理所有等待您的事情。

于 2015-09-05T20:31:51.250 回答
0

Here is an example for how to wait on Angular if you're using WebDriverJS. Originally I thought you had to create a custom condition, but wait accepts any function.

// Wait for Angular to Finish
function angularReady(): any  {
  return $browser.executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)")
     .then(function(angularIsReady) {                        
                    return angularIsReady === true;
                  });
}

$browser.wait(angularReady, 5000).then(...);

Sadly this doesn't work with PhantomJS because of CSP (content-security-policy) and unsafe-eval. Can't wait for headless Chrome 59 on Windows.

于 2017-05-26T17:07:32.497 回答
0

对于包含 iframe 并使用 AngularJS 开发的 HTML 页面的特殊问题,以下技巧为我节省了很多时间:在 DOM 中,我清楚地看到有一个 iframe 包装了所有内容。所以下面的代码应该可以工作:

driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

但它不起作用,并告诉我“无法切换到框架。窗口已关闭”。然后我将代码修改为:

driver.switchTo().defaultContent();
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

在这之后一切都很顺利。很明显,Angular 正在使用 iframe 处理某些内容,并且在加载页面后,当您期望驱动程序专注于默认内容时,它被一些已经被 Angular 框架删除的内容所关注。希望这可以帮助你们中的一些人。

于 2016-06-02T12:58:45.323 回答
0

我已经根据 D Sayar 的回答实现了用法 ,它可能对某人有所帮助。您只需将那里提到的所有布尔函数复制到单个类中,然后在 PageCallingUtility() 方法下面添加。该方法调用内部依赖。

在您的正常使用中,您需要直接调用 PageCallingUtility() 方法。

public void PageCallingUtility()
{
    if (DomHasLoaded() == true)
    {
        if (IsJqueryBeingUsed() == true)
        {
            JqueryHasLoaded();
        }

        if (AngularIsBeingUsed() == true)
        {
            AngularHasLoaded();
        }
    }
}
于 2018-11-23T07:35:06.730 回答
0

如果您不想完全切换到 Protractor,但您确实想等待 Angular,我建议使用Paul Hammants ngWebDriver (Java)。它基于量角器,但您不必进行切换。

我通过编写一个操作类解决了这个问题,在该类中我等待 Angular(使用 ngWebDriver 的 waitForAngularRequestsToFinish()),然后再执行操作(单击、填充、检查等)。

有关代码片段,请参阅我对这个问题的回答

于 2017-01-11T14:50:28.183 回答
-1

除了eddiec的建议。如果您测试 AngularJS 应用程序,我强烈建议您考虑量角器

Protractor 将帮助您解决等待问题(同步、异步)。不过有一些注意事项

1 - 你需要用 javascript 开发你的测试

2 -处理流程有一些不同的机制

于 2014-08-01T04:33:05.900 回答