├── .gitignore ├── .npmignore ├── MIT-LICENSE.txt ├── README.md ├── bin └── cli.js ├── lib ├── cli_util.js └── display.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /MIT-LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Derek Brans http://github.com/dbrans 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | browserstack-cli 2 | ================ 3 | 4 | Awesome command line interface for the browserstack api. 5 | 6 | Please submit pull requests to https://github.com/airportyh/browserstack-cli/ 7 | 8 | ## Installation 9 | 10 | npm install -g browserstack-cli 11 | 12 | ## Usage 13 | 14 | ### Setup 15 | 16 | Setup your credentials and API key. This will prompt for your BrowserStack username/password and your tunneling API keys, which you can get from their [automated browser testing page](http://www.browserstack.com/automated-browser-testing-api) and [local testing page](http://www.browserstack.com/local-testing#cmd-tunnel) while you are logged in. 17 | 18 | browserstack setup 19 | 20 | ### Available Browsers 21 | 22 | Get a list of available browsers: 23 | 24 | browserstack browsers 25 | 26 | ### Launch a Browser 27 | 28 | Launch firefox 3.6 and point it to google.com: 29 | 30 | browserstack launch firefox:3.6 http://google.com 31 | 32 | Launch will use the latest version if none is specified: 33 | 34 | browserstack launch firefox http://google.com 35 | 36 | Using the ``--attach`` option keeps the program running until it receives a SIGTERM or a SIGINT (CTRL-C) signal, at which point it kills the remote browser and then exits. 37 | 38 | browserstack launch --attach firefox http://google.com 39 | 40 | Can you launch mobile browsers? Yes. 41 | 42 | browserstack launch "iPhone 5" http://google.com 43 | 44 | ### List Active Jobs 45 | 46 | browserstack jobs 47 | 48 | ### Killing Jobs 49 | 50 | Kill a job by ID 51 | 52 | browserstack kill 514664 53 | 54 | or kill'em all 55 | 56 | browserstack killall 57 | 58 | ### Tunneling 59 | 60 | browserstack tunnel localhost:8080 61 | 62 | ## Usage 63 | 64 | Usage: cli.js [options] [command] 65 | 66 | Commands: 67 | 68 | setup Initial setup 69 | launch Launch a browser 70 | browsers List available browsers 71 | jobs List active jobs 72 | kill Kill an active job 73 | killall Kill all active jobs 74 | tunnel Setup tunneling 75 | status Get the current status 76 | 77 | Options: 78 | 79 | -h, --help output usage information 80 | -V, --version output the version number 81 | -u, --user Browserstack authentication 82 | -a, --attach Attach process to launched browser 83 | -o, --os The os of the browser or device. Defaults to "win" 84 | -t, --timeout Launch duration after which browsers exit 85 | -p, --private Use the private web tunneling key for manual testing 86 | -k, --key Tunneling key 87 | 88 | ## Programmatic API 89 | 90 | `browserstack-cli` is supported by a companion library [browseroverflow](https://github.com/airportyh/browseroverflow) which is essentially a one-to-one mapping of `browserstack-cli's` commands to API calls. 91 | 92 | ## Issues, Questions? 93 | 94 | To ask a question or report an issue, please open a [github issue](https://github.com/airportyh/browserstack-cli/issues/new). 95 | 96 | ## Contributors 97 | 98 | * [Derek Brans](http://github.com/dbrans) 99 | * [Toby Ho](http://github.com/airportyh) 100 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var program = require('commander') 4 | var BrowserStack = require('browseroverflow') 5 | var cli_util = require('../lib/cli_util') 6 | var exitIfErrorElse = cli_util.exitIfErrorElse 7 | var hangOnTillExit = cli_util.hangOnTillExit 8 | 9 | var display = require('../lib/display') 10 | 11 | program.version(require(__dirname + '/../package').version) 12 | 13 | program 14 | .option('-u, --user ', 'Browserstack authentication') 15 | .option('-a, --attach', "Attach process to launched browser") 16 | .option('-o, --os', 'The os of the browser or device. Defaults to "win"') 17 | .option('-t, --timeout ', "Launch duration after which browsers exit", 30) 18 | .option('-p, --private', "Use the private web tunneling key for manual testing") 19 | .option('-k, --key ', "Tunneling key") 20 | 21 | 22 | program 23 | .command('setup') 24 | .description('Initial setup') 25 | .action(setup) 26 | 27 | function setup(){ 28 | makeBS().setup(program) 29 | } 30 | 31 | program 32 | .command('launch ') 33 | .description('Launch a browser') 34 | .action(launchBrowser) 35 | 36 | function launchBrowser(browserSpec, url){ 37 | var browser = browserSpec.split(':')[0] 38 | var version = browserSpec.split(':')[1] 39 | makeBS().launch({ 40 | browser: browser, 41 | browser_version: version, 42 | url: url 43 | }, exitIfErrorElse(function(job){ 44 | console.log('Launched job ' + job.id + '.') 45 | if (program.attach){ 46 | console.log('Ctrl-C to kill job.') 47 | hangOnTillExit(function(){ 48 | killJob(job.id) 49 | }) 50 | } 51 | })) 52 | } 53 | 54 | program 55 | .command('browsers') 56 | .description('List available browsers') 57 | .action(listBrowsers) 58 | 59 | function listBrowsers(){ 60 | makeBS().browsers(exitIfErrorElse(function(browsers){ 61 | display.displayBrowsers(browsers) 62 | })) 63 | } 64 | 65 | program 66 | .command('jobs') 67 | .description('List active jobs') 68 | .action(listJobs) 69 | 70 | function listJobs(){ 71 | makeBS().jobs(exitIfErrorElse(function(jobs){ 72 | if (jobs.length === 0){ 73 | console.log('No active jobs.') 74 | }else{ 75 | display.displayJobs(jobs) 76 | } 77 | })) 78 | } 79 | 80 | program 81 | .command('kill ') 82 | .description('Kill an active job') 83 | .action(killJob) 84 | 85 | function killJob(jobId){ 86 | makeBS().kill(jobId, exitIfErrorElse(function(info){ 87 | console.log('Killed job ' + jobId + ' which ran for ' + 88 | Math.round(info.time) + 's.') 89 | })) 90 | } 91 | 92 | program 93 | .command('killall') 94 | .description('Kill all active jobs') 95 | .action(killAllJobs) 96 | 97 | function killAllJobs(){ 98 | makeBS().killAllJobs(exitIfErrorElse(function(){ 99 | console.log('Killed all the jobs.') 100 | })) 101 | } 102 | 103 | program 104 | .command('tunnel ') 105 | .description('Setup tunneling') 106 | .action(makeATunnel) 107 | 108 | function makeATunnel(hostAndPort){ 109 | makeBS().tunnel({ 110 | hostAndPort: hostAndPort, 111 | key: program.key, 112 | usePrivateKey: program['private'] 113 | }, exitIfErrorElse(function(){ 114 | console.log('Tunnel is running.') 115 | process.stdin.resume() 116 | })) 117 | } 118 | 119 | program 120 | .command('status') 121 | .description('Get the current status') 122 | .action(getStatus) 123 | 124 | function getStatus(){ 125 | makeBS().status(exitIfErrorElse(function(status){ 126 | for (var prop in status){ 127 | console.log(display.capitalize(prop.replace(/_/g, ' ')) + ': ' + status[prop]) 128 | } 129 | })) 130 | } 131 | 132 | program.parse(process.argv) 133 | 134 | if (program.args.length === 0){ 135 | program.outputHelp() 136 | } 137 | 138 | function makeBS(){ 139 | return BrowserStack(config()) 140 | 141 | function config(){ 142 | if (!program.user) return {} 143 | var parts = program.user.split(':') 144 | if (parts.length !== 2){ 145 | console.error('--user option should be in format "user:password"') 146 | process.exit(1) 147 | } 148 | return { 149 | username: parts[0], 150 | password: parts[1] 151 | } 152 | } 153 | } 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /lib/cli_util.js: -------------------------------------------------------------------------------- 1 | exports.hangOnTillExit = hangOnTillExit 2 | function hangOnTillExit(fun){ 3 | process.stdin.resume() 4 | 'SIGINT SIGTERM SIGHUP exit'.split(' ').forEach(function(evt){ 5 | process.on(evt, function(){ 6 | process.stdin.pause() 7 | fun() 8 | }) 9 | }) 10 | } 11 | 12 | exports.exitIfErrorElse = exitIfErrorElse 13 | function exitIfErrorElse(callback){ 14 | return function(err){ 15 | if (err){ 16 | console.error(err.message) 17 | return process.exit(1) 18 | } 19 | var args = Array.prototype.slice.call(arguments, 1) 20 | callback.apply(this, args) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/display.js: -------------------------------------------------------------------------------- 1 | var Table = require('cli-table') 2 | 3 | exports.displayBrowsers = displayBrowsers 4 | function displayBrowsers(browsers){ 5 | var table = new Table({ 6 | head: ['Browser', 'Device', 'OS'], 7 | colWidth: [100, 100, 100] 8 | }) 9 | browsers.forEach(function(browser){ 10 | table.push([ 11 | browserDisplay(browser), 12 | deviceDisplay(browser), 13 | osDisplay(browser) 14 | ]) 15 | }) 16 | console.log(table.toString()) 17 | } 18 | 19 | exports.displayJobs = displayJobs 20 | function displayJobs(jobs){ 21 | var table = new Table({ 22 | head: ['ID', 'Browser/Device', 'OS', 'Status'], 23 | colWidth: [100, 100, 100] 24 | }) 25 | jobs.forEach(function(job){ 26 | table.push([ 27 | job.id, 28 | job.browser ? browserDisplay(job) : deviceDisplay(job), 29 | osDisplay(job), 30 | job.status 31 | ]) 32 | }) 33 | console.log(table.toString()) 34 | } 35 | 36 | function browserDisplay(browser){ 37 | var ret = browser.browser 38 | if (browser.browser_version){ 39 | ret += ' (' + browser.browser_version + ')' 40 | } 41 | return ret 42 | } 43 | 44 | function osDisplay(browser){ 45 | return browser.os + ' (' + browser.os_version + ')' 46 | } 47 | 48 | function deviceDisplay(browser){ 49 | return browser.device || 'NA' 50 | } 51 | 52 | exports.capitalize = capitalize 53 | function capitalize(str){ 54 | return str.substring(0, 1).toUpperCase() + str.substring(1) 55 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": [ 3 | { 4 | "name": "Derek Brans", 5 | "email": "dbrans@gmail.com", 6 | "website": "http://github.com/dbrans" 7 | }, 8 | { 9 | "name": "Toby Ho", 10 | "email": "airportyh@gmail.com", 11 | "website": "http://github.com/airportyh" 12 | } 13 | ], 14 | "name": "browserstack-cli", 15 | "description": "A command line interface for the BrowserStack API.", 16 | "version": "0.2.10", 17 | "homepage": "https://github.com/airportyh/browserstack-cli", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/airportyh/browserstack-cli.git" 21 | }, 22 | "engines": { 23 | "node": ">=0.6.0" 24 | }, 25 | "dependencies": { 26 | "commander": "1.x", 27 | "browseroverflow": "~0.0.6", 28 | "cli-table": "~0.2.0" 29 | }, 30 | "bin": { 31 | "browserstack": "bin/cli.js" 32 | } 33 | } 34 | --------------------------------------------------------------------------------