Canonical way to debug Protractor-to-Angular sync

2019-02-16 13:25发布

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.

2条回答
虎瘦雄心在
2楼-- · 2019-02-16 13:44

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;
      }]);
  });
});
查看更多
老娘就宠你
3楼-- · 2019-02-16 13:52

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

查看更多
登录 后发表回答