Skip to content
This repository was archived by the owner on Jul 29, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions lib/clientsidescripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ function wrapWithHelpers(fun) {

/**
* Wait until Angular has finished rendering and has
* no outstanding $http calls before continuing.
* no outstanding $http calls before continuing. The specific Angular app
* is determined by the rootSelector.
*
* Asynchronous.
*
Expand All @@ -50,6 +51,10 @@ functions.waitForAngular = function(rootSelector, callback) {
var el = document.querySelector(rootSelector);

try {
if (window.getAngularTestability) {
window.getAngularTestability(el).whenStable(callback);
return;
}
if (!window.angular) {
throw new Error('angular could not be found on the window');
}
Expand All @@ -68,6 +73,32 @@ functions.waitForAngular = function(rootSelector, callback) {
}
};

/**
* Wait until all Angular2 applications on the page have become stable.
*
* Asynchronous.
*
* @param {function(string)} callback callback. If a failure occurs, it will
* be passed as a parameter.
*/
functions.waitForAllAngular2 = function(callback) {
try {
var testabilities = window.getAllAngularTestabilities();
var count = testabilities.length;
var decrement = function() {
count--;
if (count === 0) {
callback();
}
};
testabilities.forEach(function(testability) {
testability.whenStable(decrement);
});
} catch (err) {
callback(err.message);
}
};

/**
* Find a list of elements in the page by their angular binding.
*
Expand Down Expand Up @@ -554,7 +585,8 @@ functions.findByCssContainingText = function(cssSelector, searchText, using) {
* Asynchronous.
*
* @param {number} attempts Number of times to retry.
* @param {function} asyncCallback callback
* @param {function({version: ?number, message: ?string})} asyncCallback callback
*
*/
functions.testForAngular = function(attempts, asyncCallback) {
var callback = function(args) {
Expand All @@ -564,19 +596,21 @@ functions.testForAngular = function(attempts, asyncCallback) {
};
var check = function(n) {
try {
if (window.angular && window.angular.resumeBootstrap) {
callback([true, null]);
if (window.getAllAngularTestabilities) {
callback({ver: 2});
} else if (window.angular && window.angular.resumeBootstrap) {
callback({ver: 1});
} else if (n < 1) {
if (window.angular) {
callback([false, 'angular never provided resumeBootstrap']);
callback({message: 'angular never provided resumeBootstrap'});
} else {
callback([false, 'retries looking for angular exceeded']);
callback({message: 'retries looking for angular exceeded'});
}
} else {
window.setTimeout(function() {check(n - 1);}, 1000);
}
} catch (e) {
callback([false, e]);
callback({message: e});
}
};
check(attempts);
Expand Down
92 changes: 63 additions & 29 deletions lib/protractor.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ Protractor.prototype.getProcessedConfig = null;
*/
Protractor.prototype.forkNewDriverInstance = null;


/**
* Instead of using a single root element, search through all angular apps
* available on the page when finding elements or waiting for stability.
* Only compatible with Angular2.
*/
Protractor.prototype.useAllAngular2AppRoots = function() {
// The empty string is an invalid css selector, so we use it to easily
// signal to scripts to not find a root element.
this.rootEl = '';
};

/**
* The same as {@code webdriver.WebDriver.prototype.executeScript},
* but with a customized description for debugging.
Expand Down Expand Up @@ -325,10 +337,20 @@ Protractor.prototype.waitForAngular = function(opt_description) {
}, 'Ignore Synchronization Protractor.waitForAngular()');
}

return this.executeAsyncScript_(
clientSideScripts.waitForAngular,
'Protractor.waitForAngular()' + description,
this.rootEl).
function runWaitForAngularScript() {
if (self.rootEl) {
return self.executeAsyncScript_(
clientSideScripts.waitForAngular,
'Protractor.waitForAngular()' + description,
self.rootEl);
} else {
return self.executeAsyncScript_(
clientSideScripts.waitForAllAngular2,
'Protractor.waitForAngular()' + description);
}
}

return runWaitForAngularScript().
then(function(browserErr) {
if (browserErr) {
throw 'Error while waiting for Protractor to ' +
Expand Down Expand Up @@ -621,40 +643,52 @@ Protractor.prototype.get = function(destination, opt_timeout) {
msg('test for angular'),
Math.floor(timeout / 1000)).
then(function(angularTestResult) {
var hasAngular = angularTestResult[0];
if (!hasAngular) {
var message = angularTestResult[1];
var angularVersion = angularTestResult.ver;
if (!angularVersion) {
var message = angularTestResult.message;
throw new Error('Angular could not be found on the page ' +
destination + ' : ' + message);
}
return angularVersion;
}, function(err) {
throw 'Error while running testForAngular: ' + err.message;
})
.then(null, deferred.reject);
.then(loadMocks, deferred.reject);

function loadMocks(angularVersion) {
if (angularVersion === 1) {
// At this point, Angular will pause for us until angular.resumeBootstrap
// is called.
var moduleNames = [];
for (var i = 0; i < self.mockModules_.length; ++i) {
var mockModule = self.mockModules_[i];
var name = mockModule.name;
moduleNames.push(name);
var executeScriptArgs = [mockModule.script, msg('add mock module ' + name)].
concat(mockModule.args);
self.executeScript_.apply(self, executeScriptArgs).
then(null, function(err) {
throw 'Error while running module script ' + name +
': ' + err.message;
})
.then(null, deferred.reject);
}

// At this point, Angular will pause for us until angular.resumeBootstrap
// is called.
var moduleNames = [];
for (var i = 0; i < this.mockModules_.length; ++i) {
var mockModule = this.mockModules_[i];
var name = mockModule.name;
moduleNames.push(name);
var executeScriptArgs = [mockModule.script, msg('add mock module ' + name)].
concat(mockModule.args);
this.executeScript_.apply(this, executeScriptArgs).
then(null, function(err) {
throw 'Error while running module script ' + name +
': ' + err.message;
})
.then(null, deferred.reject);
self.executeScript_(
'angular.resumeBootstrap(arguments[0]);',
msg('resume bootstrap'),
moduleNames)
.then(null, deferred.reject);
} else {
// TODO: support mock modules in Angular2. For now, error if someone
// has tried to use one.
if (self.mockModules_.length > 1) {
deferred.reject('Trying to load mock modules on an Angular2 app ' +
'is not yet supported.');
}
}
}

this.executeScript_(
'angular.resumeBootstrap(arguments[0]);',
msg('resume bootstrap'),
moduleNames)
.then(null, deferred.reject);

this.driver.controlFlow().execute(function() {
return self.plugins_.onPageStable().then(function() {
deferred.fulfill();
Expand Down
3 changes: 3 additions & 0 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ Runner.prototype.createBrowser = function(plugins) {
if (config.debuggerServerPort) {
browser_.debuggerServerPort_ = config.debuggerServerPort;
}
if (config.useAllAngular2AppRoots) {
browser_.useAllAngular2AppRoots();
}
var self = this;


Expand Down
37 changes: 37 additions & 0 deletions spec/angular2Conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
var env = require('./environment.js');

// This is the configuration for a smoke test for an Angular2 application.
//
// *** NOTE ***
// As Angular2 is in rapid development, the test application that ships with
// the Protractor repository does not yet contain an Angular2 section. This
// configuration assumes that you are serving the examples from the
// angular/angular repository at localhost:8000.
// See https://github.com/angular/angular/blob/master/DEVELOPER.md for
// setup instructions.
//
// TODO: when Angular2 is beta, include a test application in the
// Protractor repository.
exports.config = {
seleniumAddress: env.seleniumAddress,

framework: 'jasmine2',

specs: [
'ng2/async_spec.js'
],

capabilities: env.capabilities,

baseUrl: 'http://localhost:8000',

// Special option for Angular2, to test against all Angular2 applications
// on the page. This means that Protractor will wait for every app to be
// stable before each action, and search within all apps when finding
// elements.
useAllAngular2AppRoots: true

// Alternatively, you could specify one root element application, to test
// against only that one:
// rootElement: 'async-app'
};
57 changes: 57 additions & 0 deletions spec/ng2/async_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
describe('async angular2 application', function() {
var URL = 'examples/src/async/index.html';

beforeEach(function() {
browser.get(URL);
});

it('should work with synchronous actions', function() {
var increment = $('#increment');
increment.$('.action').click();

expect(increment.$('.val').getText()).toEqual('1');
});

it('should wait for asynchronous actions', function() {
var timeout = $('#delayedIncrement');

// At this point, the async action is still pending, so the count should
// still be 0.
expect(timeout.$('.val').getText()).toEqual('0');

timeout.$('.action').click();

expect(timeout.$('.val').getText()).toEqual('1');
});

it('should turn off when ignoreSynchronization is true', function() {
var timeout = $('#delayedIncrement');

// At this point, the async action is still pending, so the count should
// still be 0.
expect(timeout.$('.val').getText()).toEqual('0');

browser.ignoreSynchronization = true;

timeout.$('.action').click();
timeout.$('.cancel').click();

browser.ignoreSynchronization = false;

// whenStable should be called since the async action is cancelled. The
// count should still be 0;
expect(timeout.$('.val').getText()).toEqual('0');
});

it('should wait for a series of asynchronous actions', function() {
var timeout = $('#multiDelayedIncrements');

// At this point, the async action is still pending, so the count should
// still be 0.
expect(timeout.$('.val').getText()).toEqual('0');

timeout.$('.action').click();

expect(timeout.$('.val').getText()).toEqual('10');
});
});