Prueba AngularJS con selenium

Tengo la aplicación SPA en la stack ASP MVC + AngularJS y me gustaría probar la interfaz de usuario. Por ahora, estoy probando Selenium con los controladores PhantomJS y WebKit.

Página de prueba – ver con un solo elemento – la lista de

  • que se carga dinámicamente desde el servidor y se une por Angular.

     
  • text
  • text2
  • Estoy tratando de pasar la prueba

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

    El problema es que en este momento no hay elementos cargados, y _driver.PageSource no contiene elementos también.

    ¿Cómo puedo esperar para cargar los artículos? Por favor, no sugieras Thread.Sleep ()

    Esto esperará cargas de página / jquery.ajax (si está presente) y llamadas de $ http, y cualquier ciclo de resumen / renderización que lo acompañe, tírelo en una función de utilidad y espere.

     /* C# Example var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout)); pageLoadWait.Until( (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; } /*"); });*/ 
  • Cree una nueva clase que le permita averiguar si su sitio web usando AngularJS ha terminado de hacer llamadas AJAX, de la siguiente manera:

     import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.ui.ExpectedCondition; public class AdditionalConditions { public static ExpectedCondition angularHasFinishedProcessing() { return new ExpectedCondition() { @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()); } }; } } 

    Puede usarlo en cualquier parte de su código utilizando el siguiente código:

     WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100); wait.until(AdditionalConditions.angularHasFinishedProcessing())); 

    Hemos tenido un problema similar en el que nuestro framework interno se usa para probar varios sitios, algunos de ellos usan JQuery y otros usan AngularJS (y 1 incluso tiene una combinación). Nuestro marco está escrito en C # por lo que era importante que cualquier JScript que se ejecutara se realizara en fragmentos mínimos (para fines de depuración). En realidad, tomó muchas de las respuestas anteriores y las mezcló juntas (así que acrediten dónde se debe el crédito @npjohns). A continuación hay una explicación de lo que hicimos:

    A continuación, se devuelve un verdadero / falso si el HTML DOM se ha cargado:

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

    Luego verificamos si se está utilizando JQuery:

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

    Si se usa JQuery, verificamos que esté cargado:

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

    Luego hacemos lo mismo con AngularJS:

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

    Si se está utilizando, verificamos que se haya cargado:

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

    Después de verificar que el DOM se haya cargado correctamente, puede usar estos valores de bool para realizar esperas personalizadas:

      var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript)); var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript)); 

    Si está usando AngularJS, entonces usar Protractor es una buena idea.

    Si usa transportador puede usar su método waitForAngular () que esperará a que se completen las solicitudes http. Sigue siendo una buena práctica esperar a que se muestren los elementos antes de actuar sobre ellos. Dependiendo de su lenguaje y de su implementación, esto podría verse en un lenguaje sincrónico.

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

    O en JS puede usar el método de espera que ejecuta una función hasta que devuelve verdadero

     browser.wait(function () { return browser.driver.isElementPresent(elementToFind); }); 

    Hice el siguiente código y me ayudó para las fallas de condición de carrera asíncrona.

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

    Ahora en el modelo Selenium PageObject, puedes esperar

     Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();"); return result == null ? false : (Boolean) result; 

    Puedes simplemente extraer el transportador para fragmentos de código útiles. Esta función se bloquea hasta que Angular termine de renderizar la página. Es una variante de la respuesta de Shahzaib Salim, excepto que está sondeando y estoy progtwigndo una callback.

     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);""") 

    Reemplaza ‘html’ por cualquier elemento que sea tu ng-app .

    Viene de https://github.com/angular/protractor/blob/71532f055c720b533fbf9dab2b3100b657966da6/lib/clientsidescripts.js#L51

    Si su aplicación web está realmente creada con Angular como usted dice, la mejor manera de realizar pruebas de extremo a extremo es con Transportador .

    Internamente, el transportador utiliza su propio método waitForAngular para garantizar que el transportador de waitForAngular espere automáticamente hasta que Angular haya terminado de modificar el DOM.

    Por lo tanto, en el caso normal, nunca necesitaría escribir una wait explícita en sus casos de prueba: Transportador lo hace por usted.

    Puede consultar el tutorial de Angular Phonecat para aprender a configurar Transportador.

    Si quiere usar Protractor en serio, querrá adoptar los objetos de la página . Si desea un ejemplo de eso, eche un vistazo a mi paquete de prueba de objetos de página para Angular Phonecat.

    Con Protractor escribes tus pruebas en Javascript (el transportador se basa en realidad en el nodo), y no en C #, pero a cambio, Protractor maneja todo lo que te espera.

    Para mi problema particular con la página HTML que contiene iframes y desarrollado con AnglularJS, el siguiente truco me ahorró mucho tiempo: en el DOM vi claramente que hay un iframe que envuelve todo el contenido. Entonces, se supone que el siguiente código funciona:

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

    Pero no funcionaba y me dijo algo como “No se puede cambiar al encuadre. La ventana estaba cerrada”. Luego modifiqué el código a:

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

    Después de esto todo fue sin problemas. Así que, evidentemente, Angular estaba destruyendo algo con iframes y justo después de cargar la página cuando espera que el controlador se centre en el contenido predeterminado, se centró en algunos ya eliminados por el marco angular. Espero que esto pueda ayudar a algunos de ustedes.

    Si no desea hacer el cambio completo a Transportador pero desea esperar a Angular, le recomiendo usar Paul Hammants ngWebDriver (Java). Está basado en transportador pero no tienes que hacer el cambio.

    Resolví el problema escribiendo una clase de acciones en la que esperé Angular (utilizando ngWebDriver’s waitForAnularRequestsToFinish ()) antes de llevar a cabo las acciones (hacer clic, completar, verificar, etc.).

    Para ver un fragmento de código, vea mi respuesta a esta pregunta

    Al lado de la sugerencia de Eddiec. Si prueba una aplicación AngularJS, le sugiero que piense en un transportador

    El transportador le ayudará a resolver el asunto de espera (sincronización, asincrónico). Sin embargo, hay algunas notas

    1 – Necesita desarrollar su prueba en javascript

    2 – Hay algunos mecanismos diferentes para manejar el flujo

    Aquí hay un ejemplo de cómo esperar en Angular si está usando WebDriverJS . Originalmente pensé que tenías que crear una condición personalizada, pero wait acepta cualquier función.

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

    Lamentablemente, esto no funciona con PhantomJS debido a CSP (content-security-policy) y unsafe-eval . No puedo esperar al Chrome 59 sin cabeza en Windows .