Skip to content

Commit 9a8fcad

Browse files
vedharishsjelin
authored andcommitted
feat(driver providers): Add BrowserStack support.
Also added BrowserStack to CI Closes angular#1013
1 parent 93abf53 commit 9a8fcad

16 files changed

+273
-24
lines changed

.travis.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,35 @@ env:
88
global:
99
- SAUCE_USERNAME=angular-ci
1010
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
11+
- BROWSER_STACK_USERNAME=angularteam1
12+
- BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
1113
- LOGS_DIR=/tmp/protractor-build/logs
1214
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
1315
matrix:
1416
- JOB=full
1517
- JOB=smoke
18+
- JOB=browserstack
1619

1720
matrix:
1821
allow_failures:
1922
- env: "JOB=smoke"
23+
- env: "JOB=browserstack"
2024
exclude:
2125
- env: JOB=smoke
2226
node_js: "5"
27+
- env: JOB=browserstack
28+
node_js: "5"
2329

2430

2531
before_script:
2632
- npm run pretest
2733
- mkdir -p $LOGS_DIR
28-
- ./scripts/sauce_connect_setup.sh
29-
- ./scripts/wait_for_browser_provider.sh
34+
- ./scripts/setup_on_travis.sh
3035

3136
script:
3237
- ./scripts/testserver.sh
33-
- ./scripts/testonsauce.sh
38+
- ./scripts/test_on_travis.sh
3439

3540
after_script:
3641
- ./scripts/print_logs.sh
42+
- ./scripts/teardown_on_travis.sh

