├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jscsrc ├── .jshintrc ├── LICENSE ├── README.md ├── example ├── detect.js └── launch.js ├── index.js ├── lib ├── config.js ├── create_profiles.js ├── darwin │ ├── chrome.js │ ├── firefox.js │ ├── index.js │ ├── opera.js │ ├── safari.js │ └── util.js ├── detect.js ├── instance.js └── run.js ├── package.json └── res ├── Preferences ├── operaprefs.ini └── phantom.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": [ 3 | "node_modules/*" 4 | ], 5 | 6 | "requireCurlyBraces": [ 7 | "if", "else", "for", "while", "do", "switch", "try", "catch" 8 | ], 9 | "requireSpaceAfterKeywords": [ 10 | "if", "else", "for", "while", "do", "switch", "return", "try", "catch" 11 | ], 12 | "requireSpaceBeforeBlockStatements": true, 13 | "requireParenthesesAroundIIFE": true, 14 | "requireSpacesInConditionalExpression": { 15 | "afterTest": true, 16 | "beforeConsequent": true, 17 | "afterConsequent": true, 18 | "beforeAlternate": true 19 | }, 20 | "requireSpacesInFunctionExpression": { 21 | "beforeOpeningCurlyBrace": true 22 | }, 23 | "disallowSpacesInFunctionExpression": { 24 | "beforeOpeningRoundBrace": true 25 | }, 26 | "requireMultipleVarDecl": true, 27 | "requireBlocksOnNewline": true, 28 | "requireSpacesInsideObjectBrackets": "all", 29 | "requireSpacesInsideArrayBrackets": "all", 30 | "disallowSpaceAfterObjectKeys": true, 31 | "requireCommaBeforeLineBreak": true, 32 | "requireOperatorBeforeLineBreak": [ 33 | "?", "=", "+", "-", "/", "*", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "|", "||", "&", "&&", "^", 34 | "+=", "*=", "-=", "/=", "^=" 35 | ], 36 | "requireSpaceBeforeBinaryOperators": [ 37 | "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "|", "||", "&", "&&", "^", 38 | "+=", "*=", "-=", "/=", "^=" 39 | ], 40 | "requireSpaceAfterBinaryOperators": [ 41 | "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=", "|", "||", "&", "&&", "^", 42 | "+=", "*=", "-=", "/=", "^=" 43 | ], 44 | "disallowSpaceAfterPrefixUnaryOperators": [ 45 | "++", "--", "+", "-", "~", "!" 46 | ], 47 | "disallowSpaceBeforePostfixUnaryOperators": [ "++", "--" ], 48 | "disallowKeywords": [ "with" ], 49 | "validateLineBreaks": "LF", 50 | "validateQuoteMarks": { 51 | "mark": "'", 52 | "escape": true 53 | }, 54 | "validateIndentation": "\t", 55 | "disallowMixedSpacesAndTabs": true, 56 | "disallowTrailingWhitespace": true, 57 | "disallowKeywordsOnNewLine": [ "else", "catch" ], 58 | "maximumLineLength": 120, 59 | "safeContextKeyword": [ "that", "bender" ], 60 | "requireDotNotation": true, 61 | "disallowYodaConditions": true 62 | } 63 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "undef": true, 4 | "unused": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is released under the MIT license: 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # browser-launcher2 2 | 3 | ## ☠ This package is no longer maintained ☠ 4 | 5 | Read more in: https://github.com/benderjs/browser-launcher2/issues/57 6 | 7 | ---- 8 | 9 | [![Get it on npm](https://nodei.co/npm/browser-launcher2.png?compact=true)](https://www.npmjs.org/package/browser-launcher2) 10 | 11 | Detect the browser versions available on your system and launch them in an 12 | isolated profile for automated testing purposes. 13 | 14 | You can launch browsers headlessly (using [Xvfb](http://en.wikipedia.org/wiki/Xvfb) or with [PhantomJS](http://phantomjs.org/)) and set 15 | the proxy configuration on the fly. 16 | 17 | It's a fork of [substack/browser-launcher](https://github.com/substack/browser-launcher) repository which seems to be no longer maintained. 18 | 19 | ## Differences from *browser-launcher* 20 | 21 | - contains fixes and pull requests for unresolved issues reported in original repository 22 | - `launcher.browsers` is an array of local browsers only, not an object as it was before 23 | - `launch` callback returns an `Instance` instead of a child process, see API section for more details 24 | - uses [win-detect-browsers](https://github.com/vweevers/win-detect-browsers) for browser detection on Windows 25 | - more browsers supported 26 | 27 | ## Supported browsers 28 | 29 | The goal for this module is to support all major browsers on every desktop platform. 30 | 31 | At the moment, `browser-launcher2` supports following browsers on Windows, Unix and OS X: 32 | 33 | - Chrome 34 | - Chromium 35 | - Firefox 36 | - IE (Windows only) 37 | - Opera 38 | - Safari 39 | - PhantomJS 40 | 41 | ## Install 42 | 43 | ``` 44 | npm install browser-launcher2 45 | ``` 46 | 47 | ## Example 48 | 49 | ### Browser launch 50 | ```js 51 | var launcher = require( 'browser-launcher2' ); 52 | 53 | launcher( function( err, launch ) { 54 | if ( err ) { 55 | return console.error( err ); 56 | } 57 | 58 | launch( 'http://cksource.com/', 'chrome', function( err, instance ) { 59 | if ( err ) { 60 | return console.error( err ); 61 | } 62 | 63 | console.log( 'Instance started with PID:', instance.pid ); 64 | 65 | instance.on( 'stop', function( code ) { 66 | console.log( 'Instance stopped with exit code:', code ); 67 | } ); 68 | } ); 69 | } ); 70 | ``` 71 | 72 | Outputs: 73 | 74 | ``` 75 | $ node example/launch.js 76 | Instance started with PID: 12345 77 | Instance stopped with exit code: 0 78 | ``` 79 | 80 | ### Browser detection 81 | ```js 82 | var launcher = require( '../' ); 83 | 84 | launcher.detect( function( available ) { 85 | console.log( 'Available browsers:' ); 86 | console.dir( available ); 87 | } ); 88 | ``` 89 | 90 | Outputs: 91 | 92 | ```bash 93 | $ node example/detect.js 94 | Available browsers: 95 | [ { name: 'chrome', 96 | version: '36.0.1985.125', 97 | type: 'chrome', 98 | command: 'google-chrome' }, 99 | { name: 'chromium', 100 | version: '36.0.1985.125', 101 | type: 'chrome', 102 | command: 'chromium-browser' }, 103 | { name: 'firefox', 104 | version: '31.0', 105 | type: 'firefox', 106 | command: 'firefox' }, 107 | { name: 'phantomjs', 108 | version: '1.9.7', 109 | type: 'phantom', 110 | command: 'phantomjs' }, 111 | { name: 'opera', 112 | version: '12.16', 113 | type: 'opera', 114 | command: 'opera' } ] 115 | ``` 116 | 117 | ### Detaching the launched browser process from your script 118 | 119 | If you want the opened browser to remain open after killing your script, first, you need to set `options.detached` to `true` (see the API). By default, killing your script will kill the opened browsers. 120 | 121 | Then, if you want your script to immediately return control to the shell, you may additionally call `unref` on the `instance` object in the callback: 122 | 123 | ```js 124 | var launcher = require('browser-launcher2'); 125 | launcher( function (err, launch) { 126 | launch( 'http://example.org/', { 127 | browser: 'chrome', 128 | detached: true 129 | }, function( err, instance ) { 130 | if ( err ) { 131 | return console.error( err ); 132 | } 133 | 134 | instance.process.unref(); 135 | instance.process.stdin.unref(); 136 | instance.process.stdout.unref(); 137 | instance.process.stderr.unref(); 138 | } ); 139 | }); 140 | ``` 141 | 142 | ## API 143 | 144 | ``` js 145 | var launcher = require('browser-launcher2'); 146 | ``` 147 | 148 | ### `launcher([configPath], callback)` 149 | 150 | Detect available browsers and pass `launch` function to the callback. 151 | 152 | **Parameters:** 153 | - *String* `configPath` - path to a browser configuration file *(Optional)* 154 | - *Function* `callback(err, launch)` - function called with `launch` function and errors (if any) 155 | 156 | ### `launch(uri, options, callback)` 157 | 158 | Open given URI in a browser and return an instance of it. 159 | 160 | **Parameters:** 161 | - *String* `uri` - URI to open in a newly started browser 162 | - *Object|String* `options` - configuration options or name of a browser to launch 163 | - *String* `options.browser` - name of a browser to launch 164 | - *String* `options.version` - version of a browser to launch, if none was given, the highest available version will be launched 165 | - *Array* `options.options` - additional command line options 166 | - *String* `options.proxy` - URI of the proxy server 167 | - *Boolean* `options.detached` - if true, then killing your script will not kill the opened browser 168 | - *Boolean* `options.noProxy` - set proxy routes to skip over 169 | - *Boolean* `options.headless` - run a browser in a headless mode (only if **Xvfb** available) 170 | - *Function* `callback(err, instance)` - function fired when started a browser `instance` or an error occurred 171 | 172 | ### `launch.browsers` 173 | 174 | This property contains an array of all known and available browsers. 175 | 176 | ### `instance` 177 | 178 | Browser instance object. 179 | 180 | **Properties:** 181 | - *String* `command` - command used to start the instance 182 | - *Array* `args` - array of command line arguments used while starting the instance 183 | - *String* `image` - instance's image name 184 | - *String* `processName` - instance's process name 185 | - *Object* `process` - reference to instance's process started with Node's `child_process.spawn` API 186 | - *Number* `pid` - instance's process PID 187 | - *Stream* `stdout` - instance's process STDOUT stream 188 | - *Stream* `stderr` - instance's process STDERR stream 189 | 190 | **Events:** 191 | - `stop` - fired when instance stops 192 | 193 | **Methods:** 194 | - `stop(callback)` - stop the instance and fire the callback once stopped 195 | 196 | ### `launcher.detect(callback)` 197 | 198 | Detects all browsers available. 199 | 200 | **Parameters:** 201 | - *Function* `callback(available)` - function called with array of all recognized browsers 202 | 203 | Each browser contains following properties: 204 | - `name` - name of a browser 205 | - `version` - browser's version 206 | - `type` - type of a browser i.e. browser's family 207 | - `command` - command used to launch a browser 208 | 209 | ### `launcher.update([configFile], callback)` 210 | 211 | Updates the browsers cache file (`~/.config/browser-launcher/config.json` is no `configFile` was given) and creates new profiles for found browsers. 212 | 213 | **Parameters:** 214 | - *String* `configFile` - path to the configuration file *Optional* 215 | - *Function* `callback(err, browsers)` - function called with found browsers and errors (if any) 216 | 217 | ## Known Issues 218 | 219 | - IE8: after several starts and stops, if you manually open IE it will come up with a pop-up asking if we want to restore tabs (#21) 220 | - Chrome @ OSX: it's not possible to launch multiple instances of Chrome at once 221 | 222 | ## License 223 | 224 | MIT 225 | -------------------------------------------------------------------------------- /example/detect.js: -------------------------------------------------------------------------------- 1 | var launcher = require( '../' ); 2 | 3 | launcher.detect( function( available ) { 4 | console.log( 'Available browsers:' ); 5 | console.dir( available ); 6 | } ); 7 | -------------------------------------------------------------------------------- /example/launch.js: -------------------------------------------------------------------------------- 1 | var launcher = require( '../' ); 2 | 3 | launcher( function( err, launch ) { 4 | if ( err ) { 5 | return console.error( err ); 6 | } 7 | 8 | launch( 'http://cksource.com/', process.env.BROWSER || 'chrome', function( err, instance ) { 9 | if ( err ) { 10 | return console.error( err ); 11 | } 12 | 13 | console.log( 'Instance started with PID:', instance.pid ); 14 | 15 | setTimeout( function() { 16 | instance.stop(); 17 | }, 10000 ); 18 | 19 | instance.on( 'stop', function( code ) { 20 | console.log( 'Instance stopped with exit code:', code ); 21 | } ); 22 | } ); 23 | } ); 24 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require( 'path' ), 2 | _ = require( 'lodash' ), 3 | configModule = require( './lib/config' ), 4 | detect = require( './lib/detect' ), 5 | run = require( './lib/run' ), 6 | createProfiles = require( './lib/create_profiles' ); 7 | 8 | /** 9 | * Check the configuration and prepare a launcher function. 10 | * If there's no config ready, detect available browsers first. 11 | * Finally, pass a launcher function to the callback. 12 | * @param {String} [configFile] Path to a configuration file 13 | * @param {Function} callback Callback function 14 | */ 15 | function getLauncher( configFile, callback ) { 16 | if ( typeof configFile === 'function' ) { 17 | callback = configFile; 18 | configFile = configModule.defaultConfigFile; 19 | } 20 | 21 | configModule.read( configFile, function( err, config ) { 22 | if ( !config ) { 23 | getLauncher.update( configFile, function( err, config ) { 24 | if ( err ) { 25 | callback( err ); 26 | } else { 27 | callback( null, wrap( config ) ); 28 | } 29 | } ); 30 | } else { 31 | callback( null, wrap( config ) ); 32 | } 33 | } ); 34 | 35 | function wrap( config ) { 36 | var res = launch.bind( null, config ); 37 | 38 | res.browsers = config.browsers; 39 | 40 | return res; 41 | } 42 | 43 | function launch( config, uri, options, callback ) { 44 | if ( typeof options === 'string' ) { 45 | options = { 46 | browser: options 47 | }; 48 | } 49 | 50 | options = options || {}; 51 | 52 | var version = options.version || options.browser.split( '/' )[ 1 ] || '*', 53 | name = options.browser.toLowerCase().split( '/' )[ 0 ], 54 | runner = run( config, name, version ); 55 | 56 | if ( !runner ) { 57 | // update the list of available browsers and retry 58 | getLauncher.update( configFile, function( err, config ) { 59 | if ( !( runner = run( config, name, version ) ) ) { 60 | return callback( name + ' is not installed in your system.' ); 61 | } 62 | 63 | runner( uri, options, callback ); 64 | } ); 65 | } else { 66 | runner( uri, options, callback ); 67 | } 68 | } 69 | } 70 | 71 | /** 72 | * Detect available browsers 73 | * @param {Function} callback Callback function 74 | */ 75 | getLauncher.detect = function( callback ) { 76 | detect( function( browsers ) { 77 | callback( browsers.map( function( browser ) { 78 | return _.pick( browser, [ 'name', 'version', 'type', 'command' ] ); 79 | } ) ); 80 | } ); 81 | }; 82 | 83 | /** 84 | * Update the browsers cache and create new profiles if necessary 85 | * @param {String} configDir Path to the configuration file 86 | * @param {Function} callback Callback function 87 | */ 88 | getLauncher.update = function( configFile, callback ) { 89 | if ( typeof configFile === 'function' ) { 90 | callback = configFile; 91 | configFile = configModule.defaultConfigFile; 92 | } 93 | 94 | detect( function( browsers ) { 95 | createProfiles( browsers, path.dirname( configFile ), function( err ) { 96 | if ( err ) { 97 | return callback( err ); 98 | } 99 | 100 | var config = { 101 | browsers: browsers 102 | }; 103 | 104 | configModule.write( configFile, config, function( err ) { 105 | if ( err ) { 106 | callback( err ); 107 | } else { 108 | callback( null, config ); 109 | } 110 | } ); 111 | } ); 112 | } ); 113 | }; 114 | 115 | module.exports = getLauncher; 116 | -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var mkdirp = require( 'mkdirp' ), 2 | fs = require( 'fs' ), 3 | path = require( 'path' ), 4 | package = require( '../package' ), 5 | defaultConfigFile = require( 'osenv' ).home() + '/.config/' + package.name + '/config.json'; 6 | 7 | exports.defaultConfigFile = defaultConfigFile; 8 | 9 | /** 10 | * Read a configuration file 11 | * @param {String} [configFile] Path to the configuration file 12 | * @param {Function} callback Callback function 13 | */ 14 | exports.read = function( configFile, callback ) { 15 | if ( typeof configFile === 'function' ) { 16 | callback = configFile; 17 | configFile = defaultConfigFile; 18 | } 19 | 20 | if ( !configFile ) { 21 | configFile = defaultConfigFile; 22 | } 23 | 24 | var configDir = path.dirname( configFile ); 25 | 26 | mkdirp( configDir, function( err ) { 27 | if ( err ) { 28 | return callback( err ); 29 | } 30 | 31 | fs.exists( configFile, function( exists ) { 32 | if ( exists ) { 33 | fs.readFile( configFile, function( err, src ) { 34 | callback( err, JSON.parse( src ), configDir ); 35 | } ); 36 | } else { 37 | callback( err, null, configDir ); 38 | } 39 | } ); 40 | } ); 41 | }; 42 | 43 | /** 44 | * Write a configuration file 45 | * @param {String} configFile Path to the configuration file 46 | * @param {Object} config Configuration object 47 | * @param {Function} callback Callback function 48 | */ 49 | exports.write = function( configFile, config, callback ) { 50 | callback = callback || function() {}; 51 | 52 | if ( typeof configFile === 'object' ) { 53 | callback = config; 54 | config = configFile; 55 | configFile = defaultConfigFile; 56 | } 57 | 58 | mkdirp( path.dirname( configFile ), function( err ) { 59 | if ( err ) { 60 | return callback( err ); 61 | } 62 | 63 | fs.writeFile( configFile, JSON.stringify( config, null, 2 ), callback ); 64 | } ); 65 | }; 66 | -------------------------------------------------------------------------------- /lib/create_profiles.js: -------------------------------------------------------------------------------- 1 | var mkdirp = require( 'mkdirp' ), 2 | path = require( 'path' ); 3 | 4 | /** 5 | * Create profiles for the given browsers 6 | * @param {Array.} browsers Array of browsers 7 | * @param {String} configDir Path to a directory, where the profiles should be put 8 | * @param {Function} callback Callback function 9 | */ 10 | module.exports = function createProfiles( browsers, configDir, callback ) { 11 | var pending = browsers.length; 12 | 13 | if ( !pending ) { 14 | return callback(); 15 | } 16 | 17 | function checkPending() { 18 | return !--pending && callback(); 19 | } 20 | 21 | browsers.forEach( function( browser ) { 22 | if ( browser.type === 'firefox' && browser.profile ) { 23 | checkPending(); 24 | } else if ( browser.profile ) { 25 | browser.profile = makeDir( browser.name, browser.version ); 26 | 27 | mkdirp( browser.profile, function( err ) { 28 | if ( err ) { 29 | callback( err ); 30 | } else { 31 | checkPending(); 32 | } 33 | } ); 34 | } else { 35 | checkPending(); 36 | } 37 | } ); 38 | 39 | function makeDir( name, version ) { 40 | var dir = name + '-' + version + '_' + getRandom(); 41 | 42 | return path.join( configDir, dir ); 43 | } 44 | }; 45 | 46 | function getRandom() { 47 | return Math.random().toString( 16 ).slice( 2 ); 48 | } 49 | -------------------------------------------------------------------------------- /lib/darwin/chrome.js: -------------------------------------------------------------------------------- 1 | var util = require( './util' ), 2 | currentPath; 3 | 4 | function getPath( callback ) { 5 | if ( currentPath ) { 6 | return callback( null, currentPath ); 7 | } 8 | 9 | util.find( 'com.google.Chrome', function( err, path ) { 10 | currentPath = path; 11 | callback( err, currentPath ); 12 | } ); 13 | } 14 | 15 | function getVersion( callback ) { 16 | getPath( function( err, path ) { 17 | if ( err ) { 18 | return callback( err, null ); 19 | } 20 | 21 | var pl = util.getInfoPath( path ); 22 | 23 | util.exists( pl, function( exists ) { 24 | if ( exists ) { 25 | util.parse( pl, function( err, data ) { 26 | callback( err, data.KSVersion ); 27 | } ); 28 | } else { 29 | callback( 'not installed', null ); 30 | } 31 | } ); 32 | } ); 33 | } 34 | 35 | exports.path = getPath; 36 | exports.version = getVersion; 37 | -------------------------------------------------------------------------------- /lib/darwin/firefox.js: -------------------------------------------------------------------------------- 1 | var path = require( 'path' ), 2 | util = require( './util' ); 3 | 4 | //Fetch all known version of Firefox on the host machine 5 | exports.all = function( callback ) { 6 | var installed = [], 7 | pending = 0, 8 | check = function() { 9 | if ( !pending ) { 10 | callback( null, installed ); 11 | } 12 | }; 13 | 14 | util.find( 'org.mozilla.firefox', function( err, p ) { 15 | if ( p ) { 16 | var items = p.split( '\n' ); 17 | pending = items.length; 18 | items.forEach( function( loc ) { 19 | var infoPath = util.getInfoPath( loc ); 20 | 21 | util.exists( infoPath, function( exits ) { 22 | if ( exits ) { 23 | util.parse( infoPath, function( err, data ) { 24 | var o = { 25 | version: data.CFBundleShortVersionString, 26 | path: path.join( loc, 'Contents/MacOS/firefox-bin' ) 27 | }; 28 | installed.push( o ); 29 | pending--; 30 | check(); 31 | } ); 32 | } else { 33 | pending--; 34 | check(); 35 | } 36 | } ); 37 | } ); 38 | } else { 39 | callback( 'not installed' ); 40 | } 41 | } ); 42 | }; 43 | -------------------------------------------------------------------------------- /lib/darwin/index.js: -------------------------------------------------------------------------------- 1 | exports.safari = require( './safari' ); 2 | exports.firefox = require( './firefox' ); 3 | exports.chrome = exports[ 'google-chrome' ] = require( './chrome' ); 4 | exports.opera = require( './opera' ); 5 | -------------------------------------------------------------------------------- /lib/darwin/opera.js: -------------------------------------------------------------------------------- 1 | var util = require( './util' ), 2 | currentPath; 3 | 4 | function getPath( callback ) { 5 | if ( currentPath ) { 6 | return callback( null, currentPath ); 7 | } 8 | 9 | util.find( 'com.operasoftware.Opera', function( err, path ) { 10 | currentPath = path; 11 | callback( err, currentPath ); 12 | } ); 13 | } 14 | 15 | function getVersion( callback ) { 16 | getPath( function( err, path ) { 17 | if ( err ) { 18 | return callback( err, null ); 19 | } 20 | 21 | var pl = util.getInfoPath( path ); 22 | 23 | util.exists( pl, function( exists ) { 24 | if ( exists ) { 25 | util.parse( pl, function( err, data ) { 26 | callback( err, data.CFBundleVersion ); 27 | } ); 28 | } else { 29 | callback( 'not installed', null ); 30 | } 31 | } ); 32 | } ); 33 | } 34 | 35 | exports.path = getPath; 36 | exports.version = getVersion; 37 | -------------------------------------------------------------------------------- /lib/darwin/safari.js: -------------------------------------------------------------------------------- 1 | var util = require( './util' ), 2 | currentPath; 3 | 4 | function getPath( callback ) { 5 | if ( currentPath ) { 6 | return callback( null, currentPath ); 7 | } 8 | 9 | util.find( 'com.apple.Safari', function( err, path ) { 10 | currentPath = path; 11 | callback( err, currentPath ); 12 | } ); 13 | } 14 | 15 | function getVersion( callback ) { 16 | getPath( function( err, path ) { 17 | if ( err ) { 18 | return callback( err, null ); 19 | } 20 | 21 | var pl = util.getInfoPath( path ); 22 | 23 | util.exists( pl, function( exists ) { 24 | if ( exists ) { 25 | util.parse( pl, function( err, data ) { 26 | callback( err, data.CFBundleShortVersionString ); 27 | } ); 28 | } else { 29 | callback( 'not installed', null ); 30 | } 31 | } ); 32 | } ); 33 | } 34 | 35 | exports.path = getPath; 36 | exports.version = getVersion; 37 | -------------------------------------------------------------------------------- /lib/darwin/util.js: -------------------------------------------------------------------------------- 1 | var exec = require( 'child_process' ).exec, 2 | fs = require( 'fs' ), 3 | path = require( 'path' ), 4 | plist = require( 'plist' ); 5 | 6 | exports.exists = require( 'fs' ).exists; 7 | 8 | exports.parse = function( file, callback ) { 9 | fs.readFile( file, { 10 | encoding: 'utf8' 11 | }, function( err, data ) { 12 | if ( !err ) { 13 | data = plist.parse( data ); 14 | } 15 | 16 | callback( err, data ); 17 | } ); 18 | }; 19 | 20 | exports.find = function( id, callback ) { 21 | var pathQuery = 'mdfind "kMDItemCFBundleIdentifier=="' + id + '"" | head -1'; 22 | 23 | exec( pathQuery, function( err, stdout ) { 24 | var loc = stdout.trim(); 25 | 26 | if ( loc === '' ) { 27 | loc = null; 28 | err = 'not installed'; 29 | } 30 | 31 | callback( err, loc ); 32 | } ); 33 | }; 34 | 35 | exports.getInfoPath = function( p ) { 36 | return path.join( p, 'Contents', 'Info.plist' ); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/detect.js: -------------------------------------------------------------------------------- 1 | var spawn = require( 'child_process' ).spawn, 2 | winDetect = require( 'win-detect-browsers' ), 3 | darwin = require( './darwin' ), 4 | extend = require( 'lodash' ).extend, 5 | browsers = { 6 | 'google-chrome': { 7 | name: 'chrome', 8 | re: /Google Chrome (\S+)/, 9 | type: 'chrome', 10 | profile: true, 11 | }, 12 | 'chromium': { 13 | name: 'chromium', 14 | re: /Chromium (\S+)/, 15 | type: 'chrome', 16 | profile: true, 17 | }, 18 | 'chromium-browser': { 19 | name: 'chromium', 20 | re: /Chromium (\S+)/, 21 | type: 'chrome', 22 | profile: true, 23 | }, 24 | 'firefox': { 25 | name: 'firefox', 26 | re: /Mozilla Firefox (\S+)/, 27 | type: 'firefox', 28 | profile: true, 29 | }, 30 | 'phantomjs': { 31 | name: 'phantomjs', 32 | re: /(\S+)/, 33 | type: 'phantom', 34 | headless: true, 35 | profile: false, 36 | }, 37 | 'safari': { 38 | name: 'safari', 39 | type: 'safari', 40 | profile: false 41 | }, 42 | 'ie': { 43 | name: 'ie', 44 | type: 'ie', 45 | profile: false 46 | }, 47 | 'opera': { 48 | name: 'opera', 49 | re: /Opera (\S+)/, 50 | type: 'opera', 51 | image: 'opera.exe', 52 | profile: true 53 | } 54 | }, 55 | winDetectMap = { 56 | chrome: 'google-chrome', 57 | chromium: 'chromium-browser' 58 | }; 59 | 60 | /** 61 | * Detect all available browsers on Windows systems. 62 | * Pass an array of detected browsers to the callback function when done. 63 | * @param {Function} callback Callback function 64 | */ 65 | function detectWindows( callback ) { 66 | winDetect( function( found ) { 67 | var available = found.map( function( browser ) { 68 | var br = browsers[ winDetectMap[ browser.name ] || browser.name ]; 69 | 70 | return extend( {}, { 71 | name: browser.name, 72 | command: browser.path, 73 | version: browser.version 74 | }, br || {} ); 75 | } ); 76 | 77 | callback( available ); 78 | } ); 79 | } 80 | 81 | /** 82 | * Check if the given browser is available (on OSX systems). 83 | * Pass its version and path to the callback function if found. 84 | * @param {String} name Name of a browser 85 | * @param {Function} callback Callback function 86 | */ 87 | function checkDarwin( name, callback ) { 88 | if ( darwin[ name ] ) { 89 | if ( darwin[ name ].all ) { 90 | darwin[ name ].all( function( err, available ) { 91 | if ( err ) { 92 | callback( 'failed to get version for ' + name ); 93 | } else { 94 | callback( err, available ); 95 | } 96 | } ); 97 | } else { 98 | darwin[ name ].version( function( err, version ) { 99 | if ( version ) { 100 | darwin[ name ].path( function( err, p ) { 101 | if ( err ) { 102 | return callback( 'failed to get path for ' + name ); 103 | } 104 | 105 | callback( null, version, p ); 106 | } ); 107 | } else { 108 | callback( 'failed to get version for ' + name ); 109 | } 110 | } ); 111 | } 112 | } else { 113 | checkOthers( name, callback ); 114 | } 115 | } 116 | 117 | /** 118 | * Check if the given browser is available (on Unix systems). 119 | * Pass its version to the callback function if found. 120 | * @param {String} name Name of a browser 121 | * @param {Function} callback Callback function 122 | */ 123 | function checkOthers( name, callback ) { 124 | var process = spawn( name, [ '--version' ] ), 125 | re = browsers[ name ].re, 126 | data = ''; 127 | 128 | process.stdout.on( 'data', function( buf ) { 129 | data += buf; 130 | } ); 131 | 132 | process.on( 'error', function() { 133 | callback( 'not installed' ); 134 | callback = null; 135 | } ); 136 | 137 | process.on( 'exit', function( code ) { 138 | if ( !callback ) { 139 | return; 140 | } 141 | 142 | if ( code !== 0 ) { 143 | return callback( 'not installed' ); 144 | } 145 | 146 | var m = re.exec( data ); 147 | 148 | if ( m ) { 149 | callback( null, m[ 1 ] ); 150 | } else { 151 | callback( null, data.trim() ); 152 | } 153 | } ); 154 | } 155 | 156 | /** 157 | * Detect all available web browsers. 158 | * Pass an array of available browsers to the callback function when done. 159 | * @param {Function} callback Callback function 160 | */ 161 | module.exports = function detect( callback ) { 162 | var available = [], 163 | names, 164 | check; 165 | 166 | if ( process.platform === 'win32' ) { 167 | return detectWindows( callback ); 168 | } else if ( process.platform === 'darwin' ) { 169 | check = checkDarwin; 170 | } else { 171 | check = checkOthers; 172 | } 173 | 174 | names = Object.keys( browsers ); 175 | 176 | function next() { 177 | var name = names.shift(); 178 | 179 | if ( !name ) { 180 | return callback( available ); 181 | } 182 | 183 | var br = browsers[ name ]; 184 | 185 | check( name, function( err, v, p ) { 186 | if ( err === null ) { 187 | if ( Array.isArray( v ) ) { 188 | v.forEach( function( item ) { 189 | available.push( extend( {}, br, { 190 | command: item.path, 191 | version: item.version 192 | } ) ); 193 | } ); 194 | } else { 195 | available.push( extend( {}, br, { 196 | command: p || name, 197 | version: v 198 | } ) ); 199 | } 200 | } 201 | 202 | next(); 203 | } ); 204 | } 205 | 206 | next(); 207 | }; 208 | -------------------------------------------------------------------------------- /lib/instance.js: -------------------------------------------------------------------------------- 1 | var EventEmitter = require( 'events' ).EventEmitter, 2 | child = require( 'child_process' ), 3 | rimraf = require( 'rimraf' ), 4 | util = require( 'util' ); 5 | 6 | /** 7 | * Web browser instance 8 | * @param {Object} options Configuration options 9 | * @param {Array} options.args Array list of command line arguments 10 | * @param {String} options.command Command used to start an instance's process 11 | * @param {String} options.cwd Instance's current working directory 12 | * @param {Boolean} options.detached Flag telling if the instance should be started in detached mode 13 | * @param {Object} options.env Instance's environment variables 14 | * @param {String} options.image Instance image (used to kill it on Windows) 15 | * @param {String} options.processName Instance process name (used to kill it on OSX) 16 | * @param {String} options.tempDir Temporary directory used by the instance 17 | */ 18 | function Instance( options ) { 19 | EventEmitter.call( this ); 20 | 21 | this.command = options.command; 22 | this.args = options.args; 23 | this.image = options.image; 24 | this.processName = options.processName; 25 | this.tempDir = options.tempDir; 26 | 27 | this.process = child.spawn( this.command, this.args, { 28 | detached: options.detached, 29 | env: options.env, 30 | cwd: options.cwd 31 | } ); 32 | 33 | this.pid = this.process.pid; 34 | this.stdout = this.process.stdout; 35 | this.stderr = this.process.stderr; 36 | 37 | // on Windows Opera uses a launcher which is stopped immediately after opening the browser 38 | // so it makes no sense to bind a listener, though we won't be noticed about crashes... 39 | if ( options.name === 'opera' && process.platform === 'win32' ) { 40 | return; 41 | } 42 | 43 | // trigger "stop" event when the process exits 44 | this.process.on( 'exit', this.emit.bind( this, 'stop' ) ); 45 | 46 | // clean-up the temp directory once the instance stops 47 | if ( this.tempDir ) { 48 | this.on( 'stop', function() { 49 | rimraf( this.tempDir, function() { /* .. */ } ); 50 | }.bind( this ) ); 51 | } 52 | } 53 | 54 | util.inherits( Instance, EventEmitter ); 55 | 56 | /** 57 | * Stop the instance 58 | * @param {Function} callback Callback function called when the instance is stopped 59 | */ 60 | Instance.prototype.stop = function( callback ) { 61 | if ( typeof callback == 'function' ) { 62 | this.once( 'stop', callback ); 63 | } 64 | 65 | // Opera case - it uses a launcher so we have to kill it somehow without a reference to the process 66 | if ( process.platform === 'win32' && this.image ) { 67 | child.exec( 'taskkill /F /IM ' + this.image ) 68 | .on( 'exit', this.emit.bind( this, 'stop' ) ); 69 | // OSX case with "open" command 70 | } else if ( this.command === 'open' ) { 71 | child.exec( 'osascript -e \'tell application "' + this.processName + '" to quit\'' ); 72 | // every other scenario 73 | } else { 74 | this.process.kill(); 75 | } 76 | }; 77 | 78 | module.exports = Instance; 79 | -------------------------------------------------------------------------------- /lib/run.js: -------------------------------------------------------------------------------- 1 | var headless = require( 'headless' ), 2 | mkdirp = require( 'mkdirp' ), 3 | os = require( 'os' ), 4 | fs = require( 'fs' ), 5 | path = require( 'path' ), 6 | Uid = require( 'uid' ), 7 | _ = require( 'lodash' ), 8 | Instance = require( './instance' ), 9 | setups = {}; 10 | 11 | /** 12 | * Setup procedure for Firefox browser: 13 | * - create a temporary directory 14 | * - create and write prefs.js file 15 | * - collect command line arguments necessary to launch the browser 16 | * @param {Object} browser Browser object 17 | * @param {String} uri URI to be opened 18 | * @param {Object} options Configuration options 19 | * @param {Function} callback Callback function 20 | */ 21 | setups.firefox = function( browser, uri, options, callback ) { 22 | var uid = Uid( 10 ), 23 | tempDir = path.join( os.tmpdir(), 'browser-launcher2' + uid ), 24 | file = path.join( tempDir, 'prefs.js' ), 25 | prefs = { 26 | 'browser.shell.checkDefaultBrowser': false, 27 | 'browser.bookmarks.restore_default_bookmarks': false, 28 | 'dom.disable_open_during_load': false, 29 | 'dom.max_script_run_time': 0 30 | }; 31 | 32 | mkdirp.sync( tempDir ); 33 | 34 | options.options = options.options || []; 35 | options.tempDir = tempDir; 36 | 37 | if ( options.proxy ) { 38 | var match = /^(?:http:\/\/)?([^:\/]+)(?::(\d+))?/.exec( options.proxy ), 39 | host = JSON.stringify( match[ 1 ] ), 40 | port = match[ 2 ] || 80; 41 | 42 | _.extend( prefs, { 43 | 'network.proxy.http': host, 44 | 'network.proxy.http_port': +port, 45 | 'network.proxy.type': 1, 46 | 'browser.cache.disk.capacity': 0, 47 | 'browser.cache.disk.smart_size.enabled': false, 48 | 'browser.cache.disk.smart_size.first_run': false, 49 | 'browser.sessionstore.resume_from_crash': false, 50 | 'browser.startup.page': 0, 51 | 'network.proxy.no_proxies_on': JSON.stringify( options.noProxy || '' ) 52 | } ); 53 | } 54 | 55 | if ( options.prefs ) { 56 | _.extend( prefs, options.prefs ); 57 | } 58 | 59 | prefs = Object.keys( prefs ).map( function( name ) { 60 | return 'user_pref("' + name + '", ' + prefs[ name ] + ');'; 61 | } ).join( '\n' ); 62 | 63 | fs.writeFile( file, prefs, function( err ) { 64 | if ( err ) { 65 | callback( err ); 66 | } else { 67 | callback( null, options.options.concat( [ 68 | uri, 69 | '--no-remote', 70 | '-profile', tempDir 71 | ] ) ); 72 | } 73 | } ); 74 | }; 75 | 76 | /** 77 | * Setup procedure for IE and Safari browsers: 78 | * - pass the URI to the command line arguments 79 | * @param {Object} browser Browser object 80 | * @param {String} uri URI to be opened 81 | * @param {Object} options Configuration options 82 | * @param {Function} callback Callback function 83 | */ 84 | setups.ie = setups.safari = function( browser, uri, options, callback ) { 85 | callback( null, [ uri ] ); 86 | }; 87 | 88 | /** 89 | * Setup procedure for Chrome browser: 90 | * - collect command line arguments necessary to launch the browser 91 | * @param {Object} browser Browser object 92 | * @param {String} uri URI to be opened 93 | * @param {Object} options Configuration options 94 | * @param {Function} callback Callback function 95 | */ 96 | setups.chrome = function( browser, uri, options, callback ) { 97 | options.options = options.options || []; 98 | 99 | callback( null, options.options.concat( [ 100 | options.proxy ? '--proxy-server=' + options.proxy : null, 101 | browser.profile ? '--user-data-dir=' + browser.profile : null, 102 | '--disable-restore-session-state', 103 | '--no-default-browser-check', 104 | '--disable-popup-blocking', 105 | '--disable-translate', 106 | '--start-maximized', 107 | '--disable-default-apps', 108 | '--disable-sync', 109 | '--enable-fixed-layout', 110 | '--no-first-run', 111 | '--noerrdialogs ', 112 | uri 113 | ] ).filter( Boolean ) ); 114 | }; 115 | 116 | /** 117 | * Setup procedure for PhantomJS: 118 | * - configure PhantomJS to open res/phantom.js script 119 | * @param {Object} browser Browser object 120 | * @param {String} uri URI to be opened 121 | * @param {Object} options Configuration options 122 | * @param {Function} callback Callback function 123 | */ 124 | setups.phantom = function( browser, uri, options, callback ) { 125 | var phantomScript = path.join( __dirname, '../res/phantom.js' ); 126 | 127 | options.options = options.options || []; 128 | 129 | callback( null, options.options.concat( [ 130 | options.proxy ? '--proxy=' + options.proxy.replace( /^http:\/\//, '' ) : null, 131 | phantomScript, 132 | uri 133 | ] ).filter( Boolean ) ); 134 | }; 135 | 136 | /** 137 | * Setup procedure for Opera browser: 138 | * - copy the default preferences file depending on the Opera version 139 | * (res/operaprefs.ini or res/Preferences) to the profile directory 140 | * - collect command line arguments necessary to launch the browser 141 | * @param {Object} browser Browser object 142 | * @param {String} uri URI to be opened 143 | * @param {Object} options Configuration options 144 | * @param {Function} callback Callback function 145 | */ 146 | setups.opera = function( browser, uri, options, callback ) { 147 | var prefs = { 148 | old: 'operaprefs.ini', 149 | blink: 'Preferences' 150 | }, 151 | engine = { 152 | old: [ 153 | // browser.profile ? '-pd ' + browser.profile : null, // disabled temporarily 154 | '-nosession', 155 | '-nomail', 156 | uri 157 | ], 158 | // using the same rules as for chrome 159 | blink: [ 160 | browser.profile ? '--user-data-dir=' + browser.profile : null, 161 | '--disable-restore-session-state', 162 | '--no-default-browser-check', 163 | '--disable-popup-blocking', 164 | '--disable-translate', 165 | '--start-maximized', 166 | '--disable-default-apps', 167 | '--disable-sync', 168 | '--enable-fixed-layout', 169 | '--no-first-run', 170 | '--noerrdialogs', 171 | uri 172 | ] 173 | }, 174 | prefFile = prefs[ major( browser.version ) >= 15 ? 'blink' : 'old' ], 175 | src = path.join( __dirname, '../res/' + prefFile ), 176 | dest = path.join( browser.profile, prefFile ); 177 | 178 | options.options = options.options || []; 179 | 180 | copy( src, dest, function( err ) { 181 | if ( err ) { 182 | callback( err ); 183 | } else { 184 | callback( 185 | null, 186 | options.options.concat( 187 | engine[ major( browser.version ) >= 15 ? 'blink' : 'old' ] 188 | ).filter( Boolean ) 189 | ); 190 | } 191 | } ); 192 | }; 193 | 194 | /** 195 | * Run a browser 196 | * @param {Object} config Configuration object 197 | * @param {String} name Browser name 198 | * @param {String} version Browser version 199 | * @return {Function} 200 | */ 201 | module.exports = function runBrowser( config, name, version ) { 202 | var browser = findMatch( config, name, version ); 203 | 204 | if ( !browser ) { 205 | return; 206 | } 207 | 208 | return function( uri, options, callback ) { 209 | // run a regular browser in a "headless" mode 210 | if ( options.headless && !browser.headless ) { 211 | headless( function( err, proc, display ) { 212 | if ( err ) { 213 | return callback( err ); 214 | } 215 | 216 | run( { 217 | DISPLAY: ':' + display 218 | } ); 219 | } ); 220 | } else { 221 | run( {} ); 222 | } 223 | 224 | function run( customEnv ) { 225 | var env = {}, 226 | cwd = process.cwd(); 227 | 228 | // copy environment variables 229 | Object.keys( process.env ).forEach( function( key ) { 230 | env[ key ] = process.env[ key ]; 231 | } ); 232 | 233 | Object.keys( customEnv ).forEach( function( key ) { 234 | env[ key ] = customEnv[ key ]; 235 | } ); 236 | 237 | // setup the browser 238 | setups[ browser.type ]( browser, uri, options, function( err, args ) { 239 | if ( err ) { 240 | return callback( err ); 241 | } 242 | 243 | // pass proxy configuration to the new environment 244 | if ( options.noProxy && env.no_proxy === undefined ) { 245 | env.no_proxy = options.noProxy; 246 | } 247 | 248 | if ( options.proxy && env.http_proxy === undefined ) { 249 | env.http_proxy = options.proxy; 250 | } 251 | 252 | if ( options.proxy && env.HTTP_PROXY === undefined ) { 253 | env.HTTP_PROXY = options.proxy; 254 | } 255 | 256 | // prepare the launch command for Windows systems 257 | if ( process.platform === 'win32' ) { 258 | // ensure all the quotes are removed 259 | browser.command = browser.command.replace( /"/g, '' ); 260 | // change directory to the app's base (Chrome) 261 | cwd = require( 'path' ).dirname( browser.command ); 262 | } 263 | 264 | // prepare the launch command for OSX systems 265 | if ( process.platform === 'darwin' ) { 266 | // use the binary paths under the hood 267 | if ( browser.name !== 'firefox' && browser.name !== 'phantomjs' ) { 268 | // open --wait-apps --new --fresh -a /Path/To/Executable --args 269 | args.unshift( 270 | '--wait-apps', 271 | '--new', 272 | '--fresh', 273 | '-a', 274 | browser.command, 275 | args.pop(), 276 | '--args' 277 | ); 278 | 279 | browser.processName = browser.command; 280 | browser.command = 'open'; 281 | } 282 | } 283 | 284 | browser.tempDir = options.tempDir; 285 | 286 | callback( null, new Instance( _.extend( {}, browser, { 287 | args: args, 288 | detached: options.detached, 289 | env: env, 290 | cwd: cwd 291 | } ) ) ); 292 | } ); 293 | } 294 | }; 295 | }; 296 | 297 | /** 298 | * Copy a file 299 | * @param {String} src Source path 300 | * @param {String} dest Destination path 301 | * @param {Function} callback Completion callback 302 | */ 303 | function copy( src, dest, callback ) { 304 | var rs = fs.createReadStream( src ), 305 | ws = fs.createWriteStream( dest ), 306 | called = false; 307 | 308 | function done( err ) { 309 | if ( !called ) { 310 | called = true; 311 | callback( err ); 312 | } 313 | } 314 | 315 | rs.on( 'error', done ); 316 | ws.on( 'error', done ); 317 | ws.on( 'close', function() { 318 | done(); 319 | } ); 320 | 321 | rs.pipe( ws ); 322 | } 323 | 324 | /** 325 | * Get the major version 326 | * @param {String} version Version string 327 | * @return {Number} 328 | */ 329 | function major( version ) { 330 | return +version.split( '.' )[ 0 ]; 331 | } 332 | 333 | /** 334 | * In the given configuration find a browser matching specified name and version 335 | * @param {Object} config Configuration object 336 | * @param {String} name Browser name 337 | * @param {String} version Browser version 338 | * @return {Object} 339 | */ 340 | function findMatch( config, name, version ) { 341 | var matching = config.browsers.filter( function( b ) { 342 | return b.name === name && matches( b.version, version ); 343 | } ).sort( function( a, b ) { 344 | return major( b.version ) - major( a.version ); 345 | } ); 346 | 347 | if ( matching.length ) { 348 | return matching[ 0 ]; 349 | } 350 | } 351 | 352 | /** 353 | * Check if the given version matches the pattern 354 | * @param {String} version Browser version string 355 | * @param {String} [pattern] Expected version pattern 356 | * @return {Boolean} 357 | */ 358 | function matches( version, pattern ) { 359 | if ( pattern === undefined || pattern === '*' ) { 360 | return true; 361 | } 362 | 363 | var vs = version.split( '.' ), 364 | ps = pattern.split( '.' ), 365 | i; 366 | 367 | for ( i = 0; i < ps.length; i++ ) { 368 | if ( ps[ i ] === 'x' || ps[ i ] === '*' ) { 369 | continue; 370 | } 371 | 372 | if ( ps[ i ] !== vs[ i ] ) { 373 | return false; 374 | } 375 | } 376 | 377 | return true; 378 | } 379 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "browser-launcher2", 3 | "version": "0.4.6", 4 | "description": "Detect, launch and stop browser versions", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "jscs . && jshint index.js lib/ example/" 8 | }, 9 | "directories": { 10 | "example": "example", 11 | "res": "res" 12 | }, 13 | "dependencies": { 14 | "headless": "^0.1.7", 15 | "lodash": "^2.4.1", 16 | "mkdirp": "^0.5.0", 17 | "osenv": "^0.1.0", 18 | "plist": "^1.0.1", 19 | "win-detect-browsers": "^1.0.1", 20 | "uid": "0.0.2", 21 | "rimraf": "~2.2.8" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git://github.com/benderjs/browser-launcher2.git" 26 | }, 27 | "homepage": "https://github.com/benderjs/browser-launcher2", 28 | "keywords": [ 29 | "browser", 30 | "headless", 31 | "phantom", 32 | "chrome", 33 | "firefox", 34 | "chromium", 35 | "safari", 36 | "ie", 37 | "opera", 38 | "osx", 39 | "windows" 40 | ], 41 | "author": "James Halliday (http://substack.net)", 42 | "contributors": [ 43 | "CKSource (http://cksource.com/)" 44 | ], 45 | "license": "MIT", 46 | "engine": { 47 | "node": ">=0.8" 48 | }, 49 | "devDependencies": { 50 | "jscs": "^1.5.8", 51 | "jshint": "^2.5.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /res/Preferences: -------------------------------------------------------------------------------- 1 | { 2 | "browser": { 3 | "check_default_browser": false 4 | }, 5 | "profile": { 6 | "content_settings": { 7 | "clear_on_exit_migrated": true 8 | }, 9 | "default_content_settings": { 10 | "popups": 1 11 | }, 12 | "password_manager_enabled": false 13 | }, 14 | "session": { 15 | "restore_on_startup": 5 16 | }, 17 | "statistics": { 18 | "collection_asked": true, 19 | "collection_enabled": false 20 | } 21 | } -------------------------------------------------------------------------------- /res/operaprefs.ini: -------------------------------------------------------------------------------- 1 | Opera Preferences version 2.1 2 | 3 | [State] 4 | Accept License=1 5 | Run=0 6 | 7 | [User Prefs] 8 | Show Default Browser Dialog=0 9 | Startup Type=2 10 | Home URL=about:blank 11 | Show Close All But Active Dialog=0 12 | Show Close All Dialog=0 13 | Show Crash Log Upload Dialog=0 14 | Show Delete Mail Dialog=0 15 | Show Download Manager Selection Dialog=0 16 | Show Geolocation License Dialog=0 17 | Show Mail Error Dialog=0 18 | Show New Opera Dialog=0 19 | Show Problem Dialog=0 20 | Show Progress Dialog=0 21 | Show Validation Dialog=0 22 | Show Widget Debug Info Dialog=0 23 | Show Startup Dialog=0 24 | Show E-mail Client=0 25 | Show Mail Header Toolbar=0 26 | Show Setupdialog On Start=0 27 | Ask For Usage Stats Percentage=0 28 | Enable Usage Statistics=0 29 | Disable Opera Package AutoUpdate=1 30 | Browser JavaScript=0 31 | 32 | [Install] 33 | Newest Used Version=12.16.1860 34 | -------------------------------------------------------------------------------- /res/phantom.js: -------------------------------------------------------------------------------- 1 | /* global phantom */ 2 | var webpage = require( 'webpage' ), 3 | currentUrl = phantom.args[ 0 ], 4 | page; 5 | 6 | function renderPage( url ) { 7 | page = webpage.create(); 8 | page.windowName = 'my-window'; 9 | page.settings.webSecurityEnabled = false; 10 | 11 | // handle redirects 12 | page.onNavigationRequested = function( url, type, willNavigate, main ) { 13 | if ( main && url != currentUrl ) { 14 | currentUrl = url; 15 | page.close(); 16 | renderPage( url ); 17 | } 18 | }; 19 | 20 | // handle logs 21 | page.onConsoleMessage = function( msg ) { 22 | console.log( 'console: ' + msg ); 23 | }; 24 | 25 | page.onError = function( msg, trace ) { 26 | console.log( 'error:', msg ); 27 | trace.forEach( function( item ) { 28 | console.log( ' ', item.file, ':', item.line ); 29 | } ); 30 | }; 31 | 32 | page.open( url, function( status ) { 33 | if ( status !== 'success' ) { 34 | phantom.exit( 1 ); 35 | } 36 | } ); 37 | } 38 | 39 | renderPage( currentUrl ); 40 | --------------------------------------------------------------------------------