Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.

Commit 6c10378

Browse files
committed
feat(protractor): expose pending $http and $timeout on a timeout
Now when a test times out while waiting for Angular to be stable, pending $timeout and $http tasks will be reported to the console.
1 parent b986944 commit 6c10378

File tree

5 files changed

+146
-1
lines changed

5 files changed

+146
-1
lines changed

lib/clientsidescripts.js

+13
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,19 @@ functions.setLocation = function(selector, url) {
646646
}
647647
};
648648

649+
/**
650+
* Retrieve the pending $http requests.
651+
*
652+
* @param {string} selector The selector housing an ng-app
653+
* @return {!Array<!Object>} An array of pending http requests.
654+
*/
655+
functions.getPendingHttpRequests = function(selector) {
656+
var el = document.querySelector(selector);
657+
var $injector = angular.element(el).injector();
658+
var $http = $injector.get('$http');
659+
return $http.pendingRequests;
660+
};
661+
649662
/* Publish all the functions as strings to pass to WebDriver's
650663
* exec[Async]Script. In addition, also include a script that will
651664
* install all the functions on window (for debugging.)

lib/protractor.js

+77-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ var clientSideScripts = require('./clientsidescripts.js');
1212
var ProtractorBy = require('./locators.js').ProtractorBy;
1313
var ExpectedConditions = require('./expectedConditions.js');
1414

15+
// jshint browser: true
1516
/* global angular */
1617

1718
var DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
@@ -276,6 +277,7 @@ Protractor.prototype.executeAsyncScript_ =
276277
*/
277278
Protractor.prototype.waitForAngular = function(opt_description) {
278279
var description = opt_description ? ' - ' + opt_description : '';
280+
var self = this;
279281
if (this.ignoreSynchronization) {
280282
return webdriver.promise.fulfilled();
281283
}
@@ -301,9 +303,41 @@ Protractor.prototype.waitForAngular = function(opt_description) {
301303
timeout = /-?[\d\.]*\ ms/.exec(err.message);
302304
}
303305
if (timeout) {
304-
throw 'Timed out waiting for Protractor to synchronize with ' +
306+
var errMsg = 'Timed out waiting for Protractor to synchronize with ' +
305307
'the page after ' + timeout + '. Please see ' +
306308
'https://github.com/angular/protractor/blob/master/docs/faq.md';
309+
var pendingTimeoutsPromise = self.executeScript_(
310+
'return window.NG_PENDING_TIMEOUTS',
311+
'Protractor.waitForAngular() - getting pending timeouts' + description
312+
);
313+
var pendingHttpsPromise = self.executeScript_(
314+
clientSideScripts.getPendingHttpRequests,
315+
'Protractor.waitForAngular() - getting pending https' + description,
316+
self.rootEl
317+
);
318+
return webdriver.promise.all([
319+
pendingTimeoutsPromise, pendingHttpsPromise]).
320+
then(function(arr) {
321+
var pendingTimeouts = arr[0] || [];
322+
var pendingHttps = arr[1] || [];
323+
324+
var key, pendingTasks = [];
325+
for (key in pendingTimeouts) {
326+
if (pendingTimeouts.hasOwnProperty(key)) {
327+
pendingTasks.push('- $timeout: ' + pendingTimeouts[key]);
328+
}
329+
}
330+
for (key in pendingHttps) {
331+
pendingTasks.push('- $http: ' + pendingHttps[key].url);
332+
}
333+
if (pendingTasks.length) {
334+
errMsg += '. The following tasks were pending:\n';
335+
errMsg += pendingTasks.join('\n');
336+
}
337+
throw errMsg;
338+
}, function() {
339+
throw errMsg;
340+
});
307341
} else {
308342
throw err;
309343
}
@@ -414,6 +448,48 @@ Protractor.prototype.addBaseMockModules_ = function() {
414448
if ($compileProvider.debugInfoEnabled) {
415449
$compileProvider.debugInfoEnabled(true);
416450
}
451+
}]).
452+
config(['$provide', function($provide) {
453+
$provide.decorator('$timeout', ['$delegate', function($delegate) {
454+
var $timeout = $delegate;
455+
456+
var taskId = 0;
457+
if (!window.NG_PENDING_TIMEOUTS) {
458+
window.NG_PENDING_TIMEOUTS = {};
459+
}
460+
461+
var extendedTimeout = function() {
462+
var args = Array.prototype.slice.call(arguments);
463+
if (typeof(args[0]) !== 'function') {
464+
return $timeout.apply(null, args);
465+
}
466+
467+
taskId++;
468+
var fn = args[0];
469+
window.NG_PENDING_TIMEOUTS[taskId] = fn.toString();
470+
var wrappedFn = (function(taskId_) {
471+
return function() {
472+
delete window.NG_PENDING_TIMEOUTS[taskId_];
473+
return fn.apply(null, arguments);
474+
};
475+
})(taskId);
476+
args[0] = wrappedFn;
477+
478+
var promise = $timeout.apply(null, args);
479+
promise.ptorTaskId_ = taskId;
480+
return promise;
481+
};
482+
483+
extendedTimeout.cancel = function() {
484+
var taskId_ = arguments[0] && arguments[0].ptorTaskId_;
485+
if (taskId_) {
486+
delete window.NG_PENDING_TIMEOUTS[taskId_];
487+
}
488+
return $timeout.cancel.apply($timeout, arguments);
489+
};
490+
491+
return extendedTimeout;
492+
}]);
417493
}]);
418494
});
419495
};

