diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9e6e4e1 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "node" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c3842c3 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +[![Build Status](https://travis-ci.org/cdagli/browser-driver-installer.svg?branch=master)](https://travis-ci.org/cdagli/browser-driver-installer) + +# browser-driver-installer +Installs Chrome and Gecko drivers that match with the specified browser versions diff --git a/driverVersions.json b/driverVersions.json index 63c2b86..53bd464 100644 --- a/driverVersions.json +++ b/driverVersions.json @@ -20,7 +20,7 @@ "driverNPMPackageVersion": "2.29.0", "driverVersion": "2.29" }, - "59": { + "59": { "driverNPMPackageVersion": "2.30.0", "driverVersion": "2.30" }, @@ -58,25 +58,25 @@ } }, "geckoDriverVersions": { - "55":{ + "55": { "driverNPMPackageVersion": "1.10.0", "driverVersion": "0.19.1" }, - "56":{ + "56": { "driverNPMPackageVersion": "1.10.0", "driverVersion": "0.19.1" }, - "57":{ + "57": { "driverNPMPackageVersion": "1.10.0", "driverVersion": "0.19.1" }, - "58":{ + "58": { "driverNPMPackageVersion": "1.10.0", "driverVersion": "0.19.1" }, - "59":{ + "59": { "driverNPMPackageVersion": "1.11.0", "driverVersion": "0.20.1" } } -} \ No newline at end of file +} diff --git a/index.js b/index.js index 1a05465..60923e4 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,5 @@ #!/usr/bin/env node + const program = require('commander'); const driverInstaller = require('./installer').driverInstaller; @@ -6,7 +7,8 @@ program .option('--chrome-version [chromeVersion]', 'Chrome browser major version string e.g. 65') .option('--chrome-driver-target-path [chromeDriverTargetPath]', 'Path to install Chrome driver executable') .option('--firefox-version [firefoxVersion]', 'Firefox browser major version string e.g. 57') - .option('--firefox-driver-target-path [firefoxDriverTargetPath]', 'Path to install Firefox driver(geckoDriver) executable') + .option('--firefox-driver-target-path [firefoxDriverTargetPath]', + 'Path to install Firefox driver(geckoDriver) executable') .parse(process.argv); driverInstaller(program.chromeVersion, program.chromeDriverTargetPath, program.firefoxVersion, program.firefoxDriverTargetPath); \ No newline at end of file diff --git a/installer.js b/installer.js index 5e4d3cd..c367105 100644 --- a/installer.js +++ b/installer.js @@ -1,87 +1,101 @@ -'use strict'; +'use strict'; /* eslint-disable no-console */ -var runNpmChildProcess = require('./runNpmChildProcess'); -var path = require('path'); -var execSync = require('child_process').execSync; -var shell = require('shelljs'); +const runNpmChildProcess = require('./runNpmChildProcess'); +const path = require('path'); +const execSync = require('child_process').execSync; +const shell = require('shelljs'); -const TEMP_DIR = 'temp'; +const TEMP_DIR = 'temp'; const CHROME_DRIVER_NAME = 'chromedriver'; const CHROME_DRIVER_BIN_PATH = path.join('node_modules', 'chromedriver', 'lib', 'chromedriver', CHROME_DRIVER_NAME); const CHROME_DRIVER_VERSION_REGEX = new RegExp(/\w+ ([0-9]+.[0-9]+).+/); -const GECKO_DRIVER_NAME = 'geckodriver'; +const GECKO_DRIVER_NAME = 'geckodriver'; const GECKO_DRIVER_BIN_PATH = path.join('node_modules', 'geckodriver', GECKO_DRIVER_NAME); const GECKO_DRIVER_VERSION_REGEX = new RegExp(/\w+\s(\d+.\d+.\d+)/); const BROWSER_MAJOR_VERSION_REGEX = new RegExp(/^(\d+)/); function installDriverWithVersion(driverName, driverBinPath, installPath, versionObject) { - if(checkDirectoryAndVersion(driverName, installPath, versionObject.driverVersion)) + if (checkDirectoryAndVersion(driverName, installPath, versionObject.driverVersion)) { - return; + return false; } shell.mkdir('-p', TEMP_DIR); - runNpmChildProcess(['install', `${driverName}@${versionObject.driverNPMPackageVersion}`, '--prefix', TEMP_DIR]).then(function () + return runNpmChildProcess(['install', `${driverName}@${versionObject.driverNPMPackageVersion}`, '--prefix', + TEMP_DIR + ]).then( + function () { shell.mkdir('-p', installPath); - console.log('package dependencies have been installed'); - shell.cp('-n',path.join(TEMP_DIR, driverBinPath), installPath); + shell.cp('-n', path.join(TEMP_DIR, driverBinPath), installPath); shell.rm('-rf', TEMP_DIR); - }, function (e) + console.log('package dependencies have been installed'); + return true; + }, + function (e) { - console.log('package dependencies installation failed with error, details: ' + e.toString()); + throw new Error('package dependencies installation failed with error, details: ' + e.toString()); }); } function checkDirectoryAndVersion(driverName, installPath, driverExpectedVersion) { - if (!shell.test('-e', installPath)) { - return false; + if (!shell.test('-e', installPath)) + { + return false; } console.log(`Directory '${installPath}' does exist.`); console.log(`Checking if the directory contains a ${driverName}...`); - if(!shell.test('-e', path.join(installPath, driverName))){ - console.log(`Could not find the ${driverName} in the directory '${installPath}'. Attempting to install it...`); - return false; + if (!shell.test('-e', path.join(installPath, driverName))) + { + console.log(`Could not find the ${driverName} in the directory '${installPath}'. Attempting to install it...`); + return false; } - - console.log(`${driverName} found.`); - const driverMajorVersion = driverVersionString(driverName, installPath); - if(driverMajorVersion !== driverExpectedVersion) + + console.log(`${driverName} found.`); + const driverMajorVersion = driverVersionString(driverName, installPath); + if (driverMajorVersion !== driverExpectedVersion) { - console.log(`${driverName} expected version (${driverExpectedVersion}) does not match with the installed version (${driverMajorVersion}).`); + console.log( + `${driverName} expected version (${driverExpectedVersion}) does not match with the installed version (${driverMajorVersion}).` + ); console.log('Removing the old version...'); shell.rm(path.join(installPath, driverName)); - return false; + return false; } - + console.log(`${driverName} version ${driverExpectedVersion} has already been installed!`); - return true; + return true; } function driverVersionString(driverName, installPath) { - let versionOutput = null; - if(driverName === CHROME_DRIVER_NAME){ + let versionOutput = null; + if (driverName === CHROME_DRIVER_NAME) + { versionOutput = execSync(path.join(installPath, driverName) + ' -v').toString(); return versionOutput.match(CHROME_DRIVER_VERSION_REGEX)[1]; - } else if (driverName === GECKO_DRIVER_NAME) + } + else if (driverName === GECKO_DRIVER_NAME) { versionOutput = execSync(path.join(installPath, driverName) + ' -V').toString(); return versionOutput.match(GECKO_DRIVER_VERSION_REGEX)[1]; - } else { + } + else + { throw new Error(`No driver exists with the name ${driverName}.`); } } -function driverInstaller(detectedChromeVersion, chromeDriverTargetPath, detectedFirefoxVersion, geckoDriverTargetPath){ +function driverInstaller(detectedChromeVersion, chromeDriverTargetPath, detectedFirefoxVersion, geckoDriverTargetPath) +{ const browserVersionsObject = JSON.parse(shell.cat(path.resolve(__dirname, 'driverVersions.json'))); // ChromeDriver NPM package versions are defined according to https://github.com/giggio/node-chromedriver/releases - const chromeDriverVersions = browserVersionsObject.chromeDriverVersions; + const chromeDriverVersions = browserVersionsObject.chromeDriverVersions; // GeckoDriver NPM package versions are defined according to https://github.com/mozilla/geckodriver/releases const geckoDriverVersions = browserVersionsObject.geckoDriverVersions; @@ -89,22 +103,45 @@ function driverInstaller(detectedChromeVersion, chromeDriverTargetPath, detected detectedChromeVersion = majorBrowserVersion(detectedChromeVersion); detectedFirefoxVersion = majorBrowserVersion(detectedFirefoxVersion); - if(detectedChromeVersion && !chromeDriverVersions[detectedChromeVersion]){ - console.log(`Failed to locate a version of ChromeDriver that matches the installed version of Chrome (${detectedChromeVersion}). Valid Chrome versions are: ${Object.keys(chromeDriverVersions).join(', ')}`) - } else if(detectedChromeVersion){ - installDriverWithVersion(CHROME_DRIVER_NAME, CHROME_DRIVER_BIN_PATH, chromeDriverTargetPath, chromeDriverVersions[detectedChromeVersion]); + if (detectedChromeVersion && !chromeDriverVersions[detectedChromeVersion]) + { + throw new Error( + `Failed to locate a version of ChromeDriver that matches the installed version of Chrome (${detectedChromeVersion}). Valid Chrome versions are: ${Object.keys(chromeDriverVersions).join(', ')}` + ); + } + else if (detectedChromeVersion && typeof (chromeDriverTargetPath) === 'string') + { + return installDriverWithVersion(CHROME_DRIVER_NAME, CHROME_DRIVER_BIN_PATH, chromeDriverTargetPath, + chromeDriverVersions[detectedChromeVersion]); + } + else + { + console.log('No Chrome version or target path is provided. Skipping...'); } - if(detectedFirefoxVersion && !geckoDriverVersions[detectedFirefoxVersion]){ - console.log(`Failed to locate a version of GeckoDriver that matches the installed version of Firefox (${detectedFirefoxVersion}). Valid Firefox versions are: ${Object.keys(geckoDriverVersions).join(', ')}`) - } else if(detectedFirefoxVersion){ - installDriverWithVersion(GECKO_DRIVER_NAME, GECKO_DRIVER_BIN_PATH, geckoDriverTargetPath, geckoDriverVersions[detectedFirefoxVersion]) + if (detectedFirefoxVersion && !geckoDriverVersions[detectedFirefoxVersion]) + { + throw new Error( + `Failed to locate a version of GeckoDriver that matches the installed version of Firefox (${detectedFirefoxVersion}). Valid Firefox versions are: ${Object.keys(geckoDriverVersions).join(', ')}` + ); } + else if (detectedFirefoxVersion && (typeof geckoDriverTargetPath) === 'string') + { + return installDriverWithVersion(GECKO_DRIVER_NAME, GECKO_DRIVER_BIN_PATH, geckoDriverTargetPath, + geckoDriverVersions[ + detectedFirefoxVersion]); + } + else + { + console.log('No Firefox version or target path is provided. Skipping...'); + } + + return false; } function majorBrowserVersion(browserVersionString) { - return browserVersionString && browserVersionString.match(BROWSER_MAJOR_VERSION_REGEX)[0]; + return (typeof browserVersionString) === 'string' && browserVersionString.match(BROWSER_MAJOR_VERSION_REGEX)[0]; } module.exports.driverInstaller = driverInstaller; \ No newline at end of file diff --git a/installer.spec.js b/installer.spec.js new file mode 100644 index 0000000..5f51608 --- /dev/null +++ b/installer.spec.js @@ -0,0 +1,76 @@ +const chai = require('chai'); +const installer = require('./installer'); +const expect = require('chai').expect; +const sinon = require('sinon'); +const chaiSinon = require('chai-sinon'); +const shell = require('shelljs'); +const path = require('path'); + +chai.use(chaiSinon); + +describe('browserDriverInstaller', function () +{ + const DEFAULT_TIMEOUT_IN_MILLIS = 60000; + const DRIVER_OUTPUT_PATH = './output'; + + this.timeout(DEFAULT_TIMEOUT_IN_MILLIS); + + beforeEach(function () + { + sinon.spy(console, 'log'); + }); + + afterEach(function () + { + console.log.restore(); + cleanTheOutput(); + }); + + function cleanTheOutput() + { + shell.rm('-rf', DRIVER_OUTPUT_PATH); + } + + it('should not attempt to install anything if one of the path, version or both parameters are not provided', + function () + { + expect(installer.driverInstaller()).to.be.false; + expect(console.log).to.have.been.calledWith( + 'No Chrome version or target path is provided. Skipping...'); + expect(console.log).to.have.been.calledWith( + 'No Firefox version or target path is provided. Skipping...'); + }); + + it('should throw an error if the provided version does not included in the JSON file', function () + { + const wrongVersionNumber = '1'; + expect(function () { installer.driverInstaller(wrongVersionNumber, '/some/target/path'); }).to.throw( + /Failed to locate a version of ChromeDriver that matches the installed version of Chrome \(1\). Valid Chrome versions are:*/ + ); + }); + + it('should install the chromedriver to specified path if the version is included in the JSON file', + function () + { + return installer.driverInstaller('54', DRIVER_OUTPUT_PATH).then(function () + { + expect(shell.test('-e', path.resolve(DRIVER_OUTPUT_PATH, 'chromedriver'))).to.be.true; + }); + }); + + it('should install the geckodriver to specified path if the version is included in the JSON file', function () + { + return installer.driverInstaller(null, null, '55', DRIVER_OUTPUT_PATH).then(function () + { + expect(shell.test('-e', path.resolve(DRIVER_OUTPUT_PATH, 'geckodriver'))).to.be.true; + }); + }); + + it('should not install again if the wanted version is already installed', function () + { + return installer.driverInstaller('54', DRIVER_OUTPUT_PATH).then(function () + { + expect(installer.driverInstaller('54', DRIVER_OUTPUT_PATH)).to.be.false; + }); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index 9b4d937..3c36ddb 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,23 @@ { - "bin": { - "browserDriverInstaller": "./index.js" - }, - "name": "browser-driver-installer", - "version": "0.0.1", - "description": "Installs Chrome and Gecko drivers that matches with the specified browser versions", - "main": "index.js", - "dependencies": { - "commander": "2.15.1", - "shelljs": "0.8.1" - }, - "scripts": { - "postinstall": "node ./index.js --chrome-version $CHROME_VERSION --chrome-driver-target-path $CHROMEDRIVER_PATH --firefox-version $FIREFOX_VERSION --firefox-driver-target-path $GECKODRIVER_PATH" - }, - "author": "Unscrambl", - "license": "Apache-2.0" + "author": "Unscrambl", + "bin": { + "browserDriverInstaller": "./index.js" + }, + "dependencies": { + "chai": "4.1.2", + "chai-sinon": "2.8.1", + "commander": "2.15.1", + "mocha": "5.1.1", + "shelljs": "0.8.1", + "sinon": "5.0.2" + }, + "description": "Installs Chrome and Gecko drivers that matches with the specified browser versions", + "license": "Apache-2.0", + "main": "index.js", + "name": "browser-driver-installer", + "scripts": { + "postinstall": "node ./index.js --chrome-version $CHROME_VERSION --chrome-driver-target-path $CHROMEDRIVER_PATH --firefox-version $FIREFOX_VERSION --firefox-driver-target-path $GECKODRIVER_PATH", + "test": "node_modules/mocha/bin/mocha *.spec.js" + }, + "version": "0.0.1" } diff --git a/runNpmChildProcess.js b/runNpmChildProcess.js index 2602d74..38493b9 100644 --- a/runNpmChildProcess.js +++ b/runNpmChildProcess.js @@ -9,7 +9,8 @@ module.exports = runNpmChildProcess; function runNpmChildProcess(args, cachePath) { args.push('--cache-min=600000', '--no-optional', '--loglevel=error'); - if(cachePath){ + if (cachePath) + { args.push('--cache=' + cachePath); } return retryNpmProcessIfItFails(args, MAX_RETRY_COUNT);