【问题标题】:Testing AngularJS with Selenium用 Selenium 测试 AngularJS
【发布时间】:2014-09-23 15:19:10
【问题描述】:

我在堆栈 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()

【问题讨论】:

    标签: angularjs selenium c#-4.0 phantomjs end-to-end


    【解决方案1】:

    如果您使用的是 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);
    });
    

    【讨论】:

      【解决方案2】:

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

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

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

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

      【讨论】:

      • 那么端到端测试呢?我想得到“幸福的道路”。对于单元测试,我使用 Jasmine。可以用 Jasmine 测试 UI 吗?
      • 量角器用于e2e。您可以将它与 Jasmine、Cucumber 和 Mocha 测试框架一起使用。但是,Mocha 的 beta 支持有限。如果使用 mocha,您将需要包含自己的断言框架。
      • 我正在使用 ASP MVC、AngularJS、Jasmine。我可以在没有 Node.JS 的情况下使用“量角器”吗?
      【解决方案3】:

      这将等待页面加载/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;
      }
      /*");
      });*/
      

      【讨论】:

      • 谢谢!由于没有加载 jQuery,我不得不将“window.angular.element('body')”更改为“window.angular.element(document.body)”。
      • 这在一些重构后工作。编辑这个的人已经把它的 C# 部分弄乱了。
      • Javascript 中的window.qa 是什么?
      • 由于某种原因 angular.element(document.body).injector() 对我来说总是未定义。
      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

        如果您的网络应用确实如您所说使用 Angular 创建的,那么进行端到端测试的最佳方法是使用 Protractor

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

        因此,在正常情况下,您永远不会需要在测试用例中写出明确的wait:Protractor 会为您完成。

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

        如果你想认真使用量角器,你会想采用。如果您想要一个示例,请查看我的 page object test suite Angular Phonecat。

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

        【讨论】:

          【解决方案6】:

          创建一个新类,让您确定使用 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()));
          

          【讨论】:

          • 完美解决我必须使用的现代 Web 应用程序设计。
          • 执行时收到以下消息:“未知错误:未定义角度”
          • 这对我来说非常有效 - 太棒了!我converted this c# version to typescript for my use case
          • 感谢 Shahzaib。有人可以在代码 sn-p 上添加评论吗?
          • 非常感谢@Shahzaib Salim,这解决了我动态处理页面加载的问题。
          【解决方案7】:

          对于包含 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 框架删除的内容所关注。 希望这对你们中的一些人有所帮助。

          【讨论】:

            【解决方案8】:

            您可以只挖掘量角器以获得有用的代码 sn-ps。这个函数会一直阻塞,直到 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

            【讨论】:

              【解决方案9】:

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

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

              代码 sn-p 见my answer to this question

              【讨论】:

                【解决方案10】:

                我们遇到过类似的问题,我们的内部框架被用于测试多个站点,其中一些使用 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));
                

                【讨论】:

                  【解决方案11】:

                  如果您使用的是 WebDriverJS,下面是一个如何等待 Angular 的示例。最初我认为您必须创建自定义条件,但 wait 接受任何功能。

                  // 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(...);
                  

                  遗憾的是,由于 CSP (content-security-policy) 和 unsafe-eval,这不适用于 PhantomJSCan't wait for headless Chrome 59 on Windows

                  【讨论】:

                    【解决方案12】:

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

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

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

                    【讨论】:

                      猜你喜欢
                      • 2019-04-17
                      • 2015-12-10
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多