Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protractor keeps waiting on finished HTTP requests

I have an RESTful API and now I am developing a angular2 app which uses data services to call the API. I then wanted to implement end to end tests using protractor. I wanted to start very low level, so I made tests to just check if my components are present when clicking on a link. The test looks like this:

describe('my-webclient', () => {
    it('app should load', () => {
        browser.get('/');
        expect(element(by.css('my-app')).isPresent()).toBe(true);
    });

    it('app should have a top navigation', () => {
        expect(element(by.css('my-nav-top')).isPresent()).toBe(true);
    });

    it('app should have a side navigation', () => {
        expect(element(by.css('my-nav-side')).isPresent()).toBe(true);
    });

    it('app should have a content', () => {
        expect(element(by.css('my-content')).isPresent()).toBe(true);
    });

    it('app should load the overview for route "/"', () => {
        expect(element(by.css('my-overview')).isPresent()).toBe(true);
    });
});

The problem is, that the overview component makes HTTP requests in a ngOnInit function using some data service. Protractor will then just block forever, although the HTTP requests have been completed long ago.

When I monitor the test, I can see how the app loads, including all the data which was fetched from my API. Then nothing happens and protractor will eventually crash, saying that the timeout was reached.

The exact error message is:

Failed: Timed out waiting for Protractor to synchronize with the page after 11 seconds. Please see https://github.com/angular/protractor/blob/master/docs/faq.md                                                                                                                                                            
While waiting for element with locator - Locator: By(css selector, my-app)

While searching for help on google, I came across several hints that protractor will wait for angular to finish everything. Well, this is exactly what I want. When I monitor the network tab in chrome's developer tools (F12), then nothing appears shortly after the app has loaded. So there are no more pending requests or anything for protractor to wait for. Still, it does, and I just don't know why.

So, here's my question: Do I have to consider anything special when dealing with data services making HTTP requests? Or: What can I do to debug why protractor is still waiting until the timeout hits.

By the way, it is definetly the data service. If I comment out everything inside OverviewComponent::ngOnInit, then the tests will pass just fine and the way I expect it.

like image 881
CharlyDelta Avatar asked Aug 27 '16 17:08

CharlyDelta


2 Answers

(Short answer - any long-running tasks (timeouts, intervals, Promises or Observables) will block testing if not disposed correctly. Http cleans up after itself, so it is likely not your problem.)

Http is most likely not your issue.

Angular 2 uses zones to detect it's stable/unstable state, and Protractor uses a specific injectable class - the Testability Class - to determine app state. Testability of all existing Angular2 apps in the page is exposed to the window object, which is how Protractor keeps tabs on Angular 2.

Angular uses zones to maintain change detection. In userland, we get some control over where our code happens (in or out of angular) through NgZone, useful for two things - testing and performance. Any task run in an angular zone (which is what happens unless we specify otherwise) will trigger change detection, and any async task we wait for flags Testability as unstable.

This means a number of things may be interfering with your app's stability:

  • a pipe that runs a timeout behind the scenes (likely in your specific case, as discussed here)
  • a component or service listening to a continuous asynchronous task:
    • Listening to Route.params observable
    • Setting a recurring Timeout or Interval
    • ...

To fix this, you need to run your tasks outside angular, using NgZone:

this.ngZone.runOutsideAngular(() => {
  this._sub = Observable.timer(2000)
    .subscribe(() => this.ngZone.run(() => {this.content = "Loaded!"}));
})

See a working example in this plunk, and this talk by Julie Ralph for more details - she's the brains behind Protractor.

like image 104
Tiago Roldão Avatar answered Oct 20 '22 11:10

Tiago Roldão


So Tiagos answer definitely pointed me in the right direction. I was scared that it might be an issue with my observables. Was relieved to discover it was just a window.setInterval() I was using to refresh my jwt. This was solved as follows:

constructor(private ngZone: NgZone) {
}

this.ngZone.runOutsideAngular(() => {
  Observable.interval(4 * 60 * 1000)
    .subscribe(data => {
      this.refreshJwt();
    });
});
like image 2
Stephen Paul Avatar answered Oct 20 '22 10:10

Stephen Paul