Skip to content

Commit ecc14f9

Browse files
committed
Initial Commit
0 parents  commit ecc14f9

File tree

12 files changed

+1436
-0
lines changed

12 files changed

+1436
-0
lines changed

.eslintrc.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"rules": {
3+
"indent": [
4+
1,
5+
2
6+
],
7+
"quotes": [
8+
2,
9+
"single"
10+
],
11+
"linebreak-style": [
12+
2,
13+
"unix"
14+
],
15+
"semi": [
16+
2,
17+
"always"
18+
],
19+
"no-console": 0,
20+
"no-trailing-spaces": 2,
21+
"no-irregular-whitespace": 2,
22+
"camelcase": 2,
23+
"brace-style": [2, "1tbs", { "allowSingleLine": true }]
24+
},
25+
"env": {
26+
"es6": true,
27+
"node": true
28+
},
29+
"extends": "eslint:recommended"
30+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
bin/
2+
node_modules/
3+
npm-debug.log

bin/darwin/x64/BrowserStackLocal

7.74 MB
Binary file not shown.

index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
var browserStackTunnel = require('./lib/browserStackTunnel');
2+
3+
function BrowserStackApi() {
4+
var tunnel = null;
5+
6+
this.start = function(options, callback) {
7+
tunnel = new browserStackTunnel(options);
8+
if(callback == null) {
9+
callback = function() {};
10+
}
11+
tunnel.start(callback);
12+
};
13+
14+
this.isRunning = function() {
15+
return (tunnel.state === 'started');
16+
};
17+
18+
this.logs = function() {
19+
};
20+
21+
this.stop = function(callback) {
22+
if(tunnel) {
23+
tunnel.stop(callback);
24+
}
25+
};
26+
}
27+
28+
module.exports = BrowserStackApi;

lib/ZipBinary.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
var path = require('path'),
2+
https = require('https'),
3+
unzip = require('unzip'),
4+
fs = require('fs'),
5+
log = require('./helper').log;
6+
7+
function ZipBinary(platform, arch, bin, ext) {
8+
var self = this;
9+
self.bin = (typeof(bin) == 'undefined' || bin == null || !bin.trim()) ? path.resolve(path.join(__dirname, '..', 'bin', arch ? path.join(platform, arch) : platform)) : bin;
10+
self.path = path.resolve(path.join(self.bin, 'BrowserStackLocal' + (ext ? '.' + ext : '')));
11+
self.command = self.path;
12+
self.args = [];
13+
14+
self.update = function (callback) {
15+
var extractStream = unzip.Extract({
16+
path: self.bin
17+
});
18+
https.get('https://www.browserstack.com/browserstack-local/BrowserStackLocal-' + platform + (arch ? '-' + arch : '') + '.zip', function (response) {
19+
log.info('Downloading binary for ' + platform + (arch ? '-' + arch : '') + ' ...');
20+
extractStream.on('close', function () {
21+
log.info('Download complete');
22+
fs.chmod(self.path, '0755', callback);
23+
});
24+
response.pipe(extractStream);
25+
});
26+
};
27+
}
28+
29+
module.exports = ZipBinary;

