Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' of https://github.com/ryanseddon/bunyip into gh…

…-pages

* 'master' of https://github.com/ryanseddon/bunyip:
  Bumped version and added documentation
  Added config check for localbrowsers so you don't need one to run them
  Experimenting with launching locally installed browsers
  Added release history info
  Added config generator command, config flag - Added config command e.g. `bunyip config config.js` fixes #1 - Added -c flag to specify path to a config file, defaults to cwd - Re-architectured helpers.js to browserstack.js - Fixed undefined value when killing browserstack workers - Bumped version to 0.1.3
  Bumped required node version, fixes #7, and added .editorconfig file
  Bumped required node version and added .editorconfig file
  Yeti 0.2.6 release has breaking changes force 0.2.5
  Update lib/helpers.js: Remove duplication.
  Remove switch statement and simplify the platform helper keywords
  Fix markdown link in readme
  Added keywords to package.json
  Bunyip initial release version 0.1.0 - Broke lib into manageable chunks - Added grunt.js - Did very simple unit testing using cli-easy - Some detailed instructions in the readme
  • Loading branch information...
commit cd07f5b5e94f8054d42199cb2b8c245022b2a1a6 2 parents 575b0a8 + d7b0ce9
@ryanseddon authored
View
8 .editorconfig
@@ -0,0 +1,8 @@
+; editorconfig.org
+root = true
+
+; Unix-style newlines
+[*]
+end_of_line = LF
+indent_style = space
+indent_size = 4
View
4 .gitignore
@@ -0,0 +1,4 @@
+/node_modules/
+/lib/config.js
+.DS_Store
+*.log
View
1  .npmignore
@@ -0,0 +1 @@
+/node_modules/
View
22 LICENSE-MIT
@@ -0,0 +1,22 @@
+Copyright (c) 2012 Ryan Seddon
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
View
114 README.md
@@ -0,0 +1,114 @@
+# bunyip
+
+Automate client-side unit testing in real browsers using the CLI
+
+## Getting Started
+Install the module with: `npm install -g bunyip`. This is a CLI tool so it needs to be globally installed.
+
+### BrowserStack account
+In order for bunyip to flex its real muscle I recommend you get a paid [BrowserStack account](http://www.browserstack.com/pricing) as all paid accounts have access to their API. Without the API you'll need to connect your own slave browsers to bunyip.
+
+### localhost sharing service
+If you wish to test on devices that are not part of your local network you'll be required to setup a tunneling service. I recommend [pagekite](https://pagekite.net/support/quickstart/) as it gives you a nice free chunk of data and allows you to specify a reusable subdomain. [Showoff.io](https://showoff.io/) is another good option.
+
+### Setup the config.js file
+If you don't wish to use BrowserStack or a localhost sharing service you can skip this step.
+
+To generate a config file you can run this command:
+
+`bunyip config path/to/config.js`
+
+This will take you through a multistep process to setup your username, password, tunnel url and tunnel command.
+
+So I if wanted to save a config file to my home directory I would do the following.
+
+`bunyip config ~/config.js`
+
+### Specify config file to use
+
+If I want to specify a specific config file to use the `-c, --cdir` flag lets you do that.
+
+`bunyip -f test/tests.html -c path/to/config.js`
+
+If you do not specify a config file to use it will look in your current working directory for a file name `config.js` otherwise it will not require one in.
+
+## Test suite adaptors
+
+Behind the scenes bunyip uses a tool called Yeti unfortunately Yeti only works with YUI Test. However I have written some [adaptors](https://github.com/ryanseddon/yeti-adaptors) for QUnit and jasmine, go check out my other repo for examples on using them with your current test suites.
+
+If you use another client-side testsuite please feel free to contribute it to my [yeti-adaptors](https://github.com/ryanseddon/yeti-adaptors) repo.
+
+## Examples
+
+```bash
+bunyip -f index.html
+```
+
+The above command will launch a simple Yeti hub on port 9000 and use the `index.html` inside your current working directory.
+
+```bash
+bunyip -f index.html -p 1337
+```
+
+This will change the port that is used. The global config value will be updated for you so don't worry.
+
+### Locally installed browsers
+
+Using the `local` command you can now open your test suite in all locally installed browsers or specify a series of browsers
+
+```bash
+bunyip -f index.html local
+```
+
+This will open in all locally installed browsers with one assumption that phantomjs is installed in `/usr/bin/`.
+
+```bash
+bunyip -f index.html local -l "firefox|chrome|safari|phantomjs"
+```
+
+This will open the installed versions of Firefox, Chrome, Safari and Phantomjs.
+
+The `local` command looks for the following browsers:
+
+* Firefox, Firefox Nightly
+* Chrome, Chrome Canary
+* Opera, Opera Next
+* Safari
+* Phantomjs
+
+### BrowserStack workers
+
+```bash
+bunyip -f index.html -b ios
+```
+
+Assuming you have a BrowserStack paid account and have setup a localhost sharing service the `-b ios` will send off a command to launch all iOS devices (3 iPhones and 3 iPads) on BrowserStack and once they're connected you can run your test suite.
+
+```bash
+bunyip -s
+```
+
+This will query the BrowserStack API for any device or browsers that are currently running on your account.
+
+```bash
+bunyip -k <id> or all
+```
+
+If you no longer need a specific worker or you wish to destroy all of them you can either specify a single worker id or `all` and it will destroy said worker(s).
+
+```bash
+bunyip -h
+```
+
+For more info specify the help flag to get more info about each command flag available.
+
+## Contributing
+In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
+
+## Release History
+* 20/08/2012 - v0.2.0 - Added a `local` command to run your test suite in locally installed browsers.
+* 15/07/2012 - v0.1.3 - Added ability to generate config file and to specify location of config file to use.
+
+## License
+Copyright (c) 2012 Ryan Seddon
+Licensed under the MIT license.
View
3  cli.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+var program = require('./lib/options');
+require("./lib/bunyip").route(program);
View
39 grunt.js
@@ -0,0 +1,39 @@
+module.exports = function(grunt) {
+
+ // Project configuration.
+ grunt.initConfig({
+ pkg: '<json:package.json>',
+ test: {
+ files: ['test/**/*.js']
+ },
+ lint: {
+ files: ['grunt.js', 'lib/**/*.js']
+ },
+ watch: {
+ files: '<config:lint.files>',
+ tasks: 'default'
+ },
+ jshint: {
+ options: {
+ curly: true,
+ eqeqeq: true,
+ immed: true,
+ latedef: true,
+ newcap: true,
+ noarg: true,
+ sub: true,
+ undef: true,
+ boss: true,
+ eqnull: true,
+ node: true
+ },
+ globals: {
+ exports: true
+ }
+ }
+ });
+
+ // Default task.
+ grunt.registerTask('default', 'lint');
+
+};
View
164 lib/browserstack.js
@@ -0,0 +1,164 @@
+var browserstack = require("browserstack"),
+ fs = require("fs");
+
+/*
+ Code heavily influenced by jayarjo's fork
+ https://github.com/jayarjo/bunyip/blob/master/lib/browserstack.js
+*/
+
+function BrowserStack(config) {
+ var workers = [],
+ bs = config.browserstack,
+ tunnellink = config.tunnellink,
+ timeout = bs.timeout || 480, // 8 minutes
+ client = browserstack.createClient(bs),
+ platforms = /^(all|ios|win|mac|android|opera)$/,
+ osMap = {},
+
+ loadBrowsers = function(browsers) {
+ browsers.forEach(function(browser, idx){
+ client.createWorker(browser, function(err, worker){
+ if(err) {
+ console.log(err);
+ console.log("Whoops! BrowserStack failed to create a worker: %s", err);
+ } else {
+ console.log(" BrowserStack "+ (browser.browser || browser.device) + " " + browser.version +" worker launched: %s", worker.id);
+ workers.push(worker.id);
+ }
+ });
+ });
+ },
+ availableBrowsers = function(cb) {
+ client.getBrowsers(function(err, browsers){
+ if(!err) {
+ cb(browsers);
+ }
+
+ console.log(err);
+ });
+ },
+ killBrowser = function(id, cb) {
+ client.terminateWorker(id, function(err, data) {
+ if(cb) {
+ cb(err, data);
+ }
+ });
+ },
+ killBrowsers = function() {
+ client.getWorkers(function(err, workers) {
+ workers.forEach(function(worker, i) {
+ killBrowser(worker.id, function(err, data) {
+ console.log("Worker %s successfully killed, it ran for %ss", this, Math.round(data.time));
+ }.bind(worker.id));
+ });
+ });
+ },
+ getWorkers = function() {
+ client.getWorkers(function(err, workers) {
+ var running = [],
+ queued = [];
+
+ workers.forEach(function(worker, i) {
+ if(worker.status === "running") {
+ running.push((worker.browser || worker.device) + " - " + worker.version + " ("+worker.id+") ");
+ } else if(worker.status === "queue") {
+ queued.push((worker.browser || worker.device) + " - " + worker.version + " ("+worker.id+") ");
+ }
+ });
+
+ console.log("Running:\n\t" + running.join("\n\t"));
+ console.log("Queued:\n\t" + queued.join("\n\t"));
+ });
+ },
+ manageBrowsers = function (program) {
+ var testURL = "http://"+config.tunnellink,
+ timeout = bs.timeout,
+ browsers = [],
+ file = false;
+
+ if(program.browsers && !platforms.test(program.browsers)) {
+ try {
+ file = fs.readFileSync(program.browsers,'utf8');
+ } catch(e) {}
+
+ if(file) {
+ // You can pass in a JSON file specifying the browsers
+ browsers = JSON.parse(file);
+
+ browsers.forEach(function(browser,i) {
+ browser.url = testURL;
+ browser.timeout = timeout;
+ });
+ } else {
+ var opt = program.browsers.split('|'),
+ versions, os, platform, data;
+
+ opt.forEach(function(browser,i) {
+ data = browser.split("/");
+ platform = data[0].split(":");
+ browser = platform[0];
+ os = platform[1];
+ versions = data[1].split(',');
+
+ versions.forEach(function(ver, i) {
+ browsers.push({
+ browser: browser,
+ device: browser,
+ os: os,
+ version: ver,
+ url: testURL,
+ timeout: timeout
+ });
+ });
+ });
+ }
+
+ this.loadBrowsers(browsers);
+ } else if(platforms.test(program.browsers)) {
+
+ if(program.browsers === "all") {
+ this.availableBrowsers(function(list){
+ list.forEach(function(browser, i) {
+ browser.url = testURL;
+ browser.timeout = timeout;
+ });
+
+ this.loadBrowsers(list);
+ });
+ } else {
+ this.platformBrowsers(program.browsers);
+ }
+ }
+ },
+ platformBrowsers = function(os) {
+ osMap = {};
+
+ availableBrowsers(function(browsers) {
+ browsers.forEach(function(browser, i) {
+ if(!osMap[browser.os]) {
+ osMap[browser.os] = [];
+ }
+ osMap[browser.os].push({
+ browser: browser.browser || "",
+ device: browser.device || "",
+ version: browser.version,
+ os: browser.os,
+ url: "http://" + config.tunnellink,
+ timeout: timeout
+ });
+ });
+
+ loadBrowsers(osMap[os]);
+ });
+ };
+
+ this.loadBrowsers = loadBrowsers;
+ this.availableBrowsers = availableBrowsers;
+ this.killBrowser = killBrowser;
+ this.killBrowsers = killBrowsers;
+ this.getWorkers = getWorkers;
+ this.manageBrowsers = manageBrowsers;
+ this.platformBrowsers = platformBrowsers;
+}
+
+module.exports = BrowserStack;
View
135 lib/bunyip.js
@@ -0,0 +1,135 @@
+#!/usr/bin/env node
+/*
+ * bunyip
+ * http://ryanseddon.github.com/Bunyip
+ *
+ * Copyright (c) 2012 Ryan Seddon
+ * Licensed under the MIT license.
+ */
+
+var util = require('util'),
+ yeti = require("../node_modules/yeti/lib/cli").route,
+ fs = require("fs"),
+ BrowserStack = require("./browserstack"),
+ LocalBrowsers = require("./localbrowsers"),
+ tunnel = require("./tunnel");
+
+var config, bs, lb,
+ browserstack = false,
+ local = false;
+
+exports.route = function(program) {
+ if(program.cdir) {
+ if(program.cdir.indexOf("/") !== -1) {
+ // Absolute path specified e.g. -c /Users/Name/config.js
+ config = require(program.cdir);
+ } else {
+ // Relative to cwd e.g. -c config.js
+ config = require(process.cwd() + "/" + program.cdir);
+ }
+ } else {
+ try {
+ // Default to current working directory and default name of config.js
+ config = require(process.cwd() + "/config.js");
+ } catch(e){}
+ }
+
+ if(program.port) {
+ var url = "http://localhost:"+program.port;
+
+ if(config) {
+ config.port = program.port;
+ config.tunnel = config.tunnel.replace("9000", config.port);
+ }
+ } else {
+ program.port = 9000;
+ }
+
+ if(program.status || program.available || program.browsers || program.kill) {
+ bs = new BrowserStack(config);
+ browserstack = true;
+ }
+
+ if(program.status) {
+ bs.getWorkers();
+ } else if(program.available) {
+ bs.availableBrowsers(function(browsers){
+ var list = "",
+ browser,
+ device = {},
+ curBrowser;
+
+ for (var i = 0; i < browsers.length; i++) {
+ curBrowser = browsers[i];
+
+ if(curBrowser.device) {
+ if(curBrowser.device === device.platform || curBrowser.version === device.version) {
+ list += " " + curBrowser.device + ", ";
+ } else {
+ list += "\n" + curBrowser.os + " "+ curBrowser.version + ": " + curBrowser.device + ",";
+ }
+ } else if(curBrowser.browser === browser) {
+ list += " " + curBrowser.version + ", ";
+ } else {
+ list += "\n" + curBrowser.browser + " ("+ curBrowser.os + "): " + curBrowser.version + ", ";
+ }
+
+ browser = curBrowser.browser;
+ device = {
+ platform: curBrowser.device,
+ version: curBrowser.version
+ };
+
+ }
+
+ console.log(list);
+ });
+ } else if(program.kill) {
+ if(program.kill ==="all") {
+ bs.killBrowsers();
+ } else {
+ bs.killBrowser(program.kill, function(err, data){
+ console.log("Worker %s successfully killed, it ran for %ss", this, Math.round(data.time));
+ }.bind(program.kill));
+ }
+ } else if(program.file) {
+ try {
+ yeti(["","", program.file, "--port=" + program.port]);
+ } catch(e) {
+ console.log(e);
+ }
+
+ if(config) {
+ tunnel.create(config);
+ }
+
+ if(browserstack) {
+ bs.manageBrowsers(program);
+ }
+
+ process.nextTick(function(){
+ program.commands.forEach(function(cmd, i){
+ // cmd.isSet idea taken from https://github.com/jayarjo/bunyip/
+ if(cmd.isSet) {
+ if(cmd.name === "local") {
+ lb = new LocalBrowsers(config, program.port);
+
+ lb.launchBrowsers(cmd.launch);
+
+ process.on('exit', lb.exitBrowsers);
+ }
+ }
+ });
+ });
+
+ process.on('exit', function () {
+ tunnel.destroy();
+ });
+ }
+};
+
+// Handle exceptions that 99% time are to do with a missing config file.
+//process.on('uncaughtException', function (err) {
+// console.log("An error occured most likely to do with the config file, see 'bunyip -h' for more info.");
+// console.dir(err);
+//});
View
191 lib/localbrowsers.js
@@ -0,0 +1,191 @@
+var fs = require("fs"),
+ exec = require('child_process').exec;
+
+function LocalBrowsers(config, port) {
+
+ var url = "http://localhost:" + port,
+ tmpdir = process.env.TMPDIR || process.env.TMP,
+ urltxt = tmpdir + "bunyipurl.txt",
+ phantom = tmpdir + "bunyipphantom.js",
+ // OSX open as new instance, do not bring to foreground and specify application name
+ cmdPrefix = "open -n -g -a",
+ activeBrowsers = {},
+ browsers = {
+ firefox: {
+ name: "Firefox",
+ win32: '"' + process.env.ProgramFiles + '\\Mozilla Firefox\\firefox.exe' + '"',
+ darwin: cmdPrefix + ' Firefox --args'
+ },
+ firefoxnightly: {
+ name: "Nightly",
+ win32: '"' + process.env.ProgramFiles + '\\Firefox Nightly\\firefox.exe' + '"',
+ darwin: cmdPrefix + ' Nightly --args'
+ },
+ chrome: {
+ name: "Google Chrome",
+ win32: '"' + process.env.LOCALAPPDATA + '\\Google\\Chrome\\Application\\chrome.exe' + '"',
+ darwin: cmdPrefix + ' "Google Chrome" --args'
+ },
+ chromecanary: {
+ name: "Google Chrome Canary",
+ win32: '"' + process.env.LOCALAPPDATA + '\\Google\\Chrome SxS\\Application\\chrome.exe' + '"',
+ darwin: cmdPrefix + ' "Google Chrome Canary" --args'
+ },
+ opera: {
+ name: "Opera",
+ win32: '"' + process.env.ProgramFiles + '\\Opera\\opera.exe' + '"',
+ darwin: cmdPrefix + ' Opera --args'
+ },
+ operanext: {
+ name: "Opera Next",
+ win32: '"' + process.env.ProgramFiles + '\\Opera Next x64\\opera.exe' + '"',
+ darwin: cmdPrefix + ' "Opera Next" --args'
+ },
+ safari: {
+ name: "Safari",
+ win32: '"' + process.env.ProgramFiles + '\\Safari\\safari.exe' + '"',
+ darwin: cmdPrefix + ' Safari'
+ },
+ ie: {
+ win32: '"' + process.env.ProgramFiles + '\\Internet Explorer\\iexplore.exe' + '"'
+ },
+ phantomjs: {
+ name: "phantomjs",
+ win32: 'phantomjs',
+ darwin: 'phantomjs'
+ }
+ };
+
+ var launchBrowsers = function(launch) {
+ if(launch === undefined) {
+ for(var browser in browsers) {
+ if(browser) {
+ cleanLaunch(browser, browsers);
+ }
+ }
+ } else {
+ var arr = launch.split("|");
+
+ activeBrowsers.isActive = true;
+
+ for(var i = 0; i < arr.length; i++) {
+ activeBrowsers[arr[i]] = browsers[arr[i]];
+ if(activeBrowsers[arr[i]]) {
+ cleanLaunch(arr[i], activeBrowsers);
+ } else {
+ console.log("Can't find any browser with the following name: %s", arr[i]);
+ process.kill();
+ }
+ }
+ }
+ };
+
+ // In order to get browsers to launch without error we need to pass specific flags to some of them
+ var cleanLaunch = function(browser, data) {
+ // Some browsers are platform specific so exit out if not available
+ if(data[browser][process.platform]) {
+ if(browser.indexOf("firefox") !== -1) {
+ // Handles firefox and firefox nightly
+ cleanLaunchFirefox(browser, data);
+ } else if(browser.indexOf("chrome") !== -1) {
+ // Handles chrome and chrome canary
+ cleanLaunchChrome(browser, data);
+ } else if(browser.indexOf("opera") !== -1) {
+ // Handles opera and opera next
+ cleanLaunchOpera(browser, data);
+ } else if(browser.indexOf("phantomjs") !== -1) {
+ // Handles opera and opera next
+ cleanLaunchPhantomjs(browser, data);
+ } else {
+ // Safari don't require special treatment
+ data[browser].process = exec(data[browser][process.platform] + " " + url);
+ }
+ }
+ };
+
+ // Force no first run, turn of default browser check and use custom data directory
+ var cleanLaunchChrome = function(browser, data) {
+ data[browser].process = exec(data[browser][process.platform] + " --no-default-browser-check --no-first-run --user-data-dir=" + tmpdir + " " + url);
+ };
+
+ // Force no first run, turn of default browser check and use custom data directory
+ var cleanLaunchOpera = function(browser, data) {
+ // Because of a bug with the -nosession cli flag and it ingoring any other options after it we need to use the
+ // urllist flag and write the file on the fly to get the url to load in opera while avoiding session recovery.
+ fs.writeFile(urltxt, url, 'utf8', function(err) {
+ if(err) {
+ return console.log(err);
+ }
+ });
+
+ data[browser].process = exec(data[browser][process.platform] + " -urllist " + urltxt + " -nosession");
+ };
+
+ // Specify it to be a new process and to create/use a custom profile
+ var cleanLaunchFirefox = function(browser, data) {
+ exec(data[browser][process.platform] + " -CreateProfile bunyip-"+browser,function(){
+ data[browser].process = exec(browsers[browser][process.platform] + " -no-remote -silent -p bunyip-"+browser + " " + url);
+ });
+ };
+
+ var cleanLaunchPhantomjs = function(browser, data) {
+ // Phantom needs the first arg to be js file that tells it what to do
+ fs.writeFile(phantom, '(new WebPage).open("' + url + '")', 'utf8', function(err) {
+ if(err) {
+ return console.log(err);
+ }
+ });
+
+ data[browser].process = exec(data[browser][process.platform] + " " + phantom);
+ };
+
+ var exitBrowsers = function() {
+ // Delete the url file opera needs, user may not have launched opera so catch it.
+ try {
+ fs.unlinkSync(urltxt);
+ } catch(e){}
+
+ // Delete the file phantomjs needs
+ try {
+ fs.unlinkSync(phantom);
+ } catch(e){}
+
+ if(activeBrowsers.isActive) {
+ for(var activeBrowser in activeBrowsers) {
+ // Some browsers are platform specific so exit out if not available
+ if(activeBrowsers[activeBrowser][process.platform]) {
+ // Windows requires taskill, OSX works best with osacript
+ if(process.platform === "win32") {
+ exec('taskkill /t /f /pid ' + activeBrowsers[activeBrowser].process.pid);
+ } else {
+ if(activeBrowsers !== "phantomjs") {
+ exec("osascript -e 'tell application \"" + activeBrowsers[activeBrowser].name + "\" to quit'");
+ } else {
+ activeBrowsers[activeBrowser].process.kill('SIGHUP');
+ }
+ }
+ }
+ }
+ } else {
+ for(var browser in browsers) {
+ // Some browsers are platform specific so exit out if not available
+ if(browsers[browser][process.platform]) {
+ if(process.platform === "win32") {
+ exec('taskkill /t /f /pid ' + browsers[browser].process.pid);
+ } else {
+ if(browser !== "phantomjs") {
+ exec("osascript -e 'tell application \"" + browsers[browser].name + "\" to quit'");
+ } else {
+ browsers[browser].process.kill('SIGHUP');
+ }
+ }
+ }
+ }
+ }
+ };
+
+ this.launchBrowsers = launchBrowsers;
+ this.exitBrowsers = exitBrowsers;
+}
+
+module.exports = LocalBrowsers;
View
61 lib/options.js
@@ -0,0 +1,61 @@
+var program = require('commander'),
+ fs = require('fs');
+
+program
+ .version('0.2.0')
+ .option('-f, --file <file>', 'specify the html testsuite to run')
+ .option('-p, --port <port>', 'specify the port [9000]', Number)
+ .option('-b, --browsers <ie:win/6.0 || file.json>', 'specify browsers to test in e.g "ie:win/6.0,7.0|iPhone 3GS:ios/3.0"', String)
+ .option('-a, --available', 'returns available browsers on browserstack')
+ .option('-s, --status', 'get status of all browserstack browser')
+ .option('-k, --kill <id>', 'kill browserstack worker process')
+ .option('-c, --cdir <path>', 'specify path to config.js file', String);
+
+/* Config file setup */
+program
+ .command('config [path]')
+ .description('create config.js file and save it at [path]')
+ .action(function(path){
+ program
+ .prompt({
+ username: 'BrowserStack username: ',
+ password: 'BrowserStack password: ',
+ domain: 'Localhost tunnel subdomain e.g. bunyip.pagekite.me: ',
+ cmd: 'Localhost tunnel command e.g. pagekite.py or show: '
+ }, function(opts) {
+ var config = {
+ browserstack: {
+ username: opts.username,
+ password: opts.password,
+ timeout: 480
+ },
+ port: 9000,
+ tunnellink: opts.domain,
+ tunnel: opts.cmd + " <port> " + opts.domain
+ };
+
+ var configuration = JSON.stringify(config, null, 4),
+ js = "var config = " + configuration + ";\n\nmodule.exports = config;";
+
+ fs.writeFile((path || ""), js, 'utf8', function(err) {
+ if(err) {
+ return console.log(err);
+ }
+ console.log("Config file successfully created!");
+ });
+
+ process.stdin.destroy();
+ });
+ });
+
+program
+ .command('local')
+ .description('working with local installed browsers')
+ .option('-l, --launch <browsers>", "launch specific browser(s) e.g. firefoxnightly|chromecanary', String)
+ .action(function(cmd, opts){
+ cmd.isSet = true;
+ });
+
+program.parse(process.argv);
+
+module.exports = program;
View
21 lib/tunnel.js
@@ -0,0 +1,21 @@
+var exec = require('child_process').exec,
+ program = require('./options'),
+ tunnel;
+
+var create = function(config) {
+ config.tunnel = config.tunnel.replace("<port>", config.port);
+ // Hook up ssh tunnel to yeti hub
+ tunnel = exec(config.tunnel);
+ },
+
+ destroy = function() {
+ // Clean up processes on exit
+ try {
+ tunnel.kill('SIGHUP');
+ } catch(e) {}
+ };
+
+module.exports = {
+ create: create,
+ destroy: destroy
+};
View
45 package.json
@@ -0,0 +1,45 @@
+{
+ "name": "bunyip",
+ "description": "Automate client-side unit testing in real browsers using the cli",
+ "version": "0.2.0",
+ "homepage": "http://ryanseddon.github.com/bunyip",
+ "author": {
+ "name": "Ryan Seddon",
+ "url": "http://www.thecssninja.com/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com:ryanseddon/bunyip.git"
+ },
+ "bugs": {
+ "url": "https://github.com/ryanseddon/bunyip/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://ryanseddon.github.com/bunyip/blob/master/LICENSE-MIT"
+ }
+ ],
+ "main": "lib/bunyip",
+ "bin": {
+ "bunyip": "./cli.js"
+ },
+ "engines": {
+ "node": "~0.8.x"
+ },
+ "scripts": {
+ "test": "vows test/*.js"
+ },
+ "dependencies": {
+ "commander": "0.6.x",
+ "browserstack": "0.1.x",
+ "yeti": "0.2.x"
+ },
+ "devDependencies": {
+ "grunt": "~0.3.9",
+ "cli-easy": "0.1.x",
+ "vows": ">= 0.5.8",
+ "mockery": ">= 1.1.2"
+ },
+ "keywords": ["unit tests", "bunyip", "browserstack", "mobile", "cli"]
+}
View
29 test/cli_test.js
@@ -0,0 +1,29 @@
+var CLIeasy = require('cli-easy'),
+ assert = require('assert');
+
+ CLIeasy.describe('bunyip')
+ .use('bunyip')
+ .discuss('when using bunyip')
+ .undiscuss()
+
+ // Single arguments
+ .discuss('calling with -h')
+ .arg('-h')
+ .expect('should return help message', /Usage\: bunyip/)
+ .undiscuss()
+
+ .discuss('calling with -V')
+ .arg('-V')
+ .expect('should return help message', /\d\.\d\.\d/)
+ .undiscuss()
+
+ .discuss('calling with -f')
+ .arg('-f')
+ .expect('should return error message that it\'s missing a file', null, /error\: option/)
+ .undiscuss()
+
+ .discuss('calling with -x')
+ .arg('-x')
+ .expect('should return unknown option error', null, /error\: unknown option \`\-x\'/)
+ .undiscuss()
+ .export(module);
Please sign in to comment.
Something went wrong with that request. Please try again.