scripts/test.js

+10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ executor.addCommandlineTest('node lib/cli.js spec/errorTest/pluginsFailingConf.j
116116
{message: 'from teardown'}
117117
]);
118118

119+
executor.addCommandlineTest('node lib/cli.js spec/errorTest/slowHttpAndTimeoutConf.js')
120+
.expectExitCode(1)
121+
.expectErrors([
122+
{message: 'The following tasks were pending[\\s\\S]*\\$http: \/slowcall'},
123+
{message: 'The following tasks were pending[\\s\\S]*' +
124+
'\\$timeout: function \\(\\) {[\\s\\S]*' +
125+
'\\$scope\\.slowAngularTimeoutStatus = \'done\';[\\s\\S]' +
126+
'*}'}
127+
]);
128+
119129
// Check ngHint plugin
120130

121131
executor.addCommandlineTest(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
describe('slow asynchronous events', function() {
2+
beforeEach(function() {
3+
browser.get('index.html#/async');
4+
});
5+
6+
it('waits for http calls', function() {
7+
var status = element(by.binding('slowHttpStatus'));
8+
var button = element(by.css('[ng-click="slowHttp()"]'));
9+
10+
expect(status.getText()).toEqual('not started');
11+
12+
button.click();
13+
14+
expect(status.getText()).toEqual('done');
15+
});
16+
17+
it('waits for $timeout', function() {
18+
var status = element(by.binding('slowAngularTimeoutStatus'));
19+
var button = element(by.css('[ng-click="slowAngularTimeout()"]'));
20+
21+
expect(status.getText()).toEqual('not started');
22+
23+
button.click();
24+
25+
expect(status.getText()).toEqual('done');
26+
});
27+
});
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var env = require('../environment.js');
2+
3+
exports.config = {
4+
seleniumAddress: env.seleniumAddress,
5+
6+
framework: 'jasmine2',
7+
8+
specs: [
9+
'baseCase/slow_http_and_timeout_spec.js'
10+
],
11+
12+
multiCapabilities: [{
13+
'browserName': 'chrome'
14+
}],
15+
16+
baseUrl: env.baseUrl,
17+
18+
allScriptsTimeout: 1000
19+
};

0 commit comments

Comments
 (0)