diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 4054f93e..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,36 +0,0 @@ -var_1: &docker_image circleci/node:8-browsers - -anchor_1: &job_defaults - working_directory: ~/workspace - docker: - - image: *docker_image - -version: 2 -jobs: - build_and_test: - <<: *job_defaults - steps: - - checkout - - run: node --version - - run: npm --version - - run: npm install - - - run: npm run test-unit - - run: npm run test-int - - run: npm run test-proxy - - run: npm run test-e2e -workflows: - version: 2 - default_workflow: - jobs: - - build_and_test - nightly: - triggers: - - schedule: - cron: "0 0 * * *" - filters: - branches: - only: - - master - jobs: - - build_and_test diff --git a/.clang-format b/.clang-format index 7d6cf97e..8d1c3c31 100644 --- a/.clang-format +++ b/.clang-format @@ -1,3 +1,3 @@ Language: JavaScript BasedOnStyle: Google -ColumnLimit: 80 \ No newline at end of file +ColumnLimit: 100 diff --git a/.gitignore b/.gitignore index 4b210ac9..683ccbc2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +built/ node_modules/ -dist/ -downloads/ +selenium/ +typings/ +.idea/ diff --git a/.npmignore b/.npmignore index be98b5ff..2a15ffa4 100644 --- a/.npmignore +++ b/.npmignore @@ -1,10 +1,13 @@ -.circleci/ -dist/spec/ -dist/**/*.spec-* -downloads/ lib/ +selenium/ spec/ +.clang-format +.gitattributes +.github/ .gitignore .npmignore -tsconfig.json \ No newline at end of file +.travis.yml +npm-debug.log +release.md +tsconfig.json diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..08ae0d13 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +language: node_js +sudo: false +node_js: + - "4" + - "5" + +env: + global: + - CXX=g++-4.8 + +addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - g++-4.8 + +before_install: + - g++-4.8 --version + +script: + - npm test diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcdcf9e..6cc0daa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,407 +1,3 @@ -# 13.0.2 - -Removes undeclared usage of `rimraf` dependency. - -# 13.0.1 - -Updates `adm-zip` to version `0.5.2`. - -# 13.0.0 - -Rewrite webdriver-manager. Highlights: - -- Fixes issues with promises that are not resolved with requests. -- Removes update-config.json file. When downloading a chromedriver binary, it - will download the cached xml, downloads the compressed files, uncompresses it, - and updates the chromedriver.config.json file. This new config file only has - the information for that binary file instead of a summary file like - update-config.json. -- Chromedriver supports downloads for versions like 2.xx and for versions like - 7x.xx.xx. -- Adds GitHub token support to download without GitHub rate limits. -- Use webdriver-manager as a lib or as a command line tool. The lib is new - so if there are issues, please feel free to write an issue. Also in the lib, - you can set the log level with: - -``` - import * as wdm from 'webdriver-manager'; - - wdm.setLogLevel('debug'); // uses loglevel logger 'webdriver-manager' -``` - -- Remove mobile support for android and appium. If this is still required, - use 12.1.1 and leave add an issue on GitHub. The appium portion was worked on - but was not migrated completely. -- Adds back support to run webdriver-manager in detached mode with - `webdriver-manager start --detach` and shutdown with - `webdriver-manager shutdown`. - -# 12.1.1 - -## Bug Fix - -- ([f17b226](https://github.com/angular/webdriver-manager/commit/f17b226342173e59b4d2fac54632185c26ca7086)) - Fix(types): Operator '==' cannot be applied to types 'string | string[]' and 'number' (#297) - - - build-enforced style changes - - add package-lock.json - -- ([7dbc1df](https://github.com/angular/webdriver-manager/commit/7dbc1dfbccc60c8836e7c1d390fd5562e0af5b9d)) - fix(clean): remove existing chrome meta files from update on clean command (#279) - - - change file name from chromedriver-response.xml to chrome-response.xml -- ([0a4c065](https://github.com/angular/webdriver-manager/commit/0a4c0658b0725154cba07ad6e7125c1dd504fa3d)) - fix(appium): change appiumPort to seleniumPort for selenium server request (#228) - -## Dependencies - -- ([6775421](https://github.com/angular/webdriver-manager/commit/6775421ea9e40db1bf547bcedcb716ba35106a80)) - deps(package): update npm audit. - - - Updates to vulnerable to zipslip. - - Update all dependencies with `npm audit fix --force`. - - closes #314 - -- ([a80ccd2](https://github.com/angular/webdriver-manager/commit/a80ccd22d494e10e8c3c6ef9af22abf38496cb14)) - deps(appium): bump up the appium version (#258) - - -# 12.0.6 - -## Bug Fix - -- ([708ade3](https://github.com/angular/webdriver-manager/commit/708ade31564ab5a48fbfcff80c37370fdc4f659a)) - fix(responses): response xml and json files (#247) - -# 12.0.5 - -## Bug Fix - -- ([242a72f](https://github.com/angular/webdriver-manager/commit/242a72ffc93037d651c9805e09b4fb30318d9f05)) - feat(start): start selenium without making web requests (#232) - -# 12.0.4 - -## Bug Fixes -- ([52d8a23](https://github.com/angular/webdriver-manager/commit/52d8a23f2d5d5021d1d9d302c492bf78a233a79d)) - fix(ignoressl): pass option to both binary and config source - - closes #207. - -- ([5af1c1c](https://github.com/angular/webdriver-manager/commit/5af1c1cdfb2d718004b02e9c0325ea6e758e78f1)) - fix(cache): change timestamp to 1 hour instead of 10 hours (#223) - - closes #221 - -# 12.0.3 - -## Bug Fixes - -- ([bb13882](https://github.com/angular/webdriver-manager/commit/bb13882f1d111fc0c16032be33a7b8dc7b1a797c)) - feat(gecko): Improve error message when Github api limit reached. (#217) - - the user. Also, API limit is reached, the error message now directly informs - when any other failure occurs the status code is reported. - This should hopefully give more info for issue #216. -- ([2cffd30](https://github.com/angular/webdriver-manager/commit/2cffd30d9ef87c5b53433f2aa73eda92b4251a76)) - fix(ignoressl): pass proxy and ignore ssl down to the binary and config source (#208) - - closes #207 and closes #221 - -# 12.0.2 - -- ([0bdf6a4](https://github.com/angular/webdriver-manager/commit/0bdf6a465ae2a4b106bb5ff948718ef4ae3f31ad)) - deps(typescript): use typescript@~2.0.0. fix any types (#203) - -# 12.0.1 - -- ([6209666](https://github.com/angular/webdriver-manager/commit/620966611f48504619a594b582060ba04a61b3a7)) - fix(gecko): add additional check for OS when getting latest (#200) - -# 12.0.0 - -### Changes to update - -This release gets the latest release for selenium standalone, chromedriver, -iedriver, and gecko driver by downloading and parsing either a json or xml file. -These json or xml files are cached in the selenium directory. This means for -users that provide an alternative cdn will also be required to provide the proper -xml or json server response to find these binaries. - -Since we are always downloading the latest, the `config.json` versions will no -longer be a place to override these. - -### Changes to start - -If a new release is out and you have old binaries, running `webdriver-manager -start` without specifying any versions should throw an error. The error will -tell the user that the binary is not present. - -### Changes to status - -Since we are downloading the latest and not maintaining a default version in -`config.json`, we are dropping the default tag. - -## Features - -- ([fe309ef](https://github.com/angular/webdriver-manager/commit/fe309ef0d85081592662164d4a24d79b0f2ed5cf)) - feat(latest): get the latest version from the cdn (#198) - - This reads the xml from the CDN to get the latest chromedriver, iedriver, - or standalone version if the version is 'latest'. If the release is from - Github, use the Github API to get the releases. Also store the downloaded - information to a cache in the output directory (default: selenium/). If - the file is older than one hour it will be rewritten. - - When getting the status, we are no longer showing the default version. - Default versions will be deprecated and will be removed from the config.json - file. - - When starting the standalone server, use the 'latest' version by default - unless specified by --versions.{binary} flag. - - Change the gulp update task to use 3.0.0-beta4 so Firefox tests will pass. - -# 11.1.1 - -## Bug Fixes - -- ([70614a2](https://github.com/angular/webdriver-manager/commit/70614a23e289088c852f5c0162a947488ffc77e0)) - fix(ie): Use 32-bit version by default for IEDriver (#181) - - closes #180 -- ([6f9a2ab](https://github.com/angular/webdriver-manager/commit/6f9a2abbf7d16f35e342f963543706ff3e1c45a1)) - fix(gecko): Respect versions.gecko in start command. (#184) - - Also bump the geckodriver version to latest. - -## Dependencies - -- ([5881c5b](https://github.com/angular/webdriver-manager/commit/5881c5bb49f330abd7804e2605df46901e87bf2a)) - deps(update): update devDependencies (#187) - - -# 11.1.0 - -- Update to set the default chrome driver version to 2.26 - -## Features - -- ([72e3d9f](https://github.com/angular/webdriver-manager/commit/72e3d9f341f1d0ba190036a72938e727d83840c7)) - feat(status): show the last downloaded version when using status (#177) - - - added a test to run update, then checks status for labels - - closes #172 - -## Bug Fixes - -- ([a3b46c7](https://github.com/angular/webdriver-manager/commit/a3b46c7a2ae59357b00fe5ce81d36964d6b0d45c)) - fix(iedriver): if downloading x64, use x64 version on start command (#173) - - - clang formatting - - closes #147 - -# 10.3 - -- Minor version update so users still on ES5/selenium 2.x can access appium/mobile fixes - See https://github.com/angular/webdriver-manager/commits/10.3.0 for details - - -# 11.0.0 - -## Breaking Change: - -- Requires node 6 since node 6 is in long term support. See (node LTS Schedule)[https://github.com/nodejs/LTS#lts-schedule]. - -## Features -- ([b5638ef](https://github.com/angular/webdriver-manager/commit/b5638ef0861843e1d42220af515adc3e03a2b65a)) - feat(update): on update, write full binary paths to file (#140) - - - Adding back in curl calls, these were removed on the new - `Downloader.getFile`. Add curl call to reflect proxies. - - - Fix output dir to read from update's options instead of Config - - - Feature will help directConnect users for Protractor. The file - will keep track of the last binary version as well as all other - binaries downloaded. - - The file will be created in the output directory. By default this is - `selenium/update-config.json`. On `clean` this file will be removed. - - ``` - webdriver-manager update --versions.chrome=2.20 --standalone=false - --gecko=false - ``` - - file created: - ``` - { - "chrome": { - "last": "/opt/src/webdriver-manager/selenium/chromedriver_2.20", - "all": ["/opt/src/webdriver-manager/selenium/chromedriver_2.20"] - } - } - ``` - - then the user wants to use 2.25: - - ``` - webdriver-manager update --versions.chrome=2.25 --standalone=false - --gecko=false - - ``` - - file created: - ``` - { - "chrome": { - "last": "/opt/src/webdriver-manager/selenium/chromedriver_2.25", - "all": ["/opt/src/webdriver-manager/selenium/chromedriver_2.20", - "/opt/src/webdriver-manager/selenium/chromedriver_2.25"] - } - } - ``` - -- ([473ab3e](https://github.com/angular/webdriver-manager/commit/473ab3e40c44468bb79e2a23d7b12753cf6e2b4d)) - feat(android): match android arch to os.arch (#164) - - The default was x86-64, but x86 cannot be emulated on ARM. This makes more sense -- ([c864c9a](https://github.com/angular/webdriver-manager/commit/c864c9af35514a4b5bf8a1d82b4339b39e5ac574)) - feat(shutdown): do not error if you try to shutdown a server which is already off (#162) - - When scripting, you might want to defensively run a `shutdown` command. If the shutdown fails - because the server is already off, you don't care. If it fails for another reason, you do care. - So I made trying to shutdown a server which is already off just a warning. I added a flag in case - you want the old behavior though. - -- ([338fffd](https://github.com/angular/webdriver-manager/commit/338fffddf68ac2767aa5c226ba5374451b9e5308)) - feat(quiet/verbose): add `--quiet` and `--verbose` flags to control the level of output (#156) - - I added the `--quiet` flag for cases like: - where currently the start --detach; ./tests.sh; webdriver-manager shutdown` - selenium server output will get mixed in with other output. - I also added the `--verbose` flag for `webdriver-manager update` in case you *really* wanted to - see all the output which gets eaten by using `--android-accept-licenses`. - -- ([91e36a3](https://github.com/angular/webdriver-manager/commit/91e36a3e56e712af2c104eafc45eeeba5997ad6a)) - feat(android on windows): Support android VMs on windows (#154) - - Closes https://github.com/angular/webdriver-manager/issues/51 - -- ([d533b03](https://github.com/angular/webdriver-manager/commit/d533b0389ac8a43b815890a644fdb9aa403ec769)) - feat(start android): extend the --detach flag to wait for appium/android (#141) - -## Bug fixes - -- ([26586f1](https://github.com/angular/webdriver-manager/commit/26586f1b341e02229d73d40827a9c1af2197ebb3)) - fix(start): wait for emulated android to really be ready before signaling (#161) - - Before, we were just waiting for the emulator to be running, rather than waiting for the OS to be - booted up and ready to instance chrome. - While I was doing that I moved some stuff into `lib/utils.ts` since I felt like too much of - `lib/cmds/start.ts` was being devoted to this one feature. - Also closes https://github.com/angular/webdriver-manager/issues/166 -- ([a7c6eb5](https://github.com/angular/webdriver-manager/commit/a7c6eb5d3d1caed2afea1ef896753d53f4ea14ed)) - fix(update/android): 2a1505f broke android -- ([3ee3e1a](https://github.com/angular/webdriver-manager/commit/3ee3e1a328087cb8c5bf869e00a325cfdeb80f6d)) - fix(fs): path.join does not handle absolute paths as desired (#152) -- ([deead0f](https://github.com/angular/webdriver-manager/commit/deead0fc55ecd00b282aedc234592181746a307c)) - fix(downloader): destroy the request after receiving the header (#144) - - Otherwise we’ll won’t terminate until the whole file was downloaded, even though we don’t need it. -- ([c16bf90](https://github.com/angular/webdriver-manager/commit/c16bf9053fc90e4b5e89ab867c514d0622ab0716)) - chore(es6): allow to use es6 promises (#160) - - - with node 6 on LTS, we can update the tsconfig to es6 - - update travis tests to use node 6 and 7 - -# 10.2.10 - -- Since 10.2.9 produced breaking changes, released as version 11.0.0 -- Version 10.2.8 is the same as 10.2.10 due to [issue #170](https://github.com/angular/webdriver-manager/issues/170). - -# 10.2.8 - -## Features - -- ([1f9713a](https://github.com/angular/webdriver-manager/commit/1f9713aff1e7d44de900ed3c74abac532d3e25ff)) - feat(start and shutdown): Added `--detach` option for `start` command and new `shutdown` command - (#130) - -- ([88cf46b](https://github.com/angular/webdriver-manager/commit/88cf46b715250559ba8a726370a83c5c2f4daed1)) - feat(version): have a way to get the package version (#136) - - closes #119 - -## Bug fixes -- ([5966b6a](https://github.com/angular/webdriver-manager/commit/5966b6ac7329878e9e16f5b1b88261c5b7f7e438)) - fix(cli): fix setting flag to false (#135) - - - This fixes `webdriver-manager update --gecko=false` - - This does not fix `webdriver-manager update --gecko=0`. Minimist interprets 0 as true. - - Add options and programs unit tests - closes #110 - -- ([35676ee](https://github.com/angular/webdriver-manager/commit/35676ee70c816d43f045fa33d02e41bf502a3a14)) - fix(gecko): follow redirects for content-length (#133) - -# 10.2.7 - -## Features - -- ([66776a0](https://github.com/angular/webdriver-manager/commit/66776a0edc97e0b2718f2fdf4eeb2c2c8b40df73)) - feat(start): add way to programmatically detect when the selenium server is running (#120) - -## Bug fixes - -- ([dc2f9f9](https://github.com/angular/webdriver-manager/commit/dc2f9f99ebd9675b02addf06732a4d8d348046bc)) - fix(cli): fix default option values, boolean and string handling (#110) (#122) - - - default option values initialize properly for `minimist` - - user-supplied boolean-type option values respected - - string-type option values are always strings - - simplify boolean-type option value access - -- ([88d6105](https://github.com/angular/webdriver-manager/commit/88d6105f538f075968c152935131bf19bf289532)) - fix(gecko): Update geckodriver to 0.11.0 and fix suffixes. (#128) - - Fixes #111 - -- ([707e015](https://github.com/angular/webdriver-manager/commit/707e015737ee3ca4b26b6d89979251f8d8c2d11d)) - fix(android): fixed four things for android: (#116) - - - Make appium default to 1.6.0 (Android N didn't work on 1.5.x) - - Make virtual devices default to `google_apis` - - Don't delete old virtual devices on update - - Update documentation - -- ([9fe4b22](https://github.com/angular/webdriver-manager/commit/9fe4b226d58fbbce2e9cf49df58f45dee7f13cf2)) - fix typo in webdriver-manager/spec/files (#125) - -# 10.2.6 - -## Features - -- ([f892ec4](https://github.com/angular/webdriver-manager/commit/f892ec41c09c210527998c966a69edc081cf418e)) - chore(chromedriver): update chromedriver version to 2.25 - -# 10.2.5 - -## Bug Fixes - -- ([b103850](https://github.com/angular/webdriver-manager/commit/b1038500466fe790cc8e3c2ff82dc3c7eb3796ba)) - fix(update): fix undefined gecko getBoolean error (#113) - - closes #107 -- ([7fbacf5](https://github.com/angular/webdriver-manager/commit/7fbacf5bc902dd3ccd1c9fbf285c8ca9a1e48ee3)) - fix(start): set the port when standalone server starts - - closes #106 - # 10.2.4 ## Bug Fixes diff --git a/LICENSE b/LICENSE index 2131a504..5cbc7da3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ -MIT License +The MIT License -Copyright (c) 2018 Craig Nishina +Copyright (c) 2016 Google, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,14 @@ 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 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. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md index 5f604512..c895e084 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,53 @@ -# webdriver-manager [![CircleCI](https://circleci.com/gh/angular/webdriver-manager/tree/replacement.svg?style=svg)](https://circleci.com/gh/angular/webdriver-manager/tree/replacement) -* [Use as a dependency](#use-as-a-dependency) -* [Use as a command line interface](#use-as-a-command-line-interface) -* [The command line interface help commands](#the-command-line-interface-help-commands) -* [Environment variables](docs/env_vars.md) +Webdriver Manager [![Build Status](https://travis-ci.org/angular/webdriver-manager.png?branch=master)](https://travis-ci.org/angular/webdriver-manager) [![Join the chat at https://gitter.im/angular/webdriver-manager](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/webdriver-manager?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +================= +A selenium server and browser driver manager for your end to end tests. This is the same tool as `webdriver-manager` from the [Protractor](https://github.com/angular/protractor) repository. -## Use as a dependency - -To install this as a dependency: `npm install -D webdriver-manager`. The -following is an example running webdriver-manager as a dependency. -The test downloads the providers and starts the selenium server standalone as -detached. After the test, it will shutdown the selenium server standalone. +**Note:** Version 9 and lower please reference [pose/webdriver-manager](https://github.com/pose/webdriver-manager). If there are features that existed in version 9 and lower, please open up an issue with the missing feature or a create a pull request. +Getting Started +--------------- ``` -import { - Options, - setLogLevel, - shutdown, - start, - update, -} from 'webdriver-manager'; - -const options: Options = { - browserDrivers: [{ - name: 'chromedriver' // For browser drivers, we just need to use a valid - // browser driver name. Other possible values - // include 'geckodriver' and 'iedriver'. - }], - server: { - name: 'selenium', - runAsNode: true, // If we want to run as a node. By default - // running as detached will set this to true. - runAsDetach: true // To run this in detached. This returns the - // process back to the parent process. - } -}; -setLogLevel('info'); // Required if we webdriver-manager to log to - // console. Not setting this will hide the logs. - -describe('some web test', () => { - beforeAll(async () => { - await update(options); - await start(options); - }); - - it('should run some web test', async () => { - // Your async / await web test with some framework. - }); - - afterAll(async () => { - await shutdown(options); // Makes the web request to shutdown the server. - // If we do not call shutdown, the java command - // will still be running the server on port 4444. - }); -}); - +npm install -g webdriver-manager ``` -## Use as a command line interface +Setting up a Selenium Server +---------------------------- -``` -npm i -g webdriver-manager +Prior to starting the selenium server, download the selenium server jar and driver binaries. By default it will download the selenium server jar and chromedriver binary. -webdriver-manager update // Downloads the latest binaries. -webdriver-manager start // Starts the selenium server standalone. +``` +webdriver-manager update ``` -Note: Installing globally will not work with Protractor if you are trying to -start a Selenium Standalone server with a "local" or "directConnect". It will -not work for these since Protractor is looking files downloaded locally to -the project. +Starting the Selenium Server +---------------------------- -## The command line interface help commands +By default, the selenium server will run on `http://localhost:4444/wd/hub`. -To get a list of commands for webdriver-manager, use the help flag. ``` -webdriver-manager --help -webdriver-manager [command] - -Commands: - webdriver-manager clean Removes downloaded files from the out_dir. - webdriver-manager shutdown Shutdown a local selenium server with GET request - webdriver-manager start Start up the selenium server. - webdriver-manager status List the current available binaries. - webdriver-manager update Install or update selected binaries. - -Options: - --version Show version number [boolean] - --help Show help [boolean] +webdriver-manager start ``` -To get a list of options that can be passed to the `webdriver-manager update` -command, use the help flag. +Other useful commands +--------------------- + +View different versions of server and driver files: ``` -webdriver-manager update --help -webdriver-manager update +webdriver-manager status +``` + +Clear out the server and driver files. If `webdriver-manager start` does not work, try to clear out the saved files. + +``` +webdriver-manager clean +``` + +Mobile Support +-------------- -Install or update selected binaries. - -Options: - --version Show version number [boolean] - --help Show help [boolean] - --out_dir Location of output. [string] - --chrome Install or update chromedriver. - [boolean] [default: true] - --gecko Install or update geckodriver.[boolean] [default: true] - --github_token Use a GitHub token to prevent rate limit issues. - [string] - --iedriver Install or update ie driver. [boolean] [default: false] - --ignore_ssl Ignore SSL certificates. [boolean] - --log_level The log level of this CLI. [string] [default: "info"] - --proxy Use a proxy server to download files. [string] - --standalone Install or update selenium server standalone. - [boolean] [default: true] - --versions.chrome The chromedriver version. [string] - --versions.gecko The geckodriver version. [string] - --versions.ie The ie driver version. [string] - --versions.standalone The selenium server standalone version. [string] -``` \ No newline at end of file +See [`mobile.md`](mobile.md). diff --git a/bin/webdriver-manager b/bin/webdriver-manager old mode 100644 new mode 100755 index 1af8bad0..db376115 --- a/bin/webdriver-manager +++ b/bin/webdriver-manager @@ -1,3 +1,56 @@ #!/usr/bin/env node -require('../dist/lib/cli'); +var path = require('path'); +var fs = require('fs'); +var cwd = process.cwd(); + +var nodeModuleName = 'webdriver-manager'; +var localInstall = path.resolve(cwd, 'node_modules', nodeModuleName); +var parentPath = path.resolve(cwd, '..'); +var dir = __dirname; +var folder = cwd.replace(parentPath, '').substring(1); + +var isProjectVersion = folder === nodeModuleName; +var isLocalVersion = false; + +var printCyan, printMagenta, printGreen; + +printCyan = printMagenta = printGreen = function(message) { + return message; +}; + +try { + var chalk = require('chalk'); + printMagenta = chalk.magenta; + printCyan = chalk.cyan; + printGreen = chalk.green; +} +catch (e) { + console.log("Error loading chalk, output will be boring."); +} + +try { + isLocalVersion = fs.statSync(localInstall).isDirectory(); +} catch(e) { +} + +// project version +if (folder === nodeModuleName) { + console.log(nodeModuleName + ': using ' + printMagenta('project version ' + + require(path.resolve('package.json')).version)); + require(path.resolve('built/lib/webdriver')); +} + +// local version +else if (isLocalVersion) { + console.log(nodeModuleName + ': using ' + printCyan('local installed version ' + + require(path.resolve(localInstall, 'package.json')).version)); + require(path.resolve(localInstall, 'built/lib/webdriver')); +} + +// global version +else { + console.log(nodeModuleName + ': using ' + printGreen('global installed version ' + + require(path.resolve(dir, '../package.json')).version)); + require(path.resolve(dir, '../built/lib/webdriver')); +} diff --git a/browsers.md b/browsers.md new file mode 100644 index 00000000..9b83daf5 --- /dev/null +++ b/browsers.md @@ -0,0 +1,20 @@ +### Tests with Protractor test suite (v 4.0.9) + +After running the Protractor test suite, the following are the supported +browsers / drivers. We support other drivers and browsers; however, since +the test suite checks only firefox and chrome, these are the only browsers +reported. + +### Current supported browsers / drivers + +| selenium standalone | firefox | chromedriver | chrome | +| ------------------- | ------- | ------------ | ------ | +| 2.53.1 | 47.0.1 | 2.24 | 53 | + + +### Investigated + +| selenium standalone | firefox 47.0.1 | firefox 49.0.1 | +| ------------------- | -------------- | -------------- | +| 2.53.1 | pass | fail | +| 3.0.0 | fail | fail | diff --git a/config.json b/config.json new file mode 100644 index 00000000..5eccc6d9 --- /dev/null +++ b/config.json @@ -0,0 +1,17 @@ +{ + "webdriverVersions": { + "selenium": "2.53.1", + "chromedriver": "2.24", + "geckodriver": "v0.9.0", + "iedriver": "2.53.1", + "androidsdk": "24.4.1", + "appium": "1.5.3" + }, + "cdnUrls": { + "selenium": "https://selenium-release.storage.googleapis.com/", + "chromedriver": "https://chromedriver.storage.googleapis.com/", + "geckodriver": "https://github.com/mozilla/geckodriver/releases/download/", + "iedriver": "https://selenium-release.storage.googleapis.com/", + "androidsdk": "http://dl.google.com/android/" + } +} diff --git a/docs/env_vars.md b/docs/env_vars.md deleted file mode 100644 index 91cebe79..00000000 --- a/docs/env_vars.md +++ /dev/null @@ -1,20 +0,0 @@ -# Environment variables - -**`JAVA_HOME`** - If the java home variable is set as an environment variable, -use it when starting the selenium server. The java executable is assumed to be -found in `JAVA_HOME/bin/java`. - -**`NO_PROXY`** - If the no proxy environment variable exists and matches the -host name, to ignore the resolve proxy. - -**`HTTPS_PROXY`** - If the https proxy environment variable exists and the url -protocol is https, use this proxy. - -**`HTTP_PROXY`** - If the http proxy environmet variable exists and the url -protocol is http or https, use this proxy. - -**`GITHUB_TOKEN`** - A GitHub personal access token with no additional scopes -required. When created, it will just have public-access. This can be set at -[https://github.com/settings/tokens][1]. - -[1]: https://github.com/settings/tokens \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 00000000..95f83d5a --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,60 @@ +'use strict'; + +var gulp = require('gulp'); +var runSequence = require('run-sequence'); +var spawn = require('child_process').spawn; + +var runSpawn = function(done, task, opt_arg) { + opt_arg = typeof opt_arg !== 'undefined' ? opt_arg : []; + var child = spawn(task, opt_arg, {stdio: 'inherit'}); + var running = false; + child.on('close', function() { + if (!running) { + running = true; + done(); + } + }); + child.on('error', function() { + if (!running) { + console.error('gulp encountered a child error'); + running = true; + done(); + } + }); +}; + +gulp.task('copy', function() { + return gulp.src(['config.json', 'package.json']) + .pipe(gulp.dest('built/')); +}); + +gulp.task('format:enforce', () => { + const format = require('gulp-clang-format'); + const clangFormat = require('clang-format'); + return gulp.src(['lib/**/*.ts']).pipe( + format.checkFormat('file', clangFormat, {verbose: true, fail: true})); +}); + +gulp.task('format', () => { + const format = require('gulp-clang-format'); + const clangFormat = require('clang-format'); + return gulp.src(['lib/**/*.ts'], { base: '.' }).pipe( + format.format('file', clangFormat)).pipe(gulp.dest('.')); +}); + +gulp.task('tsc', function(done) { + runSpawn(done, 'node', ['node_modules/typescript/bin/tsc']); +}); + +gulp.task('prepublish', function(done) { + runSequence('format', 'tsc', 'copy', done); +}); + +gulp.task('default',['prepublish']); +gulp.task('build',['prepublish']); + +gulp.task('test', ['build'], function(done) { + var opt_arg = []; + opt_arg.push('node_modules/jasmine/bin/jasmine.js'); + runSpawn(done, 'node', opt_arg); +}); diff --git a/lib/binaries/android_sdk.ts b/lib/binaries/android_sdk.ts new file mode 100644 index 00000000..00b899c5 --- /dev/null +++ b/lib/binaries/android_sdk.ts @@ -0,0 +1,81 @@ +import * as child_process from 'child_process'; +import {arch, type} from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + + +/** + * The android sdk binary. + */ +export class AndroidSDK extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'android'; + static versionDefault = Config.binaryVersions().android; + static isDefault = false; + static shortName = ['android']; + static DEFAULT_API_LEVELS = '24'; + static DEFAULT_ABIS = 'x86_64'; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().android); + + this.name = 'android-sdk'; + this.versionCustom = AndroidSDK.versionDefault; + this.prefixDefault = 'android-sdk_r'; + this.suffixDefault = '.zip'; + } + + id(): string { + return AndroidSDK.id; + } + + versionDefault(): string { + return AndroidSDK.versionDefault; + } + + suffix(ostype: string): string { + if (ostype === 'Darwin') { + return '-macosx' + this.suffixDefault; + } else if (ostype === 'Linux') { + return '-linux.tgz'; + } else if (ostype === 'Windows_NT') { + return '-windows' + this.suffixDefault; + } + } + + url(ostype: string): string { + return this.cdn + this.filename(ostype); + } + + zipContentName(ostype: string): string { + if (ostype === 'Darwin') { + return this.name + '-macosx'; + } else if (ostype === 'Linux') { + return this.name + '-linux'; + } else if (ostype === 'Windows_NT') { + return this.name + '-windows'; + } + } + + executableSuffix(): string { + return ''; + } + + remove(sdkPath: string): void { + try { + let avds = require(path.join(sdkPath, 'available_avds.json')); + let version = path.basename(sdkPath).slice(this.prefixDefault.length); + avds.forEach((avd: string) => { + child_process.spawnSync( + path.join(sdkPath, 'tools', 'android'), + ['delete', 'avd', '-n', avd + '-v' + version + '-wd-manager']); + }); + } catch (e) { + } + rimraf.sync(sdkPath); + } +} diff --git a/lib/binaries/appium.ts b/lib/binaries/appium.ts new file mode 100644 index 00000000..07ec8ab3 --- /dev/null +++ b/lib/binaries/appium.ts @@ -0,0 +1,45 @@ +import * as child_process from 'child_process'; +import {arch, type} from 'os'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; + +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + + +/** + * The appium binary. + */ +export class Appium extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'appium'; + static versionDefault = Config.binaryVersions().appium; + static isDefault = false; + static shortName = ['appium']; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().appium); + + this.name = 'appium'; + this.versionCustom = Appium.versionDefault; + this.prefixDefault = 'appium-'; + this.suffixDefault = ''; + } + + id(): string { + return Appium.id; + } + + versionDefault(): string { + return Appium.versionDefault; + } + + executableSuffix(): string { + return ''; + } + + remove(sdkPath: string): void { + rimraf.sync(sdkPath); + } +} diff --git a/lib/binaries/binary.ts b/lib/binaries/binary.ts new file mode 100644 index 00000000..e93e83a8 --- /dev/null +++ b/lib/binaries/binary.ts @@ -0,0 +1,113 @@ +import * as fs from 'fs'; + +/** + * operating system enum + */ +export enum OS { + Windows_NT, + Linux, + Darwin +} + +/** + * Dictionary to map the binary's id to the binary object + */ +export interface BinaryMap { [id: string]: T; } + + +/** + * The binary object base class + */ +export class Binary { + static os: OS[]; // the operating systems, the binary can run on + static id: string; // the binaries key identifier + static isDefault: boolean; // to download by default + static versionDefault: string; // a static default version variable + static shortName: string[]; // the names used for a binary download + name: string; // used for logging to console + prefixDefault: string; // start of the file name + versionCustom: string; // version of file + suffixDefault: string; // file type for downloading + cdn: string; // url protocol and host + arch: string; + + constructor(cdn?: string) { + this.cdn = cdn; + } + + /** + * @param ostype The operating system. + * @returns The executable file type. + */ + executableSuffix(ostype: string): string { + if (ostype == 'Windows_NT') { + return '.exe'; + } else { + return ''; + } + } + + /** + * @param ostype The operating system. + * @returns The file name for the executable. + */ + executableFilename(ostype: string): string { + return this.prefix() + this.version() + this.executableSuffix(ostype); + } + + prefix(): string { + return this.prefixDefault; + } + + version(): string { + return this.versionCustom; + } + + suffix(ostype?: string, arch?: string): string { + return this.suffixDefault; + } + + filename(ostype?: string, arch?: string): string { + return this.prefix() + this.version() + this.suffix(ostype, arch); + } + + /** + * @param ostype The operating system. + * @returns The file name for the file inside the downloaded zip file + */ + zipContentName(ostype: string): string { + return this.name + this.executableSuffix(ostype); + } + + shortVersion(version: string): string { + return version.slice(0, version.lastIndexOf('.')); + } + + /** + * A base class method that should be overridden. + */ + id(): string { + return 'not implemented'; + } + + /** + * A base class method that should be overridden. + */ + versionDefault(): string { + return 'not implemented'; + } + + /** + * A base class method that should be overridden. + */ + url(ostype?: string, arch?: string): string { + return 'not implemented'; + } + + /** + * Delete an instance of this binary from the file system + */ + remove(filename: string): void { + fs.unlinkSync(filename); + } +} diff --git a/lib/binaries/chrome_driver.ts b/lib/binaries/chrome_driver.ts new file mode 100644 index 00000000..645ed880 --- /dev/null +++ b/lib/binaries/chrome_driver.ts @@ -0,0 +1,68 @@ +import {arch, type} from 'os'; +import * as semver from 'semver'; + +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + + +/** + * The chrome driver binary. + */ +export class ChromeDriver extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'chrome'; + static versionDefault = Config.binaryVersions().chrome; + static isDefault = true; + static shortName = ['chrome']; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().chrome); + + this.name = 'chromedriver'; + this.versionCustom = ChromeDriver.versionDefault; + this.prefixDefault = 'chromedriver_'; + this.suffixDefault = '.zip'; + } + + id(): string { + return ChromeDriver.id; + } + + versionDefault(): string { + return ChromeDriver.versionDefault; + } + + suffix(ostype: string, arch: string): string { + if (ostype === 'Darwin') { + let version: string = this.version(); + + if (version.split('.').length === 2) { + // we need to make the version valid semver since there is only a major and a minor + version = `${version}.0`; + } + + if (semver.gt(version, '2.23.0')) { + // after chromedriver version 2.23, the name of the binary changed + // They no longer provide a 32 bit binary + return 'mac64' + this.suffixDefault; + } else { + return 'mac32' + this.suffixDefault; + } + } else if (ostype === 'Linux') { + if (arch === 'x64') { + return 'linux64' + this.suffixDefault; + } else { + return 'linux32' + this.suffixDefault; + } + } else if (ostype === 'Windows_NT') { + return 'win32' + this.suffixDefault; + } + } + + url(ostype: string, arch: string): string { + let urlBase = this.cdn + this.version() + '/'; + let filename = this.prefix() + this.suffix(ostype, arch); + return urlBase + filename; + } +} diff --git a/lib/binaries/gecko_driver.ts b/lib/binaries/gecko_driver.ts new file mode 100644 index 00000000..ccad396f --- /dev/null +++ b/lib/binaries/gecko_driver.ts @@ -0,0 +1,56 @@ +import * as path from 'path'; + +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + +/** + * The gecko driver binary. + */ +export class GeckoDriver extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'gecko'; + static versionDefault = Config.binaryVersions().gecko; + static isDefault = true; + static shortName = ['gecko']; + + static suffixes: {[key: string]: string} = { + 'Darwin': '-mac.tar.gz', + 'Linux': '-linux64.tar.gz', + 'Windows_NT': '-win64.zip' + }; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().gecko); + + this.name = 'geckodriver'; + this.versionCustom = GeckoDriver.versionDefault; + this.prefixDefault = 'geckodriver-'; + } + + id(): string { + return GeckoDriver.id; + } + + versionDefault(): string { + return GeckoDriver.versionDefault; + } + + suffix(ostype: string, arch: string): string { + if (!GeckoDriver.supports(ostype, arch)) { + throw new Error('GeckoDriver doesn\'t support ${ostype} ${arch}!'); + } + + return GeckoDriver.suffixes[ostype]; + } + + static supports(ostype: string, arch: string): boolean { + return arch == 'x64' && (ostype in GeckoDriver.suffixes); + } + + url(ostype: string, arch: string): string { + let urlBase = this.cdn + this.version() + '/'; + let filename = this.prefix() + this.version() + this.suffix(ostype, arch); + return urlBase + filename; + } +} diff --git a/lib/binaries/ie_driver.ts b/lib/binaries/ie_driver.ts new file mode 100644 index 00000000..587c8527 --- /dev/null +++ b/lib/binaries/ie_driver.ts @@ -0,0 +1,52 @@ +import * as os from 'os'; + +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + + +/** + * The internet explorer binary. + */ +export class IEDriver extends Binary { + static os = [OS.Windows_NT]; + static id = 'ie'; + static versionDefault = Config.binaryVersions().ie; + static isDefault = false; + static shortName = ['ie', 'ie32']; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().ie); + + this.name = 'IEDriverServer'; + this.versionCustom = IEDriver.versionDefault; + this.prefixDefault = 'IEDriverServer'; + this.suffixDefault = '.zip'; + this.arch = os.arch(); + } + + id(): string { + return IEDriver.id; + } + + versionDefault(): string { + return IEDriver.versionDefault; + } + + version(): string { + if (os.type() == 'Windows_NT') { + if (this.arch == 'x64') { + return '_x64_' + this.versionCustom; + } else { + return '_Win32_' + this.versionCustom; + } + } + return ''; + } + + url(): string { + let urlBase = this.cdn + this.shortVersion(this.versionCustom) + '/'; + let filename = this.prefix() + this.version() + this.suffix(); + return urlBase + filename; + } +} diff --git a/lib/binaries/index.ts b/lib/binaries/index.ts new file mode 100644 index 00000000..986505ba --- /dev/null +++ b/lib/binaries/index.ts @@ -0,0 +1,7 @@ +export * from './binary'; +export * from './chrome_driver'; +export * from './gecko_driver'; +export * from './ie_driver'; +export * from './android_sdk'; +export * from './appium'; +export * from './stand_alone'; diff --git a/lib/binaries/stand_alone.ts b/lib/binaries/stand_alone.ts new file mode 100644 index 00000000..d915d181 --- /dev/null +++ b/lib/binaries/stand_alone.ts @@ -0,0 +1,42 @@ +import {Config} from '../config'; + +import {Binary, OS} from './binary'; + + +/** + * The selenium server jar. + */ +export class StandAlone extends Binary { + static os = [OS.Windows_NT, OS.Linux, OS.Darwin]; + static id = 'standalone'; + static versionDefault = Config.binaryVersions().selenium; + static isDefault = true; + static shortName = ['standalone']; + + constructor(alternateCDN?: string) { + super(alternateCDN || Config.cdnUrls().selenium); + + this.name = 'selenium standalone'; + this.versionCustom = StandAlone.versionDefault; + this.prefixDefault = 'selenium-server-standalone-'; + this.suffixDefault = '.jar'; + } + + id(): string { + return StandAlone.id; + } + + versionDefault(): string { + return StandAlone.versionDefault; + } + + url(): string { + let urlBase = this.cdn + this.shortVersion(this.version()) + '/'; + let filename = this.prefix() + this.version() + this.suffix(); + return urlBase + filename; + } + + executableSuffix(ostype?: string): string { + return '.jar'; + } +} diff --git a/lib/cli/cli.ts b/lib/cli/cli.ts new file mode 100644 index 00000000..e9971b61 --- /dev/null +++ b/lib/cli/cli.ts @@ -0,0 +1,143 @@ +import * as chalk from 'chalk'; +import * as path from 'path'; + +import {Config} from '../config'; + +import {MinimistArgs, Options} from './options'; +import {Program, Programs} from './programs'; + + + +/** + * The Cli contains the usage and the collection of programs. + * + * Printing help for all the programs in the following order: + * usage, commands, and options. If the options are used in multiple programs, + * it will list it once. + */ +export class Cli { + programs: Programs = {}; + usageText: string; + version: string; + + /** + * Register a program to the command line interface. + * @returns The cli for method chaining. + */ + program(prog: Program): Cli { + this.programs[prog.cmd] = prog; + return this; + } + + /** + * Add a usage for the command line interface. + * @returns The cli for method chaining. + */ + usage(usageText: string): Cli { + this.usageText = usageText; + return this; + } + + /** + * Prints help for the programs registered to the cli. + */ + printHelp(): void { + console.log('Usage: ' + this.usageText); + console.log('\nCommands:'); + let cmdDescriptionPos = this.posCmdDescription(); + for (let cmd in this.programs) { + let prog = this.programs[cmd]; + prog.printCmd(cmdDescriptionPos); + } + let descriptionPos = this.posDescription(); + let defaultPos = this.posDefault(); + let extOptions: Options = {}; + console.log('\nOptions:'); + // print all options + for (let cmd in this.programs) { + let prog = this.programs[cmd]; + prog.printOptions(descriptionPos, defaultPos, extOptions); + } + } + + /** + * For commands, gets the position where the description should start so they + * are aligned. + * @returns The position where the command description should start. + */ + posCmdDescription(): number { + let position = -1; + for (let cmd in this.programs) { + position = Math.max(position, cmd.length + 6); + } + return position; + } + + /** + * For options, gets the position where the description should start so they + * are aligned. + * @returns The position where the option description should start. + */ + posDescription(): number { + let position = -1; + for (let cmd in this.programs) { + let prog = this.programs[cmd]; + position = Math.max(position, prog.posDescription()); + } + return position; + } + + /** + * For options, get the position where the default values should start so they + * are aligned. + * @returns The position where the option default values should start. + */ + posDefault(): number { + let position = -1; + for (let cmd in this.programs) { + let prog = this.programs[cmd]; + position = Math.max(position, prog.posDefault()); + } + return position; + } + + /** + * Go through all programs and add options to the collection. + * @returns The options used in the programs. + */ + getOptions(): Options { + let allOptions: Options = {}; + for (let cmd in this.programs) { + let prog = this.programs[cmd]; + allOptions = prog.getOptions_(allOptions); + } + return allOptions; + } + + /** + * Get the options used by the programs and create the minimist options + * to ensure that minimist parses the values properly. + * @returns The options for minimist. + */ + getMinimistOptions(): Object { + let allOptions = this.getOptions(); + let minimistOptions: MinimistArgs = {}; + let minimistBoolean: string[] = []; + let minimistString: string[] = []; + let minimistNumber: string[] = []; + for (let opt in allOptions) { + let option = allOptions[opt]; + if (option.type === 'boolean') { + minimistBoolean.push(option.opt); + } else if (option.type === 'string') { + minimistString.push(option.opt); + } else if (option.type === 'number') { + minimistNumber.push(option.opt); + } + } + minimistOptions['boolean'] = minimistBoolean; + minimistOptions['string'] = minimistString; + minimistOptions['number'] = minimistNumber; + return minimistOptions; + } +} diff --git a/lib/cli/index.ts b/lib/cli/index.ts index 50408e33..3b0816a3 100644 --- a/lib/cli/index.ts +++ b/lib/cli/index.ts @@ -1,174 +1,4 @@ -import * as yargs from 'yargs'; -import * as clean from '../cmds/clean'; -import * as shutdown from '../cmds/shutdown'; -import * as start from '../cmds/start'; -import * as status from '../cmds/status'; -import * as update from '../cmds/update'; - -const CHROME = 'chrome'; -const chromeOption: yargs.Options = { - describe: 'Install or update chromedriver.', - default: true, - type: 'boolean' -}; -const CHROME_LOGS = 'chrome_logs'; -const chromeLogsOption: yargs.Options = { - describe: 'File path to chrome logs.', - type: 'string' -}; -const DETACH = 'detach'; -const detachOption: yargs.Options = { - describe: 'Once the selenium server is up and running, return ' + - 'control to the parent process and continue running the server ' + - 'in the background.', - default: false, - type: 'boolean' -}; -const EDGE = 'edge'; -const edgeOption: yargs.Options = { - describe: 'Use an installed Microsoft edge driver. Usually installed: ' + - '"C:\Program Files (x86)\Microsoft Web Driver\MirosoftWebDriver.exe"', - type: 'string' -}; -const GECKO = 'gecko'; -const geckoOption: yargs.Options = { - describe: 'Install or update geckodriver.', - default: true, - type: 'boolean' -}; -const GITHUB_TOKEN = 'github_token'; -const githubTokenOption: yargs.Options = { - describe: 'Use a GitHub token to prevent rate limit issues.', - type: 'string' -}; -const IEDRIVER = 'iedriver'; -const ieOption: yargs.Options = { - describe: 'Install or update ie driver.', - default: false, - type: 'boolean' -}; -const IGNORE_SSL = 'ignore_ssl'; -const ignoreSSLOption: yargs.Options = { - describe: 'Ignore SSL certificates.', - type: 'boolean' -}; -const LOG_LEVEL = 'log_level'; -const logLevelOption: yargs.Options = { - describe: 'The log level of this CLI.', - default: 'info', - type: 'string' -}; -const OUT_DIR = 'out_dir'; -const outDirOption: yargs.Options = { - describe: 'Location of output.', - type: 'string' -}; -const PROXY = 'proxy'; -const proxyOption: yargs.Options = { - describe: 'Use a proxy server to download files.', - type: 'string' -}; -const STANDALONE = 'standalone'; -const standaloneOption: yargs.Options = { - describe: 'Install or update selenium server standalone.', - default: true, - type: 'boolean' -}; -const STANDALONE_NODE = 'standalone_node'; -const standaloneNodeOption: yargs.Options = { - describe: 'Start the selenium server standalone with role set to "node".', - type: 'boolean' -}; -const VERSIONS_CHROME = 'versions.chrome'; -const versionsChromeOption: yargs.Options = { - describe: 'The chromedriver version.', - type: 'string' -}; -const VERSIONS_GECKO = 'versions.gecko'; -const versionsGeckoOption: yargs.Options = { - describe: 'The geckodriver version.', - type: 'string' -}; -const VERSIONS_IE = 'versions.ie'; -const versionsIeOption: yargs.Options = { - describe: 'The ie driver version.', - type: 'string' -}; -const VERSIONS_STANDALONE = 'versions.standalone'; -const versionsStandaloneOption: yargs.Options = { - describe: 'The selenium server standalone version.', - type: 'string' -}; - -// tslint:disable-next-line:no-unused-expression -yargs - .command( - 'clean', 'Removes downloaded files from the out_dir.', - (yargs: yargs.Argv) => { - return yargs.option(LOG_LEVEL, logLevelOption) - .option(OUT_DIR, outDirOption); - }, - (argv: yargs.Arguments) => { - clean.handler(argv); - }) - .command( - 'shutdown', 'Shutdown a local selenium server with GET request', - (yargs: yargs.Argv) => { - return yargs.option(LOG_LEVEL, logLevelOption); - }, - (argv: yargs.Arguments) => { - shutdown.handler(argv); - }) - .command( - 'start', 'Start up the selenium server.', - (yargs: yargs.Argv) => { - return yargs.option(CHROME, chromeOption) - .option(CHROME_LOGS, chromeLogsOption) - .option(DETACH, detachOption) - .option(EDGE, edgeOption) - .option(GECKO, geckoOption) - .option(IEDRIVER, ieOption) - .option(LOG_LEVEL, logLevelOption) - .option(OUT_DIR, outDirOption) - .option(STANDALONE, standaloneOption) - .option(STANDALONE_NODE, standaloneNodeOption) - .option(VERSIONS_CHROME, versionsChromeOption) - .option(VERSIONS_GECKO, versionsGeckoOption) - .option(VERSIONS_IE, versionsIeOption) - .option(VERSIONS_STANDALONE, versionsStandaloneOption); - }, - (argv: yargs.Arguments) => { - start.handler(argv); - }) - .command( - 'status', 'List the current available binaries.', - (yargs: yargs.Argv) => { - return yargs.option(LOG_LEVEL, logLevelOption) - .option(OUT_DIR, outDirOption); - }, - (argv: yargs.Arguments) => { - status.handler(argv); - }) - .command( - 'update', 'Install or update selected binaries.', - (yargs: yargs.Argv) => { - return yargs.option(OUT_DIR, outDirOption) - .option(CHROME, chromeOption) - .option(GECKO, geckoOption) - .option(GITHUB_TOKEN, githubTokenOption) - .option(IEDRIVER, ieOption) - .option(IGNORE_SSL, ignoreSSLOption) - .option(LOG_LEVEL, logLevelOption) - .option(OUT_DIR, outDirOption) - .option(PROXY, proxyOption) - .option(STANDALONE, standaloneOption) - .option(VERSIONS_CHROME, versionsChromeOption) - .option(VERSIONS_GECKO, versionsGeckoOption) - .option(VERSIONS_IE, versionsIeOption) - .option(VERSIONS_STANDALONE, versionsStandaloneOption); - }, - (argv: yargs.Arguments) => { - update.handler(argv); - }) - .help() - .argv; \ No newline at end of file +export * from './cli'; +export * from './options'; +export * from './programs'; +export * from './logger'; diff --git a/lib/cli/logger.ts b/lib/cli/logger.ts new file mode 100644 index 00000000..771e425e --- /dev/null +++ b/lib/cli/logger.ts @@ -0,0 +1,268 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +// Will use chalk if chalk is available to add color to console logging +let chalk: any; +let printRed: Function; +let printYellow: Function; +let printGray: Function; + +try { + chalk = require('chalk'); + printRed = chalk.red; + printYellow = chalk.yellow; + printGray = chalk.gray; +} catch (e) { + printRed = printYellow = printGray = (msg: any) => { + return msg; + }; +} + +export enum LogLevel { + ERROR, + WARN, + INFO, + DEBUG +} + +export enum WriteTo { + CONSOLE, + FILE, + BOTH, + NONE +} + +let logFile = 'webdriver.log'; // the default log file name + +/** + * Logger class adds timestamp output, log levels, and identifiers to help + * when debugging. Also could write to console, file, both, or none. + */ +export class Logger { + static logLevel: LogLevel = LogLevel.INFO; + static showTimestamp: boolean = true; + static showId: boolean = true; + static writeTo: WriteTo = WriteTo.CONSOLE; + static fd: any; + static firstWrite: boolean = false; + + /** + * Set up the write location. If writing to a file, get the file descriptor. + * @param writeTo The enum for where to write the logs. + * @param opt_logFile An optional parameter to override the log file location. + */ + static setWrite(writeTo: WriteTo, opt_logFile?: string): void { + if (opt_logFile) { + logFile = opt_logFile; + } + Logger.writeTo = writeTo; + if (Logger.writeTo == WriteTo.FILE || Logger.writeTo == WriteTo.BOTH) { + Logger.fd = fs.openSync(path.resolve(logFile), 'a'); + Logger.firstWrite = false; + } + } + + /** + * Creates a logger instance with an ID for the logger. + * @constructor + */ + constructor(private id: string) {} + + /** + * Log INFO + * @param ...msgs multiple arguments to be logged. + */ + info(...msgs: any[]): void { + this.log_(LogLevel.INFO, msgs); + } + + /** + * Log DEBUG + * @param ...msgs multiple arguments to be logged. + */ + debug(...msgs: any[]): void { + this.log_(LogLevel.DEBUG, msgs); + } + + /** + * Log WARN + * @param ...msgs multiple arguments to be logged. + */ + warn(...msgs: any[]): void { + this.log_(LogLevel.WARN, msgs); + } + + /** + * Log ERROR + * @param ...msgs multiple arguments to be logged. + */ + error(...msgs: any[]): void { + this.log_(LogLevel.ERROR, msgs); + } + + /** + * For the log level set, check to see if the messages should be logged. + * @param logLevel The log level of the message. + * @param msgs The messages to be logged + */ + log_(logLevel: LogLevel, msgs: any[]): void { + switch (Logger.logLevel) { + case LogLevel.ERROR: + if (logLevel <= LogLevel.ERROR) { + this.print_(logLevel, msgs); + } + break; + case LogLevel.WARN: + if (logLevel <= LogLevel.WARN) { + this.print_(logLevel, msgs); + } + break; + case LogLevel.INFO: + if (logLevel <= LogLevel.INFO) { + this.print_(logLevel, msgs); + } + break; + case LogLevel.DEBUG: + if (logLevel <= LogLevel.DEBUG) { + this.print_(logLevel, msgs); + } + break; + default: + throw new Error('Log level undefined'); + } + } + + /** + * Format with timestamp, log level, identifier, and message and log to + * specified medium (console, file, both, none). + * @param logLevel The log level of the message. + * @param msgs The messages to be logged. + */ + print_(logLevel: LogLevel, msgs: any[]): void { + let consoleLog: string = ''; + let fileLog: string = ''; + + if (Logger.showTimestamp) { + consoleLog += Logger.timestamp_(WriteTo.CONSOLE); + fileLog += Logger.timestamp_(WriteTo.FILE); + } + consoleLog += Logger.level_(logLevel, this.id, WriteTo.CONSOLE); + fileLog += Logger.level_(logLevel, this.id, WriteTo.FILE); + if (Logger.showId) { + consoleLog += Logger.id_(logLevel, this.id, WriteTo.CONSOLE); + fileLog += Logger.id_(logLevel, this.id, WriteTo.FILE); + } + consoleLog += ' -'; + fileLog += ' - '; + + switch (Logger.writeTo) { + case WriteTo.CONSOLE: + msgs.unshift(consoleLog); + console.log.apply(console, msgs); + break; + case WriteTo.FILE: + // for the first line written to the file, add a space + if (!Logger.firstWrite) { + fs.writeSync(Logger.fd, '\n'); + Logger.firstWrite = true; + } + fileLog += ' ' + Logger.msgToFile_(msgs); + fs.writeSync(Logger.fd, fileLog + '\n'); + break; + case WriteTo.BOTH: + // for the first line written to the file, add a space + if (!Logger.firstWrite) { + fs.writeSync(Logger.fd, '\n'); + Logger.firstWrite = true; + } + fileLog += ' ' + Logger.msgToFile_(msgs); + fs.writeSync(Logger.fd, fileLog + '\n'); + msgs.unshift(consoleLog); + console.log.apply(console, msgs); + break; + case WriteTo.NONE: + break; + } + } + + /** + * Get a timestamp formatted with [hh:mm:ss] + * @param writeTo The enum for where to write the logs. + * @return The string of the formatted timestamp + */ + static timestamp_(writeTo: WriteTo): string { + let d = new Date(); + let ts = '['; + let hours = d.getHours() < 10 ? '0' + d.getHours() : d.getHours(); + let minutes = d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes(); + let seconds = d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds(); + if (writeTo == WriteTo.CONSOLE) { + ts += printGray(hours + ':' + minutes + ':' + seconds) + ']'; + } else { + ts += hours + ':' + minutes + ':' + seconds + ']'; + } + ts += ' '; + return ts; + } + + /** + * Get the identifier of the logger as '/' + * @param logLevel The log level of the message. + * @param writeTo The enum for where to write the logs. + * @return The string of the formatted id + */ + static id_(logLevel: LogLevel, id: string, writeTo: WriteTo): string { + let level = LogLevel[logLevel].toString(); + if (writeTo === WriteTo.FILE) { + return '/' + id; + } else if (logLevel === LogLevel.ERROR) { + return printRed('/' + id); + } else if (logLevel === LogLevel.WARN) { + return printYellow('/' + id); + } else { + return '/' + id; + } + } + + /** + * Get the log level formatted with the first letter. For info, it is I. + * @param logLevel The log level of the message. + * @param writeTo The enum for where to write the logs. + * @return The string of the formatted log level + */ + static level_(logLevel: LogLevel, id: string, writeTo: WriteTo): string { + let level = LogLevel[logLevel].toString(); + if (writeTo === WriteTo.FILE) { + return level[0]; + } else if (logLevel === LogLevel.ERROR) { + return printRed(level[0]); + } else if (logLevel === LogLevel.WARN) { + return printYellow(level[0]); + } else { + return level[0]; + } + } + + /** + * Convert the list of messages to a single string message. + * @param msgs The list of messages. + * @return The string of the formatted messages + */ + static msgToFile_(msgs: any[]): string { + let log = ''; + for (let pos = 0; pos < msgs.length; pos++) { + let msg = msgs[pos]; + let ret: any; + if (typeof msg === 'object') { + ret = JSON.stringify(msg); + } else { + ret = msg; + } + if (pos !== msgs.length - 1) { + ret += ' '; + } + log += ret; + } + return log; + } +} diff --git a/lib/cli/options.ts b/lib/cli/options.ts new file mode 100644 index 00000000..85915042 --- /dev/null +++ b/lib/cli/options.ts @@ -0,0 +1,52 @@ +export interface MinimistArgs { [opt: string]: string[] } + +export interface Args { [opt: string]: number|string|boolean } + +export interface Options { [opt: string]: Option; } + +export class Option { + opt: string; + description: string; + type: string; + defaultValue: number|string|boolean; + value: number|string|boolean; + + constructor( + opt: string, description: string, type: string, defaultValue?: number|string|boolean) { + this.opt = opt; + this.description = description; + this.type = type; + if (defaultValue) { + this.defaultValue = defaultValue; + } + } + + getValue_(): number|string|boolean { + if (this.value) { + return this.value; + } else { + return this.defaultValue; + } + } + + getNumber(): number { + let value = this.getValue_(); + if (value && typeof value === 'number') { + return +value; + } + return null; + } + + getString(): string { + let value = this.getValue_(); + if (value && typeof value === 'string') { + return '' + value; + } + return null; + } + + getBoolean(): boolean { + let value = this.getValue_(); + return value ? true : false; + } +} diff --git a/lib/cli/programs.ts b/lib/cli/programs.ts new file mode 100644 index 00000000..76a5d8cd --- /dev/null +++ b/lib/cli/programs.ts @@ -0,0 +1,243 @@ +import * as minimist from 'minimist'; + +import {Args, MinimistArgs, Option, Options} from './options'; + + +/** + * Dictionary that maps the command and the program. + */ +export interface Programs { [cmd: string]: Program; } + +/** + * A program has a command, a description, options, and a run method + */ +export class Program { + static MIN_SPACING: number = 4; + cmd: string; + cmdDescription: string + options: Options = {}; + runMethod: Function; + helpDescription: string; + version: string; + + /** + * Register a command and the description. + * @param cmd The command. + * @param cmdDescription The description of the command. + * @returns The program for method chaining. + */ + command(cmd: string, cmdDescription: string): Program { + this.cmd = cmd; + this.cmdDescription = cmdDescription; + return this; + } + + /** + * Register a new option. + * @param opt The option. + * @param description The description of the option. + * @param type The type of value expected: boolean, number, or string + * @param defaultValue The option's default value. + * @returns The program for method chaining. + */ + option(opt: string, description: string, type: string, opt_defaultValue?: number|string|boolean): + Program { + this.options[opt] = new Option(opt, description, type, opt_defaultValue); + return this; + } + + /** + * Adds an option to the program. + * @param option The option. + * @returns The program for method chaining. + */ + addOption(option: Option): Program { + this.options[option.opt] = option; + return this; + } + + /** + * Registers a method that will be used to run the program. + * @param runMethod The method that will be used to run the program. + * @returns The program for method chaining. + */ + action(runMethod: Function): Program { + this.runMethod = runMethod; + return this; + } + + /** + * Adds the value to the options and passes the updated options to the run + * method. + * @param args The arguments that will be parsed to run the method. + */ + run(json: JSON): void { + for (let opt in this.options) { + this.options[opt].value = this.getValue_(opt, json); + } + this.runMethod(this.options); + } + + private getValue_(key: string, json: JSON): string { + let keyList: string[] = key.split('.'); + let tempJson: any = json; + while (keyList.length > 0) { + let keyItem = keyList[0]; + if (tempJson[keyItem]) { + tempJson = tempJson[keyItem]; + keyList = keyList.slice(1); + } else { + return undefined; + } + } + return tempJson.toString(); + } + + /** + * Prints the command with the description. The description will have spaces + * between the cmd so that the starting position is "posDescription". If the + * gap between the cmd and the description is less than MIN_SPACING or + * posDescription is undefined, the spacing will be MIN_SPACING. + * + * @param opt_postDescription Starting position of the description. + */ + printCmd(opt_posDescription?: number): void { + let log = ' ' + this.cmd; + let spacing = Program.MIN_SPACING; + + if (opt_posDescription) { + let diff = opt_posDescription - log.length; + if (diff < Program.MIN_SPACING) { + spacing = Program.MIN_SPACING; + } else { + spacing = diff; + } + } + log += Array(spacing).join(' ') + this.cmdDescription; + console.log(log); + } + + /** + * Prints the options with the option descriptions and default values. + * The posDescription and posDefault is the starting position for the option + * description. If extOptions are provided, check to see if we have already + * printed those options. Also, once we print the option, add them to the extOptions. + * + * @param posDescription Position to start logging the description. + * @param posDefault Position to start logging the default value. + * @param opt_extOptions A collection of options that will be updated. + */ + printOptions(posDescription: number, posDefault: number, opt_extOptions?: Options): void { + for (let opt in this.options) { + // we have already logged it + if (opt_extOptions && opt_extOptions[opt]) { + continue; + } + + let option = this.options[opt]; + let log = ' --' + option.opt; + let spacing = Program.MIN_SPACING; + + // description + let diff = posDescription - log.length; + if (diff < Program.MIN_SPACING) { + spacing = Program.MIN_SPACING; + } else { + spacing = diff; + } + log += Array(spacing).join(' ') + option.description; + + // default value + if (option.defaultValue) { + spacing = Program.MIN_SPACING; + let diff = posDefault - log.length - 1; + if (diff <= Program.MIN_SPACING) { + spacing = Program.MIN_SPACING; + } else { + spacing = diff; + } + log += Array(spacing).join(' '); + log += '[default: ' + option.defaultValue + ']'; + } + + console.log(log); + if (opt_extOptions) { + opt_extOptions[option.opt] = option; + } + } + } + + /** + * Assuming that the this program can run by itself, to print out the program's + * help. Also assuming that the commands are called cmd-run and cmd-help. + */ + printHelp(): void { + console.log( + '\n' + + 'Usage: ' + this.cmd + ' [options]\n' + + ' ' + this.cmd + ' help\n' + + 'Description: ' + this.cmdDescription + '\n'); + console.log('Options:'); + this.printOptions(this.posDescription(), this.posDefault()); + } + + posDescription(): number { + return this.lengthOf_('opt') + 2 * Program.MIN_SPACING; + } + + posDefault(): number { + return this.posDescription() + this.lengthOf_('description') + Program.MIN_SPACING; + } + + lengthOf_(param: string): number { + let maxLength = -1; + for (let opt in this.options) { + let option = this.options[opt]; + if (param === 'description') { + maxLength = Math.max(maxLength, option.description.length); + } else if (param === 'opt') { + maxLength = Math.max(maxLength, option.opt.length); + } + } + return maxLength; + } + + /** + * Create a collection of options used by this program. + * @returns The options used in the programs. + */ + getOptions_(allOptions: Options): Options { + for (let opt in this.options) { + allOptions[opt] = this.options[opt]; + } + return allOptions; + } + + /** + * Get the options used by the program and create the minimist options + * to ensure that minimist parses the values properly. + * @returns The options for minimist. + */ + getMinimistOptions() { + let allOptions: Options = {}; + allOptions = this.getOptions_(allOptions); + let minimistOptions: MinimistArgs = {}; + let minimistBoolean: string[] = []; + let minimistString: string[] = []; + let minimistNumber: string[] = []; + for (let opt in allOptions) { + let option = allOptions[opt]; + if (option.type === 'boolean') { + minimistBoolean.push(option.opt); + } else if (option.type === 'string') { + minimistString.push(option.opt); + } else if (option.type === 'number') { + minimistNumber.push(option.opt); + } + } + minimistOptions['boolean'] = minimistBoolean; + minimistOptions['string'] = minimistString; + minimistOptions['number'] = minimistNumber; + return minimistOptions; + } +} diff --git a/lib/cmds/clean.spec-e2e.ts b/lib/cmds/clean.spec-e2e.ts deleted file mode 100644 index 69c9e533..00000000 --- a/lib/cmds/clean.spec-e2e.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {clean} from './clean'; -import {convertArgs2AllOptions} from './utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -describe('using the cli', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - describe('a user runs clean', () => { - it('should not log or throw errors if no folder exists', () => { - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const statusLog = clean(options); - expect(statusLog).toBe(''); - }); - - it('should not log or throw errors if empty folder exists', () => { - fs.mkdirSync(tmpDir); - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const statusLog = clean(options); - expect(statusLog).toBe(''); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/clean.spec-int.ts b/lib/cmds/clean.spec-int.ts deleted file mode 100644 index f48555c1..00000000 --- a/lib/cmds/clean.spec-int.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import * as yargs from 'yargs'; - -import {clean} from './clean'; -import {convertArgs2AllOptions} from './utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tmpDir = path.resolve(os.tmpdir(), 'test'); -const argv: yargs.Arguments = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' -}; -const options = convertArgs2AllOptions(argv); - -describe('clean cmd', () => { - describe('with files', () => { - beforeAll(() => { - // create the directory - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - // create files that should be deleted. - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'chromedriver_2.41'), 'w')); - fs.closeSync( - fs.openSync(path.resolve(tmpDir, 'chromedriver_foo.zip'), 'w')); - fs.closeSync( - fs.openSync(path.resolve(tmpDir, 'chromedriver.config.json'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'chromedriver.xml'), 'w')); - }); - - afterAll(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should remove the files', () => { - expect(fs.readdirSync(tmpDir).length).toBe(4); - const cleanList = clean(options).split('\n'); - expect(cleanList.length).toBe(4); - expect(fs.readdirSync(tmpDir).length).toBe(0); - }); - }); - - describe('with no files', () => { - it('should return nothing', () => { - spyOn(fs, 'unlinkSync'); - expect(clean(options)).toBe(''); - expect(fs.unlinkSync).not.toHaveBeenCalled(); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/clean.ts b/lib/cmds/clean.ts index c92165f5..39d5961d 100644 --- a/lib/cmds/clean.ts +++ b/lib/cmds/clean.ts @@ -1,49 +1,40 @@ -import * as loglevel from 'loglevel'; -import * as yargs from 'yargs'; -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; -import {addOptionsBinary, convertArgs2AllOptions} from './utils'; +import * as minimist from 'minimist'; +import * as path from 'path'; -const log = loglevel.getLogger('webdriver-manager'); +import {Options, Program} from '../cli'; +import {Config} from '../config'; +import {FileManager} from '../files'; -/** - * Handles removing files that were downloaded and logs the files. - * @param argv The argv from yargs. - */ -export function handler(argv: yargs.Arguments) { - log.setLevel(argv.log_level); - const options = convertArgs2AllOptions(argv); - log.info(clean(options)); -} +import * as Opt from './'; +import {Opts} from './opts'; -/** - * Goes through all the providers and removes the downloaded files. - * @param options The constructed set of all options. - * @returns A list of deleted files. - */ -export function clean(options: Options): string { - const optionsBinary = addOptionsBinary(options); - return cleanBinary(optionsBinary); +let prog = new Program() + .command('clean', 'removes all downloaded driver files from the out_dir') + .action(clean) + .addOption(Opts[Opt.OUT_DIR]); + +export var program = prog; + +// stand alone runner +let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); +if (argv._[0] === 'clean-run') { + prog.run(JSON.parse(JSON.stringify(argv))); +} else if (argv._[0] === 'clean-help') { + prog.printHelp(); } /** - * Goes through all the providers and removes the downloaded files. - * @param optionsBinary The constructed set of all options with binaries. - * @returns A list of deleted files. + * Parses the options and cleans the output directory of binaries. + * @param: options */ -export function cleanBinary(optionsBinary: OptionsBinary): string { - const filesCleaned: string[] = []; - for (const browserDriver of optionsBinary.browserDrivers) { - const cleanedFiles = browserDriver.binary.cleanFiles(); - if (cleanedFiles) { - filesCleaned.push(cleanedFiles); +function clean(options: Options): void { + let outputDir = Config.getSeleniumDir(); + if (options[Opt.OUT_DIR].getString()) { + if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { + outputDir = options[Opt.OUT_DIR].getString(); + } else { + outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); } } - if (optionsBinary.server && optionsBinary.server.binary) { - const cleanedFiles = optionsBinary.server.binary.cleanFiles(); - if (cleanedFiles) { - filesCleaned.push(cleanedFiles); - } - } - return (filesCleaned.sort()).join('\n'); -} \ No newline at end of file + FileManager.removeExistingFiles(outputDir); +} diff --git a/lib/cmds/cmds.spec-e2e.ts b/lib/cmds/cmds.spec-e2e.ts deleted file mode 100644 index b494239f..00000000 --- a/lib/cmds/cmds.spec-e2e.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {SeleniumServer} from '../provider/selenium_server'; - -import {clean} from './clean'; -import {startBinary} from './start'; -import {status} from './status'; -import {update} from './update'; -import {addOptionsBinary, convertArgs2AllOptions, convertArgs2Options} from './utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -describe('using the cli', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - describe('a user runs update', () => { - xit('should download the files', async () => { - const argv = { - _: ['foobar'], - chrome: true, - standalone: true, - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2Options(argv); - await update(options); - const existFiles = fs.readdirSync(tmpDir); - expect(existFiles.length).toBe(7); - }); - }); - - describe('a user runs status', () => { - xit('should get the list of versions', () => { - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const statusLog = status(options); - log.debug(statusLog); - const lines = statusLog.split('\n'); - expect(lines.length).toBe(2); - }); - }); - - describe('a user runs start', () => { - xit('should start the selenium server standalone in role=node', async () => { - const argv = { - _: ['foobar'], - chrome: true, - standalone: true, - standalone_node: true, - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2Options(argv); - const optionsBinary = addOptionsBinary(options); - // Do not await this promise to start the server since the promise is - // never resolved by waiting, it is either killed by pid or get request. - const startProcess = startBinary(optionsBinary); - - // Arbitrarily wait for the server to start. - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - const seleniumServer = (optionsBinary.server.binary as SeleniumServer); - expect(seleniumServer.seleniumProcess).toBeTruthy(); - expect(seleniumServer.seleniumProcess.pid).toBeTruthy(); - - // Stop the server using the get request. - expect(seleniumServer.runAsNode).toBeTruthy(); - await seleniumServer.stopServer(); - - // Check to see that the exit code is 0. - expect(await startProcess).toBe(0); - await new Promise((resolve, _) => { - setTimeout(resolve, 5000); - }); - }); - - xit('should start the selenium server standalone', async () => { - const argv = { - _: ['foobar'], - chrome: true, - standalone: true, - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2Options(argv); - const optionsBinary = addOptionsBinary(options); - // Do not await this promise to start the server since the promise is - // never resolved by waiting, it is either killed by pid or get request. - const startProcess = startBinary(optionsBinary); - - // Arbitrarily wait for the server to start. - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - const seleniumServer = (optionsBinary.server.binary as SeleniumServer); - expect(seleniumServer.seleniumProcess).toBeTruthy(); - expect(seleniumServer.seleniumProcess.pid).toBeTruthy(); - - // Stop the server using the get request. - expect(seleniumServer.runAsNode).toBeFalsy(); - await seleniumServer.stopServer(); - - // Check to see that the exit code is greater than 1. - // Observed to be 1 and sometimes 143. - expect(await startProcess).toBeGreaterThanOrEqual(1); - await new Promise((resolve, _) => { - setTimeout(resolve, 5000); - }); - }); - }); - - describe('a user runs clean', () => { - xit('should remove the files', () => { - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const cleanLogs = clean(options); - log.debug(cleanLogs); - const lines = cleanLogs.split('\n'); - expect(lines.length).toBe(7); - const existFiles = fs.readdirSync(tmpDir); - expect(existFiles.length).toBe(0); - }); - }); -}); diff --git a/lib/cmds/index.ts b/lib/cmds/index.ts new file mode 100644 index 00000000..d6ee736f --- /dev/null +++ b/lib/cmds/index.ts @@ -0,0 +1 @@ +export * from './opts'; diff --git a/lib/cmds/initialize.ts b/lib/cmds/initialize.ts new file mode 100644 index 00000000..30371819 --- /dev/null +++ b/lib/cmds/initialize.ts @@ -0,0 +1,263 @@ +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as glob from 'glob'; +import * as ini from 'ini'; +import * as os from 'os'; +import * as path from 'path'; +import * as q from 'q'; + +import {Logger} from '../cli'; + +const noop = () => {}; + +// Make a function which configures a child process to automatically respond +// to a certain question +function respondFactory(question: string, answer: string): Function { + return (child: child_process.ChildProcess) => { + (child.stdin).setDefaultEncoding('utf-8'); + child.stdout.on('data', (data: Buffer | String) => { + if (data != null) { + if (data.toString().indexOf(question) != -1) { + child.stdin.write(answer + '\n'); + } + } + }); + }; +} + +// Run a command on the android SDK +function runAndroidSDKCommand( + sdkPath: string, cmd: string, args: string[], spawnOptions: Object, + config_fun?: Function): q.Promise { + let child = + child_process.spawn(path.join(sdkPath, 'tools', 'android'), [cmd].concat(args), spawnOptions); + + if (config_fun) { + config_fun(child); + }; + + let deferred = q.defer() + child.on('exit', (code: number) => { + if (deferred != null) { + if (code) { + deferred.reject(code); + } else { + deferred.resolve(); + } + deferred = null; + } + }); + child.on('error', (err: Error) => { + if (deferred != null) { + deferred.reject(err); + deferred = null; + } + }); + return deferred.promise; +} + +// Download updates via the android SDK +function downloadAndroidUpdates( + sdkPath: string, targets: string[], search_all: boolean, auto_accept: boolean): q.Promise { + return runAndroidSDKCommand( + sdkPath, 'update', + ['sdk', '-u'].concat(search_all ? ['-a'] : []).concat(['-t', targets.join(',')]), + {stdio: auto_accept ? 'pipe' : 'inherit'}, + auto_accept ? respondFactory('Do you accept the license', 'y') : noop); +} + +// Setup hardware acceleration for x86-64 emulation +function setupHardwareAcceleration(sdkPath: string) { + // TODO(sjelin): check that the BIOS option is set properly on linux + if (os.type() == 'Darwin') { + console.log('Enabling hardware acceleration (requires root access)'); + child_process.spawnSync( + 'sudo', [path.join( + sdkPath, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', + 'silent_install.sh')], + {stdio: 'inherit'}); + } else if (os.type() == 'Windows_NT') { + console.log('Enabling hardware acceleration (requires admin access)'); + child_process.spawnSync( + 'runas', + [ + '/noprofile', '/user:Administrator', + path.join( + sdkPath, 'extras', 'intel', 'Hardware_Accelerated_Execution_Manager', + 'silent_install.bat') + ], + {stdio: 'inherit'}); + } +} + +// Get a list of all the SDK download targets for a given set of APIs and ABIs +function getAndroidSDKTargets(apiLevels: string[], abis: string[]): string[] { + return apiLevels + .map((level) => { + return 'android-' + level; + }) + .concat(abis.reduce((targets, abi) => { + let abiParts: string[] = abi.split('/'); + let deviceType: string = 'default'; + let architecture: string; + if (abiParts.length == 1) { + architecture = abiParts[0]; + } else { + deviceType = abiParts[0]; + architecture = abiParts[1]; + } + if (deviceType.toUpperCase() == 'DEFAULT') { + deviceType = 'android'; + } + return targets.concat(apiLevels.map((level) => { + return 'sys-img-' + architecture + '-' + deviceType + '-' + level; + })); + }, [])); +} + +// All the information about an android virtual device +class AVDDescriptor { + api: string; + deviceType: string; + architecture: string; + abi: string; + name: string; + + constructor(api: string, deviceType: string, architecture: string) { + this.api = api; + this.deviceType = deviceType; + this.architecture = architecture; + this.abi = (deviceType.toUpperCase() == 'DEFAULT' ? '' : deviceType + '/') + architecture; + this.name = [api, deviceType, architecture].join('-'); + } + + avdName(version: string): string { + return this.name + '-v' + version + '-wd-manager'; + } +} + +// Gets the descriptors for all AVDs which are possible to make given the +// SDKs which were downloaded +function getAVDDescriptors(sdkPath: string): q.Promise { + let deferred = q.defer(); + glob(path.join(sdkPath, 'system-images', '*', '*', '*'), (err: Error, files: string[]) => { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(files.map((file: string) => { + let info = file.split(path.sep).slice(-3); + return new AVDDescriptor(info[0], info[1], info[2]); + })); + } + }); + return deferred.promise; +} + +function sequentialForEach(array: T[], func: (x: T) => q.Promise): q.Promise { + let ret = q(null); + + array.forEach((x: T) => { + ret = ret.then(() => { + return func(x); + }); + }); + + return ret; +} + +// Configures the hardware.ini file for a system image of a new AVD +function configureAVDHardware(sdkPath: string, desc: AVDDescriptor): q.Promise { + let file = path.join( + sdkPath, 'system-images', desc.api, desc.deviceType, desc.architecture, 'hardware.ini'); + return q.nfcall(fs.stat, file) + .then( + (stats: fs.Stats) => { + return q.nfcall(fs.readFile, file); + }, + (err: Error) => { + return q(''); + }) + .then((contents: string | Buffer) => { + let config: any = ini.parse(contents.toString()); + config['hw.keyboard'] = 'yes'; + config['hw.battery'] = 'yes'; + config['hw.ramSize'] = 1024; + return q.nfcall(fs.writeFile, file, ini.stringify(config)); + }); +} + +// Make an android virtual device +function makeAVD(sdkPath: string, desc: AVDDescriptor, version: string): q.Promise { + return runAndroidSDKCommand(sdkPath, 'delete', ['avd', '--name', desc.avdName(version)], {}) + .then(noop, noop) + .then(() => { + return runAndroidSDKCommand( + sdkPath, 'create', + ['avd', '--name', desc.avdName(version), '--target', desc.api, '--abi', desc.abi], + {stdio: 'pipe'}, + respondFactory('Do you wish to create a custom hardware profile', 'no')); + }); +} + +// Initialize the android SDK +export function android( + sdkPath: string, apiLevels: string[], abis: string[], acceptLicenses: boolean, version: string, + logger: Logger): void { + let avdDescriptors: AVDDescriptor[]; + let tools = ['platform-tool', 'tool']; + if ((os.type() == 'Darwin') || (os.type() == 'Windows_NT')) { + tools.push('extra-intel-Hardware_Accelerated_Execution_Manager'); + } + + logger.info('android-sdk: Downloading additional SDK updates'); + downloadAndroidUpdates(sdkPath, tools, false, acceptLicenses) + .then(() => { + return setupHardwareAcceleration(sdkPath); + }) + .then(() => { + logger.info( + 'android-sdk: Downloading more additional SDK updates ' + + '(this may take a while)'); + return downloadAndroidUpdates( + sdkPath, ['build-tools-24.0.0'].concat(getAndroidSDKTargets(apiLevels, abis)), true, + acceptLicenses); + }) + .then(() => { + return getAVDDescriptors(sdkPath); + }) + .then((descriptors: AVDDescriptor[]) => { + avdDescriptors = descriptors; + logger.info('android-sdk: Configuring virtual device hardware'); + return sequentialForEach(avdDescriptors, (descriptor: AVDDescriptor) => { + return configureAVDHardware(sdkPath, descriptor); + }); + }) + .then(() => { + return sequentialForEach(avdDescriptors, (descriptor: AVDDescriptor) => { + logger.info('android-sdk: Setting up virtual device "' + descriptor.name + '"'); + return makeAVD(sdkPath, descriptor, version); + }); + }) + .then(() => { + return q.nfcall( + fs.writeFile, path.join(sdkPath, 'available_avds.json'), + JSON.stringify(avdDescriptors.map((descriptor: AVDDescriptor) => { + return descriptor.name; + }))); + }) + .then(() => { + logger.info('android-sdk: Initialization complete'); + }) + .done(); +}; + +export function iOS(logger: Logger) { + if (os.type() != 'Darwin') { + throw new Error('Must be on a Mac to simulate iOS devices.'); + } + try { + fs.statSync('/Applications/Xcode.app'); + } catch (e) { + logger.warn('You must install the xcode commandline tools!'); + } +} diff --git a/lib/cmds/options.ts b/lib/cmds/options.ts deleted file mode 100644 index 0b8848ee..00000000 --- a/lib/cmds/options.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * An options object to update and start the server. - */ -export interface Options { - // A list of browser drivers. - browserDrivers?: BrowserDriver[]; - // The server. - server?: Server; - // The proxy url (must include protocol with url) - proxy?: string; - // To ignore SSL certs when making requests. - ignoreSSL?: boolean; - // The location where files should be saved. - outDir?: string; - // Use a github token for github requests. - githubToken?: string; -} - -/** - * Contains information about a browser driver. - */ -export interface BrowserDriver { - // The name of the browser driver. - name?: 'chromedriver'|'geckodriver'|'iedriver'; - // The version which does not have to follow semver. - version?: string; -} - -/** - * Contains information about the selenium server standalone. This includes - * options to start the server along with options to send to the server. - */ -export interface Server { - // The name of the server option. - name?: 'selenium'; - // The version which does not have to follow semver. - version?: string; - // Run as role = node option. - runAsNode?: boolean; - // The relative or full path to the chrome logs file. - chromeLogs?: string; - // The full path to the edge driver server. - edge?: string; - // Detach the server and return the process to the parent. - runAsDetach?: boolean; - // Port number to start the server. - port?: number; -} \ No newline at end of file diff --git a/lib/cmds/options_binary.ts b/lib/cmds/options_binary.ts deleted file mode 100644 index ab549b85..00000000 --- a/lib/cmds/options_binary.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {ProviderInterface} from '../provider/provider'; -import {BrowserDriver, Options, Server} from './options'; - -export interface OptionsBinary extends Options { - browserDrivers?: BrowserDriverBinary[]; - server?: ServerBinary; -} - -export interface BrowserDriverBinary extends BrowserDriver { - // The binary provider object. - binary?: ProviderInterface; -} - -export interface ServerBinary extends Server { - // The binary provider object. - binary?: ProviderInterface; -} \ No newline at end of file diff --git a/lib/cmds/opts.ts b/lib/cmds/opts.ts new file mode 100644 index 00000000..bc2ef130 --- /dev/null +++ b/lib/cmds/opts.ts @@ -0,0 +1,91 @@ +import {AndroidSDK, Appium, ChromeDriver, GeckoDriver, IEDriver, StandAlone} from '../binaries'; +import {Cli, Option, Options} from '../cli'; +import {Config} from '../config'; + +export const OUT_DIR = 'out_dir'; +export const SELENIUM_PORT = 'seleniumPort'; +export const APPIUM_PORT = 'appium-port'; +export const AVD_PORT = 'avd-port'; +export const IGNORE_SSL = 'ignore_ssl'; +export const PROXY = 'proxy'; +export const ALTERNATE_CDN = 'alternate_cdn'; +export const STANDALONE = 'standalone'; +export const CHROME = 'chrome'; +export const IE = 'ie'; +export const IE32 = 'ie32'; +export const EDGE = 'edge'; +export const GECKO = 'gecko'; +export const ANDROID = 'android'; +export const IOS = 'ios'; +export const VERSIONS_CHROME = 'versions.chrome'; +export const VERSIONS_GECKO = 'versions.gecko'; +export const VERSIONS_STANDALONE = 'versions.standalone'; +export const VERSIONS_IE = 'versions.ie'; +export const VERSIONS_ANDROID = 'versions.android'; +export const VERSIONS_APPIUM = 'versions.appium'; +export const CHROME_LOGS = 'chrome_logs'; +export const LOGGING = 'logging'; +export const ANDROID_API_LEVELS = 'android-api-levels'; +export const ANDROID_ABIS = 'android-abis'; +export const ANDROID_ACCEPT_LICENSES = 'android-accept-licenses'; +export const AVDS = 'avds'; +export const AVD_USE_SNAPSHOTS = 'avd-use-snapshots'; + +/** + * The options used by the commands. + */ +var opts: Options = {}; +opts[OUT_DIR] = new Option(OUT_DIR, 'Location to output/expect', 'string', Config.getSeleniumDir()); +opts[SELENIUM_PORT] = + new Option(SELENIUM_PORT, 'Optional port for the selenium standalone server', 'string'); +opts[APPIUM_PORT] = new Option(APPIUM_PORT, 'Optional port for the appium server', 'string'); +opts[AVD_PORT] = new Option( + AVD_PORT, 'Optional port for android virtual devices. See mobile.md for details', 'string'); +opts[IGNORE_SSL] = new Option(IGNORE_SSL, 'Ignore SSL certificates', 'boolean', false); +opts[PROXY] = new Option(PROXY, 'Proxy to use for the install or update command', 'string'); +opts[ALTERNATE_CDN] = new Option(ALTERNATE_CDN, 'Alternate CDN to binaries', 'string'); +opts[STANDALONE] = new Option( + STANDALONE, 'Install or update selenium standalone', 'boolean', StandAlone.isDefault); +opts[CHROME] = + new Option(CHROME, 'Install or update chromedriver', 'boolean', ChromeDriver.isDefault); +opts[GECKO] = new Option(GECKO, 'Install or update geckodriver', 'boolean', GeckoDriver.isDefault); +opts[IE] = new Option(IE, 'Install or update ie driver', 'boolean', IEDriver.isDefault); +opts[IE32] = new Option(IE32, 'Install or update 32-bit ie driver', 'boolean', IEDriver.isDefault); +opts[EDGE] = new Option( + EDGE, 'Use installed Microsoft Edge driver', 'string', + 'C:\\Program Files (x86)\\Microsoft Web Driver\\MicrosoftWebDriver.exe'); +opts[ANDROID] = new Option(ANDROID, 'Update/use the android sdk', 'boolean', AndroidSDK.isDefault); +opts[IOS] = new Option(IOS, 'Update the iOS sdk', 'boolean', false); +opts[VERSIONS_CHROME] = new Option( + VERSIONS_CHROME, 'Optional chrome driver version', 'string', ChromeDriver.versionDefault); +opts[VERSIONS_GECKO] = new Option( + VERSIONS_GECKO, 'Optional gecko driver version', 'string', GeckoDriver.versionDefault); +opts[VERSIONS_ANDROID] = new Option( + VERSIONS_ANDROID, 'Optional android sdk version', 'string', AndroidSDK.versionDefault); +opts[VERSIONS_STANDALONE] = new Option( + VERSIONS_STANDALONE, 'Optional seleniuim standalone server version', 'string', + StandAlone.versionDefault); +opts[VERSIONS_APPIUM] = + new Option(VERSIONS_APPIUM, 'Optional appium version', 'string', Appium.versionDefault); +opts[VERSIONS_IE] = new Option( + VERSIONS_IE, 'Optional internet explorer driver version', 'string', IEDriver.versionDefault); +opts[CHROME_LOGS] = new Option(CHROME_LOGS, 'File path to chrome logs', 'string', undefined); +opts[LOGGING] = new Option(LOGGING, 'File path to logging properties file', 'string', undefined); +opts[ANDROID_API_LEVELS] = new Option( + ANDROID_API_LEVELS, 'Which versions of the android API you want to emulate', 'string', + AndroidSDK.DEFAULT_API_LEVELS); +opts[ANDROID_ABIS] = new Option( + ANDROID_ABIS, 'Which ABIs you want to use in android emulation', 'string', + AndroidSDK.DEFAULT_ABIS); +opts[ANDROID_ACCEPT_LICENSES] = + new Option(ANDROID_ACCEPT_LICENSES, 'Automatically accept android licenses', 'boolean', false); +opts[AVDS] = new Option( + AVDS, + 'Android virtual devices to emulate. Use "all" for emulating all possible devices, and "none" for no devices', + 'string', 'all'); +opts[AVD_USE_SNAPSHOTS] = new Option( + AVD_USE_SNAPSHOTS, + 'Rather than booting a new AVD every time, save/load snapshots of the last time it was used', + 'boolean', true); + +export var Opts = opts; diff --git a/lib/cmds/shutdown.ts b/lib/cmds/shutdown.ts deleted file mode 100644 index 3963b01b..00000000 --- a/lib/cmds/shutdown.ts +++ /dev/null @@ -1,44 +0,0 @@ -import * as loglevel from 'loglevel'; -import * as yargs from 'yargs'; - -import {SeleniumServer} from '../provider/selenium_server'; -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; -import {addOptionsBinary} from './utils'; - -const log = loglevel.getLogger('webdriver-manager'); - -/** - * Handles making the get request to stop the selenium server standalone if the - * server has been started with role = node. - * @param argv The argv from yargs. - */ -export async function handler(argv: yargs.Arguments) { - log.setLevel(argv.log_level); - const seleniumServer = new SeleniumServer(); - seleniumServer.runAsNode = true; - const options: Options = {server: {name: 'selenium', runAsNode: true}}; - await shutdown(options); -} - -/** - * Shutdown the selenium server with a get request. If the server is not - * started with role = node, nothing will happen. - * @param options The constructed option for the server. - * @returns A promise for the get request. - */ -export function shutdown(options: Options): Promise { - const optionsBinary = addOptionsBinary(options); - return shutdownBinary(optionsBinary); -} - -/** - * Shutodown the selenium server with a get request. If the server is not - * started with role = node, nothing will happen. - * @param optionsBinary The constructed option for the server with binary. - * @returns A promise for the get request. - */ -export function shutdownBinary(optionsBinary: OptionsBinary): Promise { - const seleniumServer = optionsBinary.server.binary as SeleniumServer; - return seleniumServer.stopServer(); -} \ No newline at end of file diff --git a/lib/cmds/start.ts b/lib/cmds/start.ts index 0f36f97b..d30fae5d 100644 --- a/lib/cmds/start.ts +++ b/lib/cmds/start.ts @@ -1,77 +1,271 @@ -import * as loglevel from 'loglevel'; +import * as childProcess from 'child_process'; +import * as fs from 'fs'; +import * as http from 'http'; +import * as minimist from 'minimist'; +import * as os from 'os'; import * as path from 'path'; -import * as yargs from 'yargs'; -import {SeleniumServer} from '../provider/selenium_server'; -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; -import {addOptionsBinary, convertArgs2Options} from './utils'; +import {AndroidSDK, Appium, Binary, BinaryMap, ChromeDriver, IEDriver, StandAlone} from '../binaries'; +import {GeckoDriver} from '../binaries/gecko_driver'; +import {Logger, Options, Program} from '../cli'; +import {Config} from '../config'; +import {FileManager} from '../files'; -const log = loglevel.getLogger('webdriver-manager'); +import * as Opt from './'; +import {Opts} from './opts'; -/** - * Starts the selenium server standalone with browser drivers. Also handles - * the SIGINT event when the server is stopped. - * @param argv The argv from yargs. - */ -export async function handler(argv: yargs.Arguments) { - log.setLevel(argv.log_level); - const options = convertArgs2Options(argv); - if (options.server.runAsDetach) { - await start(options); - process.exit(); - } else { - process.stdin.resume(); - process.on('SIGINT', () => { - const optionsBinary = addOptionsBinary(options); - const seleniumServer = (optionsBinary.server.binary as SeleniumServer); - process.kill(seleniumServer.seleniumProcess.pid); - process.exit(process.exitCode); - }); - start(options).then(() => {}); - } +let logger = new Logger('start'); +let prog = new Program() + .command('start', 'start up the selenium server') + .action(start) + .addOption(Opts[Opt.OUT_DIR]) + .addOption(Opts[Opt.SELENIUM_PORT]) + .addOption(Opts[Opt.APPIUM_PORT]) + .addOption(Opts[Opt.AVD_PORT]) + .addOption(Opts[Opt.VERSIONS_STANDALONE]) + .addOption(Opts[Opt.VERSIONS_CHROME]) + .addOption(Opts[Opt.VERSIONS_ANDROID]) + .addOption(Opts[Opt.VERSIONS_APPIUM]) + .addOption(Opts[Opt.CHROME_LOGS]) + .addOption(Opts[Opt.LOGGING]) + .addOption(Opts[Opt.ANDROID]) + .addOption(Opts[Opt.AVDS]) + .addOption(Opts[Opt.AVD_USE_SNAPSHOTS]); + +if (os.type() === 'Darwin') { + prog.addOption(Opts[Opt.IOS]); } -/** - * Goes through all the option providers and creates a set of java options - * to pass to java when starting the selenium server standalone. - * @param options The constructed options. - * @returns Promise starting the server with the resolved exit code. - */ -export function start(options: Options): Promise { - const optionsBinary = addOptionsBinary(options); - return startBinary(optionsBinary); +if (os.type() === 'Windows_NT') { + prog.addOption(Opts[Opt.VERSIONS_IE]) + .addOption(Opts[Opt.IE32]) + .addOption(Opts[Opt.IE]) + .addOption(Opts[Opt.EDGE]); +} + +export var program = prog; + +// stand alone runner +let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); +if (argv._[0] === 'start-run') { + prog.run(JSON.parse(JSON.stringify(argv))); +} else if (argv._[0] === 'start-help') { + prog.printHelp(); } /** - * Goes through all the option providers and creates a set of java options - * to pass to java when starting the selenium server standalone. - * @param optionsBinary The constructed options with binaries. - * @returns Promise starting the server with the resolved exit code. + * Parses the options and starts the selenium standalone server. + * @param options */ -export function startBinary(optionsBinary: OptionsBinary): Promise { - const javaOpts: {[key: string]: string} = {}; - for (const browserDriver of optionsBinary.browserDrivers) { - if (browserDriver.binary) { - javaOpts[browserDriver.binary.seleniumFlag] = - browserDriver.binary.getBinaryPath(browserDriver.version); +function start(options: Options) { + let osType = os.type(); + let binaries = FileManager.setupBinaries(); + let seleniumPort = options[Opt.SELENIUM_PORT].getString(); + let outputDir = Config.getSeleniumDir(); + if (options[Opt.OUT_DIR].getString()) { + if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { + outputDir = options[Opt.OUT_DIR].getString(); + } else { + outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); + } + } + + try { + // check if folder exists + fs.statSync(outputDir).isDirectory(); + } catch (e) { + // if the folder does not exist, quit early. + logger.warn('the out_dir path ' + outputDir + ' does not exist, run webdriver-manager update'); + return; + } + + let chromeLogs: string = null; + let loggingFile: string = null; + if (options[Opt.CHROME_LOGS].getString()) { + if (path.isAbsolute(options[Opt.CHROME_LOGS].getString())) { + chromeLogs = options[Opt.CHROME_LOGS].getString(); + } else { + chromeLogs = path.resolve(Config.getBaseDir(), options[Opt.CHROME_LOGS].getString()); } } + binaries[StandAlone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString(); + binaries[ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString(); + if (options[Opt.VERSIONS_IE]) { + binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); + } + binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); + binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); + let downloadedBinaries = FileManager.downloadedBinaries(outputDir); - if (optionsBinary.server) { - if (optionsBinary.server.chromeLogs) { - const chromeLogs = - optionsBinary.server.chromeLogs.replace('"', '').replace('\'', ''); - javaOpts['-Dwebdriver.chrome.logfile'] = path.resolve(chromeLogs); + if (downloadedBinaries[StandAlone.id] == null) { + logger.error( + 'Selenium Standalone is not present. Install with ' + + 'webdriver-manager update --standalone'); + process.exit(1); + } + let args: string[] = []; + if (osType === 'Linux') { + // selenium server may take a long time to start because /dev/random is BLOCKING if there is not + // enough entropy the solution is to use /dev/urandom, which is NON-BLOCKING (use /dev/./urandom + // because of a java bug) + // https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/1301 + // https://bugs.openjdk.java.net/browse/JDK-6202721 + args.push('-Djava.security.egd=file:///dev/./urandom'); + } + + if (options[Opt.LOGGING].getString()) { + if (path.isAbsolute(options[Opt.LOGGING].getString())) { + loggingFile = options[Opt.LOGGING].getString(); + } else { + loggingFile = path.resolve(Config.getBaseDir(), options[Opt.LOGGING].getString()); } - if (optionsBinary.server.edge) { - const edge = optionsBinary.server.edge.replace('"', '').replace('\'', ''); - javaOpts['-Dwebdriver.edge.driver'] = path.resolve(edge); + args.push('-Djava.util.logging.config.file=' + loggingFile); + } + + if (seleniumPort) { + args.push('-port', seleniumPort); + } + if (downloadedBinaries[ChromeDriver.id] != null) { + args.push( + '-Dwebdriver.chrome.driver=' + + path.join(outputDir, binaries[ChromeDriver.id].executableFilename(osType))); + if (chromeLogs != null) { + args.push('-Dwebdriver.chrome.logfile=' + chromeLogs); + } + } + if (downloadedBinaries[GeckoDriver.id] != null) { + args.push( + '-Dwebdriver.gecko.driver=' + + path.join(outputDir, binaries[GeckoDriver.id].executableFilename(osType))); + } + if (downloadedBinaries[IEDriver.id] != null) { + if (options[Opt.IE32]) { + binaries[IEDriver.id].arch = 'Win32'; } - if (optionsBinary.server.binary) { - return (optionsBinary.server.binary as SeleniumServer) - .startServer(javaOpts, optionsBinary.server.version); + args.push( + '-Dwebdriver.ie.driver=' + + path.join(outputDir, binaries[IEDriver.id].executableFilename(osType))); + } + if (options[Opt.EDGE]) { + // validate that the file exists prior to adding it to args + try { + let edgeFile = options[Opt.EDGE].getString(); + if (fs.statSync(edgeFile).isFile()) { + args.push('-Dwebdriver.edge.driver=' + options[Opt.EDGE].getString()); + } + } catch (err) { + // Either the default file or user specified location of the edge + // driver does not exist. } } - return Promise.reject('Could not start the server'); -} \ No newline at end of file + if (options[Opt.ANDROID].getBoolean()) { + if (downloadedBinaries[AndroidSDK.id] != null) { + let avds = options[Opt.AVDS].getString(); + startAndroid( + outputDir, binaries[AndroidSDK.id], avds.split(','), + options[Opt.AVD_USE_SNAPSHOTS].getBoolean(), options[Opt.AVD_PORT].getString()); + } else { + logger.warn('Not starting android because it is not installed'); + } + } + if (downloadedBinaries[Appium.id] != null) { + startAppium(outputDir, binaries[Appium.id], options[Opt.APPIUM_PORT].getString()); + } + + // log the command to launch selenium server + args.push('-jar'); + args.push(path.join(outputDir, binaries[StandAlone.id].filename())); + + let argsToString = ''; + for (let arg in args) { + argsToString += ' ' + args[arg]; + } + logger.info('java' + argsToString); + + let seleniumProcess = spawnCommand('java', args); + logger.info('seleniumProcess.pid: ' + seleniumProcess.pid); + seleniumProcess.on('exit', (code: number) => { + logger.info('Selenium Standalone has exited with code ' + code); + killAndroid(); + killAppium(); + process.exit(code); + }); + process.stdin.resume(); + process.stdin.on('data', (chunk: Buffer) => { + logger.info('Attempting to shut down selenium nicely'); + let port = seleniumPort || '4444'; + http.get('http://localhost:' + port + '/selenium-server/driver/?cmd=shutDownSeleniumServer'); + killAndroid(); + killAppium(); + }); + process.on('SIGINT', () => { + logger.info('Staying alive until the Selenium Standalone process exits'); + }); +} + +function spawnCommand(command: string, args?: string[]) { + let osType = os.type(); + let windows: boolean = osType === 'Windows_NT'; + let winCommand = windows ? 'cmd' : command; + let finalArgs: string[] = windows ? ['/c'].concat([command], args) : args; + + return childProcess.spawn(winCommand, finalArgs, {stdio: 'inherit'}); +} + +// Manage processes used in android emulation +let androidProcesses: childProcess.ChildProcess[] = []; + +function startAndroid( + outputDir: string, sdk: Binary, avds: string[], useSnapshots: boolean, port: string): void { + let sdkPath = path.join(outputDir, sdk.executableFilename(os.type())); + if (avds[0] == 'all') { + avds = require(path.join(sdkPath, 'available_avds.json')); + } else if (avds[0] == 'none') { + avds.length = 0; + } + avds.forEach((avd: string, i: number) => { + logger.info('Booting up AVD ' + avd); + // Credit to appium-ci, which this code was adapted from + let emuBin = 'emulator'; // TODO(sjelin): get the 64bit linux version working + let emuArgs = [ + '-avd', + avd + '-v' + sdk.versionCustom + '-wd-manager', + '-netfast', + ]; + if (!useSnapshots) { + emuArgs = emuArgs.concat(['-no-snapshot-load', '-no-snapshot-save']); + } + if (port) { + emuArgs = emuArgs.concat(['-ports', (port + 2 * i) + ',' + (port + 2 * i + 1)]); + } + if (emuBin !== 'emulator') { + emuArgs = emuArgs.concat(['-qemu', '-enable-kvm']); + } + androidProcesses.push( + childProcess.spawn(path.join(sdkPath, 'tools', emuBin), emuArgs, {stdio: 'inherit'})); + }); +} + +function killAndroid() { + androidProcesses.forEach((androidProcess: childProcess.ChildProcess) => { + androidProcess.kill(); + }); + androidProcesses.length = 0; +} + +// Manage appium process +let appiumProcess: childProcess.ChildProcess; + +function startAppium(outputDir: string, binary: Binary, port: string) { + logger.info('Starting appium server'); + appiumProcess = childProcess.spawn( + path.join(outputDir, binary.filename(), 'node_modules', '.bin', 'appium'), + port ? ['--port', port] : []); +} + +function killAppium() { + if (appiumProcess != null) { + appiumProcess.kill(); + appiumProcess = null; + } +} diff --git a/lib/cmds/start_stop.spec-int.ts b/lib/cmds/start_stop.spec-int.ts deleted file mode 100644 index 4ea19a09..00000000 --- a/lib/cmds/start_stop.spec-int.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as fs from 'fs'; -import * as http from 'http'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {findPort} from '../../spec/support/helpers/port_finder'; -import {ChromeDriver} from '../provider/chromedriver'; -import {SeleniumServer} from '../provider/selenium_server'; - -import {OptionsBinary} from './options_binary'; -import {shutdownBinary} from './shutdown'; -import {startBinary} from './start'; -import {updateBinary} from './update'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); -loglevel.getLogger('webdriver-manager').setLevel('info'); -const tmpDir = path.resolve(os.tmpdir(), 'test'); -const selenium = - new SeleniumServer({outDir: tmpDir, runAsDetach: true, runAsNode: true}); - -const optionsBinary: OptionsBinary = { - outDir: tmpDir, - browserDrivers: [{binary: new ChromeDriver({outDir: tmpDir})}], - server: {binary: selenium, runAsDetach: true, runAsNode: true} -}; -let port: number; - -describe('start and stop cmd', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('start', () => { - beforeAll(async () => { - await updateBinary(optionsBinary); - }); - - it('should run the detached server', async () => { - port = await findPort(4000, 5000); - optionsBinary.server.port = port; - (optionsBinary.server.binary as SeleniumServer).port = port; - await startBinary(optionsBinary); - const hubUrl = `http://localhost:${port}/wd/hub/static/resource/hub.html`; - const responseCode = new Promise((resolve, reject) => { - http.get(hubUrl, res => { - if (res.statusCode === 200) { - resolve(res.statusCode); - } else { - reject('Should be 200'); - } - }); - }); - expect(await responseCode).toBe(200); - }); - }); - - describe('stop', () => { - it('should shutdown the detached server', async () => { - const hubUrl = `http://localhost:${port}/wd/hub/static/resource/hub.html`; - await shutdownBinary(optionsBinary); - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - // tslint:disable-next-line:no-any - const noResponse = new Promise((resolve, _) => { - http.get(hubUrl, _ => {}).on('error', (err) => { - resolve(err); - }); - }); - expect((await noResponse)['code']).toBe('ECONNREFUSED'); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/status.spec-e2e.ts b/lib/cmds/status.spec-e2e.ts deleted file mode 100644 index 655da5d4..00000000 --- a/lib/cmds/status.spec-e2e.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {status} from './status'; -import {convertArgs2AllOptions} from './utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -describe('using the cli', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - describe('a user runs status', () => { - it('should log an empty string when folder does not exist', () => { - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const statusLog = status(options); - expect(statusLog).toBe(''); - }); - - it('should log an empty string when folder is empty', () => { - fs.mkdirSync(tmpDir); - const argv = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - const statusLog = status(options); - expect(statusLog).toBe(''); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/status.spec-int.ts b/lib/cmds/status.spec-int.ts deleted file mode 100644 index 9da1db91..00000000 --- a/lib/cmds/status.spec-int.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import * as yargs from 'yargs'; - -import {statusBinary} from './status'; -import {addOptionsBinary, convertArgs2AllOptions} from './utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tmpDir = path.resolve(os.tmpdir(), 'test'); -const argv: yargs.Arguments = { - _: ['foobar'], - out_dir: tmpDir, - '$0': 'bin\\webdriver-manager' -}; -const options = convertArgs2AllOptions(argv); -const optionsBinary = addOptionsBinary(options); -for (const browserDriver of optionsBinary.browserDrivers) { - browserDriver.binary.osType = 'Linux'; -} -optionsBinary.server.binary.osType = 'Linux'; - -describe('status cmd', () => { - describe('with files', () => { - beforeAll(() => { - // create the directory - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - const contents = { - last: 'chromedriver_2.41', - all: ['chromedriver_2.20', 'chromedriver_2.41'] - }; - fs.writeFileSync( - path.resolve(tmpDir, 'chromedriver.config.json'), - JSON.stringify(contents)); - }); - - afterAll(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should get the status for chromedriver', () => { - const lines = statusBinary(optionsBinary).split('\n'); - expect(lines.length).toBe(1); - expect(lines[0]).toBe('chromedriver: 2.20, 2.41 (latest)'); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/status.ts b/lib/cmds/status.ts index e1b10c3f..385abc08 100644 --- a/lib/cmds/status.ts +++ b/lib/cmds/status.ts @@ -1,49 +1,77 @@ -import * as loglevel from 'loglevel'; -import * as yargs from 'yargs'; -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; -import {addOptionsBinary, convertArgs2AllOptions} from './utils'; +import * as fs from 'fs'; +import * as minimist from 'minimist'; +import * as path from 'path'; -const log = loglevel.getLogger('webdriver-manager'); +import {Logger, Options, Program} from '../cli'; +import {Config} from '../config'; +import {FileManager} from '../files'; -/** - * Displays which versions of providers that have been downloaded. - * @param argv The argv from yargs. - */ -export function handler(argv: yargs.Arguments) { - log.setLevel(argv.log_level); - const options = convertArgs2AllOptions(argv); - console.log(status(options)); -} +import * as Opt from './'; +import {Opts} from './opts'; -/** - * Gets a list of versions for server and browser drivers. - * @param options The constructed set of all options. - * @returns A string of the versions downloaded. - */ -export function status(options: Options): string { - const optionsBinary = addOptionsBinary(options); - return statusBinary(optionsBinary); +let logger = new Logger('status'); +let prog = new Program() + .command('status', 'list the current available drivers') + .addOption(Opts[Opt.OUT_DIR]) + .action(status); + +export var program = prog; + +// stand alone runner +let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); +if (argv._[0] === 'status-run') { + prog.run(JSON.parse(JSON.stringify(argv))); +} else if (argv._[0] === 'status-help') { + prog.printHelp(); } /** - * Gets a list of versions for server and browser drivers. - * @param optionsBinary The constructed set of all options with binaries. - * @returns A string of the versions downloaded. + * Parses the options and logs the status of the binaries downloaded. + * @param options */ -export function statusBinary(optionsBinary: OptionsBinary): string { - const binaryVersions = []; - for (const browserDriver of optionsBinary.browserDrivers) { - const status = browserDriver.binary.getStatus(); - if (status) { - binaryVersions.push(`${browserDriver.name}: ${status}`); +function status(options: Options) { + let binaries = FileManager.setupBinaries(); + let outputDir = Config.getSeleniumDir(); + if (options[Opt.OUT_DIR].value) { + if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { + outputDir = options[Opt.OUT_DIR].getString(); + } else { + outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); } } - if (optionsBinary.server && optionsBinary.server.binary) { - const status = optionsBinary.server.binary.getStatus(); - if (status) { - binaryVersions.push(`${optionsBinary.server.name}: ${status}`); + + try { + // check if folder exists + fs.statSync(outputDir).isDirectory(); + } catch (e) { + // if the folder does not exist, quit early. + logger.warn('the out_dir path ' + outputDir + ' does not exist'); + return; + } + + + let downloadedBinaries = FileManager.downloadedBinaries(outputDir); + // log which binaries have been downloaded + for (let bin in downloadedBinaries) { + let downloaded = downloadedBinaries[bin]; + let log = downloaded.name + ' '; + log += downloaded.versions.length == 1 ? 'version available: ' : 'versions available: '; + for (let ver in downloaded.versions) { + let version = downloaded.versions[ver]; + log += version; + if (downloaded.binary.versionDefault() === version) { + log += ' [default]'; + } + if (+ver != downloaded.versions.length - 1) { + log += ', '; + } } + logger.info(log); } - return (binaryVersions.sort()).join('\n'); -} \ No newline at end of file + // for binaries that are available for the operating system, show them here + for (let bin in binaries) { + if (downloadedBinaries[bin] == null) { + logger.info(binaries[bin].name + ' is not present'); + } + } +} diff --git a/lib/cmds/update.spec-int.ts b/lib/cmds/update.spec-int.ts deleted file mode 100644 index f2c7e877..00000000 --- a/lib/cmds/update.spec-int.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {ChromeDriver} from '../provider/chromedriver'; -import {GeckoDriver} from '../provider/geckodriver'; -import {IEDriver} from '../provider/iedriver'; -import {SeleniumServer} from '../provider/selenium_server'; -import {OptionsBinary} from './options_binary'; -import {updateBinary} from './update'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tmpDir = path.resolve(os.tmpdir(), 'test'); - -describe('update cmd', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - beforeEach(() => { - // create the directory - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - xit('should download the chromdriver files', async () => { - const optionsBinary: OptionsBinary = { - outDir: tmpDir, - browserDrivers: [{binary: new ChromeDriver({outDir: tmpDir})}] - }; - await updateBinary(optionsBinary); - expect(fs.readdirSync(tmpDir).length).toBe(4); - }); - - it('should download the geckodriver files', async () => { - const optionsBinary: OptionsBinary = { - outDir: tmpDir, - browserDrivers: [{binary: new GeckoDriver({outDir: tmpDir})}] - }; - await updateBinary(optionsBinary); - expect(fs.readdirSync(tmpDir).length).toBe(4); - }); - - it('should download selenium server files', async () => { - const optionsBinary: OptionsBinary = { - outDir: tmpDir, - server: {binary: new SeleniumServer({outDir: tmpDir})} - }; - await updateBinary(optionsBinary); - expect(fs.readdirSync(tmpDir).length).toBe(3); - }); - - it('should download iedriver files', async () => { - const iedriver = new IEDriver({outDir: tmpDir}); - iedriver.osType = 'Windows_NT'; - const optionsBinary: - OptionsBinary = {outDir: tmpDir, browserDrivers: [{binary: iedriver}]}; - await updateBinary(optionsBinary); - expect(fs.readdirSync(tmpDir).length).toBe(4); - }); - - xit('should download default files', async () => { - const optionsBinary: OptionsBinary = { - outDir: tmpDir, - browserDrivers: [ - {binary: new ChromeDriver({outDir: tmpDir})}, - {binary: new GeckoDriver({outDir: tmpDir})}, - ], - server: {binary: new SeleniumServer({outDir: tmpDir})} - }; - await updateBinary(optionsBinary); - expect(fs.readdirSync(tmpDir).length).toBe(11); - }); -}); \ No newline at end of file diff --git a/lib/cmds/update.ts b/lib/cmds/update.ts index 3853c828..5c2afdcc 100644 --- a/lib/cmds/update.ts +++ b/lib/cmds/update.ts @@ -1,46 +1,240 @@ -import * as loglevel from 'loglevel'; -import * as yargs from 'yargs'; -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; -import {addOptionsBinary, convertArgs2Options} from './utils'; +import * as AdmZip from 'adm-zip'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; +import * as minimist from 'minimist'; +import * as os from 'os'; +import * as path from 'path'; +import * as q from 'q'; +import * as rimraf from 'rimraf'; -const log = loglevel.getLogger('webdriver-manager'); +import {AndroidSDK, Appium, Binary, ChromeDriver, GeckoDriver, IEDriver, StandAlone} from '../binaries'; +import {Logger, Options, Program} from '../cli'; +import {Config} from '../config'; +import {Downloader, FileManager} from '../files'; -/** - * Updates / downloads the providers binaries. - * @param argv The argv from yargs. - */ -export async function handler(argv: yargs.Arguments) { - log.setLevel(argv.log_level); - const options = convertArgs2Options(argv); - await update(options); +import * as Opt from './'; +import {android as initializeAndroid, iOS as checkIOS} from './initialize'; +import {Opts} from './opts'; + +let logger = new Logger('update'); +let prog = new Program() + .command('update', 'install or update selected binaries') + .action(update) + .addOption(Opts[Opt.OUT_DIR]) + .addOption(Opts[Opt.IGNORE_SSL]) + .addOption(Opts[Opt.PROXY]) + .addOption(Opts[Opt.ALTERNATE_CDN]) + .addOption(Opts[Opt.STANDALONE]) + .addOption(Opts[Opt.CHROME]) + .addOption(Opts[Opt.ANDROID]) + .addOption(Opts[Opt.ANDROID_API_LEVELS]) + .addOption(Opts[Opt.ANDROID_ABIS]) + .addOption(Opts[Opt.ANDROID_ACCEPT_LICENSES]); + +if (GeckoDriver.supports(os.type(), os.arch())) { + prog.addOption(Opts[Opt.VERSIONS_GECKO]).addOption(Opts[Opt.GECKO]); } -/** - * Updates / downloads the providers binaries. - * @param options The constructed options. - * @returns Promise when binaries are all downloaded. - */ -export function update(options: Options): Promise { - const optionsBinary = addOptionsBinary(options); - return updateBinary(optionsBinary); +if (os.type() === 'Darwin') { + prog.addOption(Opts[Opt.IOS]); +} + +if (os.type() === 'Windows_NT') { + prog.addOption(Opts[Opt.IE]).addOption(Opts[Opt.IE32]); +} + +prog.addOption(Opts[Opt.VERSIONS_STANDALONE]) + .addOption(Opts[Opt.VERSIONS_CHROME]) + .addOption(Opts[Opt.VERSIONS_APPIUM]) + .addOption(Opts[Opt.VERSIONS_ANDROID]); + +if (os.type() === 'Windows_NT') { + prog.addOption(Opts[Opt.VERSIONS_IE]); +} +export let program = prog; + +// stand alone runner +let argv = minimist(process.argv.slice(2), prog.getMinimistOptions()); +if (argv._[0] === 'update-run') { + prog.run(JSON.parse(JSON.stringify(argv))); +} else if (argv._[0] === 'update-help') { + prog.printHelp(); } + /** - * Updates / downloads the providers binaries. - * @param optionsBinary The constructed options with binaries. - * @returns Promise when binaries are all downloaded. + * Parses the options and downloads binaries if they do not exist. + * @param options */ -export function updateBinary(optionsBinary: OptionsBinary): Promise { - const promises = []; - if (optionsBinary.browserDrivers) { - for (const provider of optionsBinary.browserDrivers) { - promises.push(provider.binary.updateBinary(provider.version)); +function update(options: Options): void { + let standalone = options[Opt.STANDALONE].getBoolean(); + let chrome = options[Opt.CHROME].getBoolean(); + let gecko = options[Opt.GECKO].getBoolean(); + let ie: boolean = false; + let ie32: boolean = false; + if (options[Opt.IE]) { + ie = options[Opt.IE].getBoolean(); + } + if (options[Opt.IE32]) { + ie32 = options[Opt.IE32].getBoolean(); + } + let android: boolean = options[Opt.ANDROID].getBoolean(); + let ios: boolean = false; + if (options[Opt.IOS]) { + ios = options[Opt.IOS].getBoolean(); + } + let outputDir = Config.getSeleniumDir(); + let android_api_levels: string[] = options[Opt.ANDROID_API_LEVELS].getString().split(','); + let android_abis: string[] = options[Opt.ANDROID_ABIS].getString().split(','); + let android_accept_licenses: boolean = options[Opt.ANDROID_ACCEPT_LICENSES].getBoolean(); + if (options[Opt.OUT_DIR].getString()) { + if (path.isAbsolute(options[Opt.OUT_DIR].getString())) { + outputDir = options[Opt.OUT_DIR].getString(); + } else { + outputDir = path.resolve(Config.getBaseDir(), options[Opt.OUT_DIR].getString()); + } + FileManager.makeOutputDirectory(outputDir); + } + let ignoreSSL = options[Opt.IGNORE_SSL].getBoolean(); + let proxy = options[Opt.PROXY].getString(); + + // setup versions for binaries + let binaries = FileManager.setupBinaries(options[Opt.ALTERNATE_CDN].getString()); + binaries[StandAlone.id].versionCustom = options[Opt.VERSIONS_STANDALONE].getString(); + binaries[ChromeDriver.id].versionCustom = options[Opt.VERSIONS_CHROME].getString(); + if (options[Opt.VERSIONS_IE]) { + binaries[IEDriver.id].versionCustom = options[Opt.VERSIONS_IE].getString(); + } + if (options[Opt.VERSIONS_GECKO]) { + binaries[GeckoDriver.id].versionCustom = options[Opt.VERSIONS_GECKO].getString(); + } + binaries[AndroidSDK.id].versionCustom = options[Opt.VERSIONS_ANDROID].getString(); + binaries[Appium.id].versionCustom = options[Opt.VERSIONS_APPIUM].getString(); + + // if the file has not been completely downloaded, download it + // else if the file has already been downloaded, unzip the file, rename it, and give it + // permissions + if (standalone) { + let binary = binaries[StandAlone.id]; + FileManager.toDownload(binary, outputDir, proxy, ignoreSSL).then((value: boolean) => { + if (value) { + Downloader.downloadBinary(binary, outputDir, proxy, ignoreSSL); + } else { + logger.info( + binary.name + ': file exists ' + + path.resolve(outputDir, binary.filename(os.type(), os.arch()))); + logger.info(binary.name + ': v' + binary.versionCustom + ' up to date'); + } + }); + } + if (chrome) { + let binary = binaries[ChromeDriver.id]; + updateBinary(binary, outputDir, proxy, ignoreSSL); + } + if (gecko) { + let binary = binaries[GeckoDriver.id]; + updateBinary(binary, outputDir, proxy, ignoreSSL); + } + if (ie) { + let binary = binaries[IEDriver.id]; + binary.arch = os.arch(); // Win32 or x64 + updateBinary(binary, outputDir, proxy, ignoreSSL); + } + if (ie32) { + let binary = binaries[IEDriver.id]; + binary.arch = 'Win32'; + updateBinary(binary, outputDir, proxy, ignoreSSL); + } + if (android) { + let binary = binaries[AndroidSDK.id]; + let sdk_path = path.join(outputDir, binary.executableFilename(os.type())); + + updateBinary(binary, outputDir, proxy, ignoreSSL).then(() => { + initializeAndroid( + path.join(outputDir, binary.executableFilename(os.type())), android_api_levels, + android_abis, android_accept_licenses, binaries[AndroidSDK.id].versionCustom, logger); + }); + } + if (ios) { + checkIOS(logger); + } + if (android || ios) { + installAppium(binaries[Appium.id], outputDir); + } +} + +function updateBinary( + binary: Binary, outputDir: string, proxy: string, ignoreSSL: boolean): q.Promise { + return FileManager.toDownload(binary, outputDir, proxy, ignoreSSL).then((value: boolean) => { + if (value) { + let deferred = q.defer(); + Downloader.downloadBinary( + binary, outputDir, proxy, ignoreSSL, + (binary: Binary, outputDir: string, fileName: string) => { + unzip(binary, outputDir, fileName); + deferred.resolve(); + }); + return deferred.promise; + } else { + logger.info( + binary.name + ': file exists ' + + path.resolve(outputDir, binary.filename(os.type(), os.arch()))); + let fileName = binary.filename(os.type(), os.arch()); + unzip(binary, outputDir, fileName); + logger.info(binary.name + ': v' + binary.versionCustom + ' up to date'); + } + }); +} + +function unzip(binary: T, outputDir: string, fileName: string): void { + // remove the previously saved file and unzip it + let osType = os.type(); + let mv = path.join(outputDir, binary.executableFilename(osType)); + try { + fs.unlinkSync(mv); + } catch (err) { + try { + rimraf.sync(mv); + } catch (err2) { + } + } + + // unzip the file + logger.info(binary.name + ': unzipping ' + fileName); + if (fileName.slice(-4) == '.zip') { + let zip = new AdmZip(path.resolve(outputDir, fileName)); + zip.extractAllTo(outputDir, true); + } else { + // We will only ever get .tar files on linux + child_process.spawnSync('tar', ['zxvf', path.resolve(outputDir, fileName), '-C', outputDir]); + } + + // rename + fs.renameSync(path.join(outputDir, binary.zipContentName(osType)), mv); + + // set permissions + if (osType !== 'Windows_NT') { + logger.info(binary.name + ': setting permissions to 0755 for ' + mv); + if (binary.id() !== AndroidSDK.id) { + fs.chmodSync(mv, '0755'); + } else { + fs.chmodSync(path.join(mv, 'tools', 'android'), '0755'); + fs.chmodSync(path.join(mv, 'tools', 'emulator'), '0755'); + // TODO(sjelin): get 64 bit versions working } } - if (optionsBinary.server && optionsBinary.server.binary) { - promises.push( - optionsBinary.server.binary.updateBinary(optionsBinary.server.version)); +} + +function installAppium(binary: Binary, outputDir: string): void { + logger.info('appium: installing appium'); + + let folder = path.join(outputDir, binary.filename()); + try { + rimraf.sync(folder); + } catch (err) { } - return Promise.all(promises); -} \ No newline at end of file + + fs.mkdirSync(folder); + fs.writeFileSync(path.join(folder, 'package.json'), '{}'); + child_process.spawn('npm', ['install', 'appium@' + binary.version()], {cwd: folder}); +} diff --git a/lib/cmds/utils.spec-unit.ts b/lib/cmds/utils.spec-unit.ts deleted file mode 100644 index 96ea951e..00000000 --- a/lib/cmds/utils.spec-unit.ts +++ /dev/null @@ -1,66 +0,0 @@ -import {convertArgs2AllOptions, convertArgs2Options} from './utils'; - -describe('utils', () => { - describe('convertArgs2AllOptions', () => { - it('should create all providers', () => { - const argv = { - _: ['foobar'], - proxy: 'http://some.proxy.com', - versions: {gecko: '0.16.0', chrome: '2.20'}, - out_dir: 'foobar_download', - ignore_ssl: false, - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2AllOptions(argv); - expect(options.browserDrivers).toBeTruthy(); - expect(options.browserDrivers.length).toBe(3); - for (const provider of options.browserDrivers) { - if (provider.name === 'geckodriver') { - expect(provider.version).toBe('0.16.0'); - } - if (provider.name === 'chromedriver') { - expect(provider.version).toBe('2.20'); - } - if (provider.name === 'iedriver') { - expect(provider.version).toBeUndefined(); - } - } - expect(options.server).toBeTruthy(); - expect(options.server.name).toBe('selenium'); - expect(options.server.version).toBeUndefined(); - expect(options.proxy).toBe('http://some.proxy.com'); - expect(options.ignoreSSL).toBeFalsy(); - expect(options.outDir).toBe('foobar_download'); - }); - }); - - describe('convertArgs2Options', () => { - it('should create the default providers', () => { - const argv = { - _: ['foobar'], - chrome: true, - gecko: true, - standalone: true, - versions: {gecko: '0.16.0', chrome: '2.20'}, - out_dir: 'foobar_download', - '$0': 'bin\\webdriver-manager' - }; - const options = convertArgs2Options(argv); - expect(options.browserDrivers).toBeTruthy(); - expect(options.browserDrivers.length).toBe(2); - for (const provider of options.browserDrivers) { - if (provider.name === 'geckodriver') { - expect(provider.version).toBe('0.16.0'); - } - if (provider.name === 'chromedriver') { - expect(provider.version).toBe('2.20'); - } - } - expect(options.server).toBeTruthy(); - expect(options.server.name).toBe('selenium'); - expect(options.server.version).toBeUndefined(); - expect(options.proxy).toBeUndefined(); - expect(options.ignoreSSL).toBeUndefined(); - }); - }); -}); \ No newline at end of file diff --git a/lib/cmds/utils.ts b/lib/cmds/utils.ts deleted file mode 100644 index 47c4b304..00000000 --- a/lib/cmds/utils.ts +++ /dev/null @@ -1,125 +0,0 @@ -import * as yargs from 'yargs'; -import {ChromeDriver} from '../provider/chromedriver'; -import {GeckoDriver} from '../provider/geckodriver'; -import {IEDriver} from '../provider/iedriver'; -import {ProviderConfig} from '../provider/provider'; -import {SeleniumServer, SeleniumServerProviderConfig} from '../provider/selenium_server'; - -import {Options} from './options'; -import {OptionsBinary} from './options_binary'; - -/** - * Converts an options object into an options binary object. - * @param options - */ -export function addOptionsBinary(options: Options): OptionsBinary { - if (!options) { - return null; - } - const providerConfig: ProviderConfig = { - ignoreSSL: options.ignoreSSL, - outDir: options.outDir, - proxy: options.proxy - }; - - const optionsBinary: OptionsBinary = options; - if (optionsBinary.browserDrivers) { - for (const browserDriver of optionsBinary.browserDrivers) { - if (browserDriver.name === 'chromedriver') { - browserDriver.binary = new ChromeDriver(providerConfig); - } else if (browserDriver.name === 'geckodriver') { - const geckoProviderConfig = providerConfig; - geckoProviderConfig.oauthToken = optionsBinary.githubToken; - browserDriver.binary = new GeckoDriver(geckoProviderConfig); - } else if (browserDriver.name === 'iedriver') { - browserDriver.binary = new IEDriver(providerConfig); - } - } - } - if (optionsBinary.server) { - const seleniumProviderConfig: SeleniumServerProviderConfig = providerConfig; - seleniumProviderConfig.outDir = optionsBinary.outDir; - seleniumProviderConfig.port = optionsBinary.server.port; - seleniumProviderConfig.runAsDetach = optionsBinary.server.runAsDetach; - seleniumProviderConfig.runAsNode = optionsBinary.server.runAsNode; - optionsBinary.server.binary = new SeleniumServer(seleniumProviderConfig); - } - return optionsBinary; -} - -/** - * Create the options with all providers. Used for clean and status commands. - * @param argv - */ -export function convertArgs2AllOptions(argv: yargs.Arguments): Options { - let versionsChrome, versionsGecko, versionsIe, versionsStandalone = undefined; - if (argv.versions) { - versionsChrome = argv.versions.chrome as string; - versionsGecko = argv.versions.gecko as string; - versionsIe = argv.versions.ie as string; - versionsStandalone = argv.versions.standalone as string; - } - return { - browserDrivers: [ - {name: 'chromedriver', version: versionsChrome}, - {name: 'geckodriver', version: versionsGecko}, - {name: 'iedriver', version: versionsIe} - ], - server: { - name: 'selenium', - version: versionsStandalone, - runAsNode: argv.standalone_node as boolean, - runAsDetach: argv.detach as boolean, - chromeLogs: argv.chrome_logs as string, - edge: argv.edge as string, - }, - ignoreSSL: argv.ignore_ssl as boolean, - outDir: argv.out_dir as string, - proxy: argv.proxy as string, - githubToken: argv.github_token as string, - }; -} - -/** - * Create the options with providers depending on argv's. Used for update and - * start commands. - * @param argv - */ -export function convertArgs2Options(argv: yargs.Arguments): Options { - const options: Options = { - browserDrivers: [], - server: null, - ignoreSSL: argv.ignore_ssl as boolean, - outDir: argv.out_dir as string, - proxy: argv.proxy as string, - githubToken: argv.github_token as string, - }; - - let versionsChrome, versionsGecko, versionsIe, versionsStandalone = undefined; - if (argv.versions) { - versionsChrome = argv.versions.chrome as string; - versionsGecko = argv.versions.gecko as string; - versionsIe = argv.versions.ie as string; - versionsStandalone = argv.versions.standalone as string; - } - if (argv.chrome as boolean) { - options.browserDrivers.push( - {name: 'chromedriver', version: versionsChrome}); - } - if (argv.gecko as boolean) { - options.browserDrivers.push({name: 'geckodriver', version: versionsGecko}); - } - if (argv.iedriver as boolean) { - options.browserDrivers.push({name: 'iedriver', version: versionsIe}); - } - if (argv.standalone as boolean) { - options.server = {}; - options.server.name = 'selenium'; - options.server.runAsNode = argv.standalone_node as boolean; - options.server.runAsDetach = argv.detach as boolean; - options.server.version = versionsStandalone; - options.server.chromeLogs = argv.chrome_logs as string; - options.server.edge = argv.edge as string; - } - return options; -} \ No newline at end of file diff --git a/lib/config.ts b/lib/config.ts new file mode 100644 index 00000000..dcba0a9f --- /dev/null +++ b/lib/config.ts @@ -0,0 +1,95 @@ +import * as fs from 'fs'; +import * as path from 'path'; + +import {Logger} from './cli'; + + +let logger = new Logger('config'); + +export interface ConfigFile { + [key: string]: string; + selenium?: string; + chrome?: string; + gecko?: string; + ie?: string; + android?: string; + appium?: string; +} + +/** + * The configuration for webdriver-manager + * + * The config.json, package.json, and selenium directory are found in the + * same location at the root directory in webdriver-manager. + * + */ +export class Config { + static configFile: string = 'config.json'; + static packageFile: string = 'package.json'; + static nodeModuleName = 'webdriver-manager'; + + static cwd = process.cwd(); + + + static localInstall: string; + static parentPath = path.resolve(Config.cwd, '..'); + static dir = __dirname; + static folder = Config.cwd.replace(Config.parentPath, '').substring(1); + + static isProjectVersion = Config.folder === Config.nodeModuleName; + static isLocalVersion = false; + + static getConfigFile_(): string { + return path.resolve(Config.dir, '..', Config.configFile); + } + + static getPackageFile_(): string { + return path.resolve(Config.dir, '..', Config.packageFile) + } + + static getSeleniumDir(): string { + return path.resolve(Config.dir, '..', '..', 'selenium/'); + } + static getBaseDir(): string { + return path.resolve(Config.dir, '..', '..'); + } + + /** + * Get the binary versions from the configuration file. + * @returns A map of the versions defined in the configuration file. + */ + static binaryVersions(): ConfigFile { + let configFile = require(Config.getConfigFile_()); + let configVersions: ConfigFile = {}; + configVersions.selenium = configFile.webdriverVersions.selenium; + configVersions.chrome = configFile.webdriverVersions.chromedriver; + configVersions.gecko = configFile.webdriverVersions.geckodriver; + configVersions.ie = configFile.webdriverVersions.iedriver; + configVersions.android = configFile.webdriverVersions.androidsdk; + configVersions.appium = configFile.webdriverVersions.appium; + return configVersions; + } + + /** + * Get the CDN urls from the configuration file. + * @returns A map of the CDN versions defined in the configuration file. + */ + static cdnUrls(): ConfigFile { + let configFile = require(Config.getConfigFile_()); + let configCdnUrls: ConfigFile = {}; + configCdnUrls.selenium = configFile.cdnUrls.selenium; + configCdnUrls.chrome = configFile.cdnUrls.chromedriver; + configCdnUrls.gecko = configFile.cdnUrls.geckodriver; + configCdnUrls.ie = configFile.cdnUrls.iedriver; + configCdnUrls.android = configFile.cdnUrls.androidsdk; + return configCdnUrls; + } + + /** + * Get the package version. + */ + static getVersion(): string { + let packageFile = require(Config.getPackageFile_()); + return packageFile.version; + } +} diff --git a/lib/files/downloaded_binary.ts b/lib/files/downloaded_binary.ts new file mode 100644 index 00000000..662300c6 --- /dev/null +++ b/lib/files/downloaded_binary.ts @@ -0,0 +1,20 @@ +import {Binary} from '../binaries/binary'; + +/** + * The downloaded binary is the binary with the list of versions downloaded. + */ +export class DownloadedBinary extends Binary { + versions: string[] = []; + binary: Binary; + + constructor(binary: Binary) { + super(); + this.binary = binary; + this.name = binary.name; + this.versionCustom = binary.versionCustom; + } + + id(): string { + return this.binary.id(); + } +} diff --git a/lib/files/downloader.ts b/lib/files/downloader.ts new file mode 100644 index 00000000..a667fda0 --- /dev/null +++ b/lib/files/downloader.ts @@ -0,0 +1,194 @@ +import * as fs from 'fs'; +import * as http from 'http'; +import * as os from 'os'; +import * as path from 'path'; +import * as q from 'q'; +import * as request from 'request'; +import * as url from 'url'; + +import {Binary} from '../binaries/binary'; +import {Logger} from '../cli'; + +let logger = new Logger('downloader'); + +/** + * The file downloader. + */ +export class Downloader { + /** + * Download the binary file. + * @param binary The binary of interest. + * @param outputDir The directory where files are downloaded and stored. + * @param opt_proxy The proxy for downloading files. + * @param opt_ignoreSSL To ignore SSL. + * @param opt_callback Callback method to be executed after the file is downloaded. + */ + static downloadBinary( + binary: Binary, outputDir: string, opt_proxy?: string, opt_ignoreSSL?: boolean, + opt_callback?: Function): void { + logger.info(binary.name + ': downloading version ' + binary.version()); + var url = binary.url(os.type(), os.arch()); + if (!url) { + logger.error(binary.name + ' v' + binary.version() + ' is not available for your system.'); + return; + } + Downloader.httpGetFile_( + url, binary.filename(os.type(), os.arch()), outputDir, opt_proxy, opt_ignoreSSL, + (filePath: string) => { + if (opt_callback) { + opt_callback(binary, outputDir, filePath); + } + }); + } + + /** + * Resolves proxy based on values set + * @param fileUrl The url to download the file. + * @param opt_proxy The proxy to connect to to download files. + * @return Either undefined or the proxy. + */ + static resolveProxy_(fileUrl: string, opt_proxy?: string): string { + let protocol = url.parse(fileUrl).protocol; + let hostname = url.parse(fileUrl).hostname; + + if (opt_proxy) { + return opt_proxy; + } else { + // If the NO_PROXY environment variable exists and matches the host name, + // to ignore the resolve proxy. + // the checks to see if it exists and equal to empty string is to help with testing + let noProxy: string = process.env.NO_PROXY || process.env.no_proxy; + if (noProxy) { + // array of hostnames/domain names listed in the NO_PROXY environment variable + let noProxyTokens = noProxy.split(','); + // check if the fileUrl hostname part does not end with one of the + // NO_PROXY environment variable's hostnames/domain names + for (let noProxyToken of noProxyTokens) { + if (hostname.indexOf(noProxyToken) !== -1) { + return undefined; + } + } + } + + // If the HTTPS_PROXY and HTTP_PROXY environment variable is set, use that as the proxy + if (protocol === 'https:') { + return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || + process.env.http_proxy; + } else if (protocol === 'http:') { + return process.env.HTTP_PROXY || process.env.http_proxy; + } + } + return null; + } + + static httpHeadContentLength(fileUrl: string, opt_proxy?: string, opt_ignoreSSL?: boolean): + q.Promise { + let deferred = q.defer(); + if (opt_ignoreSSL) { + logger.info('ignoring SSL certificate'); + } + + let options = { + method: 'HEAD', + url: fileUrl, + strictSSL: !opt_ignoreSSL, + rejectUnauthorized: !opt_ignoreSSL, + proxy: Downloader.resolveProxy_(fileUrl, opt_proxy) + }; + + let contentLength = 0; + request(options).on('response', (response) => { + contentLength = response.headers['content-length']; + deferred.resolve(contentLength); + }); + return deferred.promise; + } + + /** + * Ceates the GET request for the file name. + * @param fileUrl The url to download the file. + * @param fileName The name of the file to download. + * @param opt_proxy The proxy to connect to to download files. + * @param opt_ignoreSSL To ignore SSL. + */ + static httpGetFile_( + fileUrl: string, fileName: string, outputDir: string, opt_proxy?: string, + opt_ignoreSSL?: boolean, callback?: Function): void { + logger.info('curl -o ' + outputDir + '/' + fileName + ' ' + fileUrl); + let filePath = path.join(outputDir, fileName); + let file = fs.createWriteStream(filePath); + let contentLength = 0; + + interface Options { + url: string; + timeout: number; + strictSSL?: boolean; + rejectUnauthorized?: boolean; + proxy?: string; + headers?: {[key: string]: any}; + [key: string]: any; + } + + let options: Options = { + url: fileUrl, + // default Linux can be anywhere from 20-120 seconds + // increasing this arbitrarily to 4 minutes + timeout: 240000 + } + + if (opt_ignoreSSL) { + logger.info('ignoring SSL certificate'); + options.strictSSL = !opt_ignoreSSL; + options.rejectUnauthorized = !opt_ignoreSSL; + } + + if (opt_proxy) { + options.proxy = Downloader.resolveProxy_(fileUrl, opt_proxy); + if (options.url.indexOf('https://') === 0) { + options.url = options.url.replace('https://', 'http://'); + } + } + + request(options) + .on('response', + (response) => { + if (response.statusCode !== 200) { + fs.unlinkSync(filePath); + logger.error('Error: Got code ' + response.statusCode + ' from ' + fileUrl); + } + contentLength = response.headers['content-length']; + }) + .on('error', + (error) => { + if ((error as any).code === 'ETIMEDOUT') { + logger.error('Connection timeout downloading: ' + fileUrl); + logger.error('Default timeout is 4 minutes.'); + + } else if ((error as any).connect) { + logger.error('Could not connect to the server to download: ' + fileUrl); + } + logger.error(error); + fs.unlinkSync(filePath); + }) + .pipe(file); + + file.on('close', function() { + fs.stat(filePath, function(err, stats) { + if (err) { + logger.error('Error: Got error ' + err + ' from ' + fileUrl); + return; + } + if (stats.size != contentLength) { + logger.error( + 'Error: corrupt download for ' + fileName + + '. Please re-run webdriver-manager update'); + fs.unlinkSync(filePath); + return; + } + if (callback) { + callback(filePath); + } + }); + }); + } +} diff --git a/lib/files/file_manager.ts b/lib/files/file_manager.ts new file mode 100644 index 00000000..d6c65fb7 --- /dev/null +++ b/lib/files/file_manager.ts @@ -0,0 +1,238 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as q from 'q'; +import * as rimraf from 'rimraf'; + +import {Binary, BinaryMap, ChromeDriver, IEDriver, AndroidSDK, Appium, StandAlone, OS} from +'../binaries'; +import {DownloadedBinary} from './downloaded_binary'; +import {Downloader} from './downloader'; +import {Logger} from '../cli'; +import {GeckoDriver} from '../binaries/gecko_driver'; + +let logger = new Logger('file_manager'); + +/** + * The File Manager class is where the webdriver manager will compile a list of + * binaries that could be downloaded and get a list of previously downloaded + * file versions. + */ +export class FileManager { + /** + * Create a directory if it does not exist. + * @param outputDir The directory to create. + */ + static makeOutputDirectory(outputDir: string) { + try { + fs.statSync(outputDir); + } catch (e) { + logger.info('creating folder ' + outputDir); + fs.mkdirSync(outputDir); + } + } + + /** + * For the operating system, check against the list of operating systems that the + * binary is available for. + * @param osType The operating system. + * @param binary The class type to have access to the static properties. + * @returns If the binary is available for the operating system. + */ + static checkOS_(osType: string, binary: typeof Binary): boolean { + for (let os in binary.os) { + if (OS[os] == osType) { + return true; + } + } + return false; + } + + /** + * For the operating system, create a list that includes the binaries + * for selenium standalone, chrome, and internet explorer. + * @param osType The operating system. + * @param alternateCDN URL of the alternative CDN to be used instead of the default ones. + * @returns A binary map that are available for the operating system. + */ + static compileBinaries_(osType: string, alternateCDN?: string): BinaryMap { + let binaries: BinaryMap = {}; + if (FileManager.checkOS_(osType, StandAlone)) { + binaries[StandAlone.id] = new StandAlone(alternateCDN); + } + if (FileManager.checkOS_(osType, ChromeDriver)) { + binaries[ChromeDriver.id] = new ChromeDriver(alternateCDN); + } + if (FileManager.checkOS_(osType, GeckoDriver)) { + binaries[GeckoDriver.id] = new GeckoDriver(alternateCDN); + } + if (FileManager.checkOS_(osType, IEDriver)) { + binaries[IEDriver.id] = new IEDriver(alternateCDN); + } + if (FileManager.checkOS_(osType, AndroidSDK)) { + binaries[AndroidSDK.id] = new AndroidSDK(alternateCDN); + } + if (FileManager.checkOS_(osType, Appium)) { + binaries[Appium.id] = new Appium(alternateCDN); + } + return binaries; + } + + /** + * Look up the operating system and compile a list of binaries that are available + * for the system. + * @param alternateCDN URL of the alternative CDN to be used instead of the default ones. + * @returns A binary map that is available for the operating system. + */ + static setupBinaries(alternateCDN?: string): BinaryMap { + return FileManager.compileBinaries_(os.type(), alternateCDN); + } + + /** + * Get the list of existing files from the output directory + * @param outputDir The directory where binaries are saved + * @returns A list of existing files. + */ + static getExistingFiles(outputDir: string): string[] { + try { + return fs.readdirSync(outputDir); + } catch (e) { + return []; + } + } + + /** + * For the binary, operating system, and system architecture, look through + * the existing files and the downloaded binary + * @param binary The binary of interest + * @param osType The operating system. + * @param existingFiles A list of existing files. + * @returns The downloaded binary with all the versions found. + */ + static downloadedVersions_(binary: Binary, osType: string, arch: string, existingFiles: string[]): + DownloadedBinary { + let versions: string[] = []; + for (let existPos in existingFiles) { + let existFile: string = existingFiles[existPos]; + // use only files that have a prefix and suffix that we care about + if (existFile.indexOf(binary.prefix()) === 0) { + let editExistFile = existFile.replace(binary.prefix(), ''); + // if the suffix matches the executable suffix, add it + if (binary.suffix(osType, arch) === binary.executableSuffix(osType)) { + versions.push(editExistFile.replace(binary.suffix(osType, arch), '')); + } + // if the suffix does not match the executable, + // the binary is something like: .exe and .zip + else if (existFile.indexOf(binary.suffix(osType, arch)) === -1) { + editExistFile = editExistFile.replace(binary.executableSuffix(osType), ''); + editExistFile = editExistFile.indexOf('_') === 0 ? + editExistFile.substring(1, editExistFile.length) : + editExistFile; + versions.push(editExistFile); + } + } + } + if (versions.length === 0) { + return null; + } + let downloadedBinary = new DownloadedBinary(binary); + downloadedBinary.versions = versions; + return downloadedBinary; + } + + /** + * Finds all the downloaded binary versions stored in the output directory. + * @param outputDir The directory where files are downloaded and stored. + * @returns An dictionary map of all the downloaded binaries found in the output folder. + */ + static downloadedBinaries(outputDir: string): BinaryMap { + let ostype = os.type(); + let arch = os.arch(); + let binaries = FileManager.setupBinaries(); + let existingFiles = FileManager.getExistingFiles(outputDir); + let downloaded: BinaryMap = {}; + for (let bin in binaries) { + let binary = FileManager.downloadedVersions_(binaries[bin], ostype, arch, existingFiles); + if (binary != null) { + downloaded[binary.id()] = binary; + } + } + return downloaded; + } + + /** + * Check to see if the binary version should be downloaded. + * @param binary The binary of interest. + * @param outputDir The directory where files are downloaded and stored. + * @returns If the file should be downloaded. + */ + static toDownload( + binary: T, outputDir: string, proxy: string, ignoreSSL: boolean): q.Promise { + let osType = os.type(); + let osArch = os.arch(); + let filePath: string; + let readData: Buffer; + let deferred = q.defer(); + let downloaded: BinaryMap = FileManager.downloadedBinaries(outputDir); + + if (downloaded[binary.id()]) { + let downloadedBinary = downloaded[binary.id()]; + let versions = downloadedBinary.versions; + let version = binary.version(); + for (let index in versions) { + let v = versions[index]; + if (v === version) { + filePath = path.resolve(outputDir, binary.filename(osType, osArch)); + readData = fs.readFileSync(filePath); + + // we have the version, verify it is the correct file size + let contentLength = + Downloader.httpHeadContentLength(binary.url(osType, osArch), proxy, ignoreSSL); + return contentLength.then((value: any): boolean => { + if (value == readData.length) { + return false; + } else { + logger.warn( + path.basename(filePath) + ' expected length ' + value + ', found ' + + readData.length); + logger.warn('removing file: ' + filePath); + return true; + } + }); + } + } + } + deferred.resolve(true); + return deferred.promise; + } + + /** + * Removes the existing files found in the output directory that match the + * binary prefix names. + * @param outputDir The directory where files are downloaded and stored. + */ + static removeExistingFiles(outputDir: string): void { + try { + fs.statSync(outputDir); + } catch (e) { + logger.warn('path does not exist ' + outputDir); + return; + } + let existingFiles = FileManager.getExistingFiles(outputDir); + if (existingFiles.length === 0) { + logger.warn('no files found in path ' + outputDir); + return; + } + + let binaries = FileManager.setupBinaries(); + existingFiles.forEach((file) => { + for (let binPos in binaries) { + let bin: Binary = binaries[binPos]; + if (file.indexOf(bin.prefix()) !== -1) { + bin.remove(path.join(outputDir, file)); + logger.info('removed ' + file); + } + } + }) + } +} diff --git a/lib/files/index.ts b/lib/files/index.ts new file mode 100644 index 00000000..74536c2e --- /dev/null +++ b/lib/files/index.ts @@ -0,0 +1,3 @@ +export * from './downloaded_binary'; +export * from './downloader'; +export * from './file_manager'; diff --git a/lib/index.ts b/lib/index.ts deleted file mode 100644 index e2e8a56a..00000000 --- a/lib/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Exports when using this module as a dependency. - -// Expose the loglevel api. -import * as loglevel from 'loglevel'; -export let setLogLevel = loglevel.getLogger('webdriver-manager').setLevel; - -// Export commands used in the cli. -export {clean} from './cmds/clean'; -// Options that are used by the exported commands. -export {Options} from './cmds/options'; -export {shutdown} from './cmds/shutdown'; -export {start} from './cmds/start'; -export {status} from './cmds/status'; -export {update} from './cmds/update'; -export {ChromeDriver} from './provider/chromedriver'; -export {GeckoDriver} from './provider/geckodriver'; -export {IEDriver} from './provider/iedriver'; -export {ProviderConfig, ProviderInterface} from './provider/provider'; -export {SeleniumServer} from './provider/selenium_server'; diff --git a/lib/provider/appium.spec-int.ts b/lib/provider/appium.spec-int.ts deleted file mode 100644 index ea2b6d01..00000000 --- a/lib/provider/appium.spec-int.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {spawnProcess} from '../../spec/support/helpers/test_utils'; -import {Appium} from './appium'; - -xdescribe('appium', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - let origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - describe('getVersion', () => { - let proc: childProcess.ChildProcess; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('with a http server', () => { - beforeAll(async () => { - proc = spawnProcess('node', ['dist/spec/server/http_server.js']); - console.log('http-server: ' + proc.pid); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - }); - - afterAll(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - process.kill(proc.pid); - }); - - it('should get the version from the local server', async () => { - const appium = new Appium({ - outDir: tmpDir, - requestUrl: 'http://127.0.0.1:8812/spec/support/files/appium.json' - }); - expect(await appium.getVersion()).toBe('10.11.12'); - }); - }); - }); - - describe('setup', () => { - beforeAll(() => { - origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - it('should create the package.json file', async () => { - const appium = new Appium({outDir: tmpDir}); - await appium.setup('10.11.12'); - const packageJson = path.resolve(tmpDir, 'appium', 'package.json'); - expect(fs.statSync(packageJson).size).not.toBe(0); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/appium.ts b/lib/provider/appium.ts deleted file mode 100644 index 792deb28..00000000 --- a/lib/provider/appium.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import * as semver from 'semver'; - -import {ProviderConfig, ProviderInterface} from './provider'; -import {requestBody} from './utils/http_utils'; - -const log = loglevel.getLogger('webdriver-manager'); - -export class Appium implements ProviderInterface { - ignoreSSL: boolean; - outDir: string; - outDirAppium: string; - proxy: string; - requestUrl = 'http://registry.npmjs.org/appium'; - - constructor(providerConfig?: ProviderConfig) { - if (providerConfig) { - this.ignoreSSL = providerConfig.ignoreSSL; - if (providerConfig.outDir) { - this.outDir = providerConfig.outDir; - } - if (providerConfig.proxy) { - this.proxy = providerConfig.proxy; - } - if (providerConfig.requestUrl) { - this.requestUrl = providerConfig.requestUrl; - } - } - } - - /** - * If no valid version is provided get version from appium - */ - async getVersion(): Promise { - const body = await requestBody( - this.requestUrl, {proxy: this.proxy, ignoreSSL: this.ignoreSSL}); - return JSON.parse(body)['dist-tags']['latest']; - } - /** - * Creates appium directory and package.json file. - * @param version Optional to provide the version number or latest. - */ - async setup(version?: string): Promise { - if (!semver.valid(version)) { - version = await this.getVersion(); - } - this.outDirAppium = path.resolve(this.outDir, 'appium'); - try { - rimraf.sync(this.outDirAppium); - } catch (err) { - } - fs.mkdirSync(this.outDirAppium); - const packageJson = { - scripts: {appium: 'appium'}, - dependencies: {appium: '^' + version} - }; - fs.writeFileSync( - path.resolve(this.outDirAppium, 'package.json'), - JSON.stringify(packageJson)); - } - - /** - * Creates an appium/package.json file and installs the appium dependency. - * @param version Optional to provide the version number or latest. - */ - async updateBinary(version?: string): Promise { - log.info('appium: installing appium'); - await this.setup(version); - childProcess.execSync('npm install', {cwd: this.outDirAppium}); - } -} \ No newline at end of file diff --git a/lib/provider/chromedriver.spec-int.ts b/lib/provider/chromedriver.spec-int.ts deleted file mode 100644 index a57ea5dc..00000000 --- a/lib/provider/chromedriver.spec-int.ts +++ /dev/null @@ -1,132 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; -import {ChromeDriver, semanticVersionParser, versionParser} from './chromedriver'; -import {convertXmlToVersionList} from './utils/cloud_storage_xml'; -import {getVersion} from './utils/version_list'; - -describe('chromedriver', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - - describe('class ChromeDriver', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('updateBinary', () => { - beforeEach(() => { - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should download the latest for MacOS', async () => { - if (await checkConnectivity('update binary for mac test')) { - const chromedriver = - new ChromeDriver({outDir: tmpDir, osType: 'Darwin'}); - await chromedriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, '.zip', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, 'mac'); - const executableFile = - path.resolve(tmpDir, 'chromedriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Windows x64', async () => { - if (await checkConnectivity('update binary for win x64 test')) { - const chromedriver = new ChromeDriver( - {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); - await chromedriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, '.zip', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, 'win32'); - const executableFile = path.resolve( - tmpDir, 'chromedriver_' + versionObj.version + '.exe'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Windows x32', async () => { - if (await checkConnectivity('update binary for win x32 test')) { - const chromedriver = new ChromeDriver( - {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x32'}); - await chromedriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, '.zip', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, 'win32'); - const executableFile = path.resolve( - tmpDir, 'chromedriver_' + versionObj.version + '.exe'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Linux x64', async () => { - if (await checkConnectivity('update binary for linux x64 test')) { - const chromedriver = new ChromeDriver( - {outDir: tmpDir, osType: 'Linux', osArch: 'x64'}); - await chromedriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, '.zip', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, 'linux64'); - const executableFile = - path.resolve(tmpDir, 'chromedriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should not download for Linux x32', async () => { - if (await checkConnectivity('update binary for linux x32 test')) { - const chromedriver = new ChromeDriver( - {outDir: tmpDir, osType: 'Linux', osArch: 'x32'}); - chromedriver.updateBinary() - .then(() => { - expect(false).toBeTruthy(); - }) - .catch(() => {}); - } - }); - }); - }); -}); diff --git a/lib/provider/chromedriver.spec-proxy.ts b/lib/provider/chromedriver.spec-proxy.ts deleted file mode 100644 index 649718dd..00000000 --- a/lib/provider/chromedriver.spec-proxy.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {proxyBaseUrl} from '../../spec/server/env'; -import {spawnProcess} from '../../spec/support/helpers/test_utils'; -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; -import {ChromeDriver, semanticVersionParser, versionParser} from './chromedriver'; -import {convertXmlToVersionList} from './utils/cloud_storage_xml'; -import {getVersion} from './utils/version_list'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -describe('chromedriver', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - - describe('class ChromeDriver', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - let proxyProc: childProcess.ChildProcess; - - describe('updateBinary', () => { - beforeEach((done) => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); - log.debug('proxy-server: ' + proxyProc.pid); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - setTimeout(done, 3000); - }); - - afterEach((done) => { - process.kill(proxyProc.pid); - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - try { - rimraf.sync(tmpDir); - } catch (err) { - } - setTimeout(done, 5000); - }); - - it('should download the binary using a proxy', async (done) => { - if (!await checkConnectivity('update binary for mac test')) { - done(); - } - const chromeDriver = new ChromeDriver({ - ignoreSSL: true, - osType: 'Darwin', - osArch: 'x64', - outDir: tmpDir, - proxy: proxyBaseUrl - }); - await chromeDriver.updateBinary(); - const configFile = path.resolve(tmpDir, 'chromedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'chromedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, '.zip', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, 'mac'); - const executableFile = - path.resolve(tmpDir, 'chromedriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/chromedriver.spec-unit.ts b/lib/provider/chromedriver.spec-unit.ts deleted file mode 100644 index 47e6afe7..00000000 --- a/lib/provider/chromedriver.spec-unit.ts +++ /dev/null @@ -1,72 +0,0 @@ -import * as fs from 'fs'; -import {ChromeDriver, osHelper, semanticVersionParser, versionParser,} from './chromedriver'; - -describe('chromedriver', () => { - describe('osHelper', () => { - it('should work for mac', () => { - expect(osHelper('Darwin', 'x64')).toBe('mac'); - }); - it('should work for windows', () => { - expect(osHelper('Windows_NT', 'x32')).toBe('win32'); - expect(osHelper('Windows_NT', 'x64')).toBe('win32'); - }); - it('should work for linux', () => { - expect(osHelper('Linux', 'x32')).toBeNull(); - expect(osHelper('Linux', 'x64')).toBe('linux64'); - }); - }); - - describe('verisonParser', () => { - it('should generate a semantic version', () => { - let version = versionParser('10.0/chromedriver_linux64.zip'); - expect(version).toBe('10.0'); - - version = versionParser('10.100/chromedriver_linux64.zip'); - expect(version).toBe('10.100'); - }); - }); - - describe('semanticVerisonParser', () => { - it('should generate a semantic version', () => { - let version = semanticVersionParser('10.0/chromedriver_linux64.zip'); - expect(version).toBe('10.0.0'); - - version = semanticVersionParser('10.100/chromedriver_linux64.zip'); - expect(version).toBe('10.100.0'); - }); - }); - - describe('class ChromeDriver', () => { - describe('getStatus', () => { - it('should get the status from the config file for Windows', () => { - const configCache = `{ - "last": "/path/to/chromedriver_100.1.exe", - "all": [ - "/path/to/chromedriver_90.0.exe", - "/path/to/chromedriver_99.0-beta.exe", - "/path/to/chromedriver_100.1.exe" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const chromedriver = new ChromeDriver({osType: 'Windows_NT'}); - expect(chromedriver.getStatus()) - .toBe('90.0, 99.0-beta, 100.1 (latest)'); - }); - - it('should get the status from the config file for not Windows', () => { - const configCache = `{ - "last": "/path/to/chromedriver_100.1", - "all": [ - "/path/to/chromedriver_90.0", - "/path/to/chromedriver_99.0-beta", - "/path/to/chromedriver_100.1" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const chromedriver = new ChromeDriver({osType: 'Darwin'}); - expect(chromedriver.getStatus()) - .toBe('90.0, 99.0-beta, 100.1 (latest)'); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/chromedriver.ts b/lib/provider/chromedriver.ts deleted file mode 100644 index 4f69d87b..00000000 --- a/lib/provider/chromedriver.ts +++ /dev/null @@ -1,286 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import {OUT_DIR, ProviderConfig, ProviderInterface} from './provider'; -import {convertXmlToVersionList, updateXml} from './utils/cloud_storage_xml'; -import {changeFilePermissions, generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, unzipFile, zipFileList,} from './utils/file_utils'; -import {requestBinary} from './utils/http_utils'; -import {getVersion} from './utils/version_list'; - -export class ChromeDriver implements ProviderInterface { - cacheFileName = 'chromedriver.xml'; - configFileName = 'chromedriver.config.json'; - ignoreSSL = false; - osType = os.type(); - osArch = os.arch(); - outDir = OUT_DIR; - proxy: string = null; - requestUrl = 'https://chromedriver.storage.googleapis.com/'; - seleniumFlag = '-Dwebdriver.chrome.driver'; - - constructor(providerConfig?: ProviderConfig) { - if (providerConfig) { - if (providerConfig.cacheFileName) { - this.cacheFileName = providerConfig.cacheFileName; - } - if (providerConfig.configFileName) { - this.configFileName = providerConfig.configFileName; - } - this.ignoreSSL = providerConfig.ignoreSSL; - if (providerConfig.osArch) { - this.osArch = providerConfig.osArch; - } - if (providerConfig.osType) { - this.osType = providerConfig.osType; - } - if (providerConfig.outDir) { - this.outDir = providerConfig.outDir; - } - if (providerConfig.proxy) { - this.proxy = providerConfig.proxy; - } - if (providerConfig.requestUrl) { - this.requestUrl = providerConfig.requestUrl; - } - } - } - - /** - * Should update the cache and download, find the version to download, - * then download that binary. - * @param version Optional to provide the version number or latest. - */ - async updateBinary(version?: string): Promise { - await updateXml(this.requestUrl, { - fileName: path.resolve(this.outDir, this.cacheFileName), - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - - const versionList = convertXmlToVersionList( - path.resolve(this.outDir, this.cacheFileName), '.zip', versionParser, - semanticVersionParser); - const versionObj = getVersion( - versionList, osHelper(this.osType, this.osArch), - formatVersion(version)); - - const chromeDriverUrl = this.requestUrl + versionObj.url; - const chromeDriverZip = path.resolve(this.outDir, versionObj.name); - - // We should check the zip file size if it exists. The size will - // be used to either make the request, or quit the request if the file - // size matches. - let fileSize = 0; - try { - fileSize = fs.statSync(chromeDriverZip).size; - } catch (err) { - } - await requestBinary(chromeDriverUrl, { - fileName: chromeDriverZip, - fileSize, - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - - // Unzip and rename all the files (a grand total of 1) and set the - // permissions. - const fileList = zipFileList(chromeDriverZip); - const fileItem = path.resolve(this.outDir, fileList[0]); - unzipFile(chromeDriverZip, this.outDir); - const renamedFileName = - renameFileWithVersion(fileItem, '_' + versionObj.version); - changeFilePermissions(renamedFileName, '0755', this.osType); - - generateConfigFile( - this.outDir, path.resolve(this.outDir, this.configFileName), - matchBinaries(this.osType), renamedFileName); - return Promise.resolve(); - } - - /** - * Gets the binary file path. - * @param version Optional to provide the version number or latest. - */ - getBinaryPath(version?: string): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - return getBinaryPathFromConfig(configFilePath, version); - } catch (_) { - return null; - } - } - - /** - * Gets a comma delimited list of versions downloaded. Also has the "latest" - * downloaded noted. - */ - getStatus(): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); - const versions: string[] = []; - for (const binaryPath of configJson['all']) { - let version = ''; - let regex = /.*chromedriver_(\d+.\d+.*)/g; - if (this.osType === 'Windows_NT') { - regex = /.*chromedriver_(\d+.\d+.*).exe/g; - } - try { - const exec = regex.exec(binaryPath); - if (exec && exec[1]) { - version = exec[1]; - } - } catch (_) { - } - - if (configJson['last'] === binaryPath) { - version += ' (latest)'; - } - versions.push(version); - } - return versions.join(', '); - } catch (_) { - return null; - } - } - - /** - * Get a line delimited list of files removed. - */ - cleanFiles(): string { - return removeFiles(this.outDir, [/chromedriver.*/g]); - } -} - -/** - * Helps translate the os type and arch to the download name associated - * with composing the download link. - * @param ostype The operating stystem type. - * @param osarch The chip architecture. - * @returns The download name associated with composing the download link. - */ -export function osHelper(ostype: string, osarch: string): string { - if (ostype === 'Darwin') { - return 'mac'; - } else if (ostype === 'Windows_NT') { - if (osarch === 'x64') { - return 'win32'; - } else if (osarch === 'x32') { - return 'win32'; - } - } else if (ostype === 'Linux') { - if (osarch === 'x64') { - return 'linux64'; - } else if (osarch === 'x32') { - return null; - } - } - return null; -} - -/** - * Captures the version name which includes the semantic version and extra - * metadata. So an example for 12.34/chromedriver_linux64.zip, - * the version is 12.34. - * - * The new version is 70.0.3538.16/chromedriver_linux64.zip. This will return - * 70.0.3538.16. - * @param xmlKey The xml key including the partial url. - */ -export function versionParser(xmlKey: string) { - const newRegex = /([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; - try { - const exec = newRegex.exec(xmlKey); - if (exec) { - return exec[1]; - } - } catch (_) { - } - const oldRegex = /([0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; - try { - const exec = oldRegex.exec(xmlKey); - if (exec) { - return exec[1]; - } - } catch (_) { - } - return null; -} - -/** - * Captures the version name which includes the semantic version and extra - * metadata. So an example for 12.34/chromedriver_linux64.zip, - * the version is 12.34.0. - * - * The new version is 70.0.3538.16/chromedriver_linux64.zip. This will return - * 70.0.3538. - * @param xmlKey The xml key including the partial url. - */ -export function semanticVersionParser(xmlKey: string): string|null { - const newRegex = /([0-9]*\.[0-9]*\.[0-9]*).[0-9]*\/chromedriver_.*\.zip/g; - try { - const exec = newRegex.exec(xmlKey); - if (exec) { - return exec[1]; - } - } catch (_) { - } - const oldRegex = /([0-9]*\.[0-9]*)\/chromedriver_.*\.zip/g; - try { - const exec = oldRegex.exec(xmlKey); - if (exec) { - return exec[1] + '.0'; - } - } catch (_) { - } - return null; -} - -/** - * Matches the installed binaries depending on the operating system. - * @param ostype The operating stystem type. - */ -export function matchBinaries(ostype: string): RegExp|null { - if (ostype === 'Darwin' || ostype === 'Linux') { - return /chromedriver_\d+.\d+.*/g; - } else if (ostype === 'Windows_NT') { - return /chromedriver_\d+.\d+.*.exe/g; - } - return null; -} - -/** - * Specifically to chromedriver, when downloading a version, the version - * you give webdriver-manager is not in the same as the output from the - * semanticVersionParser. So getting the version to the versionList will not - * be just a dictionary look up versionList[version]. The version will have - * to be formatted. - * - * Example: - * 2.44 will return with the formatted semantic version 2.44.0 - * 70.0.1.1 will return with the formatted semantic version 70.0.1 - * - * @param version The actual version. - */ -export function formatVersion(version: string): string|null { - const newRegex = /([0-9]*\.[0-9]*\.[0-9]*).[0-9]*/g; - try { - const exec = newRegex.exec(version); - if (exec) { - return exec[1]; - } - } catch (_) { - // no-op: if exec[1] is not reachable, move to the next regex. - } - const oldRegex = /([0-9]*\.[0-9]*)/g; - try { - const exec = oldRegex.exec(version); - if (exec) { - return exec[1] + '.0'; - } - } catch (_) { - // no-op: if exec[1] is not reachable, move on to return null. - } - return null; -} \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-int.ts b/lib/provider/geckodriver.spec-int.ts deleted file mode 100644 index c58aac12..00000000 --- a/lib/provider/geckodriver.spec-int.ts +++ /dev/null @@ -1,135 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; -import {GeckoDriver} from './geckodriver'; -import {convertJsonToVersionList} from './utils/github_json'; -import {getVersion} from './utils/version_list'; - -describe('geckodriver', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - describe('class GeckoDriver', () => { - describe('updateBinary', () => { - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - beforeEach(() => { - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should download the latest for MacOS', async () => { - if (await checkConnectivity('update binary for mac test')) { - const geckodriver = - new GeckoDriver({outDir: tmpDir, osType: 'Darwin'}); - await geckodriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'macos'); - const executableFile = - path.resolve(tmpDir, 'geckodriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Windows x64', async () => { - if (await checkConnectivity('update binary for win64 test')) { - const geckodriver = new GeckoDriver( - {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); - await geckodriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'win64'); - const executableFile = path.resolve( - tmpDir, 'geckodriver_' + versionObj.version + '.exe'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Windows x32', async () => { - if (await checkConnectivity('update binary for win32 test')) { - const geckodriver = new GeckoDriver( - {outDir: tmpDir, osType: 'Windows_NT', osArch: 'x32'}); - await geckodriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'win64'); - const executableFile = path.resolve( - tmpDir, 'geckodriver_' + versionObj.version + '.exe'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Linux x64', async () => { - if (await checkConnectivity('update binary for linux64 test')) { - const geckodriver = - new GeckoDriver({outDir: tmpDir, osType: 'Linux', osArch: 'x64'}); - await geckodriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'linux64'); - const executableFile = - path.resolve(tmpDir, 'geckodriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - - it('should download the latest for Linux x32', async () => { - if (await checkConnectivity('update binary for linux32 test')) { - const geckodriver = - new GeckoDriver({outDir: tmpDir, osType: 'Linux', osArch: 'x32'}); - await geckodriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'linux32'); - const executableFile = - path.resolve(tmpDir, 'geckodriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-proxy.ts b/lib/provider/geckodriver.spec-proxy.ts deleted file mode 100644 index 2a190513..00000000 --- a/lib/provider/geckodriver.spec-proxy.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {proxyBaseUrl} from '../../spec/server/env'; -import {spawnProcess} from '../../spec/support/helpers/test_utils'; -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; -import {GeckoDriver} from './geckodriver'; -import {convertJsonToVersionList} from './utils/github_json'; -import {getVersion} from './utils/version_list'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -describe('geckodriver', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - - describe('class GeckoDriver', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - let proxyProc: childProcess.ChildProcess; - - describe('updateBinary', () => { - beforeEach((done) => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); - log.debug('proxy-server: ' + proxyProc.pid); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - setTimeout(done, 3000); - }); - - afterEach((done) => { - process.kill(proxyProc.pid); - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - try { - rimraf.sync(tmpDir); - } catch (err) { - } - setTimeout(done, 5000); - }); - - it('should download the binary using a proxy', async (done) => { - if (!await checkConnectivity('update binary for mac test')) { - done(); - } - const geckoDriver = new GeckoDriver( - {outDir: tmpDir, osType: 'Darwin', proxy: proxyBaseUrl}); - await geckoDriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'geckodriver.config.json'); - const jsonFile = path.resolve(tmpDir, 'geckodriver.json'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(jsonFile).size).toBeTruthy(); - - const versionList = convertJsonToVersionList(jsonFile); - const versionObj = getVersion(versionList, 'macos'); - const executableFile = - path.resolve(tmpDir, 'geckodriver_' + versionObj.version); - expect(fs.statSync(executableFile).size).toBeTruthy(); - done(); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/geckodriver.spec-unit.ts b/lib/provider/geckodriver.spec-unit.ts deleted file mode 100644 index 493853df..00000000 --- a/lib/provider/geckodriver.spec-unit.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as fs from 'fs'; -import {GeckoDriver, osHelper} from './geckodriver'; - -describe('geckodriver', () => { - describe('osHelper', () => { - it('should work for mac', () => { - expect(osHelper('Darwin', 'x64')).toBe('macos'); - }); - it('should work for windows', () => { - expect(osHelper('Windows_NT', 'x32')).toBe('win32'); - expect(osHelper('Windows_NT', 'x64')).toBe('win64'); - }); - it('should work for linux', () => { - expect(osHelper('Linux', 'x32')).toBe('linux32'); - expect(osHelper('Linux', 'x64')).toBe('linux64'); - }); - it('should return null when the type / arch is not known', () => { - expect(osHelper('FooBarOS', '')).toBeNull(); - expect(osHelper('Windows_NT', 'arm')).toBeNull(); - expect(osHelper('Linux', 'arm64')).toBeNull(); - }); - }); - - describe('class GeckoDriver', () => { - describe('getStatus', () => { - it('should get the status from the config file for Windows', () => { - const configCache = `{ - "last": "/path/to/geckodriver_100.1.0.exe", - "all": [ - "/path/to/geckodriver_90.0.0.exe", - "/path/to/geckodriver_99.0.0-beta.exe", - "/path/to/geckodriver_100.1.0.exe" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const geckodriver = new GeckoDriver({osType: 'Windows_NT'}); - expect(geckodriver.getStatus()) - .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); - }); - - it('should get the status from the config file for not Windows', () => { - const configCache = `{ - "last": "/path/to/geckodriver_100.1.0", - "all": [ - "/path/to/geckodriver_90.0.0", - "/path/to/geckodriver_99.0.0-beta", - "/path/to/geckodriver_100.1.0" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const geckodriver = new GeckoDriver({osType: 'Darwin'}); - expect(geckodriver.getStatus()) - .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); - }); - }); - }); -}); diff --git a/lib/provider/geckodriver.ts b/lib/provider/geckodriver.ts deleted file mode 100644 index d4bbf8c3..00000000 --- a/lib/provider/geckodriver.ts +++ /dev/null @@ -1,212 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import {OUT_DIR, ProviderConfig, ProviderInterface} from './provider'; -import {changeFilePermissions, generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, tarFileList, uncompressTarball, unzipFile, zipFileList,} from './utils/file_utils'; -import {convertJsonToVersionList, updateJson} from './utils/github_json'; -import {requestBinary} from './utils/http_utils'; -import {getVersion} from './utils/version_list'; - -export interface GeckoDriverProviderConfig extends ProviderConfig { - oauthToken?: string; -} - -export class GeckoDriver implements ProviderInterface { - cacheFileName = 'geckodriver.json'; - configFileName = 'geckodriver.config.json'; - ignoreSSL = false; - oauthToken: string; - osType = os.type(); - osArch = os.arch(); - outDir = OUT_DIR; - proxy: string = null; - requestUrl = 'https://api.github.com/repos/mozilla/geckodriver/releases'; - seleniumFlag = '-Dwebdriver.gecko.driver'; - - constructor(providerConfig?: GeckoDriverProviderConfig) { - if (providerConfig) { - if (providerConfig.cacheFileName) { - this.cacheFileName = providerConfig.cacheFileName; - } - if (providerConfig.configFileName) { - this.configFileName = providerConfig.configFileName; - } - this.ignoreSSL = providerConfig.ignoreSSL; - if (providerConfig.osArch) { - this.osArch = providerConfig.osArch; - } - if (providerConfig.osType) { - this.osType = providerConfig.osType; - } - if (providerConfig.outDir) { - this.outDir = providerConfig.outDir; - } - if (providerConfig.proxy) { - this.proxy = providerConfig.proxy; - } - if (providerConfig.requestUrl) { - this.requestUrl = providerConfig.requestUrl; - } - if (providerConfig.oauthToken) { - this.oauthToken = providerConfig.oauthToken; - } - } - } - - /** - * Should update the cache and download, find the version to download, - * then download that binary. - * @param version Optional to provide the version number or latest. - */ - async updateBinary(version?: string): Promise { - await updateJson( - this.requestUrl, { - fileName: path.resolve(this.outDir, this.cacheFileName), - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }, - this.oauthToken); - - const versionList = - convertJsonToVersionList(path.resolve(this.outDir, this.cacheFileName)); - const versionObj = - getVersion(versionList, osHelper(this.osType, this.osArch), version); - - const geckoDriverUrl = versionObj.url; - const geckoDriverCompressed = path.resolve(this.outDir, versionObj.name); - - // We should check the zip file size if it exists. The size will - // be used to either make the request, or quit the request if the file - // size matches. - let fileSize = 0; - try { - fileSize = fs.statSync(geckoDriverCompressed).size; - } catch (err) { - } - await requestBinary(geckoDriverUrl, { - fileName: geckoDriverCompressed, - fileSize, - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - - // Uncompress tarball (for linux and mac) or unzip the file for Windows. - // Rename all the files (a grand total of 1) and set the permissions. - let fileList: string[]; - if (this.osType === 'Windows_NT') { - fileList = zipFileList(geckoDriverCompressed); - } else { - fileList = await tarFileList(geckoDriverCompressed); - } - const fileItem = path.resolve(this.outDir, fileList[0]); - - if (this.osType === 'Windows_NT') { - unzipFile(geckoDriverCompressed, this.outDir); - } else { - await uncompressTarball(geckoDriverCompressed, this.outDir); - } - - const renamedFileName = - renameFileWithVersion(fileItem, '_' + versionObj.version); - - changeFilePermissions(renamedFileName, '0755', this.osType); - generateConfigFile( - this.outDir, path.resolve(this.outDir, this.configFileName), - matchBinaries(this.osType), renamedFileName); - return Promise.resolve(); - } - - /** - * Gets the binary file path. - * @param version Optional to provide the version number or latest. - */ - getBinaryPath(version?: string): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - return getBinaryPathFromConfig(configFilePath, version); - } catch (_) { - return null; - } - } - - /** - * Gets a comma delimited list of versions downloaded. Also has the "latest" - * downloaded noted. - */ - getStatus(): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); - const versions: string[] = []; - for (const binaryPath of configJson['all']) { - let version = ''; - let regex = /.*geckodriver_(\d+.\d+.\d+.*)/g; - if (this.osType === 'Windows_NT') { - regex = /.*geckodriver_(\d+.\d+.\d+.*).exe/g; - } - try { - const exec = regex.exec(binaryPath); - if (exec && exec[1]) { - version = exec[1]; - } - } catch (_) { - } - - if (configJson['last'] === binaryPath) { - version += ' (latest)'; - } - versions.push(version); - } - return versions.join(', '); - } catch (_) { - return null; - } - } - - /** - * Get a line delimited list of files removed. - */ - cleanFiles(): string { - return removeFiles(this.outDir, [/geckodriver.*/g]); - } -} - -/** - * Helps translate the os type and arch to the download name associated - * with composing the download link. - * @param ostype The operating stystem type. - * @param osarch The chip architecture. - * @returns The download name associated with composing the download link. - */ -export function osHelper(ostype: string, osarch: string): string { - if (ostype === 'Darwin') { - return 'macos'; - } else if (ostype === 'Windows_NT') { - if (osarch === 'x64') { - return 'win64'; - } else if (osarch === 'x32') { - return 'win32'; - } - } else if (ostype === 'Linux') { - if (osarch === 'x64') { - return 'linux64'; - } else if (osarch === 'x32') { - return 'linux32'; - } - } - return null; -} - -/** - * Matches the installed binaries depending on the operating system. - * @param ostype The operating stystem type. - */ -export function matchBinaries(ostype: string): RegExp|null { - if (ostype === 'Darwin' || ostype === 'Linux') { - return /geckodriver_\d+.\d+.\d+/g; - } else if (ostype === 'Windows_NT') { - return /geckodriver_\d+.\d+.\d+.exe/g; - } - return null; -} \ No newline at end of file diff --git a/lib/provider/iedriver.spec-int.ts b/lib/provider/iedriver.spec-int.ts deleted file mode 100644 index a8867841..00000000 --- a/lib/provider/iedriver.spec-int.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; - -import {IEDriver, semanticVersionParser, versionParser} from './iedriver'; -import {convertXmlToVersionList} from './utils/cloud_storage_xml'; -import {getVersion} from './utils/version_list'; - -describe('iedriver', () => { - describe('class IE Driver', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - beforeEach(() => { - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should download the file', async () => { - if (await checkConnectivity('update binary for windows test')) { - const ieDriver = - new IEDriver({outDir: tmpDir, osType: 'Windows_NT', osArch: 'x64'}); - await ieDriver.updateBinary(); - - const configFile = path.resolve(tmpDir, 'iedriver.config.json'); - const xmlFile = path.resolve(tmpDir, 'iedriver.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, 'IEDriverServer_', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, ''); - const executableFile = path.resolve( - tmpDir, 'IEDriverServer_' + versionObj.version + '.exe'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/iedriver.spec-unit.ts b/lib/provider/iedriver.spec-unit.ts deleted file mode 100644 index ab646b30..00000000 --- a/lib/provider/iedriver.spec-unit.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as fs from 'fs'; -import {IEDriver} from './iedriver'; - -describe('iedriver', () => { - describe('class IEDriver', () => { - describe('getStatus', () => { - it('should get the status from the config file for Windows', () => { - const configCache = `{ - "last": "/path/to/IEDriverServer_100.1.0.exe", - "all": [ - "/path/to/IEDriverServer_90.0.0.exe", - "/path/to/IEDriverServer_99.0.0-beta.exe", - "/path/to/IEDriverServer_100.1.0.exe" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const iedriver = new IEDriver({osType: 'Windows_NT'}); - expect(iedriver.getStatus()) - .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/iedriver.ts b/lib/provider/iedriver.ts deleted file mode 100644 index 2af32b20..00000000 --- a/lib/provider/iedriver.ts +++ /dev/null @@ -1,207 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; - -import {OUT_DIR, ProviderConfig, ProviderInterface,} from './provider'; -import {convertXmlToVersionList, updateXml,} from './utils/cloud_storage_xml'; -import {generateConfigFile, getBinaryPathFromConfig, removeFiles, renameFileWithVersion, unzipFile, zipFileList,} from './utils/file_utils'; -import {requestBinary} from './utils/http_utils'; -import {getVersion} from './utils/version_list'; - -export class IEDriver implements ProviderInterface { - cacheFileName = 'iedriver.xml'; - configFileName = 'iedriver.config.json'; - ignoreSSL = false; - osType = os.type(); - osArch = os.arch(); - outDir = OUT_DIR; - proxy: string = null; - requestUrl = 'https://selenium-release.storage.googleapis.com/'; - seleniumFlag = '-Dwebdriver.ie.driver'; - - constructor(providerConfig?: ProviderConfig) { - if (providerConfig) { - if (providerConfig.cacheFileName) { - this.cacheFileName = providerConfig.cacheFileName; - } - if (providerConfig.configFileName) { - this.configFileName = providerConfig.configFileName; - } - this.ignoreSSL = providerConfig.ignoreSSL; - if (providerConfig.osArch) { - this.osArch = providerConfig.osArch; - } - if (providerConfig.osType) { - this.osType = providerConfig.osType; - } - if (providerConfig.outDir) { - this.outDir = providerConfig.outDir; - } - if (providerConfig.proxy) { - this.proxy = providerConfig.proxy; - } - if (providerConfig.requestUrl) { - this.requestUrl = providerConfig.requestUrl; - } - } - } - - /** - * Should update the cache and download, find the version to download, - * then download that binary. - * @param version Optional to provide the version number or latest. - */ - async updateBinary(version?: string): Promise { - await updateXml(this.requestUrl, { - fileName: path.resolve(this.outDir, this.cacheFileName), - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - const versionList = convertXmlToVersionList( - path.resolve(this.outDir, this.cacheFileName), '.zip', versionParser, - semanticVersionParser); - const versionObj = - getVersion(versionList, osHelper(this.osType, this.osArch), version); - - const chromeDriverUrl = this.requestUrl + versionObj.url; - const chromeDriverZip = path.resolve(this.outDir, versionObj.name); - - // We should check the zip file size if it exists. The size will - // be used to either make the request, or quit the request if the file - // size matches. - let fileSize = 0; - try { - fileSize = fs.statSync(chromeDriverZip).size; - } catch (err) { - } - await requestBinary(chromeDriverUrl, { - fileName: chromeDriverZip, - fileSize, - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - - // Unzip and rename all the files (a grand total of 1) and set the - // permissions. - const fileList = zipFileList(chromeDriverZip); - const fileItem = path.resolve(this.outDir, fileList[0]); - - unzipFile(chromeDriverZip, this.outDir); - const renamedFileName = - renameFileWithVersion(fileItem, '_' + versionObj.version); - generateConfigFile( - this.outDir, path.resolve(this.outDir, this.configFileName), - matchBinaries(this.osType), renamedFileName); - return Promise.resolve(); - } - - /** - * Gets the binary file path. - * @param version Optional to provide the version number or latest. - */ - getBinaryPath(version?: string): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - return getBinaryPathFromConfig(configFilePath, version); - } catch (_) { - return null; - } - } - - /** - * Gets a comma delimited list of versions downloaded. Also has the "latest" - * downloaded noted. - */ - getStatus(): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); - const versions: string[] = []; - for (const binaryPath of configJson['all']) { - let version = ''; - const regex = /.*IEDriverServer_(\d+.\d+.\d+.*).exe/g; - try { - const exec = regex.exec(binaryPath); - if (exec && exec[1]) { - version = exec[1]; - } - } catch (_) { - } - - if (configJson['last'] === binaryPath) { - version += ' (latest)'; - } - versions.push(version); - } - return versions.join(', '); - } catch (_) { - return null; - } - } - - /** - * Get a line delimited list of files removed. - */ - cleanFiles(): string { - return removeFiles(this.outDir, [/IEDriverServer.*/g, /iedriver.*/g]); - } -} - -/** - * Helps translate the os type and arch to the download name associated - * with composing the download link. - * @param ostype The operating stystem type. - * @param osarch The chip architecture. - * @returns The download name associated with composing the download link. - */ -export function osHelper(ostype: string, osarch: string): string { - if (ostype === 'Windows_NT') { - if (osarch === 'x64') { - return 'Win32'; - } else if (osarch === 'x32') { - return 'Win32'; - } - } - return null; -} - -/** - * Captures the version name which includes the semantic version and extra - * metadata. So an example for 12.34/IEDriverServer_win32_12.34.56.zip, - * the version is 12.34.56. - * @param xmlKey The xml key including the partial url. - */ -export function versionParser(xmlKey: string) { - const regex = /.*\/IEDriverServer_[a-zA-Z0-9]*_([0-9]*.[0-9]*.[0-9]*).zip/g; - try { - return regex.exec(xmlKey)[1]; - } catch (_) { - return null; - } -} - -/** - * Captures the semantic version name which includes the semantic version and - * extra metadata. So an example for 12.34/IEDriverServer_win32_12.34.56.zip, - * the version is 12.34.56. - * @param xmlKey The xml key including the partial url. - */ -export function semanticVersionParser(xmlKey: string) { - const regex = /.*\/IEDriverServer_[a-zA-Z0-9]*_([0-9]*.[0-9]*.[0-9]*).zip/g; - try { - return regex.exec(xmlKey)[1]; - } catch (_) { - return null; - } -} - -/** - * Matches the installed binaries depending on the operating system. - * @param ostype The operating stystem type. - */ -export function matchBinaries(ostype: string): RegExp|null { - if (ostype === 'Windows_NT') { - return /IEDriverServer_\d+.\d+.\d+.exe/g; - } - return null; -} diff --git a/lib/provider/provider.ts b/lib/provider/provider.ts deleted file mode 100644 index 33779484..00000000 --- a/lib/provider/provider.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as path from 'path'; - -// Change the output directory for all providers. -// This will download to the webdriver-manager/downloads directory. -export const OUT_DIR = path.resolve(__dirname, '..', '..', '..', 'downloads'); - -/** - * The provider updateBinary interface implemented by all providers. - */ -export interface ProviderInterface { - cleanFiles?: () => string; - getBinaryPath?: (version?: string) => string | null; - getStatus?: () => string | null; - updateBinary: (version?: string) => Promise; - seleniumFlag?: string; - osType?: string; -} - -/** - * The provider configuration is passed to the Provider and can override - * the default behavior of the provider. - */ -export interface ProviderConfig { - // The request url to get the list of binaries available to download. - requestUrl?: string; - // The location of the output directory where to store the cache file, - // the config file and the binaries. - outDir?: string; - // The cache file name is just the file name and not the full path. - // The file contains the body returned from the request url. - cacheFileName?: string; - // The config file name is just the file name and not the full path. - // The file contains a json object of the list of all downloaded - // binaries and the last downloaded binary. - configFileName?: string; - // The os type of this system. - osType?: string; - // The os architecture of this system. - osArch?: string; - // The proxy requests must go through (optional). - proxy?: string; - // Set the requests to ignore SSL (optional). - ignoreSSL?: boolean; - // Catch all for other things. - [key: string]: string|boolean|number; -} diff --git a/lib/provider/selenium_server.spec-int.ts b/lib/provider/selenium_server.spec-int.ts deleted file mode 100644 index 576ed5f6..00000000 --- a/lib/provider/selenium_server.spec-int.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; - -import {checkConnectivity} from '../../spec/support/helpers/test_utils'; -import {SeleniumServer, semanticVersionParser, versionParser} from './selenium_server'; -import {convertXmlToVersionList} from './utils/cloud_storage_xml'; -import {getVersion} from './utils/version_list'; - -describe('selenium_server', () => { - describe('class Selenium Server', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - beforeEach(() => { - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - try { - rimraf.sync(tmpDir); - } catch (err) { - } - }); - - it('should download the file', async () => { - if (await checkConnectivity('update binary for mac test')) { - const seleniumServer = new SeleniumServer({outDir: tmpDir}); - await seleniumServer.updateBinary(); - - const configFile = path.resolve(tmpDir, 'selenium-server.config.json'); - const xmlFile = path.resolve(tmpDir, 'selenium-server.xml'); - expect(fs.statSync(configFile).size).toBeTruthy(); - expect(fs.statSync(xmlFile).size).toBeTruthy(); - - const versionList = convertXmlToVersionList( - xmlFile, 'selenium-server-standalone', versionParser, - semanticVersionParser); - const versionObj = getVersion(versionList, ''); - const executableFile = path.resolve( - tmpDir, - 'selenium-server-standalone-' + versionObj.version + '.jar'); - expect(fs.statSync(executableFile).size).toBeTruthy(); - } - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/selenium_server.spec-unit.ts b/lib/provider/selenium_server.spec-unit.ts deleted file mode 100644 index 0701216d..00000000 --- a/lib/provider/selenium_server.spec-unit.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as fs from 'fs'; -import {SeleniumServer, semanticVersionParser, versionParser,} from './selenium_server'; - -describe('selenium_server', () => { - describe('verisonParser', () => { - it('should generate a semantic version', () => { - let version = - versionParser('10.1/selenium-server-standalone-10.1.200.jar'); - expect(version).toBe('10.1.200'); - - version = - versionParser('10.1/selenium-server-standalone-10.1.200-beta.jar'); - expect(version).toBe('10.1.200-beta'); - }); - }); - - describe('semanticVerisonParser', () => { - it('should generate a semantic version', () => { - let version = - semanticVersionParser('10.1/selenium-server-standalone-10.1.200.jar'); - expect(version).toBe('10.1.200'); - - version = semanticVersionParser( - '10.1/selenium-server-standalone-10.1.200-beta.jar'); - expect(version).toBe('10.1.200'); - }); - }); - - describe('class SeleniumServer', () => { - describe('getCmdStartServer', () => { - const configBinaries = `{ - "last": "path/to/selenium-server-3.0.jar", - "all": ["path/to/selenium-server-1.0.jar", - "path/to/selenium-server-2.0.jar", - "path/to/selenium-server-3.0.jar" - ] - }`; - const javaArgs = '-role node ' + - '-servlet org.openqa.grid.web.servlet.LifecycleServlet ' + - '-registerCycle 0 -port 4444'; - const javaArgsPort = '-port 4444'; - it('should use a selenium server with no options', () => { - spyOn(fs, 'readFileSync').and.returnValue(configBinaries); - const seleniumServer = new SeleniumServer(); - expect(seleniumServer.getCmdStartServer(null).join(' ')) - .toContain('-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); - expect(seleniumServer.getCmdStartServer({}).join(' ')) - .toContain('-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); - }); - - it('should use a selenium server with options', () => { - spyOn(fs, 'readFileSync').and.returnValue(configBinaries); - const seleniumServer = new SeleniumServer(); - const cmd = seleniumServer.getCmdStartServer( - {'-Dwebdriver.chrome.driver': 'path/to/chromedriver'}); - expect(cmd.join(' ')) - .toContain( - '-Dwebdriver.chrome.driver=path/to/chromedriver ' + - '-jar path/to/selenium-server-3.0.jar ' + javaArgsPort); - }); - - it('should use a selenium server with node options', () => { - spyOn(fs, 'readFileSync').and.returnValue(configBinaries); - const seleniumServer = new SeleniumServer(); - seleniumServer.runAsDetach = true; - seleniumServer.runAsNode = true; - const cmd = seleniumServer.getCmdStartServer( - {'-Dwebdriver.chrome.driver': 'path/to/chromedriver'}); - expect(cmd.join(' ')) - .toContain( - '-Dwebdriver.chrome.driver=path/to/chromedriver ' + - '-jar path/to/selenium-server-3.0.jar ' + javaArgs); - }); - }); - - describe('getStatus', () => { - it('should get the status from the config file', () => { - const configCache = `{ - "last": "/path/to/selenium-server-standalone-100.1.0.jar", - "all": [ - "/path/to/selenium-server-standalone-90.0.0.jar", - "/path/to/selenium-server-standalone-99.0.0-beta.jar", - "/path/to/selenium-server-standalone-100.1.0.jar" - ] - }`; - spyOn(fs, 'readFileSync').and.returnValue(configCache); - const seleniumServer = new SeleniumServer(); - expect(seleniumServer.getStatus()) - .toBe('90.0.0, 99.0.0-beta, 100.1.0 (latest)'); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/selenium_server.ts b/lib/provider/selenium_server.ts deleted file mode 100644 index 0fa2f783..00000000 --- a/lib/provider/selenium_server.ts +++ /dev/null @@ -1,335 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as request from 'request'; - -import {OUT_DIR, ProviderConfig, ProviderInterface,} from './provider'; -import {convertXmlToVersionList, updateXml} from './utils/cloud_storage_xml'; -import {generateConfigFile, getBinaryPathFromConfig, removeFiles,} from './utils/file_utils'; -import {curlCommand, initOptions, requestBinary} from './utils/http_utils'; -import {getVersion} from './utils/version_list'; - -const log = loglevel.getLogger('webdriver-manager'); - -export interface SeleniumServerProviderConfig extends ProviderConfig { - port?: number; - runAsNode?: boolean; - runAsDetach?: boolean; -} - -export class SeleniumServer implements ProviderInterface { - cacheFileName = 'selenium-server.xml'; - configFileName = 'selenium-server.config.json'; - ignoreSSL = false; - osType = os.type(); - osArch = os.arch(); - outDir = OUT_DIR; - port = 4444; - proxy: string = null; - requestUrl = 'https://selenium-release.storage.googleapis.com/'; - seleniumProcess: childProcess.ChildProcess; - runAsNode = false; - runAsDetach = false; - - constructor(providerConfig?: SeleniumServerProviderConfig) { - if (providerConfig) { - if (providerConfig.cacheFileName) { - this.cacheFileName = providerConfig.cacheFileName; - } - if (providerConfig.configFileName) { - this.configFileName = providerConfig.configFileName; - } - this.ignoreSSL = providerConfig.ignoreSSL; - if (providerConfig.osArch) { - this.osArch = providerConfig.osArch; - } - if (providerConfig.osType) { - this.osType = providerConfig.osType; - } - if (providerConfig.outDir) { - this.outDir = providerConfig.outDir; - } - if (providerConfig.port) { - this.port = providerConfig.port; - } - if (providerConfig.proxy) { - this.proxy = providerConfig.proxy; - } - if (providerConfig.requestUrl) { - this.requestUrl = providerConfig.requestUrl; - } - if (providerConfig.runAsNode) { - this.runAsNode = providerConfig.runAsNode; - } - if (providerConfig.runAsDetach) { - this.runAsDetach = providerConfig.runAsDetach; - this.runAsNode = true; - } - } - } - - /** - * Should update the cache and download, find the version to download, - * then download that binary. - * @param version Optional to provide the version number or latest. - */ - async updateBinary(version?: string): Promise { - await updateXml(this.requestUrl, { - fileName: path.resolve(this.outDir, this.cacheFileName), - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - const versionList = convertXmlToVersionList( - path.resolve(this.outDir, this.cacheFileName), - 'selenium-server-standalone', versionParser, semanticVersionParser); - const versionObj = getVersion(versionList, '', version); - - const seleniumServerUrl = this.requestUrl + versionObj.url; - const seleniumServerJar = path.resolve(this.outDir, versionObj.name); - - // We should check the jar file size if it exists. The size will - // be used to either make the request, or quit the request if the file - // size matches. - let fileSize = 0; - try { - fileSize = fs.statSync(seleniumServerJar).size; - } catch (err) { - } - await requestBinary(seleniumServerUrl, { - fileName: seleniumServerJar, - fileSize, - ignoreSSL: this.ignoreSSL, - proxy: this.proxy - }); - generateConfigFile( - this.outDir, path.resolve(this.outDir, this.configFileName), - matchBinaries(), seleniumServerJar); - return Promise.resolve(); - } - - /** - * Starts selenium standalone server and handles emitted exit events. - * @param opts The options to pass to the jar file. - * @param version The optional version of the selenium jar file. - * @returns A promise so the server can run while awaiting its completion. - */ - startServer(opts: {[key: string]: string}, version?: string): - Promise { - const java = this.getJava(); - return new Promise(async (resolve, _) => { - if (this.runAsDetach) { - this.runAsNode = true; - const cmd = this.getCmdStartServer(opts, version); - log.info(`${java} ${cmd.join(' ')}`); - this.seleniumProcess = - childProcess.spawn(java, cmd, {detached: true, stdio: 'ignore'}); - log.info(`selenium process id: ${this.seleniumProcess.pid}`); - await new Promise((resolve, _) => { - setTimeout(resolve, 2000); - }); - this.seleniumProcess.unref(); - await new Promise((resolve, _) => { - setTimeout(resolve, 500); - }); - resolve(0); - } else { - const cmd = this.getCmdStartServer(opts, version); - log.info(`${java} ${cmd.join(' ')}`); - this.seleniumProcess = - childProcess.spawn(java, cmd, {stdio: 'inherit'}); - log.info(`selenium process id: ${this.seleniumProcess.pid}`); - - this.seleniumProcess.on('exit', (code: number) => { - log.info(`Selenium Standalone has exited with code: ${code}`); - resolve(code); - }); - this.seleniumProcess.on('error', (err: Error) => { - log.error(`Selenium Standalone server encountered an error: ${err}`); - }); - } - }); - } - - /** - * Get the binary file path. - * @param version Optional to provide the version number or the latest. - */ - getBinaryPath(version?: string): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - return getBinaryPathFromConfig(configFilePath, version); - } catch (_) { - return null; - } - } - - /** - * Get the selenium server start command (not including the java command) - * @param opts The options to pass to the jar file. - * @param version The optional version of the selenium jar file. - * @returns The spawn arguments array. - */ - getCmdStartServer(opts: {[key: string]: string}, version?: string): string[] { - const jarFile = this.getBinaryPath(version); - const options: string[] = []; - if (opts) { - for (const opt of Object.keys(opts)) { - options.push(`${opt}=${opts[opt]}`); - } - } - options.push('-jar'); - options.push(jarFile); - - if (this.runAsNode) { - options.push('-role'); - options.push('node'); - - options.push('-servlet'); - options.push('org.openqa.grid.web.servlet.LifecycleServlet'); - - options.push('-registerCycle'); - options.push('0'); - } - options.push('-port'); - options.push(this.port.toString()); - - return options; - } - - /** - * Gets the java command either by the JAVA_HOME environment variable or - * just the java command. - */ - getJava(): string { - let java = 'java'; - if (process.env.JAVA_HOME) { - java = path.resolve(process.env.JAVA_HOME, 'bin', 'java'); - if (this.osType === 'Windows_NT') { - java += '.exe'; - } - } - return java; - } - - /** - * If we are running the selenium server role = node, send - * the command to stop the server via http get request. Reference: - * https://github.com/SeleniumHQ/selenium/issues/2852#issuecomment-268324091 - * - * If we are not running as the selenium server role = node, kill the - * process with pid. - * - * @param host The protocol and ip address, default http://127.0.0.1 - * @param port The port number, default 4444 - * @returns A promise of the http get request completing. - */ - stopServer(host?: string, port?: number): Promise { - if (this.runAsNode) { - if (!host) { - host = 'http://127.0.0.1'; - } - if (!port) { - port = this.port; - } - const stopUrl = - host + ':' + port + '/extra/LifecycleServlet?action=shutdown'; - const options = initOptions(stopUrl, {}); - log.info(curlCommand(options)); - return new Promise((resolve, _) => { - const req = request(options); - req.on('response', response => { - response.on('end', () => { - resolve(); - }); - }); - }); - } else if (this.seleniumProcess) { - process.kill(this.seleniumProcess.pid); - return Promise.resolve(); - } else { - return Promise.reject( - 'Could not stop the server, server is not running.'); - } - } - - /** - * Gets a comma delimited list of versions downloaded. Also has the "latest" - * downloaded noted. - */ - getStatus(): string|null { - try { - const configFilePath = path.resolve(this.outDir, this.configFileName); - const configJson = JSON.parse(fs.readFileSync(configFilePath).toString()); - const versions: string[] = []; - for (const binaryPath of configJson['all']) { - let version = ''; - const regex = /.*selenium-server-standalone-(\d+.\d+.\d+.*).jar/g; - try { - const exec = regex.exec(binaryPath); - if (exec && exec[1]) { - version = exec[1]; - } - } catch (_) { - } - - if (configJson['last'] === binaryPath) { - version += ' (latest)'; - } - versions.push(version); - } - return versions.join(', '); - } catch (_) { - return null; - } - } - - /** - * Get a line delimited list of files removed. - */ - cleanFiles(): string { - return removeFiles(this.outDir, [/selenium-server.*/g]); - } -} - -/** - * Captures the version name which includes the semantic version and extra - * metadata. So an example for 12.34/selenium-server-standalone-12.34.56.jar, - * the version is 12.34.56. For metadata, - * 12.34/selenium-server-standalone-12.34.56-beta.jar is 12.34.56-beta. - * @param xmlKey The xml key including the partial url. - */ -export function versionParser(xmlKey: string) { - // Capture the version name 12.34.56 or 12.34.56-beta - const regex = /.*selenium-server-standalone-(\d+.\d+.\d+.*).jar/g; - try { - return regex.exec(xmlKey)[1]; - } catch (_) { - return null; - } -} - -/** - * Captures the version name which includes the semantic version and extra - * metadata. So an example for 12.34/selenium-server-standalone-12.34.56.jar, - * the version is 12.34.56. For metadata, - * 12.34/selenium-server-standalone-12.34.56-beta.jar is still 12.34.56. - * @param xmlKey The xml key including the partial url. - */ -export function semanticVersionParser(xmlKey: string) { - // Only capture numbers 12.34.56 - const regex = /.*selenium-server-standalone-(\d+.\d+.\d+).*.jar/g; - try { - return regex.exec(xmlKey)[1]; - } catch (_) { - return null; - } -} - -/** - * Matches the installed binaries. - */ -export function matchBinaries(): RegExp|null { - return /selenium-server-standalone-\d+.\d+.\d+.*.jar/g; -} \ No newline at end of file diff --git a/lib/provider/utils/README.md b/lib/provider/utils/README.md deleted file mode 100644 index ddb01942..00000000 --- a/lib/provider/utils/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Utils - -Developer notes for scope of each file: - -* **cloud_storage_xml** handles the Google Cloud Storage specific items like -downloading the xml and converting the xml into a version list. -* **file_utils** manages files including reading xml and json files, -uncompressing files, renaming files, and checking if we should renew the cache -of xml or json files. -* **github_json** handles the GitHub specific items like downloading the json -and converting that to a version list. Also adds GitHub specific headers to the -request including oauth token. -* **http_utils** handles requests like downloading a binary or getting the -contents of the body. -* **version_list** is a data object to help organize the versions from the -cache. The versions must be in semantic version format. \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.spec-int.ts b/lib/provider/utils/cloud_storage_xml.spec-int.ts deleted file mode 100644 index 938bae83..00000000 --- a/lib/provider/utils/cloud_storage_xml.spec-int.ts +++ /dev/null @@ -1,173 +0,0 @@ - -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import {httpBaseUrl} from '../../../spec/server/env'; -import {spawnProcess} from '../../../spec/support/helpers/test_utils'; -import {convertXmlToVersionList, updateXml} from './cloud_storage_xml'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -function chromedriverVersionParser(key: string): string { - const regex = /([0-9]*.[0-9]*)\/chromedriver_.*.zip/g; - try { - return regex.exec(key)[1]; - } catch (_) { - return null; - } -} - -function chromedriverSemanticVersionParser(key: string): string { - const regex = /([0-9]*.[0-9]*)\/chromedriver_.*.zip/g; - try { - return regex.exec(key)[1] + '.0'; - } catch (_) { - return null; - } -} - -describe('cloud_storage_xml', () => { - const tmpDir = path.resolve(os.tmpdir(), 'test'); - let proc: childProcess.ChildProcess; - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('with a http server', () => { - beforeAll(async () => { - proc = spawnProcess('node', ['dist/spec/server/http_server.js']); - log.debug('http-server: ' + proc.pid); - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - }); - - afterAll(async () => { - process.kill(proc.pid); - await new Promise((resolve, _) => { - setTimeout(resolve, 5000); - }); - }); - - describe('updateXml', () => { - const fileName = path.resolve(tmpDir, 'foo.xml'); - const xmlUrl = httpBaseUrl + '/spec/support/files/foo.xml'; - - beforeAll(() => { - try { - fs.mkdirSync(tmpDir); - } catch (_) { - // If the directory already exists, we are in the desired state. - } - try { - fs.unlinkSync(fileName); - } catch (_) { - // If the file does not exist, we are in the desired state. - } - }); - - afterAll(() => { - try { - fs.unlinkSync(fileName); - fs.rmdirSync(tmpDir); - } catch (_) { - } - }); - - it('should request and write the file if it does not exist', async () => { - try { - fs.statSync(fileName); - expect('file should not exist.').toBeFalsy(); - } catch (_) { - try { - const xmlContent = await updateXml(xmlUrl, {fileName}); - expect(fs.statSync(fileName).size).toBeGreaterThan(0); - expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) - .toBe('2.0/foobar.zip'); - } catch (_) { - expect('thrown error from update xml.').toBeFalsy(); - } - } - }); - - it('should request and write the file if it is expired', async () => { - const mtime = Date.now() - (60 * 60 * 1000) - 5000; - - // Maintain the fs.statSync method before being spyed on. - // Spy on the fs.statSync method and return fake values. - const fsStatSync = fs.statSync; - spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); - - try { - const xmlContent = await updateXml(xmlUrl, {fileName}); - expect(fsStatSync(fileName).size).toBeGreaterThan(0); - expect(fsStatSync(fileName).size).not.toBe(1000); - expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) - .toBe('2.0/foobar.zip'); - } catch (_) { - expect('debugging required').toBeFalsy(); - } - }); - - it('should read the file when it is not expired', async () => { - const initialStats = fs.statSync(fileName); - const mtime = Date.now(); - - // Maintain the fs.statSync method before being spyed on. - // Spy on the fs.statSync method and return fake values. - const fsStatSync = fs.statSync; - spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); - - try { - const xmlContent = await updateXml(xmlUrl, {fileName}); - expect(fsStatSync(fileName).size).toBe(initialStats.size); - expect(fsStatSync(fileName).mtime.getMilliseconds()) - .toBe(initialStats.mtime.getMilliseconds()); - expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) - .toBe('2.0/foobar.zip'); - } catch (_) { - expect('debugging required').toBeFalsy(); - } - }); - }); - - describe('convertXmlToVersionList', () => { - const fileName = 'spec/support/files/chromedriver.xml'; - - it('should convert an xml file an object from the xml file', () => { - const versionList = convertXmlToVersionList( - fileName, '.zip', chromedriverVersionParser, - chromedriverSemanticVersionParser); - expect(Object.keys(versionList).length).toBe(3); - expect(versionList['2.0.0']).toBeTruthy(); - expect(versionList['2.10.0']).toBeTruthy(); - expect(versionList['2.20.0']).toBeTruthy(); - expect(Object.keys(versionList['2.0.0']).length).toBe(4); - expect(Object.keys(versionList['2.10.0']).length).toBe(4); - expect(Object.keys(versionList['2.20.0']).length).toBe(4); - expect(versionList['2.0.0']['chromedriver_linux32.zip']['size']) - .toBe(7262134); - expect(versionList['2.10.0']['chromedriver_linux32.zip']['size']) - .toBe(2439424); - expect(versionList['2.20.0']['chromedriver_linux32.zip']['size']) - .toBe(2612186); - }); - - it('should return a null value if the file does not exist', () => { - const versionList = convertXmlToVersionList( - 'spec/support/files/does_not_exist.xml', '.zip', - chromedriverVersionParser, chromedriverSemanticVersionParser); - expect(versionList).toBeNull(); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.spec-unit.ts b/lib/provider/utils/cloud_storage_xml.spec-unit.ts deleted file mode 100644 index e943bc9d..00000000 --- a/lib/provider/utils/cloud_storage_xml.spec-unit.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as fs from 'fs'; -import {convertXmlToVersionList} from './cloud_storage_xml'; - -const contents = ` - - - foobar_driver - - 2.0/foobar.zip - 10 - - - 2.1/foobar.zip - 11 - -`; - -export function versionParser(key: string): string { - const regex = /([0-9]*.[0-9]*)\/foobar.*.zip/g; - try { - return regex.exec(key)[1]; - } catch (err) { - return null; - } -} - -export function semanticVersionParser(key: string): string { - const regex = /([0-9]*.[0-9]*)\/foobar.*.zip/g; - try { - return regex.exec(key)[1] + '.0'; - } catch (err) { - return null; - } -} - -describe('cloud_storage_xml', () => { - describe('convertXmlToVersionList', () => { - it('should convert an xml file an object from the xml file', () => { - spyOn(fs, 'readFileSync').and.returnValue(contents); - const versionList = convertXmlToVersionList( - 'foobar', '.zip', versionParser, semanticVersionParser); - expect(Object.keys(versionList).length).toBe(2); - expect(versionList['2.0.0']['foobar.zip'].url).toBe('2.0/foobar.zip'); - expect(versionList['2.0.0']['foobar.zip'].size).toBe(10); - expect(versionList['2.1.0']['foobar.zip'].url).toBe('2.1/foobar.zip'); - expect(versionList['2.1.0']['foobar.zip'].size).toBe(11); - }); - - it('should return null when the method to read an xml file returns null', - () => { - const versionList = convertXmlToVersionList( - 'foo', '.zip', versionParser, semanticVersionParser); - expect(versionList).toBeNull(); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/cloud_storage_xml.ts b/lib/provider/utils/cloud_storage_xml.ts deleted file mode 100644 index 6b980e46..00000000 --- a/lib/provider/utils/cloud_storage_xml.ts +++ /dev/null @@ -1,64 +0,0 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as semver from 'semver'; -import {convertXml2js, readXml} from './file_utils'; -import {isExpired} from './file_utils'; -import {HttpOptions, JsonObject, requestBody} from './http_utils'; -import {VersionList} from './version_list'; - -/** - * Read the xml file from cache. If the cache time has been exceeded or the - * file does not exist, make an http request and write it to the file. - * @param xmlUrl The xml url. - * @param httpOptions The http options for the request. - */ -export async function updateXml( - xmlUrl: string, httpOptions: HttpOptions): Promise { - if (isExpired(httpOptions.fileName)) { - const contents = await requestBody(xmlUrl, httpOptions); - const dir = path.dirname(httpOptions.fileName); - try { - fs.mkdirSync(dir); - } catch (err) { - } - fs.writeFileSync(httpOptions.fileName, contents); - return convertXml2js(contents); - } else { - return readXml(httpOptions.fileName); - } -} - -/** - * Returns a list of versions and the partial url paths. - * @param fileName the location of the xml file to read. - * @returns the version list from the xml file. - */ -export function convertXmlToVersionList( - fileName: string, matchFile: string, - versionParser: (key: string) => string | null, - semanticVersionParser: (key: string) => string): VersionList|null { - const xmlJs = readXml(fileName); - if (!xmlJs) { - return null; - } - const versionList: VersionList = {}; - for (const content of xmlJs['ListBucketResult']['Contents']) { - const key = content['Key'][0] as string; - if (key.includes(matchFile)) { - const version = versionParser(key); - if (version) { - const semanticVersion = semanticVersionParser(key); - if (!semver.valid(semanticVersion)) { - continue; - } - const name = key.split('/')[1]; - const size = +content['Size'][0]; - if (!versionList[semanticVersion]) { - versionList[semanticVersion] = {}; - } - versionList[semanticVersion][name] = {name, size, url: key, version}; - } - } - } - return versionList; -} \ No newline at end of file diff --git a/lib/provider/utils/file_utils.spec-int.ts b/lib/provider/utils/file_utils.spec-int.ts deleted file mode 100644 index 929827a7..00000000 --- a/lib/provider/utils/file_utils.spec-int.ts +++ /dev/null @@ -1,190 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; -import * as rimraf from 'rimraf'; -import {generateConfigFile, removeFiles, tarFileList, uncompressTarball, unzipFile, zipFileList} from './file_utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tarballFile = path.resolve('spec/support/files/bar.tar.gz'); -const zipFile = path.resolve('spec/support/files/bar.zip'); - -describe('file_utils', () => { - describe('tarFileList', () => { - it('should have a file list', async () => { - const fileList = await tarFileList(tarballFile); - expect(fileList).toBeTruthy(); - expect(fileList.length).toBe(1); - expect(fileList[0]).toBe('bar'); - }); - - it('should return an error if the file does not exist', async () => { - try { - await tarFileList('file_does_not_exist'); - expect(false).toBeTruthy(); - } catch (err) { - expect(err).toBeTruthy(); - } - }); - }); - - describe('untarFile', () => { - let tmpDir: string; - - beforeAll(() => { - tmpDir = path.resolve(os.tmpdir(), 'test'); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - rimraf.sync(tmpDir); - }); - - it('should uncompress the file', async () => { - const untarFiles = await uncompressTarball(tarballFile, tmpDir); - const untarBar = path.resolve(tmpDir, 'bar'); - expect(untarFiles).toBeTruthy(); - expect(untarFiles.length).toBe(1); - expect(untarFiles[0]).toBe(untarBar); - expect(fs.statSync(untarBar).size).toBe(30); - }); - }); - - describe('zipFileList', () => { - it('should have a file list', () => { - const fileList = zipFileList(zipFile); - expect(fileList).toBeTruthy(); - expect(fileList.length).toBe(1); - expect(fileList[0]).toBe('bar'); - }); - - it('should return an error if the file does not exist', () => { - try { - zipFileList('file_does_not_exist'); - expect(false).toBeTruthy(); - } catch (err) { - expect(err).toBeTruthy(); - } - }); - }); - - describe('unzipFile', () => { - let tmpDir: string; - - beforeAll(() => { - tmpDir = path.resolve(os.tmpdir(), 'test'); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - rimraf.sync(tmpDir); - }); - - it('should uncompress the file', () => { - const zipFiles = unzipFile(zipFile, tmpDir); - const unzipBar = path.resolve(tmpDir, 'bar'); - expect(zipFiles).toBeTruthy(); - expect(zipFiles.length).toBe(1); - expect(zipFiles[0]).toBe(unzipBar); - expect(fs.statSync(unzipBar).size).toBe(30); - }); - }); - - describe('generateConfigFile', () => { - let tmpDir: string; - - beforeAll(() => { - tmpDir = path.resolve(os.tmpdir(), 'test'); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterAll(() => { - rimraf.sync(tmpDir); - }); - - it('should write the file', () => { - // Creates empty files in the temp directory. - ['foo.zip', - 'foo_.zip', - 'foo_12.2', - 'foo_12.4', - 'foo.xml', - 'foo_.xml', - 'bar.tar.gz', - 'bar_10.1.1', - 'bar_10.1.2', - 'bar.json', - ].forEach(fileName => { - fs.closeSync(fs.openSync(path.resolve(tmpDir, fileName), 'w')); - }); - const tmpFile = path.resolve(tmpDir, 'foobar.config.json'); - const lastBinary = path.resolve(tmpDir, 'foo_12.4'); - - const fileBinaryPathRegex: RegExp = /foo_\d+.\d+/g; - generateConfigFile(tmpDir, tmpFile, fileBinaryPathRegex, lastBinary); - - const contents = fs.readFileSync(tmpFile).toString(); - const jsonContents = JSON.parse(contents); - expect(jsonContents['last']).toBe(lastBinary); - expect(jsonContents['all'].length).toBe(2); - }); - }); - - describe('removeFiles', () => { - let tmpDir: string; - - beforeEach(() => { - tmpDir = path.resolve(os.tmpdir(), 'test'); - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - }); - - afterEach(() => { - rimraf.sync(tmpDir); - }); - - it('should remove files', () => { - log.debug(tmpDir); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-123'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-456'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-789'), 'w')); - - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-123'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-456'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'baz-789'), 'w')); - - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-123'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-456'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'foo-789'), 'w')); - - expect(removeFiles(tmpDir, [/bar-.*/g])) - .toBe('bar-123\nbar-456\nbar-789'); - expect(fs.readdirSync(tmpDir).length).toBe(6); - expect(removeFiles(tmpDir, [ - /foo-.*/g, /baz-.*/g - ])).toBe('baz-123\nbaz-456\nbaz-789\nfoo-123\nfoo-456\nfoo-789'); - expect(fs.readdirSync(tmpDir).length).toBe(0); - }); - - it('should not remove files if nothing is matched', () => { - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-123'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-456'), 'w')); - fs.closeSync(fs.openSync(path.resolve(tmpDir, 'bar-789'), 'w')); - expect(removeFiles(tmpDir, [/zebra-.*/g])).toBe(''); - expect(fs.readdirSync(tmpDir).length).toBe(3); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/file_utils.spec-unit.ts b/lib/provider/utils/file_utils.spec-unit.ts deleted file mode 100644 index ebca840e..00000000 --- a/lib/provider/utils/file_utils.spec-unit.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as fs from 'fs'; -import {convertXml2js, getBinaryPathFromConfig, getMatchingFiles, isExpired, readJson, readXml} from './file_utils'; -import {JsonObject} from './http_utils'; - -const xmlContents = ` - - - foobar_driver - - 2.0/foobar.zip - 10 - - - 2.1/foobar.zip - 11 - -`; - -const jsonObjectContents = `{ - "foo": "abc", - "bar": 123, - "baz": { - "num": 101, - "list": ["a", "b", "c"] - } -}`; - -const jsonArrayContents = `[{ - "foo": "abc" -}, { - "foo": "def" -}, { - "foo": "ghi" -}]`; - - -describe('file_utils', () => { - describe('isExpired', () => { - it('should return true if the file is zero', () => { - const mtime = Date.now() - 1000; - spyOn(fs, 'statSync').and.returnValue({size: 0, mtime}); - expect(isExpired('foobar.xml')).toBeTruthy(); - }); - - it('should return true if the file is zero', () => { - const mtime = Date.now() - (60 * 60 * 1000) - 5000; - spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); - expect(isExpired('foobar.xml')).toBeTruthy(); - }); - - it('should return true if the file is zero', () => { - const mtime = Date.now() - (60 * 60 * 1000) + 5000; - spyOn(fs, 'statSync').and.returnValue({size: 1000, mtime}); - expect(isExpired('foobar.xml')).toBeFalsy(); - }); - }); - - describe('readXml', () => { - it('should read the file', () => { - spyOn(fs, 'readFileSync').and.returnValue(xmlContents); - const xmlContent = readXml('foobar'); - expect(xmlContent['ListBucketResult']['Name'][0]).toBe('foobar_driver'); - expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) - .toBe('2.0/foobar.zip'); - }); - - it('should get null if reading the file fails', () => { - const xmlContent = readXml('foobar'); - expect(xmlContent).toBeNull(); - }); - }); - - describe('convertXml2js', () => { - it('should convert the content to json', () => { - const xmlContent = convertXml2js(xmlContents); - expect(xmlContent['ListBucketResult']['Name'][0]).toBe('foobar_driver'); - expect(xmlContent['ListBucketResult']['Contents'][0]['Key'][0]) - .toBe('2.0/foobar.zip'); - }); - }); - - describe('readJson', () => { - it('should read the json object from file', () => { - spyOn(fs, 'readFileSync').and.returnValue(jsonObjectContents); - const jsonObj = readJson('foobar') as JsonObject; - expect(jsonObj['foo']).toBe('abc'); - expect(jsonObj['bar']).toBe(123); - expect(jsonObj['baz']['num']).toBe(101); - expect(jsonObj['baz']['list'][0]).toBe('a'); - expect(jsonObj['baz']['list'][1]).toBe('b'); - expect(jsonObj['baz']['list'][2]).toBe('c'); - }); - - it('should read the json array from file', () => { - spyOn(fs, 'readFileSync').and.returnValue(jsonArrayContents); - const jsonArray = readJson('foobar') as JsonObject[]; - expect(jsonArray.length).toBe(3); - expect(jsonArray[0]['foo']).toBe('abc'); - expect(jsonArray[1]['foo']).toBe('def'); - expect(jsonArray[2]['foo']).toBe('ghi'); - }); - - it('should get null if reading the file fails', () => { - const jsoNContent = readJson('foobar'); - expect(jsoNContent).toBeNull(); - }); - }); - - describe('getMatchingFiles', () => { - it('should find a set of matching files', () => { - const existingFiles = [ - 'foo.zip', - 'foo_.zip', - 'foo_12.2', - 'foo_12.4', - 'foo.xml', - 'foo_.xml', - 'bar.tar.gz', - 'bar_10.1.1', - 'bar_10.1.2', - 'bar.json', - ]; - const fileBinaryPathRegex: RegExp = /foo_\d+.\d+/g; - spyOn(fs, 'readdirSync').and.returnValue(existingFiles); - const matchedFiles = getMatchingFiles('/path/to', fileBinaryPathRegex); - expect(matchedFiles[0]).toContain('foo_12.2'); - expect(matchedFiles[1]).toContain('foo_12.4'); - }); - }); - - describe('getBinaryPathFromConfig', () => { - const configBinaries = `{ - "last": "foo-1.0", - "all": ["foo-1.0", "bar-1.1", "baz-1.2"] - }`; - it('should find the latest download', () => { - spyOn(fs, 'readFileSync').and.returnValue(configBinaries); - const last = getBinaryPathFromConfig('path-does-not-exist'); - expect(last).toBe('foo-1.0'); - }); - - it('should find the download from a version', () => { - spyOn(fs, 'readFileSync').and.returnValue(configBinaries); - expect(getBinaryPathFromConfig('path-does-not-exist', '1.0')) - .toBe('foo-1.0'); - expect(getBinaryPathFromConfig('path-does-not-exist', '1.2')) - .toBe('baz-1.2'); - }); - }); -}); diff --git a/lib/provider/utils/file_utils.ts b/lib/provider/utils/file_utils.ts deleted file mode 100644 index 0bd33304..00000000 --- a/lib/provider/utils/file_utils.ts +++ /dev/null @@ -1,252 +0,0 @@ -import * as AdmZip from 'adm-zip'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as tar from 'tar'; -import * as xml2js from 'xml2js'; -import {JsonObject} from './http_utils'; - -/** - * Check to see if the modified timestamp is expired. - * @param fileName THe xml filename. - */ -export function isExpired(fileName: string): boolean { - try { - const timestamp = new Date(fs.statSync(fileName).mtime).getTime(); - const size = fs.statSync(fileName).size; - const now = Date.now(); - - if (size > 0 && (now - (60 * 60 * 1000) < timestamp)) { - return false; - } else { - return true; - } - } catch (err) { - return true; - } -} - -/** - * Reads the json file from file. - * @param fileName The json filename to read. - * @returns - */ -export function readJson(fileName: string): JsonObject[]|JsonObject|null { - try { - const contents = fs.readFileSync(fileName).toString(); - return JSON.parse(contents); - } catch (err) { - return null; - } -} - -/** - * Reads the xml file. - * @param fileName The xml filename to read. - */ -export function readXml(fileName: string): JsonObject|null { - try { - const contents = fs.readFileSync(fileName).toString(); - return convertXml2js(contents); - } catch (err) { - return null; - } -} - -/** - * Convert the xml file to an object. - * @param content The xml contents. - */ -export function convertXml2js(content: string): JsonObject|null { - let retResult: JsonObject = null; - xml2js.parseString(content, (err, result) => { - retResult = result; - }); - return retResult; -} - -/** - * Renames a file with a semantic version. - * @param srcFileName The full path to the original file name. - * @param versionNumber The semver number. - * @returns The renamed file name. - */ -export function renameFileWithVersion( - srcFileName: string, versionNumber: string): string { - const dirName = path.dirname(srcFileName); - const extName = path.extname(srcFileName); - const baseName = path.basename(srcFileName, extName); - const dstFileName = path.resolve(dirName, baseName + versionNumber + extName); - fs.renameSync(srcFileName, dstFileName); - return dstFileName; -} - -/** - * Gets a list of files in the zip file. - * @param zipFileName The zip file. - * @returns A list of files in the zip file. - */ -export function zipFileList(zipFileName: string): string[] { - const fileList: string[] = []; - const zip = new AdmZip(zipFileName); - zip.getEntries().forEach(entry => { - fileList.push(entry.name); - }); - return fileList; -} - -/** - * Uncompress the zip file to a destination directory. - * @param zipFileName The zip file. - * @param dstDir The destination directory for the contents of the zip file. - * @returns A list of uncompressed files. - */ -export function unzipFile(zipFileName: string, dstDir: string): string[] { - const fileList: string[] = []; - const zip = new AdmZip(zipFileName); - zip.extractAllTo(dstDir, true); - for (const fileItem of zipFileList(zipFileName)) { - fileList.push(path.resolve(dstDir, fileItem)); - } - return fileList; -} - -/** - * Gets a list of files in the tarball file. - * @param tarball The tarball file. - * @returns A lsit of files in the tarball file. - */ -export function tarFileList(tarball: string): Promise { - const fileList: string[] = []; - return tar - .list({ - file: tarball, - onentry: entry => { - fileList.push(entry['path'].toString()); - } - }) - .then(() => { - return fileList; - }); -} - -/** - * Uncompress the tar file to a destination directory. - * @param tarball The tarball file. - * @param dstDir The destination directory for the contents of the zip file. - * @returns A list of uncompressed files. - */ -export async function uncompressTarball( - tarball: string, dstDir: string): Promise { - try { - fs.mkdirSync(path.resolve(dstDir)); - } catch (err) { - } - - const fileList = await tarFileList(tarball); - return tar.extract({file: tarball}).then(() => { - const dstFiles: string[] = []; - for (const fileItem of fileList) { - const dstFileName = path.resolve(dstDir, fileItem); - fs.renameSync(path.resolve(fileItem), dstFileName); - dstFiles.push(dstFileName); - } - return dstFiles; - }); -} - -/** - * Change the permissions for Linux and MacOS with chmod. - * @param fileName The full path to the filename to change permissions. - * @param mode The number to modify. - * @param osType The OS type to decide if we need to change permissions on the - * file. - */ -export function changeFilePermissions( - fileName: string, mode: string, osType: string) { - if (osType === 'Darwin' || osType === 'Linux') { - fs.chmodSync(path.resolve(fileName), mode); - } -} - -/** - * Writes a config file that matches the regex pattern. - * @param outDir The output directory. - * @param fileName The full path to the file name. - * @param fileBinaryPathRegex The regExp to match files in the outDir. - * @param lastFileBinaryPath The full path to the last binary file downloaded. - */ -export function generateConfigFile( - outDir: string, fileName: string, fileBinaryPathRegex: RegExp, - lastFileBinaryPath?: string) { - const configData: JsonObject = {}; - if (lastFileBinaryPath) { - configData['last'] = lastFileBinaryPath; - } - configData['all'] = getMatchingFiles(outDir, fileBinaryPathRegex); - fs.writeFileSync(fileName, JSON.stringify(configData)); -} - -/** - * Gets matching files form the outDir and returns it as an array. - * @param outDir The output directory. - * @param fileBinaryPathRegex The regExp to match files in the outDir. - */ -export function getMatchingFiles( - outDir: string, fileBinaryPathRegex: RegExp): string[] { - const existFiles = fs.readdirSync(outDir); - const matchingFiles: string[] = []; - for (const existFile of existFiles) { - if (existFile.match(fileBinaryPathRegex)) { - matchingFiles.push(path.resolve(outDir, existFile)); - } - } - return matchingFiles; -} - -/** - * Get the binary path from the configuration file. The configuration file - * should be formatted as { 'last': string, 'all': string[] }. In the 'all' - * array, we should match the 'version'. The version does not necessarily have - * to be a valid semantic version. - * @param cacheFilePath The cache file path. - * @param version An optional version that is not necessarily semver. - */ -export function getBinaryPathFromConfig( - cacheFilePath: string, version?: string): string|null { - const cacheJson = JSON.parse(fs.readFileSync(cacheFilePath).toString()); - let binaryPath = null; - if (!version) { - binaryPath = cacheJson['last']; - } else { - for (const cachePath of cacheJson['all']) { - if (cachePath.match(version)) { - binaryPath = cachePath; - } - } - } - return binaryPath; -} - -/** - * Removes the files that match the regular expressions and returns a string - * of removed files. - * @param outDir The output directory. - * @param fileRegexes The regExp to match files to remove in the outDir. - */ -export function removeFiles(outDir: string, fileRegexes: RegExp[]): string { - try { - const existFiles = fs.readdirSync(outDir); - const removedFiles: string[] = []; - for (const fileRegex of fileRegexes) { - for (const existFile of existFiles) { - if (existFile.match(fileRegex)) { - removedFiles.push(existFile); - fs.unlinkSync(path.resolve(outDir, existFile)); - } - } - } - return (removedFiles.sort()).join('\n'); - } catch (_) { - return null; - } -} \ No newline at end of file diff --git a/lib/provider/utils/github_json.spec-int.ts b/lib/provider/utils/github_json.spec-int.ts deleted file mode 100644 index 53d372cd..00000000 --- a/lib/provider/utils/github_json.spec-int.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as path from 'path'; -import {checkConnectivity} from '../../../spec/support/helpers/test_utils'; -import {convertJsonToVersionList, requestRateLimit} from './github_json'; - -const fileName = path.resolve('spec/support/files/gecko.json'); - -describe('github_json', () => { - describe('requestRateLimit', () => { - it('should get rate limit assuming quota exists', async () => { - if (await checkConnectivity('rate limit test')) { - const rateLimit = await requestRateLimit(); - expect(rateLimit).toBeTruthy(); - const rateLimitObj = JSON.parse(rateLimit); - expect(rateLimitObj['resources']).toBeTruthy(); - expect(rateLimitObj['resources']['core']).toBeTruthy(); - } - }); - }); - - describe('convertJsonToVersionList', () => { - it('should convert the json', () => { - const geckoVersionList = convertJsonToVersionList(fileName); - expect(Object.keys(geckoVersionList).length).toBe(3); - expect(geckoVersionList['0.20.0']).toBeTruthy(); - expect(geckoVersionList['0.20.1']).toBeTruthy(); - expect(geckoVersionList['0.21.0']).toBeTruthy(); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/github_json.spec-unit.ts b/lib/provider/utils/github_json.spec-unit.ts deleted file mode 100644 index fb3767e3..00000000 --- a/lib/provider/utils/github_json.spec-unit.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {hasQuota, RequestMethod} from './github_json'; - -describe('github_json', () => { - describe('hasQuota', () => { - it('should return true when there is quota', async () => { - const requestMethod: RequestMethod = - (jsonUrl: string, {}, oauthToken?: string): Promise => { - return Promise.resolve( - '{ "resources": { "core": { "remaining": 1 } } }'); - }; - const result = await hasQuota(null, requestMethod); - expect(result).toBeTruthy(); - }); - - it('should return false when there is no quota', async () => { - const requestMethod = - (jsonUrl: string, {}, oauthToken?: string): Promise => { - return Promise.resolve( - '{ "resources": { "core": { "remaining": 0 } } }'); - }; - const result = await hasQuota(null, requestMethod); - expect(result).toBeFalsy(); - }); - - it('should return false when something wrong happened', async () => { - const requestMethod = - (jsonUrl: string, {}, oauthToken?: string): Promise => { - return Promise.resolve(''); - }; - const result = await hasQuota(null, requestMethod); - expect(result).toBeFalsy(); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/github_json.ts b/lib/provider/utils/github_json.ts deleted file mode 100644 index 111c506c..00000000 --- a/lib/provider/utils/github_json.ts +++ /dev/null @@ -1,151 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as path from 'path'; - -import {isExpired, readJson} from './file_utils'; -import {HttpOptions, JsonObject, requestBody} from './http_utils'; -import {VersionList} from './version_list'; - -const log = loglevel.getLogger('webdriver-manager'); - -export interface RequestMethod { - (jsonUrl: string, httpOptions: HttpOptions, - oauthToken?: string): Promise; -} - -/** - * Read the json file from cache. If the cache time has been exceeded or the - * file does not exist, make an http request and write it to the file. - * @param jsonUrl The json url. - * @param httpOptions The http options for the request. - * @param oauthToken An optional oauth token. - */ -export async function updateJson( - jsonUrl: string, httpOptions: HttpOptions, - oauthToken?: string): Promise { - if (isExpired(httpOptions.fileName)) { - let contents: string; - - // Create the folder to store the cache. - const dir = path.dirname(httpOptions.fileName); - try { - fs.mkdirSync(dir); - } catch (err) { - } - - // Check the rate limit and if there is quota for this request. - if (await hasQuota(oauthToken)) { - contents = await requestGitHubJson(jsonUrl, httpOptions, oauthToken); - fs.writeFileSync(httpOptions.fileName, contents); - return JSON.parse(contents); - } else { - return null; - } - } else { - return readJson(httpOptions.fileName); - } -} - - -/** - * Get the GitHub rate limit with the oauth token. - * @param oauthToken An optional oauth token. - * @param requestMethod An overriding requesting method. - * @returns A promised string of the response body. - */ -export function requestRateLimit( - oauthToken?: string, requestMethod?: RequestMethod): Promise { - const rateLimitUrl = 'https://api.github.com/rate_limit'; - if (requestMethod) { - return requestMethod(rateLimitUrl, {}, oauthToken); - } else { - return requestGitHubJson(rateLimitUrl, {}, oauthToken); - } -} - -/** - * Request the GitHub json url and log the curl. - * @param jsonUrl The json url. - * @param httpOptions The http options for the request. - * @param oauthToken An optional oauth token. - * @returns A promised string of the response body. - */ -export function requestGitHubJson( - jsonUrl: string, httpOptions: HttpOptions, - oauthToken?: string): Promise { - if (!httpOptions.headers) { - httpOptions.headers = {}; - } - httpOptions.headers['User-Agent'] = 'angular/webdriver-manager'; - if (oauthToken) { - httpOptions.headers['Authorization'] = 'token ' + oauthToken; - } else if (process.env['GITHUB_TOKEN'] || process.env['github_token']) { - const token = process.env['GITHUB_TOKEN'] || process.env['github_token']; - httpOptions.headers['Authorization'] = 'token ' + token; - } - return requestBody(jsonUrl, httpOptions); -} - -/** - * Check quota for remaining GitHub requests. - * @param oauthToken An optional oauth token. - * @param requestMethod An overriding requesting method. - */ -export async function hasQuota( - oauthToken?: string, requestMethod?: RequestMethod): Promise { - try { - const requesteRateLimit = await requestRateLimit(oauthToken, requestMethod); - if (!requesteRateLimit) { - throw new Error( - 'Request encountered an error. Received null, expecting json.'); - } - const rateLimit = JSON.parse(requesteRateLimit); - if (rateLimit['resources']['core']['remaining'] === 0) { - if (oauthToken) { - log.warn('[WARN] No remaining quota for requests to GitHub.'); - } else { - log.warn( - '[WARN] Provide an oauth token. ' + - 'See https://github.com/settings/tokens'); - } - log.warn('[WARN] Stopping updates for gecko driver.'); - return false; - } - return true; - } catch (err) { - log.error('[ERROR]: ', err); - return false; - } -} - -/** - * Returns a list of versions and the partial url paths. - * @param fileName the location of the xml file to read. - * @returns the version list from the xml file. - */ -export function convertJsonToVersionList(fileName: string): VersionList|null { - const githubJson = readJson(fileName) as JsonObject[]; - if (!githubJson) { - return null; - } - const versionList: VersionList = {}; - for (const githubObj of githubJson) { - interface Asset { - name: string; - browser_download_url: string; - size: number; - } - const assets = githubObj['assets'] as JsonObject[]; - const version = (githubObj['tag_name'] as string).replace('v', ''); - versionList[version] = {}; - - for (const asset of assets) { - const name = asset['name'] as string; - const downloadUrl = asset['browser_download_url']; - const size = asset['size']; - versionList[version][name] = {name, size, url: downloadUrl, version} as - JsonObject; - } - } - return versionList; -} \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-int.ts b/lib/provider/utils/http_utils.spec-int.ts deleted file mode 100644 index 6952d94b..00000000 --- a/lib/provider/utils/http_utils.spec-int.ts +++ /dev/null @@ -1,116 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; - -import {httpBaseUrl} from '../../../spec/server/env'; -import {spawnProcess} from '../../../spec/support/helpers/test_utils'; -import {requestBinary, requestBody} from './http_utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tmpDir = path.resolve(os.tmpdir(), 'test'); -const fileName = path.resolve(tmpDir, 'bar.zip'); -const binaryUrl = httpBaseUrl + '/spec/support/files/bar.zip'; -const fooJsonUrl = httpBaseUrl + '/spec/support/files/foo_json.json'; -const fooArrayUrl = httpBaseUrl + '/spec/support/files/foo_array.json'; -const fooXmlUrl = httpBaseUrl + '/spec/support/files/foo.xml'; -const barZipSize = 171; - -describe('http_utils', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - let proc: childProcess.ChildProcess; - - beforeAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - }); - - afterAll(() => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('with a http server', () => { - beforeAll(async () => { - proc = spawnProcess('node', ['dist/spec/server/http_server.js']); - log.debug('http-server: ' + proc.pid); - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - try { - fs.unlinkSync(fileName); - } catch (err) { - } - }); - - afterAll(async () => { - try { - fs.unlinkSync(fileName); - fs.rmdirSync(tmpDir); - } catch (err) { - } - - process.kill(proc.pid); - await new Promise((resolve, _) => { - setTimeout(resolve, 3000); - }); - }); - - describe('requestBinary', () => { - it('should download the file if no file exists or ' + - 'the content lenght is different', - (done) => { - requestBinary(binaryUrl, {fileName, fileSize: 0}) - .then((result) => { - expect(result).toBeTruthy(); - expect(fs.statSync(fileName).size).toBe(barZipSize); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - - it('should not download the file if the file exists', (done) => { - requestBinary(binaryUrl, {fileName, fileSize: barZipSize}) - .then((result) => { - expect(result).toBeFalsy(); - expect(fs.statSync(fileName).size).toBe(barZipSize); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - }); - - describe('requestBody', () => { - it('should download a json object file', async () => { - const foo = await requestBody(fooJsonUrl, {}); - const fooJson = JSON.parse(foo); - expect(fooJson['foo']).toBe('abc'); - expect(fooJson['bar']).toBe(123); - }); - - it('should download a json array file', async () => { - const foo = await requestBody(fooArrayUrl, {}); - const fooJson = JSON.parse(foo); - expect(fooJson.length).toBe(3); - expect(fooJson[0]['foo']).toBe('abc'); - expect(fooJson[1]['foo']).toBe('def'); - expect(fooJson[2]['foo']).toBe('ghi'); - }); - - it('should get the xml file', async () => { - const text = await requestBody(fooXmlUrl, {}); - expect(text.length).toBeGreaterThan(0); - }); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-proxy.ts b/lib/provider/utils/http_utils.spec-proxy.ts deleted file mode 100644 index 3efb2070..00000000 --- a/lib/provider/utils/http_utils.spec-proxy.ts +++ /dev/null @@ -1,112 +0,0 @@ -import * as childProcess from 'child_process'; -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as os from 'os'; -import * as path from 'path'; - -import {httpBaseUrl, proxyBaseUrl} from '../../../spec/server/env'; -import {spawnProcess} from '../../../spec/support/helpers/test_utils'; - -import {requestBinary, requestBody} from './http_utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); -log.setLevel('debug'); - -const tmpDir = path.resolve(os.tmpdir(), 'test'); -const fileName = path.resolve(tmpDir, 'bar.zip'); -const binaryUrl = proxyBaseUrl + '/spec/support/files/bar.zip'; -const fooJsonUrl = proxyBaseUrl + '/spec/support/files/foo_json.json'; -const fooArrayUrl = proxyBaseUrl + '/spec/support/files/foo_array.json'; -const fooXmlUrl = proxyBaseUrl + '/spec/support/files/foo.xml'; -const barZipSize = 171; -const headers = { - 'host': httpBaseUrl -}; - -describe('binary_utils', () => { - const origTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; - let httpProc: childProcess.ChildProcess; - let proxyProc: childProcess.ChildProcess; - - beforeAll((done) => { - jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000; - httpProc = spawnProcess('node', ['dist/spec/server/http_server.js']); - log.debug('http-server: ' + httpProc.pid); - proxyProc = spawnProcess('node', ['dist/spec/server/proxy_server.js']); - log.debug('proxy-server: ' + proxyProc.pid); - setTimeout(done, 3000); - - try { - fs.mkdirSync(tmpDir); - } catch (err) { - } - try { - fs.unlinkSync(fileName); - } catch (err) { - } - }); - - afterAll((done) => { - try { - fs.unlinkSync(fileName); - fs.rmdirSync(tmpDir); - } catch (err) { - } - - process.kill(httpProc.pid); - process.kill(proxyProc.pid); - setTimeout(done, 5000); - jasmine.DEFAULT_TIMEOUT_INTERVAL = origTimeout; - }); - - describe('requestBinary', () => { - it('should download the file if no file exists or ' + - 'the content lenght is different', - (done) => { - requestBinary(binaryUrl, {fileName, fileSize: 0, headers}) - .then((result) => { - expect(result).toBeTruthy(); - expect(fs.statSync(fileName).size).toBe(barZipSize); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - - it('should not download the file if the file exists', (done) => { - requestBinary(binaryUrl, {fileName, fileSize: barZipSize, headers}) - .then((result) => { - expect(result).toBeFalsy(); - expect(fs.statSync(fileName).size).toBe(barZipSize); - done(); - }) - .catch(err => { - done.fail(err); - }); - }); - }); - - describe('requestBody', () => { - it('should download a json object file', async () => { - const foo = await requestBody(fooJsonUrl, {headers}); - const fooJson = JSON.parse(foo); - expect(fooJson['foo']).toBe('abc'); - expect(fooJson['bar']).toBe(123); - }); - - it('should download a json array file', async () => { - const foo = await requestBody(fooArrayUrl, {headers}); - const fooJson = JSON.parse(foo); - expect(fooJson.length).toBe(3); - expect(fooJson[0]['foo']).toBe('abc'); - expect(fooJson[1]['foo']).toBe('def'); - expect(fooJson[2]['foo']).toBe('ghi'); - }); - - it('should get the xml file', async () => { - const text = await requestBody(fooXmlUrl, {headers}); - expect(text.length).toBeGreaterThan(0); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.spec-unit.ts b/lib/provider/utils/http_utils.spec-unit.ts deleted file mode 100644 index eb687ada..00000000 --- a/lib/provider/utils/http_utils.spec-unit.ts +++ /dev/null @@ -1,178 +0,0 @@ -import {addHeader, initOptions, optionsProxy, optionsSSL, RequestOptionsValue, resolveProxy} from './http_utils'; - -describe('http utils', () => { - describe('initOptions', () => { - it('should create options', () => { - const requestUrl = 'http://foobar.com'; - const options = initOptions(requestUrl, {}); - expect(options['url']).toBe(requestUrl); - expect(options['timeout']).toBe(240000); - expect(options['proxy']).toBeUndefined(); - expect(options['strictSSL']).toBeUndefined(); - expect(options['rejectUnauthorized']).toBeUndefined(); - }); - - it('should create options with a proxy', () => { - const requestUrl = 'http://foobar.com'; - const proxy = 'http://baz.com'; - const options = initOptions(requestUrl, {proxy}); - expect(options['url']).toBe(requestUrl); - expect(options['timeout']).toBe(240000); - expect(options['proxy']).toBe(proxy); - expect(options['strictSSL']).toBeUndefined(); - expect(options['rejectUnauthorized']).toBeUndefined(); - }); - - it('should create options with SSL', () => { - const requestUrl = 'http://foobar.com'; - const options = initOptions(requestUrl, {ignoreSSL: true}); - expect(options['url']).toBe(requestUrl); - expect(options['timeout']).toBe(240000); - expect(options['proxy']).toBeUndefined(); - expect(options['strictSSL']).toBeFalsy(); - expect(options['rejectUnauthorized']).toBeFalsy(); - }); - - it('should create options with SSL and proxy', () => { - const requestUrl = 'http://foobar.com'; - const proxy = 'http://baz.com'; - const options = initOptions(requestUrl, {ignoreSSL: true, proxy}); - expect(options['url']).toBe(requestUrl); - expect(options['timeout']).toBe(240000); - expect(options['proxy']).toBe(proxy); - expect(options['strictSSL']).toBeFalsy(); - expect(options['rejectUnauthorized']).toBeFalsy(); - }); - }); - - describe('optionsSSL', () => { - it('should set strictSSL and rejectUnauthorized', () => { - let options: RequestOptionsValue = {url: 'http://foobar.com'}; - const ignoreSSL = true; - options = optionsSSL(options, ignoreSSL); - expect(options['strictSSL']).toBeFalsy(); - expect(options['rejectUnauthorized']).toBeFalsy(); - }); - - it('should set not set strictSSL and rejectUnauthorized', () => { - let options: RequestOptionsValue = {url: 'http://foobar.com'}; - const ignoreSSL = false; - options = optionsSSL(options, ignoreSSL); - expect(options['strictSSL']).toBeUndefined(); - expect(options['rejectUnauthorized']).toBeUndefined(); - }); - }); - - describe('optionsProxy', () => { - it('should set the proxy', () => { - const requestUrl = 'http://foobar.com'; - let options: RequestOptionsValue = {url: requestUrl}; - const proxy = 'http://baz.com'; - options = optionsProxy(options, requestUrl, proxy); - expect(options['proxy']).toBe(proxy); - }); - - it('should not set the proxy when the proxy is undefined', () => { - const requestUrl = 'http://foobar.com'; - let options: RequestOptionsValue = {url: requestUrl}; - options = optionsProxy(options, requestUrl, undefined); - expect(options['proxy']).toBeUndefined(); - }); - }); - - describe('resolveProxy', () => { - it('should return the proxy', () => { - const requestUrl = 'http://foobar.com/foo/bar/index.html'; - const proxy = 'http://baz.com'; - const resolvedProxy = resolveProxy(requestUrl, proxy); - expect(resolvedProxy).toBe(proxy); - }); - - it('should return the http proxy env for a http request url', () => { - const requestUrl = 'http://foobar.com/foo/bar/index.html'; - const proxy = 'http://baz.com'; - process.env['HTTP_PROXY'] = proxy; - expect(process.env['HTTP_PROXY']).toBe(proxy); - - const resolvedProxy = resolveProxy(requestUrl, undefined); - expect(resolvedProxy).toBe(proxy); - - delete process.env['HTTP_PROXY']; - expect(process.env['HTTP_PROXY']).toBeUndefined(); - }); - - it('should be undefined for a https proxy env for a http request', () => { - const requestUrl = 'http://foobar.com/foo/bar/index.html'; - const proxy = 'https://baz.com'; - process.env['HTTPS_PROXY'] = proxy; - expect(process.env['HTTPS_PROXY']).toBe(proxy); - - const resolvedProxy = resolveProxy(requestUrl, undefined); - expect(resolvedProxy).toBeUndefined(); - - delete process.env['HTTPS_PROXY']; - expect(process.env['HTTPS_PROXY']).toBeUndefined(); - }); - - - it('should return the https proxy env for a https request', () => { - const requestUrl = 'https://foobar.com/foo/bar/index.html'; - const proxy = 'https://baz.com'; - process.env['HTTPS_PROXY'] = proxy; - expect(process.env['HTTPS_PROXY']).toBe(proxy); - - const resolvedProxy = resolveProxy(requestUrl, undefined); - expect(resolvedProxy).toBe(proxy); - - delete process.env['HTTPS_PROXY']; - expect(process.env['HTTPS_PROXY']).toBeUndefined(); - }); - - it('should return the http proxy env for a https request', () => { - const requestUrl = 'https://foobar.com/foo/bar/index.html'; - const proxy = 'http://baz.com'; - process.env['HTTP_PROXY'] = proxy; - expect(process.env['HTTP_PROXY']).toBe(proxy); - - const resolvedProxy = resolveProxy(requestUrl, undefined); - expect(resolvedProxy).toBe(proxy); - - delete process.env['HTTP_PROXY']; - expect(process.env['HTTP_PROXY']).toBeUndefined(); - }); - }); - - describe('addHeader', () => { - let options: RequestOptionsValue; - beforeEach(() => { - options = {url: 'http://foo.bar'}; - }); - it('should create a new header if no header exists', () => { - const modifiedOptions = addHeader(options, 'foo', 'bar'); - expect(modifiedOptions.headers).toBeTruthy(); - expect(Object.keys(modifiedOptions.headers).length).toBe(1); - expect(modifiedOptions.headers['foo']).toBe('bar'); - }); - - it('should add a header to an existing header without destroying the value', - () => { - let modifiedOptions = addHeader(options, 'foo1', 'bar1'); - modifiedOptions = addHeader(options, 'foo2', 'bar2'); - expect(modifiedOptions.headers).toBeTruthy(); - expect(Object.keys(modifiedOptions.headers).length).toBe(2); - expect(modifiedOptions.headers['foo1']).toBe('bar1'); - expect(modifiedOptions.headers['foo2']).toBe('bar2'); - }); - - it('should replace the header if selecting the same header name', () => { - let modifiedOptions = addHeader(options, 'foo', 'bar'); - expect(modifiedOptions.headers).toBeTruthy(); - expect(Object.keys(modifiedOptions.headers).length).toBe(1); - expect(modifiedOptions.headers['foo']).toBe('bar'); - - modifiedOptions = addHeader(options, 'foo', 'baz'); - expect(Object.keys(modifiedOptions.headers).length).toBe(1); - expect(modifiedOptions.headers['foo']).toBe('baz'); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/http_utils.ts b/lib/provider/utils/http_utils.ts deleted file mode 100644 index 21d8ceb3..00000000 --- a/lib/provider/utils/http_utils.ts +++ /dev/null @@ -1,287 +0,0 @@ -import * as fs from 'fs'; -import * as loglevel from 'loglevel'; -import * as path from 'path'; -import * as request from 'request'; -import * as url from 'url'; - -const log = loglevel.getLogger('webdriver-manager'); - -/** - * The request options that extend the request. This is not exported - * in preference to build an HttpOptions object with extra metadata - * and the http_utils methods will help build this object. - */ -export interface RequestOptionsValue extends request.OptionsWithUrl { - proxy?: string; - ignoreSSL?: boolean; -} - -/** - * A json object interface. - */ -export interface JsonObject { - // tslint:disable-next-line:no-any - [key: string]: any; -} - -/** - * The http option interface to build the request. - */ -export interface HttpOptions { - // The full file path. - fileName?: string; - // The file size or content length fo the file. - fileSize?: number; - // Headers to send with the request. - headers?: {[key: string]: string|number|string[]}; - // When making the request, to ignore SSL. - ignoreSSL?: boolean; - // When making the request, use the proxy url provided. - proxy?: string; -} - -/** - * Initialize the request options. - * @param requestUrl The request url. - * @param httpOptions The http options for the request. - */ -export function initOptions( - requestUrl: string, httpOptions: HttpOptions): RequestOptionsValue { - let options: RequestOptionsValue = { - url: requestUrl, - // default Linux can be anywhere from 20-120 seconds - // increasing this arbitrarily to 4 minutes - timeout: 240000 - }; - options = optionsSSL(options, httpOptions.ignoreSSL); - options = optionsProxy(options, requestUrl, httpOptions.proxy); - if (httpOptions.headers) { - for (const key of Object.keys(httpOptions.headers)) { - options = addHeader(options, key, httpOptions.headers[key]); - } - } - return options; -} - -/** - * Set ignore SSL option. - * @param options The HTTP options - * @param optIgnoreSSL The ignore SSL option. - */ -export function optionsSSL( - options: RequestOptionsValue, optIgnoreSSL: boolean): RequestOptionsValue { - if (optIgnoreSSL) { - options.strictSSL = !optIgnoreSSL; - options.rejectUnauthorized = !optIgnoreSSL; - } - return options; -} - -export function optionsProxy( - options: RequestOptionsValue, requestUrl: string, - optProxy: string): RequestOptionsValue { - if (optProxy) { - options.proxy = resolveProxy(requestUrl, optProxy); - if (url.parse(requestUrl).protocol === 'https:') { - options.url = requestUrl.replace('https:', 'http:'); - } - } - return options; -} - -/** - * Resolves proxy based on values set. - * @param requestUrl The url to download the file. - * @param optProxy The proxy to connect to to download files. - * @return Either undefined or the proxy. - */ -export function resolveProxy(requestUrl: string, optProxy: string): string { - if (optProxy) { - return optProxy; - } else { - const protocol = url.parse(requestUrl).protocol; - const hostname = url.parse(requestUrl).hostname; - // If the NO_PROXY environment variable exists and matches the host name, - // to ignore the resolve proxy. - // Check to see if it exists and equal to empty string is to help with - // testing - const noProxy: string = process.env.NO_PROXY || process.env.no_proxy; - if (noProxy) { - // array of hostnames/domain names listed in the NO_PROXY environment - // variable - const noProxyTokens = noProxy.split(','); - // check if the fileUrl hostname part does not end with one of the - // NO_PROXY environment variable's hostnames/domain names - for (const noProxyToken of noProxyTokens) { - if (hostname.indexOf(noProxyToken) !== -1) { - return undefined; - } - } - } - - // If the HTTPS_PROXY and HTTP_PROXY environment variable is set, - // use that as the proxy - const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy; - const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy; - if (protocol === 'https:') { - return httpsProxy || httpProxy; - } else if (protocol === 'http:') { - return httpProxy; - } - } - return undefined; -} - -/** - * Builds a curl command for logging purposes. - * @param requestOptions The request options. - * @param fileName The file name path. - * @returns The curl command. - */ -export function curlCommand( - requestOptions: RequestOptionsValue, fileName?: string) { - let curl = `${requestOptions.url}`; - if (requestOptions.proxy) { - const pathUrl = url.parse(requestOptions.url.toString()).path; - const host = url.parse(requestOptions.url.toString()).host; - if (requestOptions.proxy) { - const modifiedUrl = url.resolve(requestOptions.proxy, pathUrl); - curl = `"${modifiedUrl}" -H "host: ${host}"`; - } - } - if (requestOptions.headers) { - for (const headerName of Object.keys(requestOptions.headers)) { - curl += ` -H "${headerName}: ${requestOptions.headers[headerName]}"`; - } - } - if (requestOptions.ignoreSSL) { - curl = `-k ${curl}`; - } - if (fileName) { - curl = `-o ${fileName} ${curl}`; - } - return `curl ${curl}`; -} - -/** - * Add a header to the request. - * @param options The options to add a header. - * @param name The key name of the header. - * @param value The value of the header. - * @returns The modified options object. - */ -export function addHeader( - options: RequestOptionsValue, name: string, - value: string|number|string[]): RequestOptionsValue { - if (!options.headers) { - options.headers = {}; - } - options.headers[name] = value; - return options; -} - -/** - * The request to download the binary. - * @param binaryUrl The download url for the binary. - * @param httpOptions The http options for the request. - * @param isLogInfo Log info or debug - */ -export function requestBinary( - binaryUrl: string, httpOptions: HttpOptions, - isLogInfo = true): Promise { - const options = initOptions(binaryUrl, httpOptions); - options.followRedirect = false; - options.followAllRedirects = false; - if (isLogInfo) { - log.info(curlCommand(options, httpOptions.fileName)); - } else { - log.debug(curlCommand(options, httpOptions.fileName)); - } - - return new Promise((resolve, reject) => { - const req = request(options); - req.on('response', response => { - let contentLength: number; - if (response.statusCode === 200) { - // Check to see if the size is the same. - // If the file size is the same, do not download and stop here. - contentLength = +response.headers['content-length']; - if (contentLength === httpOptions.fileSize) { - response.destroy(); - resolve(false); - } else { - // Only pipe if the headers are different length. - const dir = path.dirname(httpOptions.fileName); - try { - fs.mkdirSync(dir); - } catch (err) { - } - const file = fs.createWriteStream(httpOptions.fileName); - req.pipe(file); - - file.on('close', () => { - fs.stat(httpOptions.fileName, (error, stats) => { - if (error) { - reject(error); - } - if (stats.size !== contentLength) { - fs.unlinkSync(httpOptions.fileName); - reject(error); - } - resolve(true); - }); - }); - file.on('error', (error) => { - reject(error); - }); - } - } else if (response.statusCode === 302) { - const location = response.headers['location'] as string; - if (!httpOptions.headers) { - httpOptions.headers = {}; - } - for (const header of Object.keys(response.headers)) { - httpOptions.headers[header] = response.headers[header]; - } - resolve(requestBinary(location, httpOptions, false)); - } else { - reject(new Error('response status code is not 200')); - } - }); - req.on('error', error => { - reject(error); - }); - }); -} - -/** - * Request the body from the url and log the curl. - * @param requestUrl The request url. - * @param httpOptions The http options for the request. - * @returns A promise string of the response body. - */ -export function requestBody( - requestUrl: string, httpOptions: HttpOptions): Promise { - const options = initOptions(requestUrl, httpOptions); - log.info(curlCommand(options, httpOptions.fileName)); - options.followRedirect = true; - return new Promise((resolve, reject) => { - const req = request(options); - req.on('response', response => { - if (response.statusCode === 200) { - let output = ''; - response.on('data', (data) => { - output += data; - }); - response.on('end', () => { - resolve(output); - }); - } else { - reject(new Error('response status code is not 200')); - } - }); - req.on('error', error => { - reject(error); - }); - }); -} \ No newline at end of file diff --git a/lib/provider/utils/version_list.spec-unit.ts b/lib/provider/utils/version_list.spec-unit.ts deleted file mode 100644 index e07aee9e..00000000 --- a/lib/provider/utils/version_list.spec-unit.ts +++ /dev/null @@ -1,75 +0,0 @@ -import {getVersionObj, getVersionObjs, VersionList} from './version_list'; - -const versionList: VersionList = { - '1.0.0': { - 'foo_mac32': {url: '1.0.0/foo_mac32', size: 10000}, - 'foo_win32': {url: '1.0.0/foo_win32', size: 10001}, - 'foo_linux64': {url: '1.0.0/foo_linux64', size: 10002} - }, - '1.0.1': { - 'foo_mac32': {url: '1.0.1/foo_mac32', size: 10100}, - 'foo_win32': {url: '1.0.1/foo_win32', size: 10101}, - 'foo_linux64': {url: '1.0.1/foo_linux64', size: 10102} - }, - '2.0.1': { - 'foo_mac32': {url: '2.0.1/foo_mac32', size: 20100}, - 'foo_win32': {url: '2.0.1/foo_win32', size: 20101}, - 'foo_linux64': {url: '2.0.1/foo_linux64', size: 20102} - }, - '3.0.1': { - 'foo_mac32': {url: '3.0.1/foo_mac32', size: 30100}, - 'foo_win32': {url: '3.0.1/foo_win32', size: 30101}, - 'foo_linux64': {url: '3.0.1/foo_linux64', size: 30102} - } -}; - -describe('version_list', () => { - describe('getVersion', () => { - it('should return the latest version when no version provided', () => { - const version = getVersionObjs(versionList); - expect(Object.keys(version).length).toBe(3); - expect(version['foo_mac32']['size']).toBe(30100); - expect(version['foo_win32']['size']).toBe(30101); - expect(version['foo_linux64']['size']).toBe(30102); - }); - - it('should return the latest version with latest option', () => { - const version = getVersionObjs(versionList, 'latest'); - expect(Object.keys(version).length).toBe(3); - expect(version['foo_mac32']['size']).toBe(30100); - expect(version['foo_win32']['size']).toBe(30101); - expect(version['foo_linux64']['size']).toBe(30102); - }); - - it('should return version 1.0.1', () => { - const version = getVersionObjs(versionList, '1.0.1'); - expect(Object.keys(version).length).toBe(3); - expect(version['foo_mac32']['size']).toBe(10100); - expect(version['foo_win32']['size']).toBe(10101); - expect(version['foo_linux64']['size']).toBe(10102); - }); - }); - - describe('getVersionObj', () => { - it('should get the partial url for mac', () => { - const versionObjMap = getVersionObjs(versionList); - const versionObj = getVersionObj(versionObjMap, 'mac32'); - expect(versionObj.url).toBe('3.0.1/foo_mac32'); - expect(versionObj.size).toBe(30100); - }); - - it('should get the partial url for windows', () => { - const versionObjMap = getVersionObjs(versionList); - const versionObj = getVersionObj(versionObjMap, 'win32'); - expect(versionObj.url).toBe('3.0.1/foo_win32'); - expect(versionObj.size).toBe(30101); - }); - - it('should get the partial url for linux', () => { - const versionObjMap = getVersionObjs(versionList); - const versionObj = getVersionObj(versionObjMap, 'linux64'); - expect(versionObj.url).toBe('3.0.1/foo_linux64'); - expect(versionObj.size).toBe(30102); - }); - }); -}); \ No newline at end of file diff --git a/lib/provider/utils/version_list.ts b/lib/provider/utils/version_list.ts deleted file mode 100644 index b869e62a..00000000 --- a/lib/provider/utils/version_list.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as fs from 'fs'; -import * as semver from 'semver'; - -/** - * An object of multiple versions of a binary. Each version could have - * different keys where each key represents a partial url path. Each - * partial url path could represent a combination of os architecture - * and os type. - */ -export interface VersionList { - // The forced version is the semver equivalent version of the - // actual version number. An example is 2.9 would translate into 2.9.0 - [forcedVersion: string]: { - - // The name of the binary file. - [name: string]: VersionObj; - }; -} - -/** - * Information about the binary file. - */ -export interface VersionObj { - // The file name. - name?: string; - - // The content length of the file. - size?: number; - - // The full or partial url get the binary. - url?: string; - - // The actual version number, not the forced semantic version. - version?: string; -} - -/** - * Encapsulates the getVersionObjs and getVersionObj into a single method. - * @param versionList The version list object. - * @param osMatch The OS name and architecture. - * @param version Optional field for the semver version number or latest. - * * @returns Either a VersionObj or null. - */ -export function getVersion( - versionList: VersionList, osMatch: string, version?: string): VersionObj| - null { - const versionObjs = getVersionObjs(versionList, version); - return getVersionObj(versionObjs, osMatch); -} - -/** - * Get the version obj from the version list. - * @param versionList The version list object. - * @param version Optional field for the semver version number or latest. - * @returns The object with paritial urls associated with the binary size. - */ -export function getVersionObjs( - versionList: VersionList, version?: string): {[key: string]: VersionObj} { - if (version && version !== 'latest') { - return versionList[version]; - } else { - let latestVersion = null; - for (const versionKey of Object.keys(versionList)) { - if (!latestVersion) { - latestVersion = versionKey; - } else { - if (semver.gt(versionKey, latestVersion)) { - latestVersion = versionKey; - } - } - } - return versionList[latestVersion]; - } -} - -/** - * Get the version obj from the map. - * @param versionObjs A map of partial urls to VersionObj - * @param osMatch The OS name and architecture. - * @returns Either a VersionObj or null. - */ -export function getVersionObj( - versionObjMap: {[key: string]: VersionObj}, osMatch: string): VersionObj| - null { - for (const name of Object.keys(versionObjMap)) { - if (name.includes(osMatch)) { - return versionObjMap[name]; - } - } - return null; -} \ No newline at end of file diff --git a/lib/webdriver.ts b/lib/webdriver.ts new file mode 100644 index 00000000..4dd6acb8 --- /dev/null +++ b/lib/webdriver.ts @@ -0,0 +1,32 @@ +import * as minimist from 'minimist'; + +import {Cli} from './cli'; +import * as clean from './cmds/clean'; +import * as start from './cmds/start'; +import * as status from './cmds/status'; +import * as update from './cmds/update'; +import {Config} from './config'; + +let commandline = new Cli() + .usage('webdriver-manager [options]') + .program(clean.program) + .program(start.program) + .program(status.program) + .program(update.program); + +let minimistOptions = commandline.getMinimistOptions(); +let argv = minimist(process.argv.slice(2), minimistOptions); +let cmd = argv._; +if (commandline.programs[cmd[0]]) { + if (cmd[0] === 'help') { + commandline.printHelp(); + } else if (cmd[1] === 'help' || argv['help'] || argv['h']) { + commandline.programs[cmd[0]].printHelp(); + } else { + commandline.programs[cmd[0]].run(JSON.parse(JSON.stringify(argv))); + } +} else { + commandline.printHelp(); +} + +export var cli = commandline; diff --git a/mobile.md b/mobile.md new file mode 100644 index 00000000..294c3e41 --- /dev/null +++ b/mobile.md @@ -0,0 +1,60 @@ +Mobile Browser Support +====================== + +Support for mobile browsers is provided via [appium](https://github.com/appium/appium). If you +have used `webdriver-manager update --android` or `webdriver-manager update --ios`, when you run +`webdriver-manager start`, Appium will automatically start on the port specified by `--appium-port`. + + +Android SDK +----------- + +`webdriver-manager` will not install the android SDK by default. If you want to test on android, +run `webdriver-manager update --android`. This will download the android SDK, Appium, and set up +some virtual android devices for you to run tests against. By default, this will create only an +android device running version 24 on x86-64. If you need a different device, you must use the +`--android-api-levels` and `--android-abis` flags. So you might run a command like this: + +``` +webdriver-manager update --android --android-api-levels 23 --android-abis armeabi-v7a +``` + +Valid values for the `--android-api-levels` flag are: `2`, `3`, `4`, `5`, ..., `24` + +Valid values for the `--android-abi` flag are: `x86_64`, `armeabi-v7a`, `x86`, `mips`, + `android-wear/x86_64`, `android-wear/armeabi-v7a`, `android-wear/x86`, `android-wear/mips`, + `android-tv/x86_64`, `android-tv/armeabi-v7a`, `android-tv/x86`, `android-tv/mips`, + `google_apis/x86_64`, `google_apis/armeabi-v7a`, `google_apis/x86`, `google_apis/mips` + + +As a practical matter, if you don't want to manually accept the license agreements, you can use +`--android-accept-licenses`, which will accept them on your behalf. + +Once you have installed the Android SDK with the virtual devices you need, use +`webdriver-manager start --android` to boot up Appium and begin emulating your android device(s). +By default `webdriver-manager` will emulate all available android devices. If you would rather +emulate a specific device, use `--avds`. So you might use: + +``` +webdriver-manager start --android --avds android-23-default-x86_64 +``` + +If you would prefer not to emulate any android virtual devices, use `--avds none`. + +If you need to specify the ports used by the android virtual devices, use `--avd_port`. The port +you specify will be used for the console of the first device, and the port one higher will be used +for its ADB. The second device will use the next two ports, and so on. + + +iOS +--------- + +When you run `webdriver-manager update --ios`, `webdriver-manager` will install Appium and check +your computer for iOS simulation capabilities. `webdriver-manager` cannot download the xcode +commandline tools for you however, nor can it agree to Apple's user agreement. The xcode +commandline tools come with several virtual devices pre-setup. If you need more, run +`xcrun simctl` for help doing that. + +Once you have installed Appium, `webdriver-manager` will launch it automatically when you run +`webdriver-manager start`. Appium will automatically handle starting iOS device emulation as +needed. diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 7ae11740..00000000 --- a/package-lock.json +++ /dev/null @@ -1,2379 +0,0 @@ -{ - "name": "webdriver-manager", - "version": "13.0.2", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/adm-zip": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.5.0.tgz", - "integrity": "sha512-FCJBJq9ODsQZUNURo5ILAQueuA8WJhRvuihS3ke2iI25mJlfV2LK8jG2Qj2z2AWg8U0FtWWqBHVRetceLskSaw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/caseless": { - "version": "0.12.1", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", - "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", - "dev": true - }, - "@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", - "dev": true - }, - "@types/form-data": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", - "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/glob": { - "version": "5.0.35", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.35.tgz", - "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/http-proxy": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.16.2.tgz", - "integrity": "sha512-GgqePmC3rlsn1nv+kx5OviPuUBU2omhnlXOaJSXFgOdsTcScNFap+OaCb2ip9Bm4m5L8EOehgT5d9M4uNB90zg==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" - } - }, - "@types/jasmine": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.3.1.tgz", - "integrity": "sha512-JnKB+cEIFuQZXizZP6N0zxma+JlvowkjefWuL61otVmXN7Ebbs4ka3IbDVIz1pc+TCiT00q925jANz3gQJ9qXw==", - "dev": true - }, - "@types/loglevel": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@types/loglevel/-/loglevel-1.5.3.tgz", - "integrity": "sha512-TzzIZihV+y9kxSg5xJMkyIkaoGkXi50isZTtGHObNHRqAAwjGNjSCNPI7AUAv0tZUKTq9f2cdkCUd/2JVZUTrA==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/node": { - "version": "10.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.12.tgz", - "integrity": "sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A==", - "dev": true - }, - "@types/request": { - "version": "2.48.1", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", - "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", - "dev": true, - "requires": { - "@types/caseless": "*", - "@types/form-data": "*", - "@types/node": "*", - "@types/tough-cookie": "*" - } - }, - "@types/rimraf": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-2.0.2.tgz", - "integrity": "sha512-Hm/bnWq0TCy7jmjeN5bKYij9vw5GrDFWME4IuxV08278NtU/VdGbzsBohcCUJ7+QMqmUq5hpRKB39HeQWJjztQ==", - "dev": true, - "requires": { - "@types/glob": "*", - "@types/node": "*" - } - }, - "@types/semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", - "dev": true - }, - "@types/tar": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/tar/-/tar-4.0.0.tgz", - "integrity": "sha512-YybbEHNngcHlIWVCYsoj7Oo1JU9JqONuAlt1LlTH/lmL8BMhbzdFUgReY87a05rY1j8mfK47Del+TCkaLAXwLw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-Set5ZdrAaKI/qHdFlVMgm/GsAv/wkXhSTuZFkJ+JI7HK+wIkIlOaUXSXieIvJ0+OvGIqtREFoE+NHJtEq0gtEw==", - "dev": true - }, - "@types/xml2js": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.3.tgz", - "integrity": "sha512-Pv2HGRE4gWLs31In7nsyXEH4uVVsd0HNV9i2dyASvtDIlOtSTr1eczPLDpdEuyv5LWH5LT20GIXwPjkshKWI1g==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-UVjo2oH79aRNcsDlFlnQ/iJ67Jd7j6uSg7jUJP/RZ/nUjAh5ElmnwlD5K/6eGgETJUgCHkiWn91B8JjXQ6ubAw==", - "dev": true - }, - "adm-zip": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz", - "integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==" - }, - "ajv": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", - "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-align": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", - "dev": true, - "requires": { - "string-width": "^2.0.0" - } - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "boxen": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", - "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", - "dev": true, - "requires": { - "ansi-align": "^2.0.0", - "camelcase": "^4.0.0", - "chalk": "^2.0.1", - "cli-boxes": "^1.0.0", - "string-width": "^2.0.0", - "term-size": "^1.2.0", - "widest-line": "^2.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "camelcase": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", - "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" - }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "dev": true, - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==" - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "clang-format": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.4.tgz", - "integrity": "sha512-sw+nrGUp3hvmANd1qF8vZPuezSYQAiXgGBiEtkXTtJnnu6b00fCqkkDIsnRKrNgg4nv6NYZE92ejvOMIXZoejw==", - "dev": true, - "requires": { - "async": "^1.5.2", - "glob": "^7.0.0", - "resolve": "^1.1.6" - } - }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "^2.0.0" - } - }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", - "dev": true - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "color-convert": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", - "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", - "dev": true, - "requires": { - "color-name": "1.1.1" - } - }, - "color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "configstore": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", - "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", - "dev": true, - "requires": { - "dot-prop": "^4.1.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "unique-string": "^1.0.0", - "write-file-atomic": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "dev": true, - "requires": { - "array-find-index": "^1.0.1" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true - } - } - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "eventemitter3": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", - "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==", - "dev": true - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "external-editor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", - "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "follow-redirects": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz", - "integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==", - "dev": true, - "requires": { - "debug": "^3.1.0" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "global-dirs": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", - "dev": true, - "requires": { - "ini": "^1.3.4" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "gts": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/gts/-/gts-0.9.0.tgz", - "integrity": "sha512-Id2Vmg0xNU1FODc0AwmaFA1h0+h6V9/zBqu4NfT8FucVOVEP7pyJ16btyHfSH/UdzTCXjV1fq+fNBEgx/50EaA==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "clang-format": "1.2.3", - "diff": "^3.5.0", - "entities": "^1.1.1", - "inquirer": "^6.0.0", - "meow": "^5.0.0", - "pify": "^4.0.0", - "rimraf": "^2.6.2", - "tslint": "^5.9.1", - "update-notifier": "^2.5.0", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "clang-format": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clang-format/-/clang-format-1.2.3.tgz", - "integrity": "sha512-x90Hac4ERacGDcZSvHKK58Ga0STuMD+Doi5g0iG2zf7wlJef5Huvhs/3BvMRFxwRYyYSdl6mpQNrtfMxE8MQzw==", - "dev": true, - "requires": { - "async": "^1.5.2", - "glob": "^7.0.0", - "resolve": "^1.1.6" - } - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", - "dev": true, - "requires": { - "eventemitter3": "^3.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true - }, - "inquirer": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", - "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^3.0.0", - "figures": "^2.0.0", - "lodash": "^4.17.10", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^6.1.0", - "string-width": "^2.1.0", - "strip-ansi": "^5.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", - "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", - "dev": true - }, - "strip-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", - "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", - "dev": true, - "requires": { - "ansi-regex": "^4.0.0" - } - } - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-installed-globally": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", - "dev": true, - "requires": { - "global-dirs": "^0.1.0", - "is-path-inside": "^1.0.0" - } - }, - "is-npm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", - "dev": true - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, - "requires": { - "path-is-inside": "^1.0.1" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jasmine": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.3.1.tgz", - "integrity": "sha512-/vU3/H7U56XsxIXHwgEuWpCgQ0bRi2iiZeUpx7Nqo8n1TpoDHfZhkPIc7CO8I4pnMzYsi3XaSZEiy8cnTfujng==", - "dev": true, - "requires": { - "glob": "^7.0.6", - "jasmine-core": "~3.3.0" - } - }, - "jasmine-core": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.3.0.tgz", - "integrity": "sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==", - "dev": true - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz", - "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "latest-version": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", - "dev": true, - "requires": { - "package-json": "^4.0.0" - } - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "requires": { - "invert-kv": "^2.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true - }, - "loglevel": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", - "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "dev": true, - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - }, - "dependencies": { - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - } - } - }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", - "dev": true - }, - "mem": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", - "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^1.0.0", - "p-is-promise": "^1.1.0" - } - }, - "meow": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", - "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0", - "yargs-parser": "^10.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" - }, - "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", - "requires": { - "mime-db": "~1.37.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" - } - }, - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "os-locale": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", - "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", - "requires": { - "execa": "^0.10.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-is-promise": { - "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", - "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", - "dev": true, - "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", - "dev": true - }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } - } - }, - "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "registry-auth-token": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", - "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", - "dev": true, - "requires": { - "rc": "^1.1.6", - "safe-buffer": "^5.0.1" - } - }, - "registry-url": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", - "dev": true, - "requires": { - "rc": "^1.0.1" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "^2.1.0" - } - }, - "rxjs": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", - "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" - }, - "semver-diff": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", - "dev": true, - "requires": { - "semver": "^5.0.3" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz", - "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", - "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "tar": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz", - "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "term-size": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", - "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", - "dev": true, - "requires": { - "execa": "^0.7.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - } - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } - } - }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", - "dev": true - }, - "tslib": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true - }, - "tslint": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", - "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.27.2" - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", - "dev": true - }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "^1.0.0" - } - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true - }, - "update-notifier": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", - "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", - "dev": true, - "requires": { - "boxen": "^1.2.1", - "chalk": "^2.0.1", - "configstore": "^3.0.0", - "import-lazy": "^2.1.0", - "is-ci": "^1.0.10", - "is-installed-globally": "^0.1.0", - "is-npm": "^1.0.0", - "latest-version": "^3.0.0", - "semver-diff": "^2.0.0", - "xdg-basedir": "^3.0.0" - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - } - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dev": true, - "requires": { - "prepend-http": "^1.0.1" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "widest-line": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", - "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==", - "dev": true, - "requires": { - "string-width": "^2.1.1" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "xdg-basedir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", - "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", - "dev": true - }, - "xml2js": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", - "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" - } - }, - "xmlbuilder": { - "version": "9.0.7", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", - "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} diff --git a/package.json b/package.json index c8dbddc9..44504f16 100644 --- a/package.json +++ b/package.json @@ -1,85 +1,62 @@ { "name": "webdriver-manager", - "version": "13.0.2", - "description": "webdriver-manager", - "bin": { - "webdriver-manager": "bin/webdriver-manager" - }, - "main": "dist/lib/index.js", - "types": "dist/lib/index.d.ts", + "version": "10.2.4", + "description": "A selenium server and browser driver manager for your end to end tests.", "scripts": { - "check": "gts check", - "clean": "gts clean", - "compile": "npm run tsc", - "fix": "gts fix", - "gts": "gts", - "http-server": "tsc && node dist/spec/server/http_server.js", - "jasmine": "jasmine", - "prepare": "npm run compile", - "pretest": "npm run compile", - "pretsc": "rm -rf dist/ && rm -rf downloads/", - "proxy-server": "tsc && node dist/spec/server/proxy_server.js", - "test": "tsc && npm run test-unit && npm run test-int && npm run test-proxy && npm run test-e2e", - "test-e2e": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-e2e.json", - "test-int": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-int.json", - "test-proxy": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-proxy.json", - "test-unit": "tsc && jasmine JASMINE_CONFIG_PATH=spec/jasmine-unit.json", - "tslint": "tslint -c tslint.json --fix lib/**/*.ts spec/**/*.ts", - "tsc": "tsc" + "prepublish": "gulp prepublish", + "test": "gulp test" }, "keywords": [ - "automation", - "browser", - "browsers", - "browser test", - "browser testing", - "chromedriver", - "geckodriver", - "iedriver", - "selenium", - "selenium-webdriver", + "angular", "test", "testing", + "protractor", "webdriver", - "webdriverjs" + "webdriverjs", + "selenium", + "selenium-webdriver" ], "repository": { "type": "git", - "url": "git+https://github.com/angular/webdriver-manager.git" + "url": "git://github.com/angular/webdriver-manager.git" }, + "bin": { + "webdriver-manager": "bin/webdriver-manager" + }, + "main": "built/lib/webdriver.js", "author": "Craig Nishina ", "license": "MIT", - "bugs": { - "url": "https://github.com/angular/webdriver-manager/issues" - }, - "homepage": "https://github.com/angular/webdriver-manager#readme", "dependencies": { - "adm-zip": "^0.5.2", - "loglevel": "^1.6.1", - "request": "^2.88.0", - "semver": "^5.6.0", - "tar": "^4.4.8", - "xml2js": "^0.4.19", - "yargs": "^12.0.5" + "adm-zip": "^0.4.7", + "chalk": "^1.1.1", + "del": "^2.2.0", + "glob": "^7.0.3", + "ini": "^1.3.4", + "minimist": "^1.2.0", + "q": "^1.4.1", + "request": "^2.69.0", + "rimraf": "^2.5.2", + "semver": "^5.3.0" }, "devDependencies": { - "@types/adm-zip": "^0.5.0", - "@types/http-proxy": "^1.16.2", - "@types/jasmine": "^3.3.1", - "@types/loglevel": "^1.5.3", - "@types/node": "^10.12.12", - "@types/request": "^2.48.1", - "@types/rimraf": "^2.0.2", - "@types/semver": "^5.5.0", - "@types/tar": "^4.0.0", - "@types/xml2js": "^0.4.3", - "@types/yargs": "^12.0.1", - "clang-format": "^1.2.4", - "gts": "^0.9.0", - "http-proxy": "^1.17.0", - "jasmine": "^3.3.1", - "rimraf": "^2.6.2", - "tslint": "^5.11.0", - "typescript": "^3.2.2" + "@types/adm-zip": "^0.4.29", + "@types/chalk": "^0.4.28", + "@types/form-data": "^0.0.32", + "@types/glob": "^5.0.29", + "@types/ini": "^1.3.28", + "@types/jasmine": "^2.2.32", + "@types/minimatch": "^2.0.28", + "@types/minimist": "^1.1.28", + "@types/node": "^6.0.37", + "@types/q": "^0.0.32", + "@types/request": "^0.0.31", + "@types/rimraf": "^0.0.28", + "@types/semver": "^5.3.30", + "clang-format": "^1.0.35", + "gulp": "^3.9.1", + "gulp-clang-format": "^1.0.23", + "jasmine": "^2.4.1", + "run-sequence": "^1.1.5", + "typescript": "^2.0.0" } } diff --git a/spec/files/downloader_spec.ts b/spec/files/downloader_spec.ts new file mode 100644 index 00000000..572adf5b --- /dev/null +++ b/spec/files/downloader_spec.ts @@ -0,0 +1,97 @@ +import {Downloader} from '../../lib/files'; + +describe('downloader', () => { + let fileUrlHttp = 'http://foobar.com'; + let fileUrlHttps = 'https://foobar.com'; + let argProxy = 'http://foobar.arg'; + let envNoProxy = 'http://foobar.com'; + let envHttpProxy = 'http://foobar.env'; + let envHttpsProxy = 'https://foobar.env'; + + it ('should return undefined when proxy arg is not used', () => { + let proxy = Downloader.resolveProxy_(fileUrlHttp); + expect(proxy).toBeUndefined(); + }); + + describe('proxy arg', () => { + let opt_proxy = 'http://bar.foo'; + it('should return the proxy arg', () => { + let proxy = Downloader.resolveProxy_(fileUrlHttp, opt_proxy); + expect(proxy).toBe(opt_proxy); + }); + + it('should always return the proxy arg with env var set', () => { + process.env.HTTP_PROXY = envHttpProxy; + process.env.HTTPS_PROXY = envHttpsProxy; + process.env.NO_PROXY = envNoProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttp, opt_proxy); + expect(proxy).toBe(opt_proxy); + }); + }); + + describe('environment varialbes', () => { + beforeEach(() => { + delete process.env.HTTP_PROXY; + delete process.env.http_proxy; + delete process.env.HTTPS_PROXY; + delete process.env.https_proxy; + delete process.env.NO_PROXY; + delete process.env.no_proxy; + }); + + it('should return the HTTP env variable', () => { + process.env.HTTP_PROXY = envHttpProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttp); + expect(proxy).toBe(envHttpProxy); + }); + + it('should return the http env variable', () => { + process.env.http_proxy = envHttpProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttp); + expect(proxy).toBe(envHttpProxy); + }); + + it('should return the HTTPS env variable for https protocol', () => { + process.env.HTTPS_PROXY = envHttpsProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttps); + expect(proxy).toBe(envHttpsProxy); + }); + + it('should return the https env variable for https protocol', () => { + process.env.https_proxy = envHttpsProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttps); + expect(proxy).toBe(envHttpsProxy); + }); + + it('should return the HTTP env variable for https protocol', () => { + process.env.HTTP_PROXY = envHttpProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttps); + expect(proxy).toBe(envHttpProxy); + }); + + it('should return the https env variable for https protocol', () => { + process.env.http_proxy = envHttpProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttps); + expect(proxy).toBe(envHttpProxy); + }); + + describe('NO_PROXY environment variable', () => { + beforeEach(() => { + delete process.env.NO_PROXY; + delete process.env.no_proxy; + }); + + it('should return null when the NO_PROXY matches the fileUrl', () => { + process.env.NO_PROXY = envNoProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttp); + expect(proxy).toBeUndefined(); + }); + + it('should return null when the no_proxy matches the fileUrl', () => { + process.env.no_proxy = envNoProxy; + let proxy = Downloader.resolveProxy_(fileUrlHttp); + expect(proxy).toBeUndefined(); + }); + }); + }); +}); diff --git a/spec/files/fileManager_spec.ts b/spec/files/fileManager_spec.ts new file mode 100644 index 00000000..9cc72cd4 --- /dev/null +++ b/spec/files/fileManager_spec.ts @@ -0,0 +1,266 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { Binary, AndroidSDK, ChromeDriver, IEDriver, Appium, StandAlone } from '../../lib/binaries'; +import { DownloadedBinary, FileManager } from '../../lib/files'; +import { BinaryMap } from '../../lib/binaries/binary'; +import { Config } from '../../lib/config'; +import { GeckoDriver } from '../../lib/binaries/gecko_driver'; + + +describe('file manager', () => { + + describe('setting up for windows', () => { + let osType = 'Windows_NT'; + + it('should find correct binaries', () => { + expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); + expect(FileManager.checkOS_(osType, IEDriver)).toBe(true); + expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); + expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); + }); + + it('should return the binary array', () => { + let binaries = FileManager.compileBinaries_(osType); + expect(binaries[StandAlone.id].name).toBe((new StandAlone()).name); + expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); + expect(binaries[IEDriver.id].name).toBe((new IEDriver()).name); + expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); + }); + }); + + describe('setting up for linux', () => { + let osType = 'Linux'; + + it('should find correct binaries', () => { + expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); + expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); + expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); + expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); + }); + + it('should return the binary array', () => { + let binaries = FileManager.compileBinaries_(osType); + expect(binaries[StandAlone.id].name).toBe((new StandAlone()).name); + expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); + expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); + expect(binaries[IEDriver.id]).toBeUndefined(); + }); + }); + + describe('setting up for mac', () => { + let osType = 'Darwin'; + + it('should find correct binaries', () => { + expect(FileManager.checkOS_(osType, ChromeDriver)).toBe(true); + expect(FileManager.checkOS_(osType, IEDriver)).toBe(false); + expect(FileManager.checkOS_(osType, StandAlone)).toBe(true); + expect(FileManager.checkOS_(osType, AndroidSDK)).toBe(true); + expect(FileManager.checkOS_(osType, Appium)).toBe(true); + }); + + it('should return the binary array', () => { + let binaries = FileManager.compileBinaries_(osType); + expect(binaries[StandAlone.id].name).toBe((new StandAlone()).name); + expect(binaries[ChromeDriver.id].name).toBe((new ChromeDriver()).name); + expect(binaries[IEDriver.id]).toBeUndefined(); + expect(binaries[AndroidSDK.id].name).toBe((new AndroidSDK()).name); + expect(binaries[Appium.id].name).toBe((new Appium()).name); + }); + }); + + describe('downloaded version checks', () => { + let existingFiles: string[]; + let selenium = new StandAlone(); + let chrome = new ChromeDriver(); + let android = new AndroidSDK(); + let appium = new Appium(); + let ie = new IEDriver(); + let ostype: string; + let arch: string; + + function setup(osType: string): void { + ostype = osType; + arch = 'x64'; + existingFiles = [ + selenium.prefix() + '2.51.0' + selenium.executableSuffix(), + selenium.prefix() + '2.52.0' + selenium.executableSuffix()]; + existingFiles.push(chrome.prefix() + '2.20' + chrome.suffix(ostype, arch)); + existingFiles.push(chrome.prefix() + '2.20' + chrome.executableSuffix(ostype)); + existingFiles.push(chrome.prefix() + '2.21' + chrome.suffix(ostype, arch)); + existingFiles.push(chrome.prefix() + '2.21' + chrome.executableSuffix(ostype)); + existingFiles.push(android.prefix() + '24.1.0' + android.suffix(ostype)); + existingFiles.push(android.prefix() + '24.1.0' + android.executableSuffix()); + existingFiles.push(android.prefix() + '24.1.1' + android.suffix(ostype)); + existingFiles.push(android.prefix() + '24.1.1' + android.executableSuffix()); + existingFiles.push(appium.prefix() + '1.5.3' + appium.suffix(ostype)); + if (ostype == 'Windows_NT') { + existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.suffix()); + existingFiles.push(ie.prefix() + '_Win32_2.51.0' + ie.executableSuffix(ostype)); + existingFiles.push(ie.prefix() + '_x64_2.51.0' + ie.suffix()); + existingFiles.push(ie.prefix() + '_x64_2.51.0' + ie.executableSuffix(ostype)); + existingFiles.push(ie.prefix() + '_Win32_2.52.0' + ie.suffix()); + existingFiles.push(ie.prefix() + '_Win32_2.52.0' + ie.executableSuffix(ostype)); + existingFiles.push(ie.prefix() + '_x64_2.52.0' + ie.suffix()); + existingFiles.push(ie.prefix() + '_x64_2.52.0' + ie.executableSuffix(ostype)); + } + } + + describe('versions for selenium', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.51.0'); + expect(downloaded.versions[1]).toBe('2.52.0'); + }); + it('should find the correct version for mac', () => { + setup('Darwin'); + let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.51.0'); + expect(downloaded.versions[1]).toBe('2.52.0'); + }); + it('should find the correct version for mac', () => { + setup('Linux'); + let downloaded = FileManager.downloadedVersions_(selenium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.51.0'); + expect(downloaded.versions[1]).toBe('2.52.0'); + }); + }); + + describe('versions for chrome', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.20'); + expect(downloaded.versions[1]).toBe('2.21'); + }); + it('should find the correct version for mac', () => { + setup('Darwin'); + let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.20'); + expect(downloaded.versions[1]).toBe('2.21'); + }); + it('should find the correct version for linux', () => { + setup('Linux'); + let downloaded = FileManager.downloadedVersions_(chrome, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('2.20'); + expect(downloaded.versions[1]).toBe('2.21'); + }); + }); + + describe('versions for android', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('24.1.0'); + expect(downloaded.versions[1]).toBe('24.1.1'); + }); + it('should find the correct version for mac', () => { + setup('Darwin'); + let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('24.1.0'); + expect(downloaded.versions[1]).toBe('24.1.1'); + }); + it('should find the correct version for linux', () => { + setup('Linux'); + let downloaded = FileManager.downloadedVersions_(android, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(2); + expect(downloaded.versions[0]).toBe('24.1.0'); + expect(downloaded.versions[1]).toBe('24.1.1'); + }); + }); + + describe('versions for appium', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + it('should find the correct version for mac', () => { + setup('Darwin'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + it('should find the correct version for linux', () => { + setup('Linux'); + let downloaded = FileManager.downloadedVersions_(appium, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(1); + expect(downloaded.versions[0]).toBe('1.5.3'); + }); + }); + + describe('versions for ie on windows', () => { + it('should find the correct version for windows', () => { + setup('Windows_NT'); + let downloaded = FileManager.downloadedVersions_(ie, ostype, arch, existingFiles); + expect(downloaded.versions.length).toBe(4); + expect(downloaded.versions[0]).toBe('Win32_2.51.0'); + expect(downloaded.versions[1]).toBe('x64_2.51.0'); + expect(downloaded.versions[2]).toBe('Win32_2.52.0'); + expect(downloaded.versions[3]).toBe('x64_2.52.0'); + }); + }); + }); + + describe('configuring the CDN location', () => { + + describe('when no custom CDN is specified', () => { + + let defaults = Config.cdnUrls(); + let binaries = FileManager.compileBinaries_('Windows_NT'); + + it('should use the default configuration for Android SDK', () => { + expect(binaries[AndroidSDK.id].cdn).toEqual(defaults[AndroidSDK.id]); + }); + + it('should use the default configuration for Appium', () => { + expect(binaries[Appium.id].cdn).toEqual(defaults[Appium.id]); + }); + + it('should use the default configuration for Chrome Driver', () => { + expect(binaries[ChromeDriver.id].cdn).toEqual(defaults[ChromeDriver.id]); + }); + + it('should use the default configuration for Gecko Driver', () => { + expect(binaries[GeckoDriver.id].cdn).toEqual(defaults[GeckoDriver.id]); + }); + + it('should use the default configuration for IE Driver', () => { + expect(binaries[IEDriver.id].cdn).toEqual(defaults[IEDriver.id]); + }); + + it('should use the default configuration for Selenium Standalone', () => { + expect(binaries[StandAlone.id].cdn).toEqual(defaults['selenium']); + }); + }); + + describe('when custom CDN is specified', () => { + + it('should configure the CDN for each binary', () => { + let customCDN = 'https://my.corporate.cdn/'; + let binaries = FileManager.compileBinaries_('Windows_NT', customCDN); + + forEachOf(binaries, binary => expect(binary.cdn).toEqual(customCDN, binary.name)); + }); + }); + + function forEachOf(binaries: BinaryMap, fn: (binary: T) => void) { + for (var key in binaries) { + fn(binaries[key]); + } + } + }); +}); diff --git a/spec/jasmine-int.json b/spec/jasmine-int.json deleted file mode 100644 index d7b836a9..00000000 --- a/spec/jasmine-int.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "spec_dir": "dist", - "spec_files": [ - "**/*.spec-int.js" - ], - "stopSpecOnExpectationFailure": false, - "random": false -} diff --git a/spec/jasmine-proxy.json b/spec/jasmine-proxy.json deleted file mode 100644 index 70c7b733..00000000 --- a/spec/jasmine-proxy.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "spec_dir": "dist", - "spec_files": [ - "**/*.spec-proxy.js" - ], - "stopSpecOnExpectationFailure": false, - "random": false -} diff --git a/spec/jasmine-unit.json b/spec/jasmine-unit.json deleted file mode 100644 index 21ed43d9..00000000 --- a/spec/jasmine-unit.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "spec_dir": "dist", - "spec_files": [ - "**/*.spec-unit.js" - ], - "stopSpecOnExpectationFailure": false, - "random": true -} diff --git a/spec/server/env.ts b/spec/server/env.ts deleted file mode 100644 index 7f7b69c3..00000000 --- a/spec/server/env.ts +++ /dev/null @@ -1,4 +0,0 @@ -export let httpPort = 8812; -export let httpBaseUrl = 'http://127.0.0.1:' + httpPort; -export let proxyPort = 8814; -export let proxyBaseUrl = 'http://127.0.0.1:' + proxyPort; \ No newline at end of file diff --git a/spec/server/http_server.ts b/spec/server/http_server.ts deleted file mode 100644 index 257de5e3..00000000 --- a/spec/server/http_server.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as fs from 'fs'; -import * as http from 'http'; -import * as path from 'path'; -import * as url from 'url'; - -import * as env from './env'; - -const port = process.argv[2] || env.httpPort; - -http.createServer( - (request: http.IncomingMessage, response: http.ServerResponse) => { - const uri = url.parse(request.url).pathname; - let fileName = path.join(process.cwd(), uri); - - try { - if (fs.statSync(fileName).isFile() || - fs.statSync(fileName).isDirectory) { - } else { - response.writeHead(404, {'Content-Type': 'text/plain'}); - response.write('404 Not Found\n'); - response.end(); - return; - } - } catch (err) { - response.writeHead(404, {'Content-Type': 'text/plain'}); - response.write('404 Not Found\n'); - response.end(); - return; - } - - if (fs.statSync(fileName).isDirectory()) { - fileName += '/index.html'; - } - - fs.readFile( - fileName, 'binary', ((err, file) => { - if (err) { - response.writeHead(500, {'Content-Type': 'text/plain'}); - response.write(err + '\n'); - response.end(); - return; - } - - response.writeHead( - 200, {'Content-Length': fs.statSync(fileName).size}); - response.write(file, 'binary'); - response.end(); - })); - }) - .listen(port); diff --git a/spec/server/proxy_server.ts b/spec/server/proxy_server.ts deleted file mode 100644 index cac79af8..00000000 --- a/spec/server/proxy_server.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as http from 'http'; -import * as httpProxy from 'http-proxy'; -import * as loglevel from 'loglevel'; -import * as env from './env'; - -const log = loglevel.getLogger('webdriver-manager-test'); - -const proxy = http.createServer((request, response) => { - let hostHeader = request.headers['host']; - log.debug( - 'request made to proxy: ' + request.url + ', ' + - 'target: ' + hostHeader); - if (hostHeader.startsWith('http://') || hostHeader.startsWith('127.0.0.1')) { - if (!hostHeader.startsWith('http://')) { - hostHeader = 'http://' + hostHeader; - } - httpProxy.createProxyServer({target: hostHeader}).web(request, response); - - } else { - if (!hostHeader.startsWith('https://')) { - hostHeader = 'https://' + hostHeader; - } - httpProxy.createProxyServer({target: hostHeader}).web(request, response); - } -}); - -proxy.listen(env.proxyPort); \ No newline at end of file diff --git a/spec/support/files/appium.json b/spec/support/files/appium.json deleted file mode 100644 index 77f0aad7..00000000 --- a/spec/support/files/appium.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dist-tags": { - "latest": "10.11.12" - } -} \ No newline at end of file diff --git a/spec/support/files/bar.tar.gz b/spec/support/files/bar.tar.gz deleted file mode 100644 index b8712672..00000000 Binary files a/spec/support/files/bar.tar.gz and /dev/null differ diff --git a/spec/support/files/bar.zip b/spec/support/files/bar.zip deleted file mode 100644 index e734af1e..00000000 Binary files a/spec/support/files/bar.zip and /dev/null differ diff --git a/spec/support/files/chromedriver.xml b/spec/support/files/chromedriver.xml deleted file mode 100644 index 0b9fc26c..00000000 --- a/spec/support/files/chromedriver.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - chromedriver - - - false - - 2.0/chromedriver_linux32.zip - 1380149859530000 - 4 - 2013-09-25T22:57:39.349Z - "c0d96102715c4916b872f91f5bf9b12c" - 7262134 - - - 2.0/chromedriver_linux64.zip - 1380149860664000 - 4 - 2013-09-25T22:57:40.449Z - "858ebaf47e13dce7600191ed59974c09" - 7433593 - - - 2.0/chromedriver_mac32.zip - 1380149857425000 - 4 - 2013-09-25T22:57:37.204Z - "efc13db5afc518000d886c2bdcb3a4bc" - 7614601 - - - 2.0/chromedriver_win32.zip - 1380149858370000 - 4 - 2013-09-25T22:57:38.165Z - "bbf8fd0fe525a06dda162619cac2b200" - 3048831 - - - 2.10/chromedriver_linux32.zip - 1398977182889000 - 3 - 2014-05-01T20:46:22.843Z - "4fecc99b066cb1a346035bf022607104" - 2439424 - - - 2.10/chromedriver_linux64.zip - 1398984058713000 - 3 - 2014-05-01T22:40:58.697Z - "058cd8b7b4b9688507701b5e648fd821" - 2301804 - - - 2.10/chromedriver_mac32.zip - 1398983790201000 - 3 - 2014-05-01T22:36:30.170Z - "fd0dafc3ada3619edda2961f2beadc5c" - 4116418 - - - 2.10/chromedriver_win32.zip - 1398985168260000 - 3 - 2014-05-01T22:59:28.242Z - "082e91e5c8994a7879710caeed62e334" - 2843903 - - - 2.10/notes.txt - 1398977183990000 - 3 - 2014-05-01T20:46:23.990Z - "27c57f1c84c22b4427f57c74f246ce1a" - 4055 - - - 2.20/chromedriver_linux32.zip - 1444349629569000 - 3 - 2015-10-09T00:13:49.568Z - "1e8cbdb84c5b70f86030297c4be3a5f9" - 2612186 - - - 2.20/chromedriver_linux64.zip - 1444346907063000 - 3 - 2015-10-08T23:28:27.062Z - "245858cc984bd946df6a1e6719c8e6f5" - 2528233 - - - 2.20/chromedriver_mac32.zip - 1444346568789000 - 3 - 2015-10-08T23:22:48.789Z - "749d4be0a317e92fd3aecaa022385439" - 3516443 - - - 2.20/chromedriver_win32.zip - 1444352264286000 - 3 - 2015-10-09T00:57:44.285Z - "028ee452b8e23890ec5ec6b0717fd295" - 2456101 - - - 2.20/notes.txt - 1444346573055000 - 3 - 2015-10-08T23:22:53.054Z - "fda30d3acf1a10aa9b193b3121787d80" - 1174 - - \ No newline at end of file diff --git a/spec/support/files/foo.xml b/spec/support/files/foo.xml deleted file mode 100644 index f72c0c54..00000000 --- a/spec/support/files/foo.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - foobar_driver - - 2.0/foobar.zip - - - 2.1/foobar.zip - - \ No newline at end of file diff --git a/spec/support/files/foo_array.json b/spec/support/files/foo_array.json deleted file mode 100644 index f4570bd7..00000000 --- a/spec/support/files/foo_array.json +++ /dev/null @@ -1,7 +0,0 @@ -[{ - "foo": "abc" -}, { - "foo": "def" -}, { - "foo": "ghi" -}] \ No newline at end of file diff --git a/spec/support/files/foo_json.json b/spec/support/files/foo_json.json deleted file mode 100644 index 46e2316e..00000000 --- a/spec/support/files/foo_json.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "foo": "abc", - "bar": 123, - "baz": { - "num": 101, - "list": ["a", "b", "c"] - } -} \ No newline at end of file diff --git a/spec/support/files/gecko.json b/spec/support/files/gecko.json deleted file mode 100644 index b3d2659e..00000000 --- a/spec/support/files/gecko.json +++ /dev/null @@ -1,734 +0,0 @@ -[ - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/11508322", - "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/11508322/assets", - "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/11508322/assets{?name,label}", - "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.21.0", - "id": 11508322, - "node_id": "MDc6UmVsZWFzZTExNTA4MzIy", - "tag_name": "v0.21.0", - "target_commitish": "master", - "name": "", - "draft": false, - "author": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "prerelease": false, - "created_at": "2018-06-15T20:46:25Z", - "published_at": "2018-06-15T20:57:11Z", - "assets": [ - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551150", - "id": 7551150, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNTA=", - "name": "geckodriver-v0.21.0-arm7hf.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3192918, - "download_count": 3888, - "created_at": "2018-06-15T20:58:12Z", - "updated_at": "2018-06-15T20:58:13Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-arm7hf.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551164", - "id": 7551164, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNjQ=", - "name": "geckodriver-v0.21.0-linux32.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3204777, - "download_count": 1049, - "created_at": "2018-06-15T20:59:29Z", - "updated_at": "2018-06-15T20:59:29Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux32.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551149", - "id": 7551149, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNDk=", - "name": "geckodriver-v0.21.0-linux64.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 3154655, - "download_count": 614375, - "created_at": "2018-06-15T20:57:57Z", - "updated_at": "2018-06-15T20:57:57Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551144", - "id": 7551144, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNDQ=", - "name": "geckodriver-v0.21.0-macos.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 1874024, - "download_count": 140027, - "created_at": "2018-06-15T20:57:11Z", - "updated_at": "2018-06-15T20:57:11Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-macos.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7573052", - "id": 7573052, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NzMwNTI=", - "name": "geckodriver-v0.21.0-win32.zip", - "label": null, - "uploader": { - "login": "andreastt", - "id": 399120, - "node_id": "MDQ6VXNlcjM5OTEyMA==", - "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/andreastt", - "html_url": "https://github.com/andreastt", - "followers_url": "https://api.github.com/users/andreastt/followers", - "following_url": "https://api.github.com/users/andreastt/following{/other_user}", - "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", - "organizations_url": "https://api.github.com/users/andreastt/orgs", - "repos_url": "https://api.github.com/users/andreastt/repos", - "events_url": "https://api.github.com/users/andreastt/events{/privacy}", - "received_events_url": "https://api.github.com/users/andreastt/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 3099996, - "download_count": 19720, - "created_at": "2018-06-18T12:34:38Z", - "updated_at": "2018-06-18T12:34:40Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-win32.zip" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/7551152", - "id": 7551152, - "node_id": "MDEyOlJlbGVhc2VBc3NldDc1NTExNTI=", - "name": "geckodriver-v0.21.0-win64.zip", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 3942715, - "download_count": 368746, - "created_at": "2018-06-15T20:58:31Z", - "updated_at": "2018-06-15T20:58:31Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-win64.zip" - } - ], - "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.21.0", - "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.21.0", - "body": "Note that with this release of geckodriver the minimum recommended\r\nFirefox and Selenium versions have changed:\r\n\r\n - Firefox 57 (and greater)\r\n - Selenium 3.11 (and greater)\r\n\r\n### Added\r\n\r\n- Support for the chrome element identifier from Firefox.\r\n\r\n- The `unhandledPromptBehavior` capability now accepts `accept and\r\n notify`, `dismiss and notify`, and `ignore` options.\r\n\r\n Note that the unhandled prompt handler is not fully supported in\r\n Firefox at the time of writing.\r\n\r\n### Changed\r\n\r\n- Firefox will now be started with the `-foreground` and `-no-remote`\r\n flags if they have not already been specified by the user in\r\n `moz:firefoxOptions`.\r\n\r\n `-foreground` will ensure the application window gets focus when\r\n Firefox is started, and `-no-remote` will prevent remote commands\r\n to this instance of Firefox and also ensure we always start a new\r\n instance.\r\n\r\n- WebDriver commands that do not have a return value now correctly\r\n return `{value: null}` instead of an empty dictionary.\r\n\r\n- The HTTP server now accepts `Keep-Alive` connections.\r\n\r\n- Firefox remote protocol command mappings updated.\r\n\r\n All Marionette commands changed to make use of the `WebDriver:`\r\n prefixes introduced with Firefox 56.\r\n\r\n- Overhaul of Firefox preferences.\r\n\r\n Already deprecated preferences in Firefox versions earlier than\r\n 57 got removed.\r\n\r\n- [webdriver crate] upgraded to 0.36.0.\r\n\r\n### Fixed\r\n\r\n- Force use of IPv4 network stack.\r\n\r\n On certain system configurations, where `localhost` resolves to\r\n an IPv6 address, geckodriver would attempt to connect to Firefox\r\n on the wrong IP stack, causing the connection attempt to time out\r\n after 60 seconds. We now ensure that geckodriver uses IPv4\r\n consistently to both connect to Firefox and for allocating a free\r\n port.\r\n\r\n- geckodriver failed to locate the correct Firefox binary if it was\r\n found under a _firefox_ or _firefox-bin_ directory, depending on\r\n the system, because it thought the parent directory was the\r\n executable.\r\n\r\n- On Unix systems (macOS, Linux), geckodriver falsely reported\r\n non-executable files as valid binaries.\r\n\r\n- When stdout and stderr is redirected by geckodriver, a bug prevented\r\n the redirections from taking effect.\r\n\r\n[README]: https://github.com/mozilla/geckodriver/blob/master/README.md\r\n[Browser Toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox\r\n\r\n[`CloseWindowResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CloseWindowResponse.html\r\n[`CookieResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CookieResponse.html\r\n[`DeleteSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.DeleteSession\r\n[`ElementClickIntercepted`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementClickIntercepted\r\n[`ElementNotInteractable`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementNotInteractable\r\n[`FullscreenWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.FullscreenWindow\r\n[`GetNamedCookie`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie\r\n[`GetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetWindowRect\r\n[`InvalidCoordinates`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.InvalidCoordinates\r\n[`MaximizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MaximizeWindow\r\n[`MinimizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MinimizeWindow\r\n[`NewSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.NewSession\r\n[`NoSuchCookie`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.NoSuchCookie\r\n[`RectResponse`]: https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html\r\n[`SendKeysParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.SendKeysParameters.html\r\n[`SessionNotCreated`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.SessionNotCreated\r\n[`SetTimeouts`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetTimeouts\r\n[`SetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetWindowRect\r\n[`StaleElementReference`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.StaleElementReference\r\n[`UnableToCaptureScreen`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnableToCaptureScreen\r\n[`UnknownCommand`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownCommand\r\n[`UnknownError`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownError\r\n[`WindowRectParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.WindowRectParameters.html\r\n\r\n[mozrunner crate]: https://crates.io/crates/mozrunner\r\n[webdriver crate]: https://crates.io/crates/webdriver\r\n\r\n[Actions]: https://w3c.github.io/webdriver/webdriver-spec.html#actions\r\n[Delete Session]: https://w3c.github.io/webdriver/webdriver-spec.html#delete-session\r\n[Element Click]: https://w3c.github.io/webdriver/webdriver-spec.html#element-click\r\n[Get Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts\r\n[Get Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect\r\n[insecure certificate]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-insecure-certificate\r\n[Minimize Window]: https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window\r\n[New Session]: https://w3c.github.io/webdriver/webdriver-spec.html#new-session\r\n[Send Alert Text]: https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text\r\n[Set Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts\r\n[Set Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect\r\n[Status]: https://w3c.github.io/webdriver/webdriver-spec.html#status\r\n[Take Element Screenshot]: https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot\r\n" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/10444528", - "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/10444528/assets", - "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/10444528/assets{?name,label}", - "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.20.1", - "id": 10444528, - "node_id": "MDc6UmVsZWFzZTEwNDQ0NTI4", - "tag_name": "v0.20.1", - "target_commitish": "master", - "name": "", - "draft": false, - "author": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "prerelease": false, - "created_at": "2018-04-08T12:36:23Z", - "published_at": "2018-04-08T12:45:28Z", - "assets": [ - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772569", - "id": 6772569, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1Njk=", - "name": "geckodriver-v0.20.1-arm7hf.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2629576, - "download_count": 17878, - "created_at": "2018-04-08T12:48:57Z", - "updated_at": "2018-04-08T12:48:58Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-arm7hf.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772587", - "id": 6772587, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1ODc=", - "name": "geckodriver-v0.20.1-linux32.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2700572, - "download_count": 50590, - "created_at": "2018-04-08T12:51:38Z", - "updated_at": "2018-04-08T12:51:39Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux32.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772557", - "id": 6772557, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1NTc=", - "name": "geckodriver-v0.20.1-linux64.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2688486, - "download_count": 3278378, - "created_at": "2018-04-08T12:45:27Z", - "updated_at": "2018-04-08T12:45:28Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-linux64.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772558", - "id": 6772558, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1NTg=", - "name": "geckodriver-v0.20.1-macos.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 1560036, - "download_count": 840320, - "created_at": "2018-04-08T12:45:28Z", - "updated_at": "2018-04-08T12:45:28Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-macos.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772612", - "id": 6772612, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI2MTI=", - "name": "geckodriver-v0.20.1-win32.zip", - "label": null, - "uploader": { - "login": "andreastt", - "id": 399120, - "node_id": "MDQ6VXNlcjM5OTEyMA==", - "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/andreastt", - "html_url": "https://github.com/andreastt", - "followers_url": "https://api.github.com/users/andreastt/followers", - "following_url": "https://api.github.com/users/andreastt/following{/other_user}", - "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", - "organizations_url": "https://api.github.com/users/andreastt/orgs", - "repos_url": "https://api.github.com/users/andreastt/repos", - "events_url": "https://api.github.com/users/andreastt/events{/privacy}", - "received_events_url": "https://api.github.com/users/andreastt/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 2483907, - "download_count": 123963, - "created_at": "2018-04-08T12:55:10Z", - "updated_at": "2018-04-08T12:55:12Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-win32.zip" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6772583", - "id": 6772583, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY3NzI1ODM=", - "name": "geckodriver-v0.20.1-win64.zip", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 3102871, - "download_count": 2386311, - "created_at": "2018-04-08T12:50:12Z", - "updated_at": "2018-04-08T12:50:12Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.1/geckodriver-v0.20.1-win64.zip" - } - ], - "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.20.1", - "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.20.1", - "body": "### Fixed\r\n\r\n- Avoid attempting to kill Firefox process that has stopped.\r\n\r\n With the change to allow Firefox enough time to shut down in\r\n 0.20.0, geckodriver started unconditionally killing the process\r\n to reap its exit status. This caused geckodriver to inaccurately\r\n report a successful Firefox shutdown as a failure.\r\n\r\n The regression should not have caused any functional problems, but\r\n the termination cause and the exit status are now reported correctly." - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/10054801", - "assets_url": "https://api.github.com/repos/mozilla/geckodriver/releases/10054801/assets", - "upload_url": "https://uploads.github.com/repos/mozilla/geckodriver/releases/10054801/assets{?name,label}", - "html_url": "https://github.com/mozilla/geckodriver/releases/tag/v0.20.0", - "id": 10054801, - "node_id": "MDc6UmVsZWFzZTEwMDU0ODAx", - "tag_name": "v0.20.0", - "target_commitish": "master", - "name": "", - "draft": false, - "author": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "prerelease": false, - "created_at": "2018-03-12T23:13:32Z", - "published_at": "2018-03-12T23:21:47Z", - "assets": [ - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489623", - "id": 6489623, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MjM=", - "name": "geckodriver-v0.20.0-arm7hf.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2849818, - "download_count": 7665, - "created_at": "2018-03-12T23:21:46Z", - "updated_at": "2018-03-12T23:21:46Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-arm7hf.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489633", - "id": 6489633, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzM=", - "name": "geckodriver-v0.20.0-linux32.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2855060, - "download_count": 3993, - "created_at": "2018-03-12T23:23:17Z", - "updated_at": "2018-03-12T23:23:17Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux32.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489638", - "id": 6489638, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2Mzg=", - "name": "geckodriver-v0.20.0-linux64.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 2804546, - "download_count": 2816108, - "created_at": "2018-03-12T23:25:38Z", - "updated_at": "2018-03-12T23:25:38Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-linux64.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489631", - "id": 6489631, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzE=", - "name": "geckodriver-v0.20.0-macos.tar.gz", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/gzip", - "state": "uploaded", - "size": 1536808, - "download_count": 673101, - "created_at": "2018-03-12T23:22:46Z", - "updated_at": "2018-03-12T23:22:47Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-macos.tar.gz" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6496559", - "id": 6496559, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0OTY1NTk=", - "name": "geckodriver-v0.20.0-win32.zip", - "label": null, - "uploader": { - "login": "andreastt", - "id": 399120, - "node_id": "MDQ6VXNlcjM5OTEyMA==", - "avatar_url": "https://avatars3.githubusercontent.com/u/399120?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/andreastt", - "html_url": "https://github.com/andreastt", - "followers_url": "https://api.github.com/users/andreastt/followers", - "following_url": "https://api.github.com/users/andreastt/following{/other_user}", - "gists_url": "https://api.github.com/users/andreastt/gists{/gist_id}", - "starred_url": "https://api.github.com/users/andreastt/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/andreastt/subscriptions", - "organizations_url": "https://api.github.com/users/andreastt/orgs", - "repos_url": "https://api.github.com/users/andreastt/repos", - "events_url": "https://api.github.com/users/andreastt/events{/privacy}", - "received_events_url": "https://api.github.com/users/andreastt/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 2510890, - "download_count": 78203, - "created_at": "2018-03-13T14:41:25Z", - "updated_at": "2018-03-13T14:41:57Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-win32.zip" - }, - { - "url": "https://api.github.com/repos/mozilla/geckodriver/releases/assets/6489635", - "id": 6489635, - "node_id": "MDEyOlJlbGVhc2VBc3NldDY0ODk2MzU=", - "name": "geckodriver-v0.20.0-win64.zip", - "label": "", - "uploader": { - "login": "AutomatedTester", - "id": 128518, - "node_id": "MDQ6VXNlcjEyODUxOA==", - "avatar_url": "https://avatars1.githubusercontent.com/u/128518?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/AutomatedTester", - "html_url": "https://github.com/AutomatedTester", - "followers_url": "https://api.github.com/users/AutomatedTester/followers", - "following_url": "https://api.github.com/users/AutomatedTester/following{/other_user}", - "gists_url": "https://api.github.com/users/AutomatedTester/gists{/gist_id}", - "starred_url": "https://api.github.com/users/AutomatedTester/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/AutomatedTester/subscriptions", - "organizations_url": "https://api.github.com/users/AutomatedTester/orgs", - "repos_url": "https://api.github.com/users/AutomatedTester/repos", - "events_url": "https://api.github.com/users/AutomatedTester/events{/privacy}", - "received_events_url": "https://api.github.com/users/AutomatedTester/received_events", - "type": "User", - "site_admin": false - }, - "content_type": "application/zip", - "state": "uploaded", - "size": 3401446, - "download_count": 1034210, - "created_at": "2018-03-12T23:24:13Z", - "updated_at": "2018-03-12T23:24:14Z", - "browser_download_url": "https://github.com/mozilla/geckodriver/releases/download/v0.20.0/geckodriver-v0.20.0-win64.zip" - } - ], - "tarball_url": "https://api.github.com/repos/mozilla/geckodriver/tarball/v0.20.0", - "zipball_url": "https://api.github.com/repos/mozilla/geckodriver/zipball/v0.20.0", - "body": "### Added\r\n\r\n- New `--jsdebugger` flag to open the [Browser Toolbox] when Firefox\r\n launches. This is useful for debugging Marionette internals.\r\n\r\n- Introduced the temporary, boolean capability\r\n `moz:useNonSpecCompliantPointerOrigin` to disable the WebDriver\r\n conforming behavior of calculating the Pointer Origin.\r\n\r\n### Changed\r\n\r\n- HTTP status code for the [`StaleElementReference`] error changed\r\n from 400 (Bad Request) to 404 (Not Found).\r\n\r\n- Backtraces from geckodriver no longer substitute for missing\r\n Marionette stacktraces.\r\n\r\n- [webdriver crate] upgraded to 0.35.0.\r\n\r\n### Fixed\r\n\r\n- The Firefox process is now given ample time to shut down, allowing\r\n enough time for the Firefox shutdown hang monitor to kick in.\r\n\r\n Firefox has an integrated background monitor that observes\r\n long-running threads during shutdown. These threads will be\r\n killed after 63 seconds in the event of a hang. To allow Firefox\r\n to shut down these threads on its own, geckodriver has to wait\r\n that time and some additional seconds.\r\n\r\n- Grapheme clusters are now accepted as input for keyboard input\r\n to actions.\r\n\r\n Input to the `value` field of the `keyDown` and `keyUp` action\r\n primitives used to only accept single characters, which means\r\n geckodriver would error when a valid grapheme cluster was sent in,\r\n for example with the tamil nadu character U+0BA8 U+0BBF.\r\n\r\n Thanks to Greg Fraley for fixing this bug.\r\n\r\n- Improved error messages for malformed capability values.\r\n\r\n[README]: https://github.com/mozilla/geckodriver/blob/master/README.md\r\n[Browser Toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox\r\n\r\n[`CloseWindowResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CloseWindowResponse.html\r\n[`CookieResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CookieResponse.html\r\n[`DeleteSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.DeleteSession\r\n[`ElementClickIntercepted`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementClickIntercepted\r\n[`ElementNotInteractable`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.ElementNotInteractable\r\n[`FullscreenWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.FullscreenWindow\r\n[`GetNamedCookie`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetNamedCookie\r\n[`GetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.GetWindowRect\r\n[`InvalidCoordinates`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.InvalidCoordinates\r\n[`MaximizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MaximizeWindow\r\n[`MinimizeWindow`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.MinimizeWindow\r\n[`NewSession`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.NewSession\r\n[`NoSuchCookie`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.NoSuchCookie\r\n[`RectResponse`]: https://docs.rs/webdriver/0.27.0/webdriver/response/struct.RectResponse.html\r\n[`SendKeysParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.SendKeysParameters.html\r\n[`SessionNotCreated`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.SessionNotCreated\r\n[`SetTimeouts`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetTimeouts\r\n[`SetWindowRect`]: https://docs.rs/webdriver/newest/webdriver/command/enum.WebDriverCommand.html#variant.SetWindowRect\r\n[`StaleElementReference`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.StaleElementReference\r\n[`UnableToCaptureScreen`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnableToCaptureScreen\r\n[`UnknownCommand`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownCommand\r\n[`UnknownError`]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html#variant.UnknownError\r\n[`WindowRectParameters`]: https://docs.rs/webdriver/newest/webdriver/command/struct.WindowRectParameters.html\r\n\r\n[mozrunner crate]: https://crates.io/crates/mozrunner\r\n[webdriver crate]: https://crates.io/crates/webdriver\r\n\r\n[Actions]: https://w3c.github.io/webdriver/webdriver-spec.html#actions\r\n[Delete Session]: https://w3c.github.io/webdriver/webdriver-spec.html#delete-session\r\n[Element Click]: https://w3c.github.io/webdriver/webdriver-spec.html#element-click\r\n[Get Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts\r\n[Get Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect\r\n[insecure certificate]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-insecure-certificate\r\n[Minimize Window]: https://w3c.github.io/webdriver/webdriver-spec.html#minimize-window\r\n[New Session]: https://w3c.github.io/webdriver/webdriver-spec.html#new-session\r\n[Send Alert Text]: https://w3c.github.io/webdriver/webdriver-spec.html#send-alert-text\r\n[Set Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#set-timeouts\r\n[Set Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#set-window-rect\r\n[Status]: https://w3c.github.io/webdriver/webdriver-spec.html#status\r\n[Take Element Screenshot]: https://w3c.github.io/webdriver/webdriver-spec.html#take-element-screenshot\r\n[WebDriver errors]: https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors\r\n\r\n[Jason Juang]: https://github.com/juangj\r\n[Joshua Bruning]: https://github.com/joshbruning\r\n[Kalpesh Krishna]: https://github.com/martiansideofthemoon\r\n[Mike Pennisi]: https://github.com/jugglinmike\r\n[Sven Jost]: https://github/mythsunwind\r\n[Vlad Filippov]: https://github.com/vladikoff" - } -] \ No newline at end of file diff --git a/spec/support/helpers/port_finder.ts b/spec/support/helpers/port_finder.ts deleted file mode 100644 index e462681d..00000000 --- a/spec/support/helpers/port_finder.ts +++ /dev/null @@ -1,53 +0,0 @@ -// Borrowed from driver_provider/local from protractor. -import * as net from 'net'; - -/** - * Find the port number that is available for the port range provided. - * Assumes that the portRangeStart < portRangeEnd and that the portRangeEnd - * should also be checked. - * @param portRangeStart - * @param portRangeEnd - */ -export async function findPort( - portRangeStart: number, portRangeEnd: number): Promise { - // When no start is provided but an end range is provided, create - // an arbitrary start point. - if (!portRangeStart && portRangeEnd) { - portRangeStart = portRangeEnd - 1000; - } - // When no end is provided but a start range is provided, create - // an arbitrary end point. - if (portRangeStart && !portRangeEnd) { - portRangeEnd = portRangeStart + 1000; - } - // If no start and end are provided, create a range from 4000 to 5000. - if (!portRangeStart && !portRangeEnd) { - portRangeStart = 4000; - portRangeEnd = 5000; - } - - let portFound = null; - for (let port = portRangeStart; port <= portRangeEnd; port++) { - let server: net.Server; - const result = await new Promise(resolve => { - // Start a server to check if we can listen to a port. - server = net.createServer() - .listen(port) - .on('error', - () => { - // EADDRINUSE or EACCES, move on. - resolve(false); - }) - .on('listening', () => { - resolve(true); - }); - }); - if (result) { - // If the server is listening, close the server. - server.close(); - portFound = port; - break; - } - } - return portFound; -} \ No newline at end of file diff --git a/spec/support/helpers/test_utils.ts b/spec/support/helpers/test_utils.ts deleted file mode 100644 index 908a10a5..00000000 --- a/spec/support/helpers/test_utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as childProcess from 'child_process'; -import * as loglevel from 'loglevel'; -import {requestBody} from '../../../lib/provider/utils/http_utils'; - -const log = loglevel.getLogger('webdriver-manager-test'); - -/** - * A command line to run. Example 'npm start', the task='npm' and the - * opt_arg=['start'] - * @param task The task string. - * @param optArg Optional task args. - * @param optIo Optional io arg. By default, it should log to console. - * @returns The child process. - */ -export function spawnProcess(task: string, optArg?: string[], optIo?: string) { - optArg = typeof optArg !== 'undefined' ? optArg : []; - let stdio: childProcess.StdioOptions = 'inherit'; - if (optIo === 'ignore') { - stdio = 'ignore'; - } - return childProcess.spawn(task, optArg, {stdio}); -} - -/** - * Check the connectivity by making a request to https://github.com. - * If the request results in an error, return false. - */ -export function checkConnectivity(testName: string): Promise { - return requestBody('https://github.com', {}) - .then(() => { - return true; - }) - .catch(() => { - log.warn('[WARN] no connectivity. skipping test ' + testName); - return false; - }); -} \ No newline at end of file diff --git a/spec/jasmine-e2e.json b/spec/support/jasmine.json similarity index 64% rename from spec/jasmine-e2e.json rename to spec/support/jasmine.json index c7f76655..0564d8cb 100644 --- a/spec/jasmine-e2e.json +++ b/spec/support/jasmine.json @@ -1,7 +1,7 @@ { - "spec_dir": "dist", + "spec_dir": "built/spec", "spec_files": [ - "**/*.spec-e2e.js" + "**/*_spec.js" ], "stopSpecOnExpectationFailure": false, "random": false diff --git a/spec/webdriver_spec.ts b/spec/webdriver_spec.ts new file mode 100644 index 00000000..1d13fffd --- /dev/null +++ b/spec/webdriver_spec.ts @@ -0,0 +1,32 @@ +import * as child_process from 'child_process'; +import * as path from 'path'; +import {cli} from '../lib/webdriver'; + +function runSpawn(task: string, opt_arg: string[]): string[] { + opt_arg = typeof opt_arg !== 'undefined' ? opt_arg : []; + let child = child_process.spawnSync(task, opt_arg, {stdio: 'pipe'}); + return child.output[1].toString().split('\n'); +}; + +describe('cli', () => { + describe('help', () => { + it ('should have usage and commands', () => { + let lines = runSpawn('node', ['built/lib/webdriver.js', 'help']); + + // very specific to make sure the + let index = 0 + expect(lines[index++].indexOf('Usage:')).toBe(0); + index++; + expect(lines[index++].indexOf('Commands:')).toBe(0); + for (let cmd in cli.programs) { + expect(lines[index++].indexOf(cmd)).toBe(2); + } + index++; + expect(lines[index++].indexOf('Options:')).toBe(0); + let options = cli.getOptions(); + for (let opt in options) { + expect(lines[index++].indexOf('--' + opt)).toBe(2); + } + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 2e85db7f..d32da744 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,16 @@ { "compilerOptions": { - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "declaration": true, - "forceConsistentCasingInFileNames": true, - "lib": [ "es2017" ], + "target": "es5", "module": "commonjs", "moduleResolution": "node", - "noEmitOnError": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "outDir": "./dist", - "pretty": true, - "removeComments": false, "sourceMap": true, - "target": "es2017", - "typeRoots": [ - "./node_modules/@types" - ], - "types": [ - "jasmine", - "node" - ], + "declaration": true, + "removeComments": false, + "noImplicitAny": true, + "outDir": "built/" }, "exclude": [ - "dist", + "built", "node_modules" ] -} \ No newline at end of file +} diff --git a/tslint.json b/tslint.json deleted file mode 100644 index c06a7d7b..00000000 --- a/tslint.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "rules": { - "array-type": [true, "array-simple"], - "arrow-return-shorthand": true, - "ban": [true, - {"name": "parseInt", "message": "tsstyle#type-coercion"}, - {"name": "parseFloat", "message": "tsstyle#type-coercion"}, - {"name": "Array", "message": "tsstyle#array-constructor"} - ], - "ban-types": [true, - ["Object", "Use {} instead."], - ["String", "Use 'string' instead."], - ["Number", "Use 'number' instead."], - ["Boolean", "Use 'boolean' instead."] - ], - "class-name": true, - "curly": [true, "ignore-same-line"], - "forin": true, - "interface-name": [true, "never-prefix"], - "jsdoc-format": true, - "label-position": true, - "member-access": [true, "no-public"], - "new-parens": true, - "no-angle-bracket-type-assertion": true, - "no-any": true, - "no-arg": true, - "no-conditional-assignment": true, - "no-construct": true, - "no-debugger": true, - "no-default-export": true, - "no-duplicate-variable": true, - "no-inferrable-types": true, - "no-namespace": [true, "allow-declarations"], - "no-reference": true, - "no-string-throw": true, - "no-unused-expression": true, - "no-var-keyword": true, - "object-literal-shorthand": true, - "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], - "prefer-const": true, - "radix": true, - "semicolon": [true, "always", "ignore-bound-class-methods"], - "switch-default": true, - "triple-equals": [true, "allow-null-check"], - "use-isnan": true, - "variable-name": [ - true, - "check-format", - "ban-keywords", - "allow-leading-underscore", - "allow-trailing-underscore" - ] - } -} \ No newline at end of file