11

The Problem Description:

We've recently got this infamous error while opening one of the pages in our application in a Protractor end-to-end test:

Failed: Timed out waiting for asynchronous Angular tasks to finish after 50 seconds. This may be because the current page is not an Angular application.

This happens on a browser.get("/some/page/"); call in one of our tests:

describe("Test", function () {
    beforeEach(function () {
        browser.get("/some/page/");
    });

    it("should test something", function () {
        // ...
    });
)};

And, what is weird about our case, is that the error is not thrown on any other page in our Angular web application - Protractor syncs with Angular without any problems. ng-app location-wise things are the same across all the pages - ng-app is defined on the root html tag:

<html class="ng-scope" lang="en-us" ng-app="myApp" ng-strict-di="">

The behavior is consistent - every time we navigate to this page with browser.get(), we get this error. Any time we navigate to any other page in our app, sync works.

Note that, of course, we can turn the sync off for this page and treat it as non-angular, but this can only be considered as a workaround.

The Questions:

What else can cause Protractor-to-Angular sync fail? What should we check?

And, in general, what is the recommended way to debug sync problems in Protractor?

Using currently latest Protractor 5.5.1, Angular 1.5.6.

alecxe
  • 462,703
  • 120
  • 1,088
  • 1,195
  • I guess there is sync issues . `https://github.com/angular/protractor/blob/master/docs/timeouts.md#how-to-disable-waiting-for-angular`. The solution which they are telling is same to turn the sync off or probably use `driver.browser.get()` instead of `browser.get()`. – Kishan Patel Mar 18 '17 at 10:55
  • @KishanPatel thanks, but as I mentioned in the question, I understand that we can turn the sync off and treat the page as non-angular - this is a workaround and it would work, but we are trying to figure out why the sync does not work for this particular page and general guidelines to debug problems like this. – alecxe Mar 18 '17 at 13:52
  • Your error clearly states that it can sync because of Angular, and there are two reasons for that to happen `$http` and `$timeout` - protractor will wait for both of those services to finish. If it's caused by `$timeout` then you should simply replace it with `$interval` and as for `$http` there is no workaround unless you want to write an XHR service and use it instead of `$http` – maurycy Mar 20 '17 at 22:39
  • 1
    @maurycy right, good points - we've heard about the `$timeout` issue before (don't recall the `$http` related one). But, how would I know for sure what exactly is causing the problem? How, through debugging, I can detect the reason for a sync failure?..Thank you. – alecxe Mar 20 '17 at 22:44

2 Answers2

7

Ok, so that question intrigued me so I came up with a programmatic solution on how to determine what protractor is waiting for:

var _injector = angular.element(document).injector();
var _$browser = _injector.get('$browser');
var _$http = _injector.get('$http');
var pendingTimeout = true;

//this is actually method that protractor is using while waiting to sync
//if callback is called immediately that means there are no $timeout or $http calls
_$browser.notifyWhenNoOutstandingRequests(function callback () {
  pendingTimeout = false
});

setTimeout(function () {
  //this is to differentiate between $http and timeouts from the "notifyWhenNoOutstandingRequests" method
  if (_$http.pendingRequests.length) {
    console.log('Outstanding $http requests', _$http.pendingRequests.length)
  } else if (pendingTimeout) {
    console.log('Outstanding timeout')
  } else {
    console.log('All fine in Angular, it has to be something else')
  }
}, 100)

Here in the plunker http://plnkr.co/edit/O0CkpnsnUuwEAV8I2Jil?p=preview you can experiment with the timeout and the $http call, my delayed endpoint will wait for 10 seconds before resolving the call, hope that this will be helpful for you

maurycy
  • 8,455
  • 1
  • 27
  • 44
1

I agree with @maurycy that the issue is $http/$timeout related. The simple fix is normally to replace $timeout with $interval as documented here: https://github.com/angular/protractor/blob/master/docs/timeouts.md

Recommendations: merge these sane defaults: allScriptsTimeout: 60000, // 1 minute jasmineNodeOpts: { defaultTimeoutInterval: 300000 // 5 minutes. Allows for 5 commands spanning the full synchronization timeout. }

If you want to find the culprit of $http/$timeout, I would use angular decorators to apply custom logic around these services. This is also a good way to mock angular services accessing third party services. https://docs.angularjs.org/guide/decorators

//DISCLOSURE: Unlinted & Untested.
beforeAll(() => {
  browser.addMockModule('culpritMock', () => {
    angular.module('culpritMock', [])
      .config(['$httpProvider',
        $httpProvider => $httpProvider.interceptors.push('httpCounterInterceptor')
      ])
      .factory('httpCounterInterceptor', ['$q', '$window', ($q, $window) => {
        if ($window.httpCounterInterceptor == null) {
          $window.httpCounterInterceptor = {};
        }
        return {
          request: config => {
            $window.httpCounterInterceptor[config.url] = 'started';
            return config;
          },
          response: response => {
            $window.httpCounterInterceptor[response.config.url] = 'completed';
            return response;
          },
          responseError: rejection => {
            $window.httpCounterInterceptor[rejection.config.url] = 'error';
            return $q.reject(rejection);
          }
        };
      }])
      .decorator('$timeout', ['$delegate', $delegate => {
        const originalTimeout = $delegate;
        function modifiedTimeout() {
          console.log(arguments);
         return originalTimeout.apply(null, arguments);
        }
        modifiedTimeout.cancel = function(promise) {
          return $delegate.cancel(promise);
        }
        return modifiedTimeout;
      }]);
  });
});
Leblanc Meneses
  • 3,001
  • 1
  • 23
  • 26