Канонический способ отладки проблем с синхронизацией Protractor-to-Angular

Описание проблемы:

Недавно мы получили эту позорную ошибку, открыв одну из страниц нашего приложения в сквозном тесте Protractor:

Не удалось: время ожидания ожидания асинхронных задач Angular заканчивается через 50 секунд. Это может быть связано с тем, что текущая страница не является приложением Angular.

Это происходит при вызове browser.get("/some/page/"); в одном из наших тестов:

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

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

И, что странно в нашем случае, заключается в том, что ошибка не выбрасывается ни на какую другую страницу в нашем веб-приложении Angular - Транспортировка синхронизирует с Angular без каких-либо проблем. ng-app местоположение одинаково на всех страницах - ng-app определено в корневом теге html:

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

Поведение согласовано - каждый раз, когда мы переходим на эту страницу с помощью browser.get(), мы получаем эту ошибку. Каждый раз, когда мы переходим к любой другой странице нашего приложения, синхронизация работает.

Обратите внимание, что, конечно, мы можем отключить синхронизацию для этой страницы и рассматривать ее как не-w504 > , но это можно рассматривать только как обходной путь.

Вопросы:

Что еще может привести к сбою синхронизации Protractor-to- Angular? Что мы должны проверить?

И, в общем, каков рекомендуемый способ отладки проблем синхронизации в Protractor?

Использование в настоящее время последней версии Protractor 5.5.1, Angular 1.5.6.

Ответ 1

Хорошо, так что вопрос заинтриговал меня, поэтому я придумал программное решение о том, как определить, что ожидает транспортир:

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)

Здесь, в plunker http://plnkr.co/edit/O0CkpnsnUuwEAV8I2Jil?p=preview, вы можете поэкспериментировать с таймаутом и вызовом $http, моя отложенная конечная точка будет ждать 10 секунд до разрешая вызов, надеюсь, что это будет полезно для вас.

Ответ 2

Я согласен с @maurycy, что проблема связана с $http/$timeout. Простое исправление обычно заменяет $timeout на $interval, как описано здесь: https://github.com/angular/protractor/blob/master/docs/timeouts.md

Рекомендации: merge these sane defaults: allScriptsTimeout: 60000, // 1 minute jasmineNodeOpts: { defaultTimeoutInterval: 300000 // 5 minutes. Allows for 5 commands spanning the full synchronization timeout. }

Если вы хотите найти виновника $http/$timeout, я бы использовал декораторы angular для применения пользовательской логики вокруг этих служб. Это также хороший способ издеваться над услугами angular, обращающимися к сторонним службам. 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;
      }]);
  });
});