lib/browserStackTunnel.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
var fs = require('fs'),
2+
childProcess = require('child_process'),
3+
os = require('os'),
4+
ZipBinary = require('./ZipBinary'),
5+
log = require('./helper').log;
6+
7+
function BrowserStackTunnel(options) {
8+
var params = [],
9+
startCallback = null,
10+
stopCallback = null,
11+
doneStart = function(params) {
12+
if(startCallback !== null) {
13+
startCallback(params);
14+
}
15+
},
16+
doneStop = function(params) {
17+
if(stopCallback !== null) {
18+
stopCallback(params);
19+
}
20+
};
21+
22+
var binary;
23+
switch (os.platform()) {
24+
case 'linux':
25+
switch (os.arch()) {
26+
case 'x64':
27+
binary = new ZipBinary('linux', 'x64', options.path);
28+
break;
29+
case 'ia32':
30+
binary = new ZipBinary('linux', 'ia32', options.path);
31+
break;
32+
}
33+
break;
34+
case 'darwin':
35+
binary = new ZipBinary('darwin', 'x64', options.path);
36+
break;
37+
default:
38+
binary = new ZipBinary('win32', null, options.path, 'exe');
39+
break;
40+
}
41+
42+
this.stdoutData = '';
43+
this.tunnel = null;
44+
45+
if (options.hosts) {
46+
params.push(options.hosts);
47+
}
48+
49+
if (options.localIdentifier) {
50+
params.push('-localIdentifier', options.localIdentifier);
51+
}
52+
53+
if (options.verbose) {
54+
params.push('-v');
55+
}
56+
57+
if (options.force) {
58+
params.push('-force');
59+
}
60+
61+
if (options.forcelocal) {
62+
params.push('-forcelocal');
63+
}
64+
65+
if (options.onlyAutomate) {
66+
params.push('-onlyAutomate');
67+
}
68+
69+
if (options.proxyHost) {
70+
params.push('-proxyHost', options.proxyHost);
71+
}
72+
73+
if (options.proxyPort) {
74+
params.push('-proxyPort', options.proxyPort);
75+
}
76+
77+
if (options.proxyUser) {
78+
params.push('-proxyUser', options.proxyUser);
79+
}
80+
81+
if (options.proxyPass) {
82+
params.push('-proxyPass', options.proxyPass);
83+
}
84+
85+
if (options.key == null || !options.key.trim()) {
86+
options.key = process.env.BROWSERSTACK_ACCESS_KEY;
87+
}
88+
89+
this.state = 'stop';
90+
this.stateMatchers = {
91+
'already_running': new RegExp('\\*\\*Error: There is another JAR already running'),
92+
'invalid_key': new RegExp('\\*\\*Error: You provided an invalid key'),
93+
'connection_failure': new RegExp('\\*\\*Error: Could not connect to server'),
94+
'newer_available': new RegExp('There is a new version of BrowserStackTunnel.jar available on server'),
95+
'started': new RegExp('Press Ctrl-C to exit')
96+
};
97+
98+
this.updateState = function (data) {
99+
var state;
100+
this.stdoutData += data.toString();
101+
for (state in this.stateMatchers) {
102+
if (this.stateMatchers.hasOwnProperty(state) && this.stateMatchers[state].test(this.stdoutData) && this.state !== state) {
103+
this.state = state;
104+
switch(state) {
105+
case('newer_available'):
106+
log.warn('BrowserStackTunnel: binary out of date');
107+
this.killTunnel();
108+
var self = this;
109+
binary.update(function () {
110+
self.startTunnel();
111+
});
112+
break;
113+
case('invalid_key'):
114+
doneStart(new Error('Invalid key'));
115+
break;
116+
case('connection_failure'):
117+
doneStart(new Error('Could not connect to server'));
118+
break;
119+
case('already_running'):
120+
doneStart(new Error('child already started'));
121+
break;
122+
default:
123+
doneStart();
124+
break;
125+
}
126+
break;
127+
}
128+
}
129+
};
130+
131+
this.killTunnel = function () {
132+
if (this.tunnel) {
133+
this.tunnel.stdout.removeAllListeners('data');
134+
this.tunnel.stderr.removeAllListeners('data');
135+
this.tunnel.removeAllListeners('error');
136+
this.tunnel.kill();
137+
this.tunnel = null;
138+
}
139+
};
140+
141+
this.exit = function () {
142+
if (this.state !== 'started' && this.state !== 'newer_available') {
143+
doneStart(new Error('child failed to start:\n' + this.stdoutData));
144+
} else if (this.state !== 'newer_available') {
145+
this.state = 'stop';
146+
doneStop();
147+
}
148+
};
149+
150+
this.cleanUp = function () {
151+
this.stdoutData = '';
152+
};
153+
154+
this._startTunnel = function () {
155+
this.cleanUp();
156+
log.info('Local started with args: ' + JSON.stringify(params));
157+
this.tunnel = childProcess.spawn(binary.command, binary.args.concat([options.key]).concat(params));
158+
this.tunnel.stdout.on('data', this.updateState.bind(this));
159+
this.tunnel.stderr.on('data', this.updateState.bind(this));
160+
this.tunnel.on('error', this.killTunnel.bind(this));
161+
this.tunnel.on('exit', this.exit.bind(this));
162+
};
163+
164+
this.startTunnel = function () {
165+
var self = this;
166+
if (!fs.existsSync(binary.path)) {
167+
log.warn('Binary not present');
168+
binary.update(function () {
169+
self._startTunnel();
170+
});
171+
} else {
172+
try {
173+
this._startTunnel();
174+
}catch(e) {
175+
log.warn('The downloaded binary might be corrupt. Retrying download');
176+
binary.update(function () {
177+
self._startTunnel();
178+
});
179+
}
180+
}
181+
};
182+
183+
this.start = function (callback) {
184+
startCallback = callback;
185+
if (this.state === 'started') {
186+
doneStart(new Error('child already started'));
187+
} else {
188+
this.startTunnel();
189+
}
190+
};
191+
192+
this.stop = function (callback) {
193+
if (this.state !== 'stop') {
194+
stopCallback = callback;
195+
} else if (this.state !== 'started') {
196+
var err = new Error('child not started');
197+
callback(err);
198+
}
199+
200+
this.killTunnel();
201+
};
202+
}
203+
204+
module.exports = BrowserStackTunnel;

