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

Commit

Permalink
feat(protractor): expose pending $http and $timeout on a timeout
Browse files Browse the repository at this point in the history
Now when a test times out while waiting for Angular to be stable, pending
$timeout and $http tasks will be reported to the console.
  • Loading branch information
hankduan committed Jun 4, 2015
1 parent b986944 commit 6c10378
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 1 deletion.
13 changes: 13 additions & 0 deletions lib/clientsidescripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,19 @@ functions.setLocation = function(selector, url) {
}
};

/**
* Retrieve the pending $http requests.
*
* @param {string} selector The selector housing an ng-app
* @return {!Array<!Object>} An array of pending http requests.
*/
functions.getPendingHttpRequests = function(selector) {
var el = document.querySelector(selector);
var $injector = angular.element(el).injector();
var $http = $injector.get('$http');
return $http.pendingRequests;
};

/* Publish all the functions as strings to pass to WebDriver's
* exec[Async]Script. In addition, also include a script that will
* install all the functions on window (for debugging.)
Expand Down
78 changes: 77 additions & 1 deletion lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var clientSideScripts = require('./clientsidescripts.js');
var ProtractorBy = require('./locators.js').ProtractorBy;
var ExpectedConditions = require('./expectedConditions.js');

// jshint browser: true
/* global angular */

var DEFER_LABEL = 'NG_DEFER_BOOTSTRAP!';
Expand Down Expand Up @@ -276,6 +277,7 @@ Protractor.prototype.executeAsyncScript_ =
*/
Protractor.prototype.waitForAngular = function(opt_description) {
var description = opt_description ? ' - ' + opt_description : '';
var self = this;
if (this.ignoreSynchronization) {
return webdriver.promise.fulfilled();
}
Expand All @@ -301,9 +303,41 @@ Protractor.prototype.waitForAngular = function(opt_description) {
timeout = /-?[\d\.]*\ ms/.exec(err.message);
}
if (timeout) {
throw 'Timed out waiting for Protractor to synchronize with ' +
var errMsg = 'Timed out waiting for Protractor to synchronize with ' +
'the page after ' + timeout + '. Please see ' +
'https://github.com/angular/protractor/blob/master/docs/faq.md';
var pendingTimeoutsPromise = self.executeScript_(
'return window.NG_PENDING_TIMEOUTS',
'Protractor.waitForAngular() - getting pending timeouts' + description
);
var pendingHttpsPromise = self.executeScript_(
clientSideScripts.getPendingHttpRequests,
'Protractor.waitForAngular() - getting pending https' + description,
self.rootEl
);
return webdriver.promise.all([
pendingTimeoutsPromise, pendingHttpsPromise]).
then(function(arr) {
var pendingTimeouts = arr[0] || [];
var pendingHttps = arr[1] || [];

var key, pendingTasks = [];
for (key in pendingTimeouts) {
if (pendingTimeouts.hasOwnProperty(key)) {
pendingTasks.push('- $timeout: ' + pendingTimeouts[key]);
}
}
for (key in pendingHttps) {
pendingTasks.push('- $http: ' + pendingHttps[key].url);
}
if (pendingTasks.length) {
errMsg += '. The following tasks were pending:\n';
errMsg += pendingTasks.join('\n');
}
throw errMsg;
}, function() {
throw errMsg;
});
} else {
throw err;
}
Expand Down Expand Up @@ -414,6 +448,48 @@ Protractor.prototype.addBaseMockModules_ = function() {
if ($compileProvider.debugInfoEnabled) {
$compileProvider.debugInfoEnabled(true);
}
}]).
config(['$provide', function($provide) {
$provide.decorator('$timeout', ['$delegate', function($delegate) {
var $timeout = $delegate;

var taskId = 0;
if (!window.NG_PENDING_TIMEOUTS) {
window.NG_PENDING_TIMEOUTS = {};
}

var extendedTimeout = function() {
var args = Array.prototype.slice.call(arguments);
if (typeof(args[0]) !== 'function') {
return $timeout.apply(null, args);
}

taskId++;
var fn = args[0];
window.NG_PENDING_TIMEOUTS[taskId] = fn.toString();
var wrappedFn = (function(taskId_) {
return function() {
delete window.NG_PENDING_TIMEOUTS[taskId_];
return fn.apply(null, arguments);
};
})(taskId);
args[0] = wrappedFn;

var promise = $timeout.apply(null, args);
promise.ptorTaskId_ = taskId;
return promise;
};

extendedTimeout.cancel = function() {
var taskId_ = arguments[0] && arguments[0].ptorTaskId_;
if (taskId_) {
delete window.NG_PENDING_TIMEOUTS[taskId_];
}
return $timeout.cancel.apply($timeout, arguments);
};

return extendedTimeout;
}]);
}]);
});
};
Expand Down
10 changes: 10 additions & 0 deletions scripts/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ executor.addCommandlineTest('node lib/cli.js spec/errorTest/pluginsFailingConf.j
{message: 'from teardown'}
]);

executor.addCommandlineTest('node lib/cli.js spec/errorTest/slowHttpAndTimeoutConf.js')
.expectExitCode(1)
.expectErrors([
{message: 'The following tasks were pending[\\s\\S]*\\$http: \/slowcall'},
{message: 'The following tasks were pending[\\s\\S]*' +
'\\$timeout: function \\(\\) {[\\s\\S]*' +
'\\$scope\\.slowAngularTimeoutStatus = \'done\';[\\s\\S]' +
'*}'}
]);

// Check ngHint plugin

executor.addCommandlineTest(
Expand Down
27 changes: 27 additions & 0 deletions spec/errorTest/baseCase/slow_http_and_timeout_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
describe('slow asynchronous events', function() {
beforeEach(function() {
browser.get('index.html#/async');
});

it('waits for http calls', function() {
var status = element(by.binding('slowHttpStatus'));
var button = element(by.css('[ng-click="slowHttp()"]'));

expect(status.getText()).toEqual('not started');

button.click();

expect(status.getText()).toEqual('done');
});

it('waits for $timeout', function() {
var status = element(by.binding('slowAngularTimeoutStatus'));
var button = element(by.css('[ng-click="slowAngularTimeout()"]'));

expect(status.getText()).toEqual('not started');

button.click();

expect(status.getText()).toEqual('done');
});
});
19 changes: 19 additions & 0 deletions spec/errorTest/slowHttpAndTimeoutConf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var env = require('../environment.js');

exports.config = {
seleniumAddress: env.seleniumAddress,

framework: 'jasmine2',

specs: [
'baseCase/slow_http_and_timeout_spec.js'
],

multiCapabilities: [{
'browserName': 'chrome'
}],

baseUrl: env.baseUrl,

allScriptsTimeout: 1000
};

0 comments on commit 6c10378

Please sign in to comment.