Testing AngularJS with Selenium
This will wait for page loads / jquery.ajax (if present) and $http calls, and any accompanying digest/render cycle, throw it in a utility function and wait away.
/* 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;}/*");});*/
Create a new class that lets you figure out whether your website using AngularJS has finished making AJAX calls, as follows:
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()); } }; }}
You can use it anywhere in the your code by using the following code:
WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);wait.until(AdditionalConditions.angularHasFinishedProcessing()));
We have had a similar issue where our in house framework is being used to test multiple sites, some of these are using JQuery and some are using AngularJS (and 1 even has a mixture!). Our framework is written in C# so it was important that any JScript being executed was done in minimal chunks (for debugging purposes). It actually took a lot of the above answers and mashed them together (so credit where credit is due @npjohns). Below is an explanation of what we did:
The following returns a true / false if the HTML DOM has loaded:
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; }
Then we check whether JQuery is being used:
public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor) { var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined"); return (bool)isTheSiteUsingJQuery; }
If JQuery is being used we then check that it's loaded:
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; }
We then do the same for AngularJS:
public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor) { string UsingAngular = @"if (window.angular){ return true; }"; var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular); return (bool) isTheSiteUsingAngular; }
If it is being used then we check that it has loaded:
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; }
After we check that the DOM has successfully loaded, you can then use these bool values to do custom waits:
var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript)); var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));