9

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?

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • There is the same topic: http://stackoverflow.com/questions/34731869/wait-for-angular-2-to-load-resolve-model-before-rendering-view-template And the best answer was http://stackoverflow.com/a/38212664/4019586 – Denis Koreyba Sep 06 '16 at 14:44
  • Can you please provide me with an example on how you used that code for AngularJS 1? – yuva May 16 '17 at 21:37

5 Answers5

15

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).

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
5

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
Jac Mos
  • 7,746
  • 4
  • 15
  • 21
2

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.");
    }
} 
Michal Filip
  • 870
  • 8
  • 9
1

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 (=

74nine
  • 838
  • 7
  • 13
0

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.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • there is a typo in the above: in the constructor params, the second param is elementRef but in the ngAfterViewInit() you're using elRef. Anyways, we tried adding this to the app under test main NG2 component. Then in Chrome console we registered to listen to angular2-loaded events passing in a simple callback to alert. Loaded the app under test and...nothing happened. – Rolandas Burbulis Jun 30 '16 at 22:47
  • @user1801355 Thanks for the hint. I somehow missed your comment - fixed. – Günter Zöchbauer Sep 06 '16 at 09:06