docs/referenceConf.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ exports.config = {
1414
//
1515
// Protractor needs to know how to connect to Drivers for the browsers
1616
// it is testing on. This is usually done through a Selenium Server.
17-
// There are four options - specify one of the following:
17+
// There are five options - specify one of the following:
1818
//
1919
// 1. seleniumServerJar - to start a standalone Selenium Server locally.
2020
// 2. seleniumAddress - to connect to a Selenium Server which is already
2121
// running.
2222
// 3. sauceUser/sauceKey - to use remote Selenium Servers via Sauce Labs.
23-
// 4. directConnect - to connect directly to the browser Drivers.
23+
// 4. bstackUser/bstackKey - to use remote Selenium Servers via BrowserStack.
24+
// 5. directConnect - to connect directly to the browser Drivers.
2425
// This option is only available for Firefox and Chrome.
2526

2627
// ---- 1. To start a standalone Selenium Server locally ---------------------
@@ -63,7 +64,13 @@ exports.config = {
6364
// ondemand.saucelabs.com:80/wd/hub
6465
sauceSeleniumAddress: null,
6566

66-
// ---- 4. To connect directly to Drivers ------------------------------------
67+
// ---- 4. To use remote browsers via BrowserStack ---------------------------
68+
// If bstackUser and bstackKey are specified, seleniumServerJar will be ignored.
69+
// The tests will be run remotely using BrowserStack.
70+
bstackUser: null,
71+
bstackKey: null,
72+
73+
// ---- 5. To connect directly to Drivers ------------------------------------
6774
// Boolean. If true, Protractor will connect directly to the browser Drivers
6875
// at the locations specified by chromeDriver and firefoxPath. Only Chrome
6976
// and Firefox are supported for direct connect.
@@ -110,7 +117,7 @@ exports.config = {
110117

111118
// Name of the process executing this capability. Not used directly by
112119
// protractor or the browser, but instead pass directly to third parties
113-
// like SauceLabs as the name of the job running this test
120+
// like BrowserStack and SauceLabs as the name of the job running this test
114121
name: 'Unnamed Job',
115122

116123
// User defined name for the capability that will display in the results log
@@ -139,6 +146,11 @@ exports.config = {
139146

140147
// Optional: override global seleniumAddress on this capability only.
141148
seleniumAddress: null
149+
150+
// Optional: Additional third-party specific capabilities can be
151+
// specified here.
152+
// For a list of BrowserStack specific capabilities, visit
153+
// https://www.browserstack.com/automate/capabilities
142154
},
143155

144156
// If you would like to run more than one instance of WebDriver on the same

docs/server-setup.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,33 @@ To connect to a running instance of a standalone Selenium Server, set this optio
5555

5656
- `seleniumAddress` - Connect to a running instance of a standalone Selenium Server. The address will be a URL.
5757

58-
Please note that if you set seleniumAddress, the settings for `seleniumServerJar`, `seleniumPort`, `seleniumArgs`, `sauceUser` and `sauceKey` will be ignored.
58+
Please note that if you set seleniumAddress, the settings for `seleniumServerJar`, `seleniumPort`, `seleniumArgs`, `bstackUser`, `bstackKey`, `sauceUser` and `sauceKey` will be ignored.
5959

6060

6161
Remote Selenium Server
6262
----------------------
6363

64-
To run your tests against a remote Selenium Server, you will need an account with a service that hosts the server (and the browser drivers). Protractor has built in support for [Sauce Labs](http://www.saucelabs.com).
64+
To run your tests against a remote Selenium Server, you will need an account with a service that hosts the server (and the browser drivers). Protractor has built in support for [BrowserStack](https://www.browserstack.com) and [Sauce Labs](http://www.saucelabs.com).
65+
66+
**Using BrowserStack as remote Selenium Server**
67+
68+
In your config file, set these options:
69+
- `bstackUser` - The username for your BrowserStack account.
70+
- `bstackKey` - The key for your BrowserStack account.
71+
72+
Please note that if you set `bstackUser` and `bstackKey`, the settings for `seleniumServerJar`, `seleniumPort`, `seleniumArgs`, `sauceUser` and `sauceKey` will be ignored.
73+
74+
You can optionally set the [`name` property](referenceConf.js#L121) in a capability in order to give the jobs a name on the server. Otherwise they will just be allotted a random hash.
75+
76+
**Using Sauce Labs as remote Selenium Server**
6577

6678
In your config file, set these options:
6779
- `sauceUser` - The username for your Sauce Labs account.
6880
- `sauceKey` - The key for your Sauce Labs account.
6981

70-
Please note that if you set `sauceUser` and `sauceKey`, the settings for `seleniumServerJar`, `seleniumPort` and `seleniumArgs` will be ignored.
82+
Please note that if you set `sauceUser` and `sauceKey`, the settings for `seleniumServerJar`, `seleniumPort`, `seleniumArgs`, `bstackUser` and `bstackKey` will be ignored.
7183

72-
You can optionally set the [`name` property](referenceConf.js#L113) in a capability in order to give the jobs a name on the server. Otherwise they will just be called `Unnamed Job`.
84+
You can optionally set the [`name` property](referenceConf.js#L121) in a capability in order to give the jobs a name on the server. Otherwise they will just be called `Unnamed Job`.
7385

7486

7587
Connecting Directly to Browser Drivers

lib/driverProviders/browserstack.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* This is an implementation of the Browserstack Driver Provider.
3+
* It is responsible for setting up the account object, tearing
4+
* it down, and setting up the driver correctly.
5+
*/
6+
7+
var util = require('util'),
8+
log = require('../logger.js'),
9+
request = require('request'),
10+
q = require('q'),
11+
DriverProvider = require('./driverProvider');
12+
13+
14+
var BrowserStackDriverProvider = function(config) {
15+
DriverProvider.call(this, config);
16+
};
17+
util.inherits(BrowserStackDriverProvider, DriverProvider);
18+
19+
20+
/**
21+
* Hook to update the BrowserStack job status.
22+
* @public
23+
* @param {Object} update
24+
* @return {q.promise} A promise that will resolve when the update is complete.
25+
*/
26+
BrowserStackDriverProvider.prototype.updateJob = function(update) {
27+
28+
var self = this;
29+
var deferredArray = this.drivers_.map(function(driver) {
30+
var deferred = q.defer();
31+
driver.getSession().then(function(session) {
32+
var jobStatus = update.passed ? 'completed' : 'error';
33+
log.puts('BrowserStack results available at https://www.browserstack.com/automate');
34+
request({
35+
url: 'https://www.browserstack.com/automate/sessions/'+session.getId()+'.json',
36+
headers: {
37+
'Content-Type': 'application/json',
38+
'Authorization': 'Basic ' + new Buffer(self.config_.bstackUser +
39+
':' + self.config_.bstackKey).toString('base64')
40+
},
41+
method: 'PUT',
42+
form: {
43+
'status': jobStatus
44+
}
45+
}, function(error){
46+
if(error) {
47+
throw new Error(
48+
'Error updating BrowserStack pass/fail status: ' + util.inspect(error)
49+
);
50+
}
51+
});
52+
deferred.resolve();
53+
});
54+
return deferred.promise;
55+
});
56+
return q.all(deferredArray);
57+
};
58+
59+
/**
60+
* Configure and launch (if applicable) the object's environment.
61+
* @public
62+
* @return {q.promise} A promise which will resolve when the environment is
63+
* ready to test.
64+
*/
65+
BrowserStackDriverProvider.prototype.setupEnv = function() {
66+
var deferred = q.defer();
67+
this.config_.capabilities['browserstack.user'] = this.config_.bstackUser;
68+
this.config_.capabilities['browserstack.key'] = this.config_.bstackKey;
69+
this.config_.seleniumAddress = 'http://hub.browserstack.com/wd/hub';
70+
71+
// Append filename to capabilities.name so that it's easier to identify tests.
72+
if (this.config_.capabilities.name &&
73+
this.config_.capabilities.shardTestFiles) {
74+
this.config_.capabilities.name += (
75+
':' + this.config_.specs.toString().replace(/^.*[\\\/]/, ''));
76+
}
77+
78+
log.puts('Using BrowserStack selenium server at ' +
79+
this.config_.seleniumAddress);
80+
deferred.resolve();
81+
return deferred.promise;
82+
};
83+
84+
// new instance w/ each include
85+
module.exports = function(config) {
86+
return new BrowserStackDriverProvider(config);
87+
};

lib/runner.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ Runner.prototype.loadDriverProvider_ = function() {
9494
runnerPath = './driverProviders/direct';
9595
} else if (this.config_.seleniumAddress) {
9696
runnerPath = './driverProviders/hosted';
97+
} else if (this.config_.bstackUser && this.config_.bstackKey) {
98+
runnerPath = './driverProviders/browserstack';
9799
} else if (this.config_.sauceUser && this.config_.sauceKey) {
98100
runnerPath = './driverProviders/sauce';
99101
} else if (this.config_.seleniumServerJar) {

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@
2727
"accessibility-developer-tools": "~2.6.0"
2828
},
2929
"devDependencies": {
30-
"expect.js": "~0.2.0",
30+
"browserstacktunnel-wrapper": "^1.4.2",
3131
"chai": "~3.3.0",
3232
"chai-as-promised": "~5.1.0",
33-
"jshint": "2.5.0",
34-
"mocha": "2.3.3",
3533
"cucumber": "~0.8.0",
34+
"expect.js": "~0.2.0",
3635
"express": "~3.3.4",
36+
"jshint": "2.5.0",
37+
"mocha": "2.3.3",
3738
"rimraf": "~2.2.6"
3839
},
3940
"repository": {

scripts/browsertstack/start_tunnel.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
var fs = require('fs');
4+
var http = require('http');
5+
var BrowserStackTunnel = require('browserstacktunnel-wrapper');
6+
7+
var HOSTNAME = 'localhost';
8+
var PORTS = [9876, 9877];
9+
var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY;
10+
var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE;
11+
var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER;
12+
13+
// We need to start fake servers, otherwise the tunnel does not start.
14+
var fakeServers = [];
15+
var hosts = [];
16+
17+
PORTS.forEach(function(port) {
18+
fakeServers.push(http.createServer(function() {}).listen(port));
19+
hosts.push({
20+
name: HOSTNAME,
21+
port: port,
22+
sslFlag: 0
23+
});
24+
});
25+
26+
var tunnel = new BrowserStackTunnel({
27+
key: ACCESS_KEY,
28+
localIdentifier: TUNNEL_IDENTIFIER,
29+
hosts: hosts
30+
});
31+
32+
console.log('Starting tunnel on ports', PORTS.join(', '));
33+
tunnel.start(function(error) {
34+
if (error) {
35+
console.error('Can not establish the tunnel', error);
36+
} else {
37+
console.log('Tunnel established.');
38+
fakeServers.forEach(function(server) {
39+
server.close();
40+
});
41+
42+
if (READY_FILE) {
43+
fs.writeFile(READY_FILE, '');
44+
}
45+
}
46+
});
47+
48+
tunnel.on('error', function(error) {
49+
console.error(error);
50+
});

scripts/browsertstack/start_tunnel.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
2+
3+
node ./scripts/browserstack/start_tunnel.js &
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
set -e -o pipefail
4+
5+
6+
echo "Shutting down Browserstack tunnel"
7+
echo "TODO: implement me"
8+
exit 1
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/bash
2+
3+
4+
# Wait for Connect to be ready before exiting
5+
# Time out if we wait for more than 2 minutes, so that we can print logs.
6+
let "counter=0"
7+
8+
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do
9+
let "counter++"
10+
if [ $counter -gt 240 ]; then
11+
echo "Timed out after 2 minutes waiting for browser provider ready file"
12+
# We must manually print logs here because travis will not run
13+
# after_script commands if the failure occurs before the script
14+
# phase.
15+
./scripts/ci/print-logs.sh
16+
exit 5
17+
fi
18+
sleep .5
19+
done

scripts/setup_on_travis.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
if [ $JOB = "browserstack" ]; then
2+
./scripts/browserstack/start_tunnel.sh
3+
./scripts/browserstack/waitfor_tunnel.sh
4+
else
5+
./scripts/sauce_connect_setup.sh
6+
./scripts/wait_for_browser_provider.sh
7+
fi

scripts/teardown_on_travis.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if [ $JOB = "browserstack" ]; then
2+
./scripts/browserstack/teardown_tunnel.sh
3+
fi

scripts/test_on_travis.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
2+
BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev`
3+
4+
if [ $JOB = "smoke" ]; then
5+
node bin/protractor spec/ciSmokeConf.js
6+
elif [ $JOB = "full" ]; then
7+
node bin/protractor spec/ciFullConf.js
8+
elif [ $JOB = "browserstack" ]; then
9+
node bin/protractor spec/ciBStackConf.js
10+
else
11+
echo "Unknown job type. Please set JOB=smoke, JOB=full, or JOB=browserstack"
12+
fi

scripts/testonsauce.sh

Lines changed: 0 additions & 9 deletions
This file was deleted.

spec/basicConf.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ var env = require('./environment.js');
22

33
// The main suite of Protractor tests.
44
exports.config = {
5-
seleniumAddress: env.seleniumAddress,
5+
bstackUser: process.env.BROWSER_STACK_USERNAME,
6+
bstackKey: process.env.BROWSER_STACK_ACCESS_KEY,
67

78
framework: 'jasmine',
89

0 commit comments

Comments
 (0)