Канонический способ отладки проблем с синхронизацией 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;
}]);
});
});