Permalink
Browse files

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...
0 parents commit 343bd6bd27dde4505c85e0c6ae933b97d3861354 @ryanseddon committed Jun 3, 2012
Showing with 550 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +1 −0 .npmignore
  3. +22 −0 LICENSE-MIT
  4. +93 −0 README.md
  5. +3 −0 cli.js
  6. +39 −0 grunt.js
  7. +172 −0 lib/bunyip.js
  8. +16 −0 lib/config-template.js
  9. +92 −0 lib/helpers.js
  10. +13 −0 lib/options.js
  11. +21 −0 lib/tunnel.js
  12. +45 −0 package.json
  13. +29 −0 test/cli_test.js
@@ -0,0 +1,4 @@
+/node_modules/
+/lib/config.js
+.DS_Store
+*.log
@@ -0,0 +1 @@
+/node_modules/
@@ -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.
@@ -0,0 +1,93 @@
+# 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. If you look inside the lib folder you'll see a `config-template.js` file, copy it and rename to config.js. Edit the values to whatever you need it to be.
+
+```js
+var config = config || {};
+
+config.browserstack = {
+ username: "foo", // Your BrowserStack username
+ password: "bar", // Your BrowserStack password
+ version: 2
+};
+
+// The tunneling service I use is https://pagekite.net/support/quickstart/
+// You can easily use another service lke showoff.io only requirement is that you can specify a fixed url name
+config.port = " 9000 ";
+config.tunnellink = "bunyip.pagekite.me/"; // The subdomain that the tunnel can be access by
+// This is the command that nodejs will execute using child_process.exec
+// If you were using showoff.io the below command would be "show" + config.port
+config.tunnel = "pagekite.py" + config.port + config.tunnellink;
+
+module.exports = config;
+```
+
+_(Can this be done in an easier fashion? Do a pull request!)_
+
+## 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.
+
+### 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
+_(Nothing yet)_
+
+## License
+Copyright (c) 2012 Ryan Seddon
+Licensed under the MIT license.
3 cli.js
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+var program = require('./lib/options');
+require("./lib/bunyip").route(program);
@@ -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');
+
+};
@@ -0,0 +1,172 @@
+#!/usr/bin/env node
+/*
+ * bunyip
+ * http://ryanseddon.github.com/Bunyip
+ *
+ * Copyright (c) 2012 Ryan Seddon
+ * Licensed under the MIT license.
+ */
+
+var util = require('util'),
+ config = require("./config"),
+ helpers = require("./helpers"),
+ yeti = require("../node_modules/yeti/lib/cli").route,
+ fs = require("fs"),
+ browserstack = require("browserstack"),
+ tunnel = require("./tunnel");
+
+var timeout = config.browserstack.timeout || 480, // 8 minutes
+ client = browserstack.createClient(config.browserstack),
+ platforms = /^(all|ios|win|mac|android|opera)$/;
+
+exports.route = function(program) {
+ if(program.port) {
+ var url = "http://localhost:"+program.port;
+ config.port = program.port;
+ }
+
+ if(program.status) {
+ 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"));
+ });
+ } else if(program.available) {
+ helpers.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") {
+ helpers.killBrowsers();
+ } else {
+ helpers.killBrowser(program.kill, function(err, data, id){
+ console.log("Worker %s successfully killed, it ran for %ss", id, Math.round(data.time));
+ });
+ }
+ } else {
+ yeti(["","", program.file, "--port=" + program.port]);
+
+ tunnel.create();
+
+ process.nextTick(function () {
+ var testURL = "http://"+config.tunnellink,
+ 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
+ });
+ });
+ });
+ }
+
+ process.nextTick(function() {
+ helpers.loadBrowsers(browsers);
+ });
+ } else if(platforms.test(program.browsers)) {
+
+ switch(program.browsers) {
+ case "all":
+ helpers.availableBrowsers(function(list){
+ list.forEach(function(browser, i) {
+ browser.url = testURL;
+ browser.timeout = timeout;
+ });
+
+ helpers.loadBrowsers(list);
+ });
+ break;
+ case "ios":
+ helpers.platformBrowsers("ios");
+ break;
+ case "android":
+ helpers.platformBrowsers("android");
+ break;
+ case "opera":
+ helpers.platformBrowsers("opera");
+ break;
+ case "win":
+ helpers.platformBrowsers("win");
+ break;
+ case "mac":
+ helpers.platformBrowsers("mac");
+ break;
+ }
+
+
+ }
+ });
+
+ process.on('exit', function () {
+ tunnel.destroy();
+ });
+ }
+};
@@ -0,0 +1,16 @@
+var config = config || {};
+
+config.browserstack = {
+ username: "", // Your BrowserStack username
+ password: "", // Your BrowserStack password
+ version: 2
+};
+
+// The tunneling service I use is https://pagekite.net/support/quickstart/
+// You can easily use another service lke showoff.io only requirement is that you can specify a fixed url name
+config.port = " 9000 ";
+config.tunnellink = "bunyip.pagekite.me/";
+// This is the command that nodejs will execute using child_process.exec
+config.tunnel = "pagekite.py" + config.port + config.tunnellink;
+
+module.exports = config;
Oops, something went wrong.

0 comments on commit 343bd6b

Please sign in to comment.