We use Selenium WebDriver to automate our UI based tests. One of our challenges is to detect when a page is truly done loading, and Angular 1 was a challenge in that regard as well. We ended up executing this piece of code specifically to detect if Angular 1 is done:
if(typeof window.angular !== \"undefined\")
{
var injector = window.angular.element(\"*[ng-app]\").eq(0).injector();
if(injector)
{
var $rootScope = injector.get(\"$rootScope\");
var $http = injector.get(\"$http\");
if($rootScope.$$phase === \"$apply\" || $rootScope.$$phase === \"$digest\" || $http.pendingRequests.length !== 0)
{
return false;
}
}
}
The app that we are testing recently switched over to use Angular 2. The code snippet above does not wait for Angular 2 to finish. Any suggestions?
In case of Angular 2, you should wait for stableness of "testabilities" of all Angular 2 apps:
functions.waitForAllAngular2 = function(callback) {
try {
var testabilities = window.getAllAngularTestabilities();
var count = testabilities.length;
var decrement = function() {
count--;
if (count === 0) {
callback();
}
};
testabilities.forEach(function(testability) {
testability.whenStable(decrement);
});
} catch (err) {
callback(err.message);
}
};
Taken from Protractor source code. Protractor is a wrapper around WebDriverJS javascript selenium bindings; designed to test AngularJS applications (not only, but best suited for).
Using @alecxe answer i ended up with following javascript one liner to check if all Angular Testabilities are stable
window.getAllAngularTestabilities().findIndex(x=>!x.isStable()) === -1
Building upon alexce's answer, this is the code we use to wait for angular in our selenium tests:
public static void waitForPageLoaded(WebDriver webDriver) {
ExpectedCondition<Boolean> expectation = new
ExpectedCondition<Boolean>() {
public Boolean apply(WebDriver driver) {
return ((JavascriptExecutor) driver).executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"if (document.readyState !== 'complete') {" +
" callback('document not ready');" +
"} else {" +
" try {" +
" var testabilities = window.getAllAngularTestabilities();" +
" var count = testabilities.length;" +
" var decrement = function() {" +
" count--;" +
" if (count === 0) {" +
" callback('complete');" +
" }" +
" };" +
" testabilities.forEach(function(testability) {" +
" testability.whenStable(decrement);" +
" });" +
" } catch (err) {" +
" callback(err.message);" +
" }" +
"}"
).toString().equals("complete");
}
};
try {
WebDriverWait wait = new WebDriverWait(webDriver, waitSeconds);
wait.until(expectation);
} catch (Throwable error) {
new Exception("Timeout waiting for Page Load Request to complete.");
}
}
I fixed this by writing an actions class in which I waited for Angular before carrying out the actions (click, fill, check etc.) using Paul Hammants ngWebDriver:
import com.paulhammant.ngwebdriver.NgWebDriver;
public class ActionsWithWaits {
private NgWebDriver ngdriver;
private JavascriptExecutor js;
public ActionsWithWaits(){
WebDriver driver = getDriver();
js = (JavascriptExecutor) driver;
driver.manage().timeouts().setScriptTimeout(9, TimeUnit.SECONDS);
ngdriver = new NgWebDriver(js);
}
public void waitForAngular(){
ngdriver.waitForAngularRequestsToFinish();
}
public void waitAndClick(WebElementFacade button){
waitForAngular();
button.click();
}
public void waitAndFillIn(String text, WebElementFacade field){
waitForAngular();
field.type(text);
}
etc..
Now you can just use those actions instead of the standard selenium actions and you don't have to worry about a thing (=
I would emit an event from within ngAfterViewInit()
in the root component
@Component({
selector: 'my-app',
...
})
export class AppComponent {
constructor(private renderer:Renderer, private elementRef:ElementRef){}
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.elementRef.nativeElement,
'dispatchEvent',
[new CustomEvent('angular2-loaded', { bubbles: true })]);
}
}
and then listen to this event using WebDriver.