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

Commit 9c9ed31

Browse files
committed
feat(launcher): allow multicapabilities to take array of promises
Enables adding `getMultiCapabilities: function(){}` to your configuration file. The function returns either multiCapabilities or a promise of a multiCapabilities that is resolved after `afterLaunch` and before driver set up. If this is specified, both capabilities and multiCapabilities will be ignored. Also allows specifying `seleniumAddress` in the capabilities/multiCapabilities object, which will override the global `seleniumAddress`. This allows you to use a different `seleniumAddress` per capabilities. Breaking Changes: `capabilities` can no longer be a promise. Use getMultiCapabilities if you need to return a promise. `seleniumAddress` can no longer be a promise. Likewise, use getMultiCapabilities.
1 parent e5ce438 commit 9c9ed31

File tree

9 files changed

+123
-111
lines changed

9 files changed

+123
-111
lines changed

docs/referenceConf.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,27 @@ exports.config = {
125125
specs: ['spec/chromeOnlySpec.js'],
126126

127127
// Spec files to be excluded on this capability only.
128-
exclude: ['spec/doNotRunInChromeSpec.js']
128+
exclude: ['spec/doNotRunInChromeSpec.js'],
129129

130+
// Optional: override global seleniumAddress on this capability only.
131+
seleniumAddress: null
130132
},
131133

132134
// If you would like to run more than one instance of WebDriver on the same
133135
// tests, use multiCapabilities, which takes an array of capabilities.
134136
// If this is specified, capabilities will be ignored.
135137
multiCapabilities: [],
136138

139+
// If you need to resolve multiCapabilities asynchronously (i.e. wait for
140+
// server/proxy, set firefox profile, etc), you can specify a function here
141+
// which will return either `multiCapabilities` or a promise to
142+
// `multiCapabilities`.
143+
// If this returns a promise, it is resolved immediately after
144+
// `beforeLaunch` is run, and before any driver is set up.
145+
// If this is specified, both capabilities and multiCapabilities will be
146+
// ignored.
147+
getMultiCapabilities: null,
148+
137149
// Maximum number of total browser sessions to run. Tests are queued in
138150
// sequence if number of browser sessions is limited by this parameter.
139151
// Use a number less than 1 to denote unlimited. Default is unlimited.

lib/driverProviders/hosted.js

+3-12
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,9 @@ util.inherits(HostedDriverProvider, DriverProvider);
2121
* ready to test.
2222
*/
2323
HostedDriverProvider.prototype.setupEnv = function() {
24-
var config = this.config_,
25-
seleniumAddress = config.seleniumAddress;
26-
27-
if (q.isPromiseAlike(seleniumAddress)) {
28-
return q.when(seleniumAddress).then(function(resolvedAddress) {
29-
config.seleniumAddress = resolvedAddress;
30-
log.puts('Using the selenium server at ' + config.seleniumAddress);
31-
});
32-
} else {
33-
log.puts('Using the selenium server at ' + this.config_.seleniumAddress);
34-
return q.fcall(function() {});
35-
}
24+
log.puts('Using the selenium server at ' +
25+
this.config_.seleniumAddress);
26+
return q.fcall(function() {});
3627
};
3728

3829
// new instance w/ each include

lib/launcher.js