lib/helper.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
var log = require('npmlog');
2+
if(process.env.NODE_ENV === 'testing') {
3+
log.level = 'silent';
4+
}
5+
6+
exports.log = log;

node-example.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
var browserstackApi = require('./index');
2+
3+
var api = new browserstackApi();
4+
var webdriver = require('selenium-webdriver');
5+
var identifier = 'adqqwdqwd';
6+
7+
var capabilities = {
8+
build: 'build',
9+
'browserName': 'chrome',
10+
'os': 'OS X',
11+
'browserstack.local': true,
12+
'browserstack.localIdentifier': identifier
13+
}
14+
15+
var options = {
16+
key: process.env.BROWSERSTACK_ACCESS_KEY,
17+
//hosts: [{
18+
// name: 'localhost',
19+
// port: 8080,
20+
// sslFlag: 0
21+
//}],
22+
//path: 'bin',
23+
localIdentifier: identifier,
24+
verbose: true,
25+
//proxyUser: '',
26+
//proxyPass: '',
27+
//proxyPort: 80,
28+
//proxyHost: 'host',
29+
force: true,
30+
forcelocal: true,
31+
onlyAutomate: true
32+
};
33+
34+
api.start(options, function(error) {
35+
console.log('Is Running ' + api.isRunning());
36+
console.log('Started');
37+
console.log('Is Running ' + api.isRunning());
38+
capabilities['browserstack.user'] = process.env.BROWSERSTACK_USERNAME;
39+
console.log('Is Running ' + api.isRunning());
40+
capabilities['browserstack.key'] = process.env.BROWSERSTACK_ACCESS_KEY
41+
console.log('Is Running ' + api.isRunning());
42+
driver = new webdriver.Builder().usingServer('http://hub.browserstack.com/wd/hub').withCapabilities(capabilities).build();
43+
console.log('Is Running ' + api.isRunning());
44+
driver.get("http://localhost:3000").then(function() {
45+
console.log('Is Running ' + api.isRunning());
46+
driver.quit().then(function() {
47+
console.log('Is Running ' + api.isRunning());
48+
api.stop(function() {
49+
console.log('Is Running ' + api.isRunning());
50+
console.log('Stopped');
51+
});
52+
});
53+
});
54+
});

package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "browserstack-node-api",
3+
"version": "0.1.0",
4+
"description": "Node client to run selenium tests on BrowserStack",
5+
"main": "index.js",
6+
"scripts": {
7+
"pretest": "./node_modules/.bin/eslint lib/* index.js",
8+
"test": "NODE_ENV=testing ./node_modules/.bin/mocha"
9+
},
10+
"keywords": [
11+
"BrowserStack",
12+
"selenium",
13+
"testing"
14+
],
15+
"author": "BrowserStack",
16+
"license": "MIT",
17+
"dependencies": {
18+
"npmlog": "2.0.2",
19+
"unzip": "0.1.11"
20+
},
21+
"devDependencies": {
22+
"eslint": "1.10.3",
23+
"expect.js": "0.3.1",
24+
"mocha": "2.4.5",
25+
"mocks": "0.0.15",
26+
"sinon": "1.17.3"
27+
}
28+
}

0 commit comments

Comments
 (0)