├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── coffeelint.json ├── gulpfile.coffee ├── package.json ├── spec └── browserstack_spec.coffee └── src ├── data └── browsers.json ├── index.coffee └── scripts └── browserstack.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | env: 3 | - MOCHA_REPORTER=spec 4 | node_js: 5 | - "0.11" 6 | - "0.10" 7 | 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Atsushi NAGASE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | hubot-browserstack 2 | ================== 3 | 4 | [![Build Status][travis-badge]][travis] 5 | [![npm-version][npm-badge]][npm] 6 | 7 | A [Hubot] script to take screenshots with [BrowserStack]. 8 | 9 | 10 | ``` 11 | me > hubot screenshot me http://www.google.com/ 12 | hubot > Started generating screenshots in http://www.browserstack.com/screenshots/d804f186e460dc4f2a30849a9686c3a8c4276c21 13 | ``` 14 | 15 | Installation 16 | ------------ 17 | 18 | 1. Add `hubot-browserstack` to dependencies. 19 | 20 | ```bash 21 | npm install --save hubot-browserstack 22 | ``` 23 | 24 | 2. Update `external-scripts.json` 25 | 26 | ```json 27 | ["hubot-browserstack"] 28 | ``` 29 | 30 | Setup 31 | ----- 32 | 33 | Grab your BrowserStack _Username_ and _Access Key_ from _Your Account_ > _[Automate]_. 34 | 35 | ```bash 36 | HUBOT_BROWSER_STACK_USERNAME=$(Your BrowserStack Username) 37 | HUBOT_BROWSER_STACK_ACCESS_KEY=$(Your BrowserStack Access Key) 38 | ``` 39 | 40 | Default browsers are listed in [browsers.json] of this module. 41 | 42 | If you prefer other browsers, you can specify JSON path with `HUBOT_BROWSER_STACK_DEFAULT_BROWSERS`. 43 | 44 | ```bash 45 | HUBOT_BROWSER_STACK_DEFAULT_BROWSERS=$HOME/data/mybrowers.json 46 | ``` 47 | 48 | Make sure relative path will be resolved from process's working directory. 49 | 50 | 51 | Author 52 | ------ 53 | 54 | [Atsushi Nagase] 55 | 56 | License 57 | ------- 58 | 59 | [MIT License] 60 | 61 | [Automate]: https://www.browserstack.com/accounts/automate 62 | [Hubot]: https://hubot.github.com/ 63 | [BrowserStack]: https://www.browserstack.com/ 64 | [browsers.json]: src/data/browsers.json 65 | [Atsushi Nagase]: http://ngs.io/ 66 | [MIT License]: LICENSE 67 | [travis-badge]: https://travis-ci.org/ngs/hubot-browserstack.svg?branch=master 68 | [npm-badge]: http://img.shields.io/npm/v/hubot-browserstack.svg 69 | [travis]: https://travis-ci.org/ngs/hubot-browserstack 70 | [npm]: https://www.npmjs.org/package/hubot-browserstack 71 | -------------------------------------------------------------------------------- /coffeelint.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrow_spacing": { 3 | "level": "ignore" 4 | }, 5 | "camel_case_classes": { 6 | "level": "error" 7 | }, 8 | "coffeescript_error": { 9 | "level": "error" 10 | }, 11 | "colon_assignment_spacing": { 12 | "level": "ignore", 13 | "spacing": { 14 | "left": 0, 15 | "right": 0 16 | } 17 | }, 18 | "cyclomatic_complexity": { 19 | "value": 10, 20 | "level": "ignore" 21 | }, 22 | "duplicate_key": { 23 | "level": "error" 24 | }, 25 | "empty_constructor_needs_parens": { 26 | "level": "ignore" 27 | }, 28 | "indentation": { 29 | "value": 2, 30 | "level": "error" 31 | }, 32 | "line_endings": { 33 | "level": "ignore", 34 | "value": "unix" 35 | }, 36 | "max_line_length": { 37 | "value": 90, 38 | "level": "ignore" 39 | }, 40 | "missing_fat_arrows": { 41 | "level": "ignore" 42 | }, 43 | "newlines_after_classes": { 44 | "value": 3, 45 | "level": "ignore" 46 | }, 47 | "no_backticks": { 48 | "level": "error" 49 | }, 50 | "no_debugger": { 51 | "level": "warn" 52 | }, 53 | "no_empty_functions": { 54 | "level": "ignore" 55 | }, 56 | "no_empty_param_list": { 57 | "level": "ignore" 58 | }, 59 | "no_implicit_braces": { 60 | "level": "ignore", 61 | "strict": true 62 | }, 63 | "no_implicit_parens": { 64 | "strict": true, 65 | "level": "ignore" 66 | }, 67 | "no_interpolation_in_single_quotes": { 68 | "level": "ignore" 69 | }, 70 | "no_plusplus": { 71 | "level": "ignore" 72 | }, 73 | "no_stand_alone_at": { 74 | "level": "ignore" 75 | }, 76 | "no_tabs": { 77 | "level": "error" 78 | }, 79 | "no_throwing_strings": { 80 | "level": "error" 81 | }, 82 | "no_trailing_semicolons": { 83 | "level": "error" 84 | }, 85 | "no_trailing_whitespace": { 86 | "level": "error", 87 | "allowed_in_comments": false, 88 | "allowed_in_empty_lines": true 89 | }, 90 | "no_unnecessary_double_quotes": { 91 | "level": "ignore" 92 | }, 93 | "no_unnecessary_fat_arrows": { 94 | "level": "warn" 95 | }, 96 | "non_empty_constructor_needs_parens": { 97 | "level": "ignore" 98 | }, 99 | "space_operators": { 100 | "level": "ignore" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | gulp = require 'gulp' 2 | gutil = require 'gulp-util' 3 | coffee = require 'gulp-coffee' 4 | mocha = require 'gulp-mocha' 5 | clean = require 'gulp-clean' 6 | watch = require 'gulp-watch' 7 | watch = require 'gulp-watch' 8 | coffeelint = require 'gulp-coffeelint' 9 | 10 | require 'coffee-script/register' 11 | 12 | gulp.task 'default', ['mocha'] 13 | 14 | gulp.task 'clean', -> 15 | gulp 16 | .src('node_modules', read: no) 17 | .pipe(clean()) 18 | 19 | coffeePipes = (pipe)-> 20 | pipe 21 | .pipe(coffeelint()) 22 | .pipe(coffeelint.reporter()) 23 | .pipe(coffee(bare: yes) 24 | .pipe(mocha reporter: process.env.MOCHA_REPORTER || 'nyan') 25 | .on('error', -> @emit 'end')) 26 | 27 | gulp.task 'mocha', -> 28 | coffeePipes gulp.src('spec/*.coffee') 29 | 30 | gulp.task 'watch', -> 31 | gulp 32 | .src(['src/**/*.coffee', 'spec/*.coffee']) 33 | .pipe watch (files)-> 34 | coffeePipes files 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hubot-browserstack", 3 | "version": "0.1.3", 4 | "author": { 5 | "name": "Atsushi Nagase", 6 | "email": "a@ngs.io", 7 | "url": "http://ngs.io/" 8 | }, 9 | "description": "A Hubot script to take screenshots with BrowserStack.", 10 | "keywords": [ 11 | "hubot", 12 | "hubot-scripts", 13 | "browserstack", 14 | "screenshots" 15 | ], 16 | "licenses": [ 17 | { 18 | "type": "MIT" 19 | } 20 | ], 21 | "main": "./src/index.coffee", 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/ngs/hubot-browserstack.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/ngs/hubot-browserstack/issues" 28 | }, 29 | "devDependencies": { 30 | "hubot": "^2.7.5", 31 | "gulp": "^3.7.0", 32 | "coffee-script": "~1.7.1", 33 | "gulp-coffee": "^2.0.1", 34 | "gulp-util": "^2.2.16", 35 | "hubot-mock-adapter": "^1.0.0", 36 | "gulp-mocha": "^0.4.1", 37 | "nock": "^0.34.1", 38 | "chai": "^1.9.1", 39 | "chai-spies": "^0.5.1", 40 | "gulp-clean": "^0.3.0", 41 | "gulp-coffeelint": "^0.3.3", 42 | "gulp-watch": "^0.6.5" 43 | }, 44 | "scripts": { 45 | "test": "gulp mocha" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/browserstack_spec.coffee: -------------------------------------------------------------------------------- 1 | path = require 'path' 2 | Robot = require("hubot/src/robot") 3 | TextMessage = require("hubot/src/message").TextMessage 4 | nock = require 'nock' 5 | process.env.HUBOT_LOG_LEVEL = 'debug' 6 | chai = require 'chai' 7 | chai.use require 'chai-spies' 8 | { expect, spy } = chai 9 | 10 | describe 'hubot-browserstack', -> 11 | robot = null 12 | user = null 13 | adapter = null 14 | nockScope = null 15 | beforeEach (done)-> 16 | process.env.HUBOT_BROWSER_STACK_ACCESS_KEY = 'fake-access-key' 17 | process.env.HUBOT_BROWSER_STACK_USERNAME = 'fake-user-name' 18 | nock.disableNetConnect() 19 | nockScope = nock('http://www.browserstack.com').post('/screenshots') 20 | robot = new Robot null, 'mock-adapter', yes, 'TestHubot' 21 | robot.adapter.on 'connected', -> 22 | robot.loadFile path.resolve('.', 'src', 'scripts'), 'browserstack.coffee' 23 | hubotScripts = path.resolve 'node_modules', 'hubot', 'src', 'scripts' 24 | robot.loadFile hubotScripts, 'help.coffee' 25 | user = robot.brain.userForId '1', { 26 | name: 'ngs' 27 | room: '#mocha' 28 | } 29 | adapter = robot.adapter 30 | waitForHelp = -> 31 | if robot.helpCommands().length > 0 32 | do done 33 | else 34 | setTimeout waitForHelp, 100 35 | do waitForHelp 36 | do robot.run 37 | 38 | afterEach -> 39 | robot.server.close() 40 | robot.shutdown() 41 | 42 | 43 | describe 'help', -> 44 | it 'should have 3', (done)-> 45 | expect(robot.helpCommands()).to.have.length 3 46 | do done 47 | 48 | it 'should parse help', (done)-> 49 | adapter.on 'send', (envelope, strings)-> 50 | ## Prefix bug with parseHelp 51 | ## https://github.com/github/hubot/pull/712 52 | try 53 | expect(strings[0]).to.equal """ 54 | TestTestHubot help - Displays all of the help commands that TestHubot knows about. 55 | TestTestHubot help - Displays all help commands that match . 56 | TestTestHubot screenshot me - Takes screenshot with Browser Stack. 57 | """ 58 | do done 59 | catch e 60 | done e 61 | adapter.receive new TextMessage user, 'TestHubot help' 62 | 63 | describe 'success', -> 64 | 65 | beforeEach (done)-> 66 | nockScope.reply 200, job_id: 'abcd1234' 67 | do done 68 | 69 | it 'should reply message', (done)-> 70 | adapter.on 'reply', (envelope, strings)-> 71 | try 72 | expect(envelope.user.id).to.equal '1' 73 | expect(envelope.user.name).to.equal 'ngs' 74 | expect(envelope.user.room).to.equal '#mocha' 75 | expect(strings).to.have.length(1) 76 | expect(strings[0]).to.equal 'Started generating screenshorts in http://www.browserstack.com/screenshots/abcd1234' 77 | do done 78 | catch e 79 | done e 80 | adapter.receive new TextMessage user, 'testhubot screenshot me https://www.google.com/' 81 | 82 | it 'should send message', (done)-> 83 | adapter.on 'send', (envelope, strings)-> 84 | try 85 | expect(envelope.user.id).to.equal '1' 86 | expect(envelope.user.name).to.equal 'ngs' 87 | expect(envelope.user.room).to.equal '#mocha' 88 | expect(strings).to.have.length(1) 89 | expect(strings[0]).to.equal 'Started generating screenshorts in http://www.browserstack.com/screenshots/abcd1234' 90 | do done 91 | catch e 92 | done e 93 | adapter.receive new TextMessage user, 'testhubot screenshot https://www.google.com/' 94 | 95 | describe 'failure', -> 96 | 97 | beforeEach (done)-> 98 | nockScope.reply 503, job_id: 'abcd1234' 99 | do done 100 | 101 | it 'should reply error message', (done)-> 102 | adapter.on 'reply', (envelope, strings)-> 103 | try 104 | expect(envelope.user.id).to.equal '1' 105 | expect(envelope.user.name).to.equal 'ngs' 106 | expect(envelope.user.room).to.equal '#mocha' 107 | expect(strings).to.have.length(1) 108 | expect(strings[0]).to.equal 'Failed to start generating screenshots: {"job_id":"abcd1234"}' 109 | do done 110 | catch e 111 | done e 112 | adapter.receive new TextMessage user, 'testhubot screenshot me https://www.google.com/' 113 | 114 | it 'should send error message', (done)-> 115 | adapter.on 'send', (envelope, strings)-> 116 | try 117 | expect(envelope.user.id).to.equal '1' 118 | expect(envelope.user.name).to.equal 'ngs' 119 | expect(envelope.user.room).to.equal '#mocha' 120 | expect(strings).to.have.length(1) 121 | expect(strings[0]).to.equal 'Failed to start generating screenshots: {"job_id":"abcd1234"}' 122 | do done 123 | catch e 124 | done e 125 | adapter.receive new TextMessage user, 'testhubot screenshot https://www.google.com/' 126 | -------------------------------------------------------------------------------- /src/data/browsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | 4 | "device": "iPad 2 (5.0)", 5 | "browser": "Mobile Safari", 6 | 7 | "os_version": "5.0", 8 | "os": "ios" 9 | }, 10 | { 11 | 12 | "device": "iPhone 4S", 13 | "browser": "Mobile Safari", 14 | 15 | "os_version": "5.1", 16 | "os": "ios" 17 | }, 18 | { 19 | 20 | "device": "iPad 3rd", 21 | "browser": "Mobile Safari", 22 | 23 | "os_version": "5.1", 24 | "os": "ios" 25 | }, 26 | { 27 | 28 | "device": "iPhone 4S (6.0)", 29 | "browser": "Mobile Safari", 30 | 31 | "os_version": "6.0", 32 | "os": "ios" 33 | }, 34 | { 35 | 36 | "device": "iPhone 5", 37 | "browser": "Mobile Safari", 38 | 39 | "os_version": "6.0", 40 | "os": "ios" 41 | }, 42 | { 43 | 44 | "device": "iPad 3rd (6.0)", 45 | "browser": "Mobile Safari", 46 | 47 | "os_version": "6.0", 48 | "os": "ios" 49 | }, 50 | { 51 | 52 | "device": "iPad Mini", 53 | "browser": "Mobile Safari", 54 | 55 | "os_version": "6.0", 56 | "os": "ios" 57 | }, 58 | { 59 | 60 | "device": "iPhone 5S", 61 | "browser": "Mobile Safari", 62 | 63 | "os_version": "7.0", 64 | "os": "ios" 65 | }, 66 | { 67 | 68 | "device": "iPad 3rd (7.0)", 69 | "browser": "Mobile Safari", 70 | 71 | "os_version": "7.0", 72 | "os": "ios" 73 | }, 74 | { 75 | 76 | "device": "Amazon Kindle Fire 2", 77 | "browser": "Android Browser", 78 | 79 | "os_version": "4.0", 80 | "os": "android" 81 | }, 82 | { 83 | 84 | "device": "Samsung Galaxy S III", 85 | "browser": "Android Browser", 86 | 87 | "os_version": "4.1", 88 | "os": "android" 89 | }, 90 | { 91 | 92 | "device": "Samsung Galaxy Tab 2 10.1", 93 | "browser": "Android Browser", 94 | 95 | "os_version": "4.0", 96 | "os": "android" 97 | }, 98 | { 99 | 100 | "device": "Google Nexus 7", 101 | "browser": "Android Browser", 102 | 103 | "os_version": "4.1", 104 | "os": "android" 105 | }, 106 | { 107 | 108 | "device": "LG Nexus 4", 109 | "browser": "Android Browser", 110 | 111 | "os_version": "4.2", 112 | "os": "android" 113 | }, 114 | { 115 | 116 | 117 | "browser": "ie", 118 | "browser_version": "6.0", 119 | "os_version": "XP", 120 | "os": "Windows" 121 | }, 122 | { 123 | 124 | 125 | "browser": "ie", 126 | "browser_version": "7.0", 127 | "os_version": "XP", 128 | "os": "Windows" 129 | }, 130 | { 131 | 132 | 133 | "browser": "ie", 134 | "browser_version": "8.0", 135 | "os_version": "7", 136 | "os": "Windows" 137 | }, 138 | { 139 | 140 | 141 | "browser": "ie", 142 | "browser_version": "9.0", 143 | "os_version": "7", 144 | "os": "Windows" 145 | }, 146 | { 147 | 148 | 149 | "browser": "ie", 150 | "browser_version": "10.0", 151 | "os_version": "7", 152 | "os": "Windows" 153 | }, 154 | { 155 | 156 | 157 | "browser": "ie", 158 | "browser_version": "11.0", 159 | "os_version": "7", 160 | "os": "Windows" 161 | }, 162 | { 163 | 164 | 165 | "browser": "firefox", 166 | "browser_version": "25.0", 167 | "os_version": "7", 168 | "os": "Windows" 169 | }, 170 | { 171 | 172 | 173 | "browser": "chrome", 174 | "browser_version": "31.0", 175 | "os_version": "7", 176 | "os": "Windows" 177 | }, 178 | { 179 | 180 | 181 | "browser": "ie", 182 | "browser_version": "10.0 Desktop", 183 | "os_version": "8", 184 | "os": "Windows" 185 | }, 186 | { 187 | 188 | 189 | "browser": "ie", 190 | "browser_version": "11.0 Desktop", 191 | "os_version": "8.1", 192 | "os": "Windows" 193 | }, 194 | { 195 | 196 | 197 | "browser": "safari", 198 | "browser_version": "7.0", 199 | "os_version": "Mavericks", 200 | "os": "OS X" 201 | } 202 | ] 203 | -------------------------------------------------------------------------------- /src/index.coffee: -------------------------------------------------------------------------------- 1 | Fs = require 'fs' 2 | Path = require 'path' 3 | 4 | module.exports = (robot) -> 5 | path = Path.resolve __dirname, 'scripts' 6 | Fs.exists path, (exists) -> 7 | if exists 8 | for file in Fs.readdirSync(path) 9 | robot.loadFile path, file 10 | -------------------------------------------------------------------------------- /src/scripts/browserstack.coffee: -------------------------------------------------------------------------------- 1 | # Description: 2 | # Takes screenshot with Browser Stack 3 | # 4 | # Commands: 5 | # hubot screenshot me - Takes screenshot with Browser Stack. 6 | path = require 'path' 7 | 8 | BASE_URL = 'http://www.browserstack.com/screenshots' 9 | 10 | module.exports = (robot) -> 11 | 12 | jsonPath = if process.env.HUBOT_BROWSER_STACK_DEFAULT_BROWSERS 13 | path.resolve process.cwd(), process.env.HUBOT_BROWSER_STACK_DEFAULT_BROWSERS 14 | else 15 | '../data/browsers.json' 16 | browsers = require jsonPath 17 | 18 | robot.respond /screenshot(\s+me)?\s+(https?:\/\/.*)$/i, (msg) -> 19 | me = /me$/.test msg.match[1] 20 | url = msg.match[2] 21 | env = process.env 22 | robot.http(BASE_URL) 23 | .header('Content-Type', 'application/json') 24 | .header('Accept', 'application/json') 25 | .auth(env.HUBOT_BROWSER_STACK_USERNAME, env.HUBOT_BROWSER_STACK_ACCESS_KEY) 26 | .post(JSON.stringify { 27 | browsers: browsers 28 | url: url 29 | }) (err, res, body) -> 30 | if res.statusCode != 200 31 | message = "Failed to start generating screenshots: #{body}" 32 | else 33 | res = JSON.parse(body) 34 | message = "Started generating screenshorts in #{BASE_URL}/#{res.job_id}" 35 | 36 | if me 37 | msg.reply message 38 | else 39 | msg.send message 40 | --------------------------------------------------------------------------------