+65-26
Original file line numberDiff line numberDiff line change
@@ -114,34 +114,73 @@ var init = function(configFile, additionalConfig) {
114114
log.debug('Running with --troubleshoot');
115115
log.debug('Protractor version: ' + require('../package.json').version);
116116
log.debug('Your base url for tests is ' + config.baseUrl);
117-
var scheduler = new TaskScheduler(config);
118-
119-
process.on('exit', function(code) {
120-
if (code) {
121-
log_('Process exited with error code ' + code);
122-
} else if (scheduler.numTasksOutstanding() > 0) {
123-
log_('BUG: launcher exited with ' +
124-
scheduler.numTasksOutstanding() + ' tasks remaining');
125-
process.exit(RUNNERS_FAILED_EXIT_CODE);
126-
}
127-
});
128-
129-
var cleanUpAndExit = function(exitCode) {
130-
return helper.runFilenameOrFn_(
131-
config.configDir, config.afterLaunch, [exitCode]).
132-
then(function(returned) {
133-
if (typeof returned === 'number') {
134-
process.exit(returned);
135-
} else {
136-
process.exit(exitCode);
137-
}
138-
}, function(err) {
139-
log_('Error:', err);
140-
process.exit(1);
141-
});
142-
};
143117

118+
// Run beforeLaunch
144119
helper.runFilenameOrFn_(config.configDir, config.beforeLaunch).then(function() {
120+
121+
// Set `multicapabilities` using `capabilities`, `multicapabilites`,
122+
// `getMultiCapabilities()`, or default
123+
return q.promise(function(resolve) {
124+
if (config.getMultiCapabilities &&
125+
typeof config.getMultiCapabilities === 'function') {
126+
if (config.multiCapabilities.length || config.capabilities) {
127+
log.warn('getMultiCapabilities() will override both capabilites ' +
128+
'and multiCapabilities');
129+
}
130+
// If getMultiCapabilities is defined and a function, use this.
131+
q.when(config.getMultiCapabilities(), function(multiCapabilities) {
132+
config.multiCapabilities = multiCapabilities;
133+
config.capabilities = null;
134+
}).then(resolve);
135+
} else {
136+
resolve();
137+
}
138+
}).then(function() {
139+
if (config.capabilities) {
140+
if (config.multiCapabilities.length) {
141+
log.warn('You have specified both capabilites and ' +
142+
'multiCapabilities. This will result in capabilities being ' +
143+
'ignored');
144+
} else {
145+
// Use capabilities if multiCapabilities is empty.
146+
config.multiCapabilities = [config.capabilities];
147+
}
148+
} else if (!config.multiCapabilities.length) {
149+
// Default to chrome if no capabilities given
150+
config.multiCapabilities = [{
151+
browserName: 'chrome'
152+
}];
153+
}
154+
});
155+
}).then(function() {
156+
var scheduler = new TaskScheduler(config);
157+
158+
process.on('exit', function(code) {
159+
if (code) {
160+
log_('Process exited with error code ' + code);
161+
} else if (scheduler.numTasksOutstanding() > 0) {
162+
log_('BUG: launcher exited with ' +
163+
scheduler.numTasksOutstanding() + ' tasks remaining');
164+
process.exit(RUNNERS_FAILED_EXIT_CODE);
165+
}
166+
});
167+
168+
// Run afterlaunch and exit
169+
var cleanUpAndExit = function(exitCode) {
170+
return helper.runFilenameOrFn_(
171+
config.configDir, config.afterLaunch, [exitCode]).
172+
then(function(returned) {
173+
if (typeof returned === 'number') {
174+
process.exit(returned);
175+
} else {
176+
process.exit(exitCode);
177+
}
178+
}, function(err) {
179+
log_('Error:', err);
180+
process.exit(1);
181+
});
182+
};
183+
145184
var totalTasks = scheduler.numTasksOutstanding();
146185
var forkProcess = false;
147186
if (totalTasks > 1) { // Start new processes only if there are >1 tasks.

lib/runner.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ var Runner = function(config) {
4646
flow.timeout(1000, 'waiting for debugger to attach');
4747
}
4848

49+
if (config.capabilities && config.capabilities.seleniumAddress) {
50+
config.seleniumAddress = config.capabilities.seleniumAddress;
51+
}
4952
this.loadDriverProvider_(config);
5053
this.setTestPreparer(config.onPrepare);
5154
};
@@ -238,10 +241,6 @@ Runner.prototype.run = function() {
238241
// 1) Setup environment
239242
//noinspection JSValidateTypes
240243
return this.driverprovider_.setupEnv().then(function() {
241-
// Resolve capabilities first, so it can be a promise
242-
return q(self.config_.capabilities).then(function(capabilities) {
243-
self.config_.capabilities = capabilities;
244-
});
245244
// 2) Create a browser and setup globals
246245
}).then(function() {
247246
browser_ = self.createBrowser();

lib/taskScheduler.js

+3-18
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
'use strict';
66

77
var ConfigParser = require('./configParser');
8-
var log = require('./logger.js');
98

109
// A queue of specs for a particular capacity
1110
var TaskQueue = function(capabilities, specLists) {
@@ -19,8 +18,10 @@ var TaskQueue = function(capabilities, specLists) {
1918
/**
2019
* A scheduler to keep track of specs that need running and their associated
2120
* capabilities. It will suggest a task (combination of capabilities and spec)
22-
* to run while observing the following config rules: capabilities,
21+
* to run while observing the following config rules:
2322
* multiCapabilities, shardTestFiles, and maxInstance.
23+
* Precondition: multiCapabilities is a non-empty array
24+
* (capabilities and getCapabilities will both be ignored)
2425
*
2526
* @constructor
2627
* @param {Object} config parsed from the config file
@@ -32,22 +33,6 @@ var TaskScheduler = function(config) {
3233
return excludes.indexOf(path) < 0;
3334
});
3435

35-
if (config.capabilities) {
36-
if (config.multiCapabilities.length) {
37-
log.warn('You have specified both capabilites and ' +
38-
'multiCapabilities. This will result in capabilities being ' +
39-
'ignored');
40-
} else {
41-
// Use capabilities if multiCapabilities is empty.
42-
config.multiCapabilities = [config.capabilities];
43-
}
44-
} else if (!config.multiCapabilities.length) {
45-
// Default to chrome if no capabilities given
46-
config.multiCapabilities = [{
47-
browserName: 'chrome'
48-
}];
49-
}
50-
5136
var taskQueues = [];
5237
config.multiCapabilities.forEach(function(capabilities) {
5338
var capabilitiesSpecs = allSpecs;

scripts/test.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ var passingTests = [
2525
'node lib/cli.js spec/ngHintSuccessConfig.js',
2626
'node lib/cli.js spec/interactionConf.js',
2727
'node lib/cli.js spec/directConnectConf.js',
28-
'node lib/cli.js spec/restartBrowserBetweenTestsConf.js'
28+
'node lib/cli.js spec/restartBrowserBetweenTestsConf.js',
29+
'node lib/cli.js spec/getCapabilitiesConf.js'
2930
];
3031

3132
passingTests.push(

spec/getCapabilitiesConf.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var env = require('./environment.js');
2+
var q = require('q');
3+
4+
exports.config = {
5+
seleniumAddress: env.seleniumAddress,
6+
7+
// Spec patterns are relative to this directory.
8+
specs: [
9+
'basic/mock*',
10+
],
11+
12+
framework: 'debugprint',
13+
getMultiCapabilities: function() {
14+
var deferred = q.defer();
15+
// Wait for a server to be ready or get capabilities asynchronously.
16+
setTimeout(function() {
17+
deferred.resolve([{
18+
'browserName': 'firefox'
19+
}]);
20+
}, 1000);
21+
return deferred.promise;
22+
},
23+
24+
baseUrl: env.baseUrl
25+
};
26+

spec/unit/runner_test.js

-23
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,6 @@ describe('the Protractor runner', function() {
3030
});
3131
});
3232

33-
it('should wait for promise capabilities to resolve', function(done) {
34-
var config = {
35-
mockSelenium: true,
36-
specs: ['*.js'],
37-
framework: 'debugprint',
38-
capabilities: q.when({
39-
'browserName': 'customfortest'
40-
})
41-
};
42-
var exitCode;
43-
Runner.prototype.exit_ = function(exit) {
44-
exitCode = exit;
45-
};
46-
var runner = new Runner(config);
47-
48-
runner.run().then(function() {
49-
expect(runner.getConfig().capabilities.browserName).
50-
toEqual('customfortest');
51-
expect(exitCode).toEqual(0);
52-
done();
53-
});
54-
});
55-
5633
it('should fail with no specs', function() {
5734
var config = {
5835
mockSelenium: true,

spec/unit/taskScheduler_test.js

+8-26
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ describe('the task scheduler', function() {
99
'spec/unit/data/fakespecA.js',
1010
'spec/unit/data/fakespecB.js'
1111
],
12-
capabilities: {
12+
multiCapabilities: [{
1313
browserName: 'chrome'
14-
}
14+
}]
1515
};
1616
var config = new ConfigParser().addConfig(toAdd).getConfig();
1717
var scheduler = new TaskScheduler(config);
@@ -30,11 +30,11 @@ describe('the task scheduler', function() {
3030
'spec/unit/data/fakespecA.js',
3131
'spec/unit/data/fakespecB.js'
3232
],
33-
capabilities: {
33+
multiCapabilities: [{
3434
shardTestFiles: true,
3535
maxInstances: 2,
3636
browserName: 'chrome'
37-
}
37+
}]
3838
};
3939
var config = new ConfigParser().addConfig(toAdd).getConfig();
4040
var scheduler = new TaskScheduler(config);
@@ -58,10 +58,10 @@ describe('the task scheduler', function() {
5858
'spec/unit/data/fakespecA.js',
5959
'spec/unit/data/fakespecB.js'
6060
],
61-
capabilities: {
61+
multiCapabilities: [{
6262
count: 2,
6363
browserName: 'chrome'
64-
}
64+
}]
6565
};
6666
var config = new ConfigParser().addConfig(toAdd).getConfig();
6767
var scheduler = new TaskScheduler(config);
@@ -113,11 +113,11 @@ describe('the task scheduler', function() {
113113
'spec/unit/data/fakespecA.js',
114114
'spec/unit/data/fakespecB.js'
115115
],
116-
capabilities: {
116+
multiCapabilities: [{
117117
shardTestFiles: true,
118118
maxInstances: 1,
119119
browserName: 'chrome'
120-
}
120+
}]
121121
};
122122
var config = new ConfigParser().addConfig(toAdd).getConfig();
123123
var scheduler = new TaskScheduler(config);
@@ -215,24 +215,6 @@ describe('the task scheduler', function() {
215215
expect(scheduler.numTasksOutstanding()).toEqual(0);
216216
});
217217

218-
it('should default to chrome when no capability is defined', function() {
219-
var toAdd = {
220-
specs: [
221-
'spec/unit/data/fakespecA.js',
222-
'spec/unit/data/fakespecB.js'
223-
]
224-
};
225-
var config = new ConfigParser().addConfig(toAdd).getConfig();
226-
var scheduler = new TaskScheduler(config);
227-
228-
var task = scheduler.nextTask();
229-
expect(task.capabilities.browserName).toEqual('chrome');
230-
expect(task.specs.length).toEqual(2);
231-
232-
task.done();
233-
expect(scheduler.numTasksOutstanding()).toEqual(0);
234-
});
235-
236218
it('should exclude capability-specific specs', function() {
237219
var toAdd = {
238220
specs: [

0 commit comments

Comments
 (0)