├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .ruby-version ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── docs ├── _config.yml ├── contributing.md ├── how-tos │ ├── execute-async-script.md │ └── use-headless-chrome.md ├── index.md └── travis-ci.md ├── jsdoc.conf ├── package-lock.json ├── package.json ├── src ├── application-state.js ├── browser-manager.js ├── browser-models │ ├── browser.js │ ├── driver-config.js │ ├── local-browser.js │ └── saucelabs-browser.js ├── download-manager.js ├── index.js ├── local-browsers │ ├── chrome.js │ ├── firefox.js │ └── safari.js ├── saucelabs-browsers │ ├── chrome.js │ ├── edge.js │ ├── firefox.js │ ├── ie.js │ └── safari.js └── webdriver-config │ ├── chrome.js │ ├── edge.js │ ├── firefox.js │ ├── ie.js │ └── safari.js └── test ├── .eslintrc ├── application-state.js ├── browser-usage.js ├── data └── example-site │ └── index.html ├── downloading-browsers.js ├── expiration-behavior.js ├── helpers └── test-server.js ├── local-browser.js ├── saucelabs-usage.js └── selenium-assistant.js /.eslintignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | coverage/ 3 | node_modules/ 4 | npm-debug.log 5 | geckodriver 6 | test/test-output/ 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "google"], 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "rules": { 8 | "no-var": 2 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | npm-debug.log 4 | geckodriver 5 | test/test-output/ 6 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.2.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: node_js 4 | 5 | env: 6 | global: 7 | - COVERALLS_PARALLEL=true 8 | 9 | notifications: 10 | webhooks: https://coveralls.io/webhook?repo_token=COVERALLS_REPO_TOKEN 11 | 12 | install: 13 | - npm ci 14 | 15 | script: 16 | - npm run test 17 | 18 | matrix: 19 | include: 20 | - name: "Linux (Latest Node)" 21 | os: linux 22 | dist: bionic 23 | # See https://docs.travis-ci.com/user/gui-and-headless-browsers/#using-services 24 | services: 25 | - xvfb 26 | node_js: 27 | - 10 28 | - name: "OS X (Latest Node)" 29 | os: osx 30 | node_js: 31 | - 10 32 | before_script: 33 | # See https://github.com/GoogleChromeLabs/selenium-assistant/issues/118 34 | - "sudo /usr/bin/safaridriver --enable" 35 | - "sudo defaults write com.apple.Safari AllowRemoteAutomation 1" 36 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gem 'jekyll', '>=3.1.6' 4 | gem 'kramdown' 5 | gem 'jekyll-github-metadata' 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [](https://travis-ci.org/GoogleChromeLabs/selenium-assistant) [](https://coveralls.io/github/GoogleChromeLabs/selenium-assistant?branch=master) [](https://david-dm.org/googlechromelabs/selenium-assistant) [](https://david-dm.org/googlechromelabs/selenium-assistant?type=dev) 2 | 3 | # selenium-assistant 4 | 5 | It can be a challenge to manage which browsers you can / can't run your tests against 6 | as well as blocking those that have known issues. 7 | 8 | This library is a simple set of helpers that helps find the browsers available 9 | in the current environment the tests are run on and adding a set of simple 10 | methods to check for versions and print useful info for logs. 11 | 12 |
13 | View Docs Here 14 |
15 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | source: . 2 | layouts_dir: ./themes/jekyll/_layouts/ 3 | includes_dir: ./themes/jekyll/_includes/ 4 | 5 | # This adds github pages data 6 | gems: ['jekyll-github-metadata'] 7 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: "Contributing" 4 | navigation_weight: 2 5 | --- 6 | # Contributing 7 | 8 | If you wish to help with this project, please feel free to raise an issue, 9 | raise a PR or add tests to prove bad / incorrect behaviour. 10 | 11 | To run tests locally it's simply: 12 | 13 | npm run test 14 | -------------------------------------------------------------------------------- /docs/how-tos/execute-async-script.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: "Execute Async Script" 4 | navigation_weight: 1 5 | --- 6 | If you run code in the browser, `executeScript()` expects the 7 | code to return immediately. 8 | 9 | `executeAsyncScript()` allows you to run code in the browser 10 | but expect an asynchronous result (i.e. it waits for a 11 | callback); 12 | 13 | ```javascript 14 | const chromeBrowser = seleniumAssistant.getLocalBrowser('chrome', 'stable'); 15 | const driver = await chromeBrowser.getSeleniumDriver(); 16 | const value = driver.executeAsyncScript((inputString, ...args) => { 17 | const cb = args[args.length - 1]; 18 | 19 | setTimeout(() => { 20 | cb(inputString); 21 | }, 2000); 22 | }, 'example-string'); 23 | ``` 24 | 25 | If the async script takes too long to execute, you can extend it with 26 | the following: 27 | 28 | ```javascript 29 | const chromeBrowser = seleniumAssistant.getLocalBrowser('chrome', 'stable'); 30 | const driver = await chromeBrowser.getSeleniumDriver(); 31 | driver.manage().timeouts().setScriptTimeout(60 * 1000); 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/how-tos/use-headless-chrome.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: "Use Headless Chrome" 4 | navigation_weight: 1 5 | --- 6 | It's possible to use headless Chrome by setting a simple option. 7 | 8 | ```javascript 9 | const chromeBrowser = seleniumAssistant.getLocalBrowser('chrome', 'stable'); 10 | const options = chromeBrowser.getSeleniumOptions(); 11 | options.addArguments('--headless'); 12 | ``` 13 | 14 | You can learn more [here](https://developers.google.com/web/updates/2017/04/headless-chrome). 15 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: "Selenium Assistant" 4 | navigation_weight: 0 5 | left_column: | 6 | # Why 7 | 8 | This library is designed to make using selenium a little easier in terms 9 | of finding different releases of a particular browser and 10 | generating a web driver instance for it. 11 | right_column: | 12 | # Install 13 | 14 | Installing of this module is simply: 15 | 16 | npm install selenium-assistant --save-dev 17 | 18 | --- 19 | # Usage 20 | 21 | Depending on the browsers you wish to test against you'll need to add 22 | the drivers for them. 23 | 24 | To use **Google Chrome**: 25 | 26 | npm install chromedriver --save-dev 27 | 28 | To use **Firefox**: 29 | 30 | npm install geckodriver --save-dev 31 | 32 | ## Local Browsers 33 | 34 | The most basic / common use of this library to get the available browsers 35 | on the current machine, filter out any browsers you may not want and 36 | then get a web driver instance for that browser, this can be done like so: 37 | 38 | ```javascript 39 | const selenium = require('selenium-webdriver'); 40 | const seleniumAssistant = require('selenium-assistant'); 41 | 42 | const browsers = seleniumAssistant.getLocalBrowsers(); 43 | browsers.forEach(browser => { 44 | // Skip if the browser isn't stable. 45 | if (browser.getReleaseName() !== 'stable') { 46 | return; 47 | } 48 | 49 | // Print out the browsers name. 50 | console.log(browser.getPrettyName()); 51 | 52 | browser.getSeleniumDriver() 53 | .then(webdriverInstance => 54 | webdriverInstance.get('https://google.com/') 55 | .then(_ => webdriverInstance.wait(selenium.until.titleIs('Google'), 1000)) 56 | ); 57 | }); 58 | ``` 59 | 60 | Make sure you checkout the reference docs for all the available APIs. 61 | 62 | For documentation on how to use the `webdriverInstace` [check out the 63 | selenium docs](http://seleniumhq.github.io/selenium/docs/api/javascript/). 64 | 65 | ## Saucelabs Browsers 66 | 67 | There will come a point where you'll want to test on browsers you don't have 68 | on your local machine (i.e. Internet Explorer or Safari). 69 | 70 | Selenium-assistant can help with this, exposing methods so that you can: 71 | 72 | 1. Set your Saucelabs username and access key. 73 | 1. Use Saucelabs connect so Saucelabs browsers can access local servers. 74 | 3. Get a working driver instance (i.e. have the correct configuration). 75 | 76 | ```javascript 77 | const seleniumAssistant = require('selenium-assistant'); 78 | 79 | seleniumAssistant.setSaucelabsDetails( 80 | SAUCELABS_USERNAME, 81 | SAUCELABS_ACCESS_KEY); 82 | 83 | seleniumAssistant.startSaucelabsConnect() 84 | .then(() => { 85 | return seleniumAssistant.getSauceLabsBrowser('microsoftedge', 'latest'); 86 | }) 87 | .then((browserInstance) => { 88 | return browserInstance.getSeleniumDriver(); 89 | }) 90 | .then((driver) => { 91 | return driver.get('http://localhost:8080/') 92 | .then(() => { 93 | // Do any testing with the driver. 94 | }) 95 | .then(() => { 96 | return seleniumAssistant.killWebDriver(driver); 97 | }); 98 | }) 99 | .then(() => { 100 | return seleniumAssistant.stopSaucelabsConnect(); 101 | }); 102 | ``` 103 | 104 | > **Note:** You don't need Saucelabs connect if you are testing a publicly 105 | > available URL. 106 | -------------------------------------------------------------------------------- /docs/travis-ci.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | title: "Travis CI Support" 4 | navigation_weight: 1 5 | --- 6 | Selenium-Assistant can be easily used with Travis. 7 | 8 | All you need to do is create a "Virtual Display", which is required for the 9 | browsers to work: 10 | 11 | In your `.travis.yml` file, run: 12 | 13 | # Read more here: https://docs.travis-ci.com/user/gui-and-headless-browsers/#Using-xvfb-to-Run-Tests-That-Require-a-GUI 14 | before_script: 15 | - "export DISPLAY=:99.0" 16 | - "sh -e /etc/init.d/xvfb start || echo \"Unable to start virtual display.\"" 17 | - sleep 3 # give xvfb some time to start 18 | 19 | If you want to speed up your travis times and your downloading browsers, add 20 | the following to your `.travis.yml` file and the browser downloads should 21 | be cached between runs. 22 | 23 | cache: 24 | directories: 25 | - node_modules 26 | - ~/.selenium-assistant 27 | -------------------------------------------------------------------------------- /jsdoc.conf: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": [ 4 | "./src" 5 | ] 6 | }, 7 | "templates": { 8 | "default": { 9 | "outputSourceFiles": false 10 | } 11 | }, 12 | "opts": { 13 | "template": "./node_modules/npm-publish-scripts/build/themes/jsdoc", 14 | "recurse": true 15 | }, 16 | "markdown": { 17 | "idInHeadings": true 18 | }, 19 | "plugins": ["plugins/markdown"] 20 | } 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selenium-assistant", 3 | "version": "6.1.0", 4 | "description": "A node module to help with use of selenium driver", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "coveralls": "cat ./coverage/lcov.info | coveralls", 8 | "istanbul": "npm run lint && istanbul cover _mocha", 9 | "lint": "eslint '.'", 10 | "mocha": "mocha", 11 | "pretest": "npm install --no-save chromedriver geckodriver", 12 | "test": "npm run istanbul && npm run coveralls" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/GoogleChrome/selenium-assistant.git" 17 | }, 18 | "engines": { 19 | "node": ">= 8.9.0" 20 | }, 21 | "keywords": [ 22 | "selenium", 23 | "webdriver" 24 | ], 25 | "author": "Matt Gaunt", 26 | "license": "Apache-2.0", 27 | "bugs": { 28 | "url": "https://github.com/GoogleChrome/selenium-assistant/issues" 29 | }, 30 | "homepage": "https://github.com/GoogleChrome/selenium-assistant#readme", 31 | "devDependencies": { 32 | "chai": "^4.2.0", 33 | "coveralls": "^3.0.5", 34 | "eslint": "^6.1.0", 35 | "eslint-config-google": "^0.13.0", 36 | "express": "^4.17.1", 37 | "firebase": "^6.3.3", 38 | "glob": "^7.1.4", 39 | "istanbul": "^0.4.5", 40 | "jsdoc": "^3.6.3", 41 | "mocha": "^6.2.0", 42 | "proxyquire": "^2.1.1", 43 | "sinon": "^7.3.2" 44 | }, 45 | "dependencies": { 46 | "chalk": "^2.4.2", 47 | "dmg": "^0.1.0", 48 | "fs-extra": "^8.1.0", 49 | "mkdirp": "^0.5.1", 50 | "node-localstorage": "^1.3.1", 51 | "request": "^2.88.0", 52 | "sauce-connect-launcher": "^1.2.7", 53 | "selenium-webdriver": "^4.0.0-alpha.7", 54 | "semver": "^6.3.0", 55 | "which": "^1.3.1", 56 | "yauzl": "^2.10.0" 57 | }, 58 | "files": [ 59 | "src/" 60 | ] 61 | } 62 | -------------------------------------------------------------------------------- /src/application-state.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const path = require('path'); 20 | const sauceConnectLauncher = require('sauce-connect-launcher'); 21 | 22 | /** 23 | * This class is a super basic class that stores shared state across the 24 | * classes in this library / module. 25 | * 26 | * @private 27 | */ 28 | class ApplicationState { 29 | /** 30 | * This constructor is never used directly, but this class stores the 31 | * overall current state of selenium-assistant while in use. 32 | */ 33 | constructor() { 34 | this._installDir = this.getDefaultInstallLocation(); 35 | } 36 | 37 | /** 38 | * To define where browsers should be installed and searched for, 39 | * define the path by calling this method. 40 | * @param {String} newInstallDir The path to install new browsers in to. 41 | */ 42 | setInstallDirectory(newInstallDir) { 43 | if (newInstallDir) { 44 | this._installDir = path.resolve(newInstallDir); 45 | } else { 46 | this._installDir = this.getDefaultInstallLocation(); 47 | } 48 | } 49 | 50 | /** 51 | * To get ther current path of where Browsers are installed and searched for, 52 | * call this method. 53 | * @return {String} The current path for installed browsers. 54 | */ 55 | getInstallDirectory() { 56 | return this._installDir; 57 | } 58 | 59 | /** 60 | * When this library is used a default path is used for installing and 61 | * searched for browsers. This allows multiple projects using this library 62 | * to share the same browsers, saving space on the users machine. 63 | * @return {String} The default path for installed browsers. 64 | */ 65 | getDefaultInstallLocation() { 66 | let installLocation; 67 | const homeLocation = process.env.HOME || process.env.USERPROFILE; 68 | if (homeLocation) { 69 | installLocation = homeLocation; 70 | } else { 71 | installLocation = '.'; 72 | } 73 | 74 | const folderName = process.platform === 'win32' ? 75 | 'selenium-assistant' : '.selenium-assistant'; 76 | return path.join(installLocation, folderName); 77 | } 78 | 79 | /** 80 | * Before attempting to use a Sauce Labs browser, you must 81 | * call this method with your Sauce Labs Username and Access Key. 82 | * @param {String} username Your Sauce Labs username. 83 | * @param {String} accessKey Your Sauce Labs accessKey. 84 | */ 85 | setSaucelabsDetails(username, accessKey) { 86 | this._saucelabs = this._saucelabs || {}; 87 | this._saucelabs.username = username; 88 | this._saucelabs.accessKey = accessKey; 89 | } 90 | 91 | /** 92 | * @return {Object} Returns an object containing the username and accessKey 93 | * for saucelabs. 94 | */ 95 | getSaucelabsDetails() { 96 | if (!this._saucelabs) { 97 | throw new Error('Saucelab details not defined.'); 98 | } 99 | 100 | return this._saucelabs; 101 | } 102 | 103 | /** 104 | * @return {Promise} Returns a promise that resolves once the connection is 105 | * open. 106 | */ 107 | startSaucelabsConnect() { 108 | if (this._sauceConnect) { 109 | return Promise.resolve(); 110 | } 111 | 112 | return new Promise((resolve, reject) => { 113 | const options = { 114 | username: this._saucelabs.username, 115 | accessKey: this._saucelabs.accessKey, 116 | // Using tunnelIdentifier means all Sauce Labs browsers must define 117 | // this capability 118 | // tunnelIdentifier: 119 | // // Slashes in the identifier breaks a pidfile requirement 120 | // `selenium-assistant_sauce-connect-tunnel_${Date.now()}`, 121 | connectRetries: 3, 122 | }; 123 | 124 | sauceConnectLauncher(options, (err, sauceConnectProcess) => { 125 | if (err) { 126 | reject(err); 127 | return; 128 | } 129 | 130 | resolve(sauceConnectProcess); 131 | }); 132 | }) 133 | .then((sauceConnectProcess) => { 134 | this._sauceConnect = sauceConnectProcess; 135 | }); 136 | } 137 | 138 | /** 139 | * @return {Promise} A promise that resolves once the connection is closed. 140 | */ 141 | stopSaucelabsConnect() { 142 | if (!this._sauceConnect) { 143 | return Promise.resolve(); 144 | } 145 | 146 | return new Promise((resolve) => { 147 | this._sauceConnect.close(resolve); 148 | }) 149 | .then(() => { 150 | this._sauceConnect = null; 151 | }); 152 | } 153 | } 154 | 155 | module.exports = new ApplicationState(); 156 | -------------------------------------------------------------------------------- /src/browser-manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const LocalChromeBrowser = require('./local-browsers/chrome'); 20 | const LocalFirefoxBrowser = require('./local-browsers/firefox'); 21 | const LocalSafariBrowser = require('./local-browsers/safari'); 22 | const SaucelabsChromeBrowser = require('./saucelabs-browsers/chrome'); 23 | const SaucelabsEdgeBrowser = require('./saucelabs-browsers/edge'); 24 | const SaucelabsFirefoxBrowser = require('./saucelabs-browsers/firefox'); 25 | const SaucelabsIEBrowser = require('./saucelabs-browsers/ie'); 26 | const SaucelabsSafariBrowser = require('./saucelabs-browsers/safari'); 27 | 28 | /** 29 | * This class is a simple helper to define the possible permutations of 30 | * browsers and create the objects which are returned by 31 | * {@link SeleniumAssistant}. 32 | * 33 | * @private 34 | */ 35 | class BrowserManager { 36 | // Details from: 37 | // https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-BrowserVersion 38 | /** 39 | * This method will return a browser instance tied to a Sauce Labs hosted 40 | * browser. 41 | * 42 | * @param {String} browserId The browser ID you wish to control. 43 | * @param {String} browserVersion The browser verions you wish to target. 44 | * This is the Sauce Labs version, not release name. Can be "latest", 45 | * "latest-1", "latest-2", "45.0" 46 | * @return {WebDriverBrowser} browser A WebDriverBrowser instance pointing 47 | * to a Sauce Labs hosted browser. 48 | */ 49 | getSauceLabsBrowser(browserId, browserVersion) { 50 | switch (browserId) { 51 | case 'chrome': 52 | return new SaucelabsChromeBrowser(browserVersion); 53 | case 'microsoftedge': 54 | return new SaucelabsEdgeBrowser(browserVersion); 55 | case 'firefox': 56 | return new SaucelabsFirefoxBrowser(browserVersion); 57 | case 'internet explorer': 58 | return new SaucelabsIEBrowser(browserVersion); 59 | case 'safari': 60 | return new SaucelabsSafariBrowser(browserVersion); 61 | default: 62 | throw new Error('Unknown Saucelabs browser request: ', browserId); 63 | } 64 | } 65 | 66 | /** 67 | *This method returns the full list of browsers this library supports, 68 | * regardless of whether the current environment has access to them or not. 69 | *
70 | * 71 | *As more browsers are specifically added, this list will grow.
72 | * 73 | * @return {ArrayA very simple method to create a {@link WebDriverBrowser} instance 92 | * with the current config based on minimal config.
93 | * 94 | *This method will throw if you request a browser that this 95 | * library doesn't support.
96 | * 97 | * @param {String} browserId The selenium browser Id 'chrome', 'firefox', etc 98 | * @param {String} release The release you want the browser to be on 99 | * 'stable', 'beta', 'unstable' or 'saucelabs' 100 | * @return {WebDriverBrowser} An instance of the browser you requested. 101 | */ 102 | getLocalBrowser(browserId, release) { 103 | if (release !== 'stable' && 104 | release !== 'beta' && 105 | release !== 'unstable') { 106 | throw new Error('Unknown release type.'); 107 | } 108 | 109 | switch (browserId) { 110 | case 'chrome': 111 | return new LocalChromeBrowser(release); 112 | case 'firefox': 113 | return new LocalFirefoxBrowser(release); 114 | case 'safari': 115 | return new LocalSafariBrowser(release); 116 | default: 117 | throw new Error('Unknown web driver browser request: ', browserId); 118 | } 119 | } 120 | } 121 | 122 | module.exports = new BrowserManager(); 123 | -------------------------------------------------------------------------------- /src/browser-models/browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const seleniumWebdriver = require('selenium-webdriver'); 20 | 21 | /** 22 | * A base class which all "types" of browser models extend. 23 | * 24 | * For example: {@link LocalBrowser|LocalBrowser} and 25 | * {@link SauceLabsBrowser|SauceLabsBrowser} 26 | */ 27 | class Browser { 28 | /** 29 | * @param {Object} config The required config for the browser 30 | * this instance should represent. 31 | */ 32 | constructor(config) { 33 | if (typeof config === 'undefined' || config === null || 34 | typeof config !== 'object') { 35 | throw new Error('No browser config provided.'); 36 | } 37 | 38 | this._config = config; 39 | this._capabilities = new seleniumWebdriver.Capabilities(); 40 | 41 | this.addCapability(seleniumWebdriver.Capability.BROWSER_NAME, this.getId()); 42 | } 43 | 44 | /** 45 | * You can define capabilities here that will be 46 | * given to the WebDriver builder when 47 | * [getSeleniumDriverBuilder()]{@link Browser#getSeleniumDriverBuilder} or 48 | * [getSeleniumDriver()]{@link Browser#getSeleniumDriver} is called. 49 | * 50 | * @param {String} key The capability key. 51 | * @param {String} value The capability value. 52 | */ 53 | addCapability(key, value) { 54 | this._capabilities.set(key, value); 55 | } 56 | 57 | /** 58 | * Get the Selenium ID of the browser (i.e. 'chrome', 'firefox', 59 | * 'microsoftedge' etc.). 60 | * @return {String} Selenium ID of this browser. 61 | */ 62 | getId() { 63 | return this._config._id; 64 | } 65 | 66 | /** 67 | * Some browsers require an executable be available on the current path 68 | * to work with Selenium. If this is the case, there may be an NPM module 69 | * that can download and add the executable to the path. This method returns 70 | * the name of the appropriate NPM module for this browser. 71 | * 72 | * For example, Chrome uses the 'chromedriver' npm module and Firefox uses 73 | * the 'geckodriver' module. 74 | * 75 | * @return {String|null} The name of the NPM driver module to use with this 76 | * browser. Null is returned if there is no driver module. 77 | */ 78 | getDriverModule() { 79 | return this._config._driverModule; 80 | } 81 | 82 | /** 83 | * This is simple a user friendly name for the browser. This is largely 84 | * used for printing friendly debug info in tests. 85 | * @return {String} A user friendly name for the browser 86 | */ 87 | getPrettyName() { 88 | return this._config._prettyName; 89 | } 90 | 91 | /** 92 | * The selenium options passed to WebDriver's Builder. 93 | * 94 | * For Chrome, this will return an instance of `selenium-webdriver/chrome`, 95 | * for Firefox it would return an instance of `selenium-webdriver/firefox`. 96 | * 97 | * @return {SeleniumOptions} An instance of the appropriate 98 | * `selenium-webdriver` options for this browser. 99 | */ 100 | getSeleniumOptions() { 101 | return this._config._options; 102 | } 103 | 104 | /** 105 | * If changes are made to the selenium options, call this method to 106 | * set them before calling 107 | * [getSeleniumDriver()]{@link Browser#getSeleniumDriver}. 108 | * 109 | * @param {SeleniumOptions} options An instance of a `selenium-webdriver` 110 | * options class. 111 | */ 112 | setSeleniumOptions(options) { 113 | this._config._options = options; 114 | } 115 | 116 | /* eslint-disable valid-jsdoc */ 117 | /** 118 | * This method returns the preconfigured WebDriver Builder. 119 | * 120 | * This is useful if you wish to customise the builder with additional 121 | * options (i.e. customise the proxy of the driver.) 122 | * @return {WebDriverBuilder} A WebDriver Builder instance, see [selenium-webdriver.Builder]{@link http://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html} for more info. 123 | */ 124 | getSeleniumDriverBuilder() { 125 | throw new Error('getSeleniumDriverBuilder() must be overriden by ' + 126 | 'subclasses'); 127 | } 128 | 129 | /** 130 | * This method resolves to a raw WebDriver instance. 131 | * 132 | * @return {PromiseHandles the prettyName and executable path for Chrome browser.
30 | * 31 | * @private 32 | * @extends WebDriverBrowser 33 | */ 34 | class LocalChromeBrowser extends LocalBrowser { 35 | /** 36 | * Create a Chrome representation of a {@link WebDriverBrowser} 37 | * instance on a specific channel. 38 | * @param {String} release The release name for this browser instance. 39 | */ 40 | constructor(release) { 41 | super(new ChromeConfig(), release); 42 | } 43 | 44 | /** 45 | *This method returns the preconfigured builder used by 46 | * getSeleniumDriver().
47 | * 48 | *This is useful if you wish to customise the builder with additional 49 | * options (i.e. customise the proxy of the driver.)
50 | * 51 | *For more info, see: 52 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
53 | * 54 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 55 | */ 56 | getSeleniumDriverBuilder() { 57 | const seleniumOptions = this.getSeleniumOptions(); 58 | seleniumOptions.setChromeBinaryPath(this.getExecutablePath()); 59 | 60 | const builder = new webdriver 61 | .Builder() 62 | .withCapabilities(this._capabilities) 63 | .forBrowser(this.getId()) 64 | .setChromeOptions(seleniumOptions); 65 | 66 | return builder; 67 | } 68 | 69 | /** 70 | * @return {string|null} The install directory with selenium-assistant's 71 | * reserved directory for installing browsers and operating files. 72 | */ 73 | _findInInstallDir() { 74 | const defaultDir = application.getInstallDirectory(); 75 | let expectedPath; 76 | if (process.platform === 'linux') { 77 | let chromeSubPath = 'chrome/google-chrome'; 78 | if (this._release === 'beta') { 79 | chromeSubPath = 'chrome-beta/google-chrome-beta'; 80 | } 81 | 82 | expectedPath = path.join( 83 | defaultDir, 'chrome', this._release, 'opt/google/', 84 | chromeSubPath); 85 | } else if (process.platform === 'darwin') { 86 | let chromeAppName = 'Google Chrome'; 87 | if (this._release === 'beta') { 88 | chromeAppName = 'Google Chrome'; 89 | } 90 | 91 | expectedPath = path.join( 92 | defaultDir, 'chrome', this._release, chromeAppName + '.app', 93 | 'Contents/MacOS/' + chromeAppName 94 | ); 95 | } 96 | 97 | try { 98 | // This will throw if it's not found 99 | fs.lstatSync(expectedPath); 100 | return expectedPath; 101 | } catch (error) { 102 | // NOOP 103 | } 104 | 105 | return null; 106 | } 107 | 108 | /** 109 | * Returns the executable for the browser 110 | * @return {String} Path of executable 111 | */ 112 | getExecutablePath() { 113 | const installDirExecutable = this._findInInstallDir(); 114 | if (installDirExecutable) { 115 | // We have a path for the browser 116 | return installDirExecutable; 117 | } 118 | 119 | try { 120 | switch (process.platform) { 121 | case 'darwin': 122 | // Chrome on OS X 123 | switch (this._release) { 124 | case 'stable': 125 | return '/Applications/Google Chrome.app/' + 126 | 'Contents/MacOS/Google Chrome'; 127 | case 'beta': 128 | return '/Applications/Google Chrome Beta.app/' + 129 | 'Contents/MacOS/Google Chrome Beta'; 130 | default: 131 | throw new Error('Unknown release: ' + this._release); 132 | } 133 | case 'linux': 134 | // Chrome on linux 135 | switch (this._release) { 136 | case 'stable': 137 | return which.sync('google-chrome'); 138 | case 'beta': 139 | return which.sync('google-chrome-beta'); 140 | default: 141 | throw new Error('Unknown release: ' + this._release); 142 | } 143 | default: 144 | throw new Error('Sorry, this platform isn\'t supported'); 145 | } 146 | } catch (err) { 147 | // NOOP 148 | } 149 | 150 | return null; 151 | } 152 | 153 | /** 154 | * A version number for the browser. This is the major version number 155 | * (i.e. for 48.0.1293, this would return 18) 156 | * @return {Integer} The major version number of this browser 157 | */ 158 | getVersionNumber() { 159 | const chromeVersion = this.getRawVersionString(); 160 | if (!chromeVersion) { 161 | return -1; 162 | } 163 | 164 | const regexMatch = chromeVersion.match(/(\d+)\.\d+\.\d+\.\d+/); 165 | if (regexMatch === null) { 166 | return -1; 167 | } 168 | 169 | return parseInt(regexMatch[1], 10); 170 | } 171 | 172 | /** 173 | * Get the minimum support version of Chrome with selenium-assistant. 174 | * @return {number} Minimum supported Chrome version. 175 | */ 176 | _getMinSupportedVersion() { 177 | // ChromeDriver only works on Chrome 47+ 178 | return 47; 179 | } 180 | 181 | /** 182 | * This method returns the pretty names for each browser release. 183 | * @return {Object} An object containing on or move of 'stable', 'beta' or 184 | * 'unstable' keys with a matching name for that release. 185 | */ 186 | static getPrettyReleaseNames() { 187 | return { 188 | stable: 'Stable', 189 | beta: 'Beta', 190 | }; 191 | } 192 | } 193 | 194 | module.exports = LocalChromeBrowser; 195 | -------------------------------------------------------------------------------- /src/local-browsers/firefox.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const fs = require('fs'); 20 | const path = require('path'); 21 | const which = require('which'); 22 | const webdriver = require('selenium-webdriver'); 23 | 24 | const LocalBrowser = require('../browser-models/local-browser.js'); 25 | const application = require('../application-state.js'); 26 | const FirefoxConfig = require('../webdriver-config/firefox.js'); 27 | 28 | /** 29 | *Handles the prettyName and executable path for Chrome browser.
30 | * 31 | * @private 32 | * @extends WebDriverBrowser 33 | */ 34 | class LocalChromeBrowser extends LocalBrowser { 35 | /** 36 | * Create a Chrome representation of a {@link WebDriverBrowser} 37 | * instance on a specific channel. 38 | * @param {String} release The release name for this browser instance. 39 | */ 40 | constructor(release) { 41 | super(new FirefoxConfig(), release); 42 | } 43 | 44 | /** 45 | *This method returns the preconfigured builder used by 46 | * getSeleniumDriver().
47 | * 48 | *This is useful if you wish to customise the builder with additional 49 | * options (i.e. customise the proxy of the driver.)
50 | * 51 | *For more info, see: 52 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
53 | * 54 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 55 | */ 56 | getSeleniumDriverBuilder() { 57 | const seleniumOptions = this.getSeleniumOptions(); 58 | seleniumOptions.setBinary(this.getExecutablePath()); 59 | 60 | const builder = new webdriver 61 | .Builder() 62 | .withCapabilities(this._capabilities) 63 | .forBrowser(this.getId()) 64 | .setFirefoxOptions(seleniumOptions); 65 | 66 | return builder; 67 | } 68 | 69 | /** 70 | * @return {string|null} The install directory with selenium-assistant's 71 | * reserved directory for installing browsers and operating files. 72 | */ 73 | _findInInstallDir() { 74 | const defaultDir = application.getInstallDirectory(); 75 | if (process.platform === 'linux') { 76 | const expectedPath = path.join( 77 | defaultDir, 'firefox', this._release, 'firefox'); 78 | 79 | try { 80 | // This will throw if it's not found 81 | fs.lstatSync(expectedPath); 82 | return expectedPath; 83 | } catch (error) { 84 | // NOOP 85 | } 86 | } else if (process.platform === 'darwin') { 87 | // Find OS X expected path 88 | let firefoxAppName; 89 | if (this._release === 'unstable') { 90 | firefoxAppName = 'Firefox Nightly.app'; 91 | } else { 92 | firefoxAppName = 'Firefox.app'; 93 | } 94 | 95 | const expectedPath = path.join( 96 | defaultDir, 'firefox', this._release, firefoxAppName, 97 | 'Contents/MacOS/firefox' 98 | ); 99 | 100 | try { 101 | // This will throw if it's not found 102 | fs.lstatSync(expectedPath); 103 | return expectedPath; 104 | } catch (error) { 105 | // NOOP 106 | } 107 | } 108 | 109 | return null; 110 | } 111 | 112 | /** 113 | * Returns the executable for the browser 114 | * @return {String} Path of executable 115 | */ 116 | getExecutablePath() { 117 | const installDirExecutable = this._findInInstallDir(); 118 | if (installDirExecutable) { 119 | // We have a path for the browser 120 | return installDirExecutable; 121 | } 122 | 123 | try { 124 | switch (process.platform) { 125 | case 'darwin': 126 | // Firefox Beta on OS X overrides Firefox stable, so realistically 127 | // this location could return ff stable as beta, but at least it will 128 | // only be returned once. 129 | if (this._release === 'stable') { 130 | return '/Applications/Firefox.app/Contents/MacOS/firefox'; 131 | } else if (this._release === 'unstable') { 132 | return '/Applications/Firefox Nightly.app/Contents/MacOS/firefox'; 133 | } 134 | break; 135 | case 'linux': 136 | // Stable firefox on Linux is the only known location we can find 137 | // otherwise it's jsut a .tar.gz that users have to put anywhere 138 | if (this._release === 'stable') { 139 | return which.sync('firefox'); 140 | } 141 | break; 142 | default: 143 | throw new Error('Sorry, this platform isn\'t supported'); 144 | } 145 | } catch (err) { 146 | // NOOP 147 | } 148 | 149 | return null; 150 | } 151 | 152 | /** 153 | * A version number for the browser. This is the major version number 154 | * (i.e. for 48.0.1293, this would return 18) 155 | * @return {Integer} The major version number of this browser 156 | */ 157 | getVersionNumber() { 158 | const firefoxVersion = this.getRawVersionString(); 159 | if (!firefoxVersion) { 160 | return -1; 161 | } 162 | 163 | const regexMatch = firefoxVersion.match(/(\d+)\.\d/); 164 | if (regexMatch === null) { 165 | return -1; 166 | } 167 | 168 | return parseInt(regexMatch[1], 10); 169 | } 170 | 171 | /** 172 | * Get the minimum support version of Firefox with selenium-assistant. 173 | * @return {number} Minimum supported Firefox version. 174 | */ 175 | _getMinSupportedVersion() { 176 | // Firefox Marionette only works on Firefox 47+ 177 | return 47; 178 | } 179 | 180 | /** 181 | * This method returns the pretty names for each browser release. 182 | * @return {Object} An object containing on or move of 'stable', 'beta' or 183 | * 'unstable' keys with a matching name for that release. 184 | */ 185 | static getPrettyReleaseNames() { 186 | return { 187 | stable: 'Stable', 188 | beta: 'Beta', 189 | unstable: 'Nightly', 190 | }; 191 | } 192 | } 193 | 194 | module.exports = LocalChromeBrowser; 195 | -------------------------------------------------------------------------------- /src/local-browsers/safari.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const fs = require('fs'); 20 | const webdriver = require('selenium-webdriver'); 21 | const safari = require('selenium-webdriver/safari'); 22 | 23 | const LocalBrowser = require('../browser-models/local-browser.js'); 24 | const SafariConfig = require('../webdriver-config/safari.js'); 25 | 26 | /** 27 | *Handles the prettyName and executable path for Chrome browser.
28 | * 29 | * @private 30 | * @extends WebDriverBrowser 31 | */ 32 | class LocalSafariBrowser extends LocalBrowser { 33 | /** 34 | * Create a Chrome representation of a {@link WebDriverBrowser} 35 | * instance on a specific channel. 36 | * @param {String} release The release name for this browser instance. 37 | */ 38 | constructor(release) { 39 | super(new SafariConfig(), release); 40 | } 41 | 42 | /** 43 | *This method returns the preconfigured builder used by 44 | * getSeleniumDriver().
45 | * 46 | *This is useful if you wish to customise the builder with additional 47 | * options (i.e. customise the proxy of the driver.)
48 | * 49 | *For more info, see: 50 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
51 | * 52 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 53 | */ 54 | getSeleniumDriverBuilder() { 55 | const seleniumOptions = this.getSeleniumOptions(); 56 | seleniumOptions.setTechnologyPreview((this._release === 'beta')); 57 | 58 | let builder = new webdriver 59 | .Builder() 60 | .withCapabilities(this._capabilities) 61 | .forBrowser(this.getId()) 62 | .setSafariOptions(seleniumOptions); 63 | 64 | // Run Safari 12 in legacy mode due to 65 | // https://github.com/SeleniumHQ/selenium/issues/6026 66 | // This is not needed in Safari 13+. 67 | if (this.getVersionNumber() === 12) { 68 | builder = builder.usingServer( 69 | new safari.ServiceBuilder().addArguments('--legacy').build().start()); 70 | } 71 | 72 | return builder; 73 | } 74 | 75 | /** 76 | * @return {string|null} The install directory with selenium-assistant's 77 | * reserved directory for installing browsers and operating files. 78 | */ 79 | _findInInstallDir() { 80 | return null; 81 | } 82 | 83 | /** 84 | * Returns the executable for the browser 85 | * @return {String} Path of executable 86 | */ 87 | getExecutablePath() { 88 | const installDirExecutable = this._findInInstallDir(); 89 | if (installDirExecutable) { 90 | // We have a path for the browser 91 | return installDirExecutable; 92 | } 93 | 94 | try { 95 | if (this._release === 'stable') { 96 | if (process.platform === 'darwin') { 97 | return '/Applications/Safari.app/' + 98 | 'Contents/MacOS/Safari'; 99 | } 100 | } else if (this._release === 'beta') { 101 | if (process.platform === 'darwin') { 102 | return '/Applications/Safari Technology Preview.app/' + 103 | 'Contents/MacOS/Safari Technology Preview'; 104 | } 105 | } 106 | } catch (err) { 107 | // NOOP 108 | } 109 | 110 | return null; 111 | } 112 | 113 | /** 114 | * Get the version string from the browser itself. From Safari this is from 115 | * `version.plist` file. 116 | * @return {String} The version string for this Safari release. 117 | */ 118 | getRawVersionString() { 119 | if (this._rawVerstionString) { 120 | return this._rawVerstionString; 121 | } 122 | 123 | const executablePath = this.getExecutablePath(); 124 | if (!executablePath) { 125 | return null; 126 | } 127 | 128 | this._rawVerstionString = null; 129 | 130 | let versionListPath; 131 | if (this._release === 'stable') { 132 | versionListPath = '/Applications/Safari.app/Contents/version.plist'; 133 | } else if (this._release === 'beta') { 134 | versionListPath = '/Applications/Safari Technology Preview.app/' + 135 | 'Contents/version.plist'; 136 | } 137 | try { 138 | const versionDoc = fs.readFileSync(versionListPath).toString(); 139 | /* eslint-disable no-useless-escape */ 140 | const results = new RegExp( 141 | 'Handles the prettyName and executable path for Chrome browser.
23 | * 24 | * @private 25 | * @extends WebDriverBrowser 26 | */ 27 | class ChromeWebDriverBrowser extends SauceLabsBrowser { 28 | /** 29 | * Create a Chrome representation of a {@link WebDriverBrowser} 30 | * instance on a specific channel. 31 | * @param {String} version The release name for this browser instance. 32 | */ 33 | constructor(version) { 34 | super(new ChromeConfig(), version); 35 | 36 | // Set default platform to windows 10 otherwise it will be an out of 37 | // date version of Chrome. 38 | this.addCapability('platform', 'Windows 10'); 39 | } 40 | 41 | /** 42 | *This method returns the preconfigured builder used by 43 | * getSeleniumDriver().
44 | * 45 | *This is useful if you wish to customise the builder with additional 46 | * options (i.e. customise the proxy of the driver.)
47 | * 48 | *For more info, see: 49 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
50 | * 51 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 52 | */ 53 | getSeleniumDriverBuilder() { 54 | let builder = super.getSeleniumDriverBuilder(); 55 | 56 | const seleniumOptions = this.getSeleniumOptions(); 57 | const capabilities = seleniumOptions.toCapabilities(this._capabilities); 58 | 59 | builder = builder.withCapabilities(capabilities); 60 | 61 | return builder; 62 | } 63 | } 64 | 65 | module.exports = ChromeWebDriverBrowser; 66 | -------------------------------------------------------------------------------- /src/saucelabs-browsers/edge.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const SauceLabsBrowser = require('../browser-models/saucelabs-browser'); 20 | const EdgeConfig = require('../webdriver-config/edge'); 21 | 22 | /** 23 | *Handles the prettyName and executable path for Chrome browser.
24 | * 25 | *Note that Edge gets cranky about which ports are used for localhost + 26 | * saucelabs.
27 | * 28 | *https://support.saucelabs.com/customer/portal/questions/14368823-requests-to-localhost-on-microsoft-edge-are-failing-over-sauce-connect
29 | * 30 | * @private 31 | * @extends WebDriverBrowser 32 | */ 33 | class EdgeWebDriverBrowser extends SauceLabsBrowser { 34 | /** 35 | * Create a Chrome representation of a {@link WebDriverBrowser} 36 | * instance on a specific channel. 37 | * @param {String} version The version name for this browser instance. 38 | */ 39 | constructor(version) { 40 | super(new EdgeConfig(), version); 41 | 42 | // Set default platform to windows 10 otherwise it will fail. 43 | this.addCapability('platform', 'Windows 10'); 44 | } 45 | 46 | /** 47 | *This method returns the preconfigured builder used by 48 | * getSeleniumDriver().
49 | * 50 | *This is useful if you wish to customise the builder with additional 51 | * options (i.e. customise the proxy of the driver.)
52 | * 53 | *For more info, see: 54 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
55 | * 56 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 57 | */ 58 | getSeleniumDriverBuilder() { 59 | let builder = super.getSeleniumDriverBuilder(); 60 | const seleniumOptions = this.getSeleniumOptions(); 61 | const capabilities = seleniumOptions.toCapabilities(this._capabilities); 62 | 63 | builder = builder 64 | .withCapabilities(capabilities); 65 | 66 | return builder; 67 | } 68 | } 69 | 70 | module.exports = EdgeWebDriverBrowser; 71 | -------------------------------------------------------------------------------- /src/saucelabs-browsers/firefox.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const SauceLabsBrowser = require('../browser-models/saucelabs-browser'); 20 | const FirefoxConfig = require('../webdriver-config/firefox'); 21 | 22 | /** 23 | *Handles the prettyName and executable path for Chrome browser.
24 | * 25 | * @private 26 | * @extends WebDriverBrowser 27 | */ 28 | class FirefoxWebDriverBrowser extends SauceLabsBrowser { 29 | /** 30 | * Create a Chrome representation of a {@link WebDriverBrowser} 31 | * instance on a specific channel. 32 | * @param {String} version The release name for this browser instance. 33 | */ 34 | constructor(version) { 35 | super(new FirefoxConfig(), version); 36 | 37 | // Set default platform to windows 10 otherwise it will be an out of 38 | // date version of Firefox. 39 | this.addCapability('platform', 'Windows 10'); 40 | } 41 | 42 | /** 43 | *This method returns the preconfigured builder used by 44 | * getSeleniumDriver().
45 | * 46 | *This is useful if you wish to customise the builder with additional 47 | * options (i.e. customise the proxy of the driver.)
48 | * 49 | *For more info, see: 50 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
51 | * 52 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 53 | */ 54 | getSeleniumDriverBuilder() { 55 | let builder = super.getSeleniumDriverBuilder(); 56 | 57 | const seleniumOptions = this.getSeleniumOptions(); 58 | // FirefoxOptions.toCapabilities() don't take in options. 59 | const capabilities = seleniumOptions.toCapabilities() 60 | .merge(this._capabilities); 61 | 62 | builder = builder 63 | .withCapabilities(capabilities); 64 | 65 | return builder; 66 | 67 | /** let builder = super.getSeleniumDriverBuilder(); 68 | builder = builder 69 | // Sauce Labs + Firefox is simple broken if I pass in the options 70 | // .setFirefoxOptions(this.getSeleniumOptions()) 71 | .withCapabilities(this._capabilities) 72 | .forBrowser(this.getId()); 73 | 74 | return builder;**/ 75 | } 76 | } 77 | 78 | module.exports = FirefoxWebDriverBrowser; 79 | -------------------------------------------------------------------------------- /src/saucelabs-browsers/ie.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const SauceLabsBrowser = require('../browser-models/saucelabs-browser'); 20 | const IEConfig = require('../webdriver-config/ie'); 21 | 22 | /** 23 | *Handles the prettyName and executable path for Chrome browser.
24 | * 25 | * @private 26 | * @extends WebDriverBrowser 27 | */ 28 | class IEWebDriverBrowser extends SauceLabsBrowser { 29 | /** 30 | * Create a Chrome representation of a {@link WebDriverBrowser} 31 | * instance on a specific channel. 32 | * @param {String} version The release name for this browser instance. 33 | */ 34 | constructor(version) { 35 | super(new IEConfig(), version); 36 | } 37 | 38 | /** 39 | *This method returns the preconfigured builder used by 40 | * getSeleniumDriver().
41 | * 42 | *This is useful if you wish to customise the builder with additional 43 | * options (i.e. customise the proxy of the driver.)
44 | * 45 | *For more info, see: 46 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
47 | * 48 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 49 | */ 50 | getSeleniumDriverBuilder() { 51 | let builder = super.getSeleniumDriverBuilder(); 52 | const seleniumOptions = this.getSeleniumOptions(); 53 | const capabilities = seleniumOptions.toCapabilities(this._capabilities); 54 | 55 | builder = builder 56 | .withCapabilities(capabilities); 57 | 58 | return builder; 59 | } 60 | } 61 | 62 | module.exports = IEWebDriverBrowser; 63 | -------------------------------------------------------------------------------- /src/saucelabs-browsers/safari.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const SauceLabsBrowser = require('../browser-models/saucelabs-browser'); 20 | const SafariConfig = require('../webdriver-config/safari'); 21 | 22 | /** 23 | *Handles the prettyName and executable path for Chrome browser.
24 | * 25 | * @private 26 | * @extends WebDriverBrowser 27 | */ 28 | class SafariDriverBrowser extends SauceLabsBrowser { 29 | /** 30 | * Create a Chrome representation of a {@link WebDriverBrowser} 31 | * instance on a specific channel. 32 | * @param {String} version The release name for this browser instance. 33 | */ 34 | constructor(version) { 35 | super(new SafariConfig(), version); 36 | } 37 | 38 | /** 39 | *This method returns the preconfigured builder used by 40 | * getSeleniumDriver().
41 | * 42 | *This is useful if you wish to customise the builder with additional 43 | * options (i.e. customise the proxy of the driver.)
44 | * 45 | *For more info, see: 46 | * {@link https://seleniumhq.github.io/selenium/docs/api/javascript/module/selenium-webdriver/index_exports_Builder.html | WebDriverBuilder Docs}
47 | * 48 | * @return {WebDriverBuilder} Builder that resolves to a webdriver instance. 49 | */ 50 | getSeleniumDriverBuilder() { 51 | let builder = super.getSeleniumDriverBuilder(); 52 | const seleniumOptions = this.getSeleniumOptions(); 53 | const capabilities = seleniumOptions.toCapabilities(this._capabilities); 54 | 55 | builder = builder 56 | .withCapabilities(capabilities); 57 | 58 | return builder; 59 | } 60 | } 61 | 62 | module.exports = SafariDriverBrowser; 63 | -------------------------------------------------------------------------------- /src/webdriver-config/chrome.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const DriverConfig = require('../browser-models/driver-config'); 20 | const seleniumChrome = require('selenium-webdriver/chrome'); 21 | 22 | /** 23 | * @private 24 | */ 25 | class ChromeConfig extends DriverConfig { 26 | /** 27 | * 28 | */ 29 | constructor() { 30 | super( 31 | 'chrome', 32 | new seleniumChrome.Options(), 33 | 'Google Chrome', 34 | 'chromedriver' 35 | ); 36 | } 37 | } 38 | 39 | module.exports = ChromeConfig; 40 | -------------------------------------------------------------------------------- /src/webdriver-config/edge.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const DriverConfig = require('../browser-models/driver-config'); 20 | const seleniumEdge = require('selenium-webdriver/edge'); 21 | 22 | /** 23 | * @private 24 | */ 25 | class Edge extends DriverConfig { 26 | /** 27 | * 28 | */ 29 | constructor() { 30 | super( 31 | 'microsoftedge', 32 | new seleniumEdge.Options(), 33 | 'Microsoft Edge' 34 | ); 35 | } 36 | } 37 | 38 | module.exports = Edge; 39 | -------------------------------------------------------------------------------- /src/webdriver-config/firefox.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const DriverConfig = require('../browser-models/driver-config'); 20 | const seleniumFirefox = require('selenium-webdriver/firefox'); 21 | 22 | /** 23 | * @private 24 | */ 25 | class Firefox extends DriverConfig { 26 | /** 27 | * 28 | */ 29 | constructor() { 30 | super( 31 | 'firefox', 32 | new seleniumFirefox.Options(), 33 | 'Firefox', 34 | 'geckodriver' 35 | ); 36 | } 37 | } 38 | 39 | module.exports = Firefox; 40 | -------------------------------------------------------------------------------- /src/webdriver-config/ie.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const DriverConfig = require('../browser-models/driver-config'); 20 | const seleniumIE = require('selenium-webdriver/ie'); 21 | 22 | /** 23 | * @private 24 | */ 25 | class IE extends DriverConfig { 26 | /** 27 | * 28 | */ 29 | constructor() { 30 | super( 31 | 'internet explorer', 32 | new seleniumIE.Options(), 33 | 'Internet Explorer' 34 | ); 35 | } 36 | } 37 | 38 | module.exports = IE; 39 | -------------------------------------------------------------------------------- /src/webdriver-config/safari.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const DriverConfig = require('../browser-models/driver-config'); 20 | const seleniumSafari = require('selenium-webdriver/safari'); 21 | 22 | /** 23 | * @private 24 | */ 25 | class Safari extends DriverConfig { 26 | /** 27 | * 28 | */ 29 | constructor() { 30 | super( 31 | 'safari', 32 | new seleniumSafari.Options(), 33 | 'Safari' 34 | ); 35 | } 36 | } 37 | 38 | module.exports = Safari; 39 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "mocha": true 5 | }, 6 | "rules": { 7 | "no-console": 0, 8 | "no-new": 0, 9 | "max-len": 0, 10 | "no-unused-expression": 0, 11 | "max-nested-callbacks": 0, 12 | "require-jsdoc": 0, 13 | "no-invalid-this": 0 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/application-state.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const path = require('path'); 20 | 21 | require('chai').should(); 22 | 23 | describe('Application State', function() { 24 | it('should be able to get default install location', function() { 25 | const applicationState = require('../src/application-state.js'); 26 | const installLocation = applicationState.getDefaultInstallLocation(); 27 | 28 | (typeof installLocation).should.equal('string'); 29 | (installLocation.length).should.be.gt(1); 30 | }); 31 | 32 | it('should return the current directory if no useful environment variables exist', function() { 33 | const originalHomeValue = process.env.HOME; 34 | const originalUserProfValue = process.env.USERPROFILE; 35 | 36 | delete process.env.HOME; 37 | delete process.env.USERPROFILE; 38 | 39 | const applicationState = require('../src/application-state.js'); 40 | const installLocation = applicationState.getDefaultInstallLocation(); 41 | 42 | process.env.HOME = originalHomeValue; 43 | process.env.USERPROFILE = originalUserProfValue; 44 | 45 | (typeof installLocation).should.equal('string'); 46 | (installLocation).should.equal('.selenium-assistant'); 47 | }); 48 | 49 | it('should start with default install directory', function() { 50 | const applicationState = require('../src/application-state.js'); 51 | applicationState.getInstallDirectory().should.equal( 52 | applicationState.getDefaultInstallLocation() 53 | ); 54 | }); 55 | 56 | it('should be able to get a change install directory', function() { 57 | const applicationState = require('../src/application-state.js'); 58 | const newPath = './test/test-output/'; 59 | applicationState.setInstallDirectory(newPath); 60 | applicationState.getInstallDirectory().should.equal(path.resolve(newPath)); 61 | }); 62 | 63 | it('should be able to pass in null to reset state to default install location', function() { 64 | const applicationState = require('../src/application-state.js'); 65 | 66 | const newPath = './test/test-output/'; 67 | applicationState.setInstallDirectory(newPath); 68 | applicationState.getInstallDirectory().should.equal(path.resolve(newPath)); 69 | 70 | applicationState.setInstallDirectory(null); 71 | applicationState.getInstallDirectory().should.equal( 72 | applicationState.getDefaultInstallLocation() 73 | ); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /test/browser-usage.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2016 Google Inc. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | const fs = require('fs'); 20 | const path = require('path'); 21 | const sinon = require('sinon'); 22 | const selenium = require('selenium-webdriver'); 23 | const seleniumAssistant = require('../src/index.js'); 24 | const TestServer = require('./helpers/test-server.js'); 25 | const expect = require('chai').expect; 26 | 27 | require('chai').should(); 28 | 29 | const TIMEOUT = 5 * 60 * 1000; 30 | const RETRIES = 3; 31 | 32 | describe('Test Usage of Browsers', function() { 33 | this.timeout(TIMEOUT); 34 | this.retries(RETRIES); 35 | 36 | const sandbox = sinon.createSandbox(); 37 | let globalDriver = null; 38 | const globalServer = new TestServer(false); 39 | let localURL = ''; 40 | 41 | function testNormalSeleniumUsage(specificBrowser) { 42 | return specificBrowser.getSeleniumDriver() 43 | .then((driver) => { 44 | globalDriver = driver; 45 | }) 46 | .then(() => { 47 | return globalDriver.get(localURL) 48 | .then(() => { 49 | return globalDriver.wait(selenium.until.titleIs('Example Site'), 1000); 50 | }); 51 | }) 52 | .then(() => seleniumAssistant.killWebDriver(globalDriver)) 53 | .then(() => { 54 | globalDriver = null; 55 | }); 56 | } 57 | 58 | function testBuilderSeleniumUsage(specificBrowser) { 59 | const builder = specificBrowser.getSeleniumDriverBuilder(); 60 | 61 | return builder.build() 62 | .then((driver) => { 63 | globalDriver = driver; 64 | }) 65 | .then(() => { 66 | return globalDriver.get(localURL); 67 | }) 68 | .then(() => { 69 | return globalDriver.wait(selenium.until.titleIs('Example Site'), 1000); 70 | }) 71 | .then(() => { 72 | return seleniumAssistant.killWebDriver(globalDriver); 73 | }) 74 | .then(() => { 75 | globalDriver = null; 76 | }); 77 | } 78 | 79 | function testBrowserInfo(specificBrowser) { 80 | const versionString = specificBrowser.getRawVersionString(); 81 | (typeof versionString).should.equal('string'); 82 | (versionString === null).should.equal(false); 83 | versionString.length.should.gt(0); 84 | 85 | const versionNumber = specificBrowser.getVersionNumber(); 86 | (typeof versionNumber).should.equal('number'); 87 | if (versionString) { 88 | versionNumber.should.not.equal(-1); 89 | } 90 | 91 | const prettyName = specificBrowser.getPrettyName(); 92 | prettyName.length.should.gt(1); 93 | 94 | const options = specificBrowser.getSeleniumOptions(); 95 | expect(options).to.exist; 96 | 97 | const executablePath = specificBrowser.getExecutablePath(); 98 | (typeof executablePath).should.equal('string'); 99 | } 100 | 101 | function setupTest(localBrowser) { 102 | describe(`Test Usage of ${localBrowser.getPrettyName()}`, function() { 103 | it(`should be able to use ${localBrowser.getId()} - ${localBrowser.getReleaseName()}`, function() { 104 | if (localBrowser.isDenyListed()) { 105 | console.warn(`Browser is denylisted ${localBrowser.getId()} - ${localBrowser.getReleaseName()}`); 106 | return; 107 | } 108 | 109 | if (!localBrowser.isValid()) { 110 | console.warn(`Browser is invalid ${localBrowser.getId()} - ${localBrowser.getReleaseName()}`); 111 | return; 112 | } 113 | 114 | testBrowserInfo(localBrowser); 115 | return testNormalSeleniumUsage(localBrowser) 116 | .then(() => testBuilderSeleniumUsage(localBrowser)); 117 | }); 118 | 119 | it('should get null for raw version output if no executable found', function() { 120 | sandbox.stub(localBrowser, 'getExecutablePath').callsFake(() => { 121 | return null; 122 | }); 123 | 124 | // To overcome version string caching 125 | localBrowser._rawVerstionString = null; 126 | 127 | const rawString = localBrowser.getRawVersionString(); 128 | (rawString === null).should.equal(true); 129 | }); 130 | 131 | it('should get -1 for version number if no executable found', function() { 132 | sandbox.stub(localBrowser, 'getExecutablePath').callsFake(() => { 133 | return null; 134 | }); 135 | 136 | // To overcome version string caching 137 | localBrowser._rawVerstionString = null; 138 | 139 | const versionNumber = localBrowser.getVersionNumber(); 140 | versionNumber.should.equal(-1); 141 | }); 142 | 143 | it('should get -1 for an unexpected raw version string', function() { 144 | sandbox.stub(localBrowser, 'getRawVersionString').callsFake(() => { 145 | return 'ImTotallyMadeUp 12345678.asdf.12345678.asdf'; 146 | }); 147 | 148 | // To overcome version string caching 149 | localBrowser._rawVerstionString = null; 150 | 151 | const versionNumber = localBrowser.getVersionNumber(); 152 | versionNumber.should.equal(-1); 153 | }); 154 | }); 155 | } 156 | 157 | before(function() { 158 | seleniumAssistant.setBrowserInstallDir(null); 159 | 160 | const expiration = process.env.TRAVIS ? 0 : 24; 161 | console.log('Downloading browsers....'); 162 | return Promise.all([ 163 | seleniumAssistant.downloadLocalBrowser('chrome', 'stable', expiration), 164 | seleniumAssistant.downloadLocalBrowser('chrome', 'beta', expiration), 165 | seleniumAssistant.downloadLocalBrowser('firefox', 'stable', expiration), 166 | seleniumAssistant.downloadLocalBrowser('firefox', 'beta', expiration), 167 | seleniumAssistant.downloadLocalBrowser('firefox', 'unstable', expiration), 168 | ]) 169 | .catch((err) => { 170 | console.warn('There was an issue downloading the browsers: ', err); 171 | }) 172 | .then(() => { 173 | console.log('Download of browsers complete.'); 174 | 175 | const serverPath = path.join(__dirname, 'data', 'example-site'); 176 | return globalServer.startServer(serverPath); 177 | }) 178 | .then((portNumber) => { 179 | localURL = `http://localhost:${portNumber}/`; 180 | }); 181 | }); 182 | 183 | afterEach(function() { 184 | sandbox.restore(); 185 | 186 | return seleniumAssistant.killWebDriver(globalDriver).catch(() => {}) 187 | .then(() => { 188 | globalDriver = null; 189 | }); 190 | }); 191 | 192 | after(function() { 193 | return globalServer.killServer(); 194 | }); 195 | 196 | const localBrowserFiles = fs.readdirSync( 197 | path.join(__dirname, '..', 'src', 'local-browsers')); 198 | localBrowserFiles.forEach((localBrowserFile) => { 199 | const LocalBrowserClass = require(`./../src/local-browsers/${localBrowserFile}`); 200 | const browserReleases = LocalBrowserClass.getPrettyReleaseNames(); 201 | Object.keys(browserReleases).forEach((release) => { 202 | const localBrowser = new LocalBrowserClass(release); 203 | setupTest(localBrowser); 204 | }); 205 | }); 206 | }); 207 | -------------------------------------------------------------------------------- /test/data/example-site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |A super simple class that will start and stop an 26 | * express server with a few nice defaults and just removes boilerplate 27 | * to server up static files.
28 | * 29 | *NOTE: This should never be used as a production 30 | * web server.
31 | * 32 | * @example 33 | * const TestServer = require('sw-testing-helpers').TestServer; 34 | * 35 | * let testServer = new TestServer(); 36 | * testServer.startServer(path.join(__dirname, '..'), 8888) 37 | * .then(portNumber => { 38 | * console.log('http://localhost:' + portNumber); 39 | * }); 40 | * 41 | * // To kill at a later stage... 42 | * testServer.killServer(); 43 | */ 44 | class TestServer { 45 | /** 46 | * Create a new TestServer instace 47 | * @param {Boolean} addDefaultRoutes Passing in true will create a redirect 48 | * for '/' -> '/test/browser-tests/' and add a 'Service-Worker-Allowed' header 49 | * with a value of '/'. 50 | */ 51 | constructor(addDefaultRoutes) { 52 | if ( 53 | typeof addDefaultRoutes !== 'undefined' && 54 | typeof addDefaultRoutes !== 'boolean' 55 | ) { 56 | throw new Error('addDefaultRoutes must be a boolean value'); 57 | } 58 | 59 | this._server = null; 60 | this._app = express(); 61 | this._useDefaults = false; 62 | 63 | if (typeof addDefaultRoutes === 'undefined' || addDefaultRoutes) { 64 | this._addDefaultRoutes(); 65 | } 66 | } 67 | 68 | _addDefaultRoutes() { 69 | this._useDefaults = true; 70 | 71 | // If the user tries to go to the root of the server, redirect them 72 | // to the browser test path 73 | this._app.get('/', function(req, res) { 74 | res.redirect('/test/browser-tests/'); 75 | }); 76 | } 77 | 78 | /** 79 | * If you need to extend the routes on the test server, you can access 80 | * the express app with this method. 81 | * @return {ExpressApp} The express app used to respond to requests. 82 | */ 83 | getExpressApp() { 84 | return this._app; 85 | } 86 | 87 | /** 88 | * This will start the express server with the provided port and host. 89 | * 90 | * @param {String} path Path to start the server on (i.e. './') 91 | * @param {Number} [portNumber=0] portNumber Optional parameter, by default will pick 92 | * a random available port. 93 | * @param {String} [host='localhost'] host Optional parameter, a host to bind 94 | * the express server to, by default this is localhost. 95 | * @return {Promise