├── .babelrc ├── .eslintrc.js ├── .gitignore ├── CHANGELOG ├── LICENSE ├── README.md ├── package.json └── src ├── LICENSE ├── README.md ├── bin └── xsolve_wtf.js ├── dist └── .gitkeep ├── package.json └── src ├── action.js ├── constants.js ├── helpers.js ├── hooks.js ├── logger.js ├── timeouts.js ├── validator.js └── xsolve_wtf.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "es6": true, 4 | "node": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "parserOptions": { 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "accessor-pairs": "error", 12 | "array-bracket-newline": "error", 13 | "array-bracket-spacing": "error", 14 | "array-callback-return": "error", 15 | "array-element-newline": "error", 16 | "arrow-body-style": "error", 17 | "arrow-parens": [ 18 | "error", 19 | "always" 20 | ], 21 | "arrow-spacing": [ 22 | "error", 23 | { 24 | "after": true, 25 | "before": true 26 | } 27 | ], 28 | "block-scoped-var": "error", 29 | "block-spacing": "off", 30 | "brace-style": [ 31 | "error", 32 | "1tbs", 33 | { 34 | "allowSingleLine": true 35 | } 36 | ], 37 | "callback-return": "error", 38 | "camelcase": "error", 39 | "capitalized-comments": "off", 40 | "class-methods-use-this": "off", 41 | "comma-dangle": "error", 42 | "comma-spacing": "off", 43 | "comma-style": [ 44 | "error", 45 | "last" 46 | ], 47 | "complexity": "error", 48 | "computed-property-spacing": [ 49 | "error", 50 | "never" 51 | ], 52 | "consistent-return": "off", 53 | "consistent-this": "off", 54 | "curly": "off", 55 | "default-case": "off", 56 | "dot-location": [ 57 | "error", 58 | "property" 59 | ], 60 | "dot-notation": "off", 61 | "eol-last": "error", 62 | "eqeqeq": "off", 63 | "for-direction": "error", 64 | "func-call-spacing": "error", 65 | "func-name-matching": "error", 66 | "func-names": [ 67 | "error", 68 | "never" 69 | ], 70 | "func-style": [ 71 | "error", 72 | "declaration" 73 | ], 74 | "generator-star-spacing": "error", 75 | "getter-return": "error", 76 | "global-require": "off", 77 | "guard-for-in": "error", 78 | "handle-callback-err": "error", 79 | "id-blacklist": "error", 80 | "id-length": "off", 81 | "id-match": "error", 82 | "indent": "off", 83 | "indent-legacy": "off", 84 | "init-declarations": "off", 85 | "jsx-quotes": "error", 86 | "key-spacing": "error", 87 | "keyword-spacing": "off", 88 | "line-comment-position": "off", 89 | "linebreak-style": [ 90 | "error", 91 | "unix" 92 | ], 93 | "lines-around-comment": "error", 94 | "lines-around-directive": "error", 95 | "max-depth": "error", 96 | "max-len": "off", 97 | "max-lines": "off", 98 | "max-nested-callbacks": "error", 99 | "max-params": "off", 100 | "max-statements": "error", 101 | "max-statements-per-line": "off", 102 | "new-parens": "off", 103 | "newline-after-var": "off", 104 | "newline-before-return": "off", 105 | "newline-per-chained-call": "off", 106 | "no-alert": "error", 107 | "no-array-constructor": "error", 108 | "no-await-in-loop": "error", 109 | "no-bitwise": "error", 110 | "no-buffer-constructor": "error", 111 | "no-caller": "error", 112 | "no-catch-shadow": "error", 113 | "no-confusing-arrow": "error", 114 | "no-console": "off", 115 | "no-continue": "error", 116 | "no-div-regex": "error", 117 | "no-duplicate-imports": "error", 118 | "no-else-return": "off", 119 | "no-empty-function": "error", 120 | "no-eq-null": "error", 121 | "no-eval": "error", 122 | "no-extend-native": "off", 123 | "no-extra-bind": "error", 124 | "no-extra-label": "error", 125 | "no-extra-parens": "off", 126 | "no-floating-decimal": "error", 127 | "no-implicit-coercion": "error", 128 | "no-implicit-globals": "error", 129 | "no-implied-eval": "error", 130 | "no-inline-comments": "off", 131 | "no-invalid-this": "off", 132 | "no-iterator": "error", 133 | "no-label-var": "error", 134 | "no-labels": "error", 135 | "no-lone-blocks": "error", 136 | "no-lonely-if": "error", 137 | "no-loop-func": "error", 138 | "no-magic-numbers": "off", 139 | "no-mixed-operators": "error", 140 | "no-mixed-requires": "off", 141 | "no-multi-assign": "error", 142 | "no-multi-spaces": "off", 143 | "no-multi-str": "error", 144 | "no-multiple-empty-lines": "error", 145 | "no-native-reassign": "error", 146 | "no-negated-condition": "off", 147 | "no-negated-in-lhs": "error", 148 | "no-nested-ternary": "error", 149 | "no-new": "error", 150 | "no-new-func": "error", 151 | "no-new-object": "error", 152 | "no-new-require": "error", 153 | "no-new-wrappers": "error", 154 | "no-octal-escape": "error", 155 | "no-param-reassign": "error", 156 | "no-path-concat": "error", 157 | "no-plusplus": "error", 158 | "no-process-env": "off", 159 | "no-process-exit": "error", 160 | "no-proto": "error", 161 | "no-prototype-builtins": "error", 162 | "no-restricted-globals": "error", 163 | "no-restricted-imports": "error", 164 | "no-restricted-modules": "error", 165 | "no-restricted-properties": "error", 166 | "no-restricted-syntax": "error", 167 | "no-return-assign": "error", 168 | "no-return-await": "error", 169 | "no-script-url": "error", 170 | "no-self-compare": "error", 171 | "no-sequences": "error", 172 | "no-shadow": "off", 173 | "no-shadow-restricted-names": "error", 174 | "no-spaced-func": "error", 175 | "no-sync": "off", 176 | "no-tabs": "error", 177 | "no-template-curly-in-string": "error", 178 | "no-ternary": "off", 179 | "no-throw-literal": "off", 180 | "no-trailing-spaces": "error", 181 | "no-undef-init": "error", 182 | "no-undefined": "off", 183 | "no-underscore-dangle": "error", 184 | "no-unmodified-loop-condition": "error", 185 | "no-unneeded-ternary": "error", 186 | "no-unused-expressions": "error", 187 | "no-use-before-define": "off", 188 | "no-useless-call": "error", 189 | "no-useless-computed-key": "error", 190 | "no-useless-concat": "error", 191 | "no-useless-constructor": "error", 192 | "no-useless-rename": "error", 193 | "no-useless-return": "error", 194 | "no-var": "off", 195 | "no-void": "error", 196 | "no-warning-comments": "off", 197 | "no-whitespace-before-property": "error", 198 | "no-with": "error", 199 | "object-curly-newline": "off", 200 | "object-curly-spacing": [ 201 | "error", 202 | "never" 203 | ], 204 | "object-property-newline": "error", 205 | "object-shorthand": "off", 206 | "one-var": "off", 207 | "one-var-declaration-per-line": "error", 208 | "operator-assignment": "error", 209 | "operator-linebreak": "error", 210 | "padded-blocks": "off", 211 | "padding-line-between-statements": "error", 212 | "prefer-arrow-callback": "off", 213 | "prefer-const": "off", 214 | "prefer-destructuring": "off", 215 | "prefer-numeric-literals": "error", 216 | "prefer-promise-reject-errors": "error", 217 | "prefer-reflect": "off", 218 | "prefer-rest-params": "off", 219 | "prefer-spread": "error", 220 | "prefer-template": "off", 221 | "quote-props": "off", 222 | "quotes": "off", 223 | "radix": "error", 224 | "require-await": "error", 225 | "require-jsdoc": "off", 226 | "rest-spread-spacing": "error", 227 | "semi": "error", 228 | "semi-spacing": "error", 229 | "semi-style": [ 230 | "error", 231 | "last" 232 | ], 233 | "sort-imports": "off", 234 | "sort-keys": "off", 235 | "sort-vars": "off", 236 | "space-before-blocks": "off", 237 | "space-before-function-paren": "off", 238 | "space-in-parens": "off", 239 | "space-infix-ops": "off", 240 | "space-unary-ops": "error", 241 | "spaced-comment": [ 242 | "error", 243 | "never" 244 | ], 245 | "strict": "error", 246 | "switch-colon-spacing": "error", 247 | "symbol-description": "error", 248 | "template-curly-spacing": [ 249 | "error", 250 | "always" 251 | ], 252 | "template-tag-spacing": "error", 253 | "unicode-bom": [ 254 | "error", 255 | "never" 256 | ], 257 | "valid-jsdoc": "error", 258 | "vars-on-top": "error", 259 | "wrap-iife": "error", 260 | "wrap-regex": "error", 261 | "yield-star-spacing": "error", 262 | "yoda": [ 263 | "error", 264 | "never" 265 | ] 266 | } 267 | }; 268 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config.js 3 | logs 4 | src/dist 5 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 0.4.0 CLI runner, config file validation 2 | 3 | 0.3.1 - Fixed build 4 | 0.3.0 - 0.3.0-beta3 released after stability tests 5 | 0.3.0-beta3 - Fixed build 6 | 0.3.0-beta2 - Chromedriver Xvfb maximize workaround (driver bug) 7 | 0.3.0-beta1 - Green tests results for 0.3.X series 8 | 0.3.0-test10 - Test release 10 for 0.3.0. 9 | 0.3.0-test9 - Test release 9 for 0.3.0. selectFileInputValue fixed. 10 | 0.3.0-test8 - Test release 8 for 0.3.0. 11 | 0.3.0-test7 - Test release 7 for 0.3.0. 12 | 0.3.0-test6 - Test release 6 for 0.3.0. 13 | 0.3.0-test5 - Test release 5 for 0.3.0. Browser exit fixed 14 | 0.3.0-test4 - Test release 4 for 0.3.0. Fixes 15 | 0.3.0-test3 - Test release 3 for 0.3.0. Fixes after eslint. Deprecated loadPageByRoute and validateUrlByRoute removed - pageUrlDate file isn't needed anymore. 16 | 0.3.0-test2 - Test release 2 for 0.3.0 17 | 0.3.0-test1 - Test release 1 for 0.3.0 18 | 19 | 0.2.8 - Fixed build 20 | 0.2.7 - Chromedriver Xvfb maximize workaround (driver bug) 21 | 0.2.6 - Babel added. cucumber.js config change required (directory change) 22 | 0.2.5 - Readme updated. loadPageByRoute and validateUrlByRoute methods marked as deprecated. 23 | 0.2.4 - NPM version 24 | 25 | 0.1.3: getAngularInputValue and validateAngularInputValue implemented, changes after CR and ESLint. 26 | 0.1.2: Bugfixes 27 | 0.1.1: Polling rate implemented, bugfixes 28 | 0.1.0: First public release - most methods implemented. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 XSolve 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | **XSolve Web Testing Framework** is test framework above Selenium Webdriver and Cucumber written in JS (Node). 4 | NPM: https://www.npmjs.com/package/xsolve_wtf 5 | 6 | Main goals: 7 | 8 | - Easy to use, high-level methods 9 | - All needed waits built-in 10 | - BDD Layer (Gherkin) 11 | - Reports useful from both business and developers point of view (Gherkin scenarios, Screenshots, Driver logs, Proxy logs) 12 | - Headless execution support 13 | - Parallel execution (not in first release) 14 | - Multiple driver configs and devices support (not in first release) 15 | 16 | # Table of Contents 17 | 18 | - [About](#about) 19 | - [Table of Contents](#table-of-contents) 20 | - [Requirements](#requirements) 21 | - [Project Setup](#project-setup) 22 | - [Installation](#installation) 23 | - [Running](#running) 24 | - [Runner](#runner) 25 | - [Required changes](#required-changes) 26 | - [Methods](#methods) 27 | - [Config](#config) 28 | - [TODO](#todo) 29 | 30 | # Requirements 31 | 32 | - Linux (currently, support for other systems may be added later) 33 | - Node.js (v6.X or newer) 34 | - NPM 3.X or later (older versions can't be used because of different `node_modules` directory structure) 35 | - Selenium Server (recommended 3.X) 36 | - BrowserMob Proxy (v2.1.X or newer) 37 | 38 | Optional: 39 | 40 | - Xvfb (for headless execution) 41 | 42 | # Project setup 43 | 44 | There are currently few requirements according to the directory and files structure. 45 | 46 | ## config.json 47 | 48 | `config.json` file must be created in the main project directory. Example `config.json` file can be found in Config section of this doc. 49 | 50 | ## cucumber runner - config (optional) 51 | 52 | If you want to run tests without using `xsolve_wtf.js` runner using `cucumber.js` command `cucumber.js` require parameters must be passed to cucumber: 53 | 54 | ```javascript 55 | --require node_modules/xsolve_wtf/dist/ --require features/ 56 | ``` 57 | 58 | It is also possible to create `cucumber.js` file in main project directory - adding parameters directly won't be needed then. 59 | 60 | ```javascript 61 | module.exports = { 62 | "default" : "--require node_modules/xsolve_wtf/dist/ --require features/" 63 | }; 64 | ``` 65 | 66 | # Installation 67 | Described for Ubuntu 16.04 LTS. 68 | 69 | ## Node.js and NPM 70 | Install Node.js and NPM from NodeSource. 71 | `curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh && sudo bash nodesource_setup.sh && 72 | sudo apt-get install build-essential nodejs` 73 | 74 | ## Java 75 | Oracle Java may be needed because of the BrowserMob Proxy (OpenJDK may not work correctly). 76 | You can install it and set as default version using: 77 | `sudo apt-get install python-software-properties && sudo add-apt-repository ppa:webupd8team/java && sudo apt-get update && sudo apt-get install oracle-java8-installer`. 78 | 79 | Verify Java version using `java -version` and change if needed using `sudo update-alternatives --config java` 80 | 81 | ## Selenium Server 82 | Download Selenium Server Standalone using wget (here version 3.4.0): 83 | `wget https://selenium-release.storage.googleapis.com/3.4/selenium-server-standalone-3.4.0.jar` 84 | 85 | or manually from Selenium website: `http://docs.seleniumhq.org/download/` 86 | 87 | ## Selenium Drivers 88 | Download Driver you want to use (Chromedriver recommended, other driver weren't tested yet). 89 | 90 | ### Chromedriver 91 | Download latest version from `https://sites.google.com/a/chromium.org/chromedriver/`. 92 | You can use wget for this: 93 | `wget http://chromedriver.storage.googleapis.com/2.30/chromedriver_linux64.zip` (here version 2.30, x86-64) 94 | 95 | Unzip (if needed install Unzip using `sudo apt-get install unzip`): 96 | `unzip chromedriver_linux64.zip` 97 | 98 | Copy chromedriver binary to `/user/bin` or `/usr/local/bin`: 99 | `sudo cp chromedriver /usr/bin` 100 | 101 | Check if Chromedriver is visible: `chromedriver --version`. 102 | 103 | Not only Chromedriver is needed - Chrome browser also has to be installed. Verify chrome installation with `google-chrome --version`. 104 | 105 | ## BrowserMob Proxy 106 | Download BrowserMob Proxy using: `wget https://github.com/lightbody/browsermob-proxy/releases/download/browsermob-proxy-2.1.4/browsermob-proxy-2.1.4-bin.zip` 107 | or manually from project page: `http://bmp.lightbody.net/`. 108 | 109 | Unzip: `unzip browsermob-proxy-2.1.4-bin.zip` 110 | 111 | ## Xvfb (optional) 112 | If you want to run tests on headless server you would also need Xvfb. 113 | `sudo apt-get install xvfb xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic x11-apps imagemagick` 114 | 115 | ## XSolve Web Testing Framework 116 | Assuming you have `xsolve_wtf` dependancy in your `package.json` file just run `npm install` - framework and all needed dependencies will be installed (cucumber etc.). 117 | 118 | 119 | # Running 120 | 121 | ## Start Selenium Server 122 | Selenium Server must be running during test execution: 123 | If you want to run Selenium Server on default port (4444): 124 | `java -jar selenium-server-standalone-3.*.jar` 125 | 126 | You can also set custom port running with `-port` parameter: 127 | `java -jar selenium-server-standalone-3.*.jar -port 4444` 128 | 129 | ### Xvfb 130 | If you want to run headlessly you must create virtual display and set it as default BEFORE running Selenium Server. 131 | You can create display using: 132 | `/usr/bin/Xvfb :99 -ac -screen 0 1920x1080x24 &` (here display number 99 with resolution 1920x1080) 133 | Then you have to set display you want to use (99 in this case): 134 | `export DISPLAY=:99` 135 | 136 | If you want to return to "normal" display you have to set value to `:0`: 137 | `export DISPLAY=:0` and rerun Selenium Webdriver if needed. 138 | 139 | ## Start BrowserMob Proxy 140 | BrowserMob Proxy also must be running during test execution. You can run proxy by running: 141 | `bin/browsermob-proxy -port 8888` from inside BrowserMob Proxy directory. 142 | 143 | ## Run tests 144 | Tests can be run using: 145 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js` 146 | 147 | More detailed information about Runner is available in [Runner](#runner) section. 148 | 149 | # Runner 150 | Runner is now available (0.4.X and newer versions) - it should be used instead of directly using cucumber runner. 151 | 152 | ## Cucumber parameters 153 | You can pass "normal" cucumber parameters - specify .feature files etc. using `--cucumber` or `-c` parameter. 154 | 155 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js -c "feature/example.feature"` 156 | 157 | If you want to use quotes (`"`) inside you have to escape them. 158 | It's possible to insert `--tags` cucumber parameter here too but it's better to use [Tags](#tags) - it's not needed to escape all quotes then. 159 | 160 | ## Tags 161 | Tags can be used using `--tags` or `-t` parameter. 162 | 163 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js --tags @disabled` 164 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js --tags "@test and not @disabled"` 165 | 166 | Make sure to use Cucumber Tag Expressions, not old-style Cucumber tags - there are not available anymore since Cucumber 2.X. 167 | https://docs.cucumber.io/tag-expressions/ 168 | 169 | ## Runner - help 170 | Help can be displayed using `--help` or `-h` parameter. 171 | 172 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js --help` 173 | 174 | ## Runner - framework version 175 | Framework version can be displayed using `--version` or `-v` parameter. 176 | 177 | `node_modules/xsolve_wtf/bin/xsolve_wtf.js --version` 178 | 179 | 180 | ## Cucumber runner 181 | It's still (0.4.X version) possible to run tests using directly cucumber runner but it's not recommended. 182 | `node_modules/cucumber/bin/cucumber.js` 183 | 184 | # Required changes 185 | 186 | ## 0.4.X version 187 | Config file structure is the same as in 0.3.X version but config file is now validated. You have to make sure it's correct according to the rules. All required parameters must be available and also all custom values must be placed in `user` or `custom`. 188 | 189 | ## 0.3.X version 190 | 191 | Config file was changed in comparison to 0.2.x version - `config.json` must be used now instead of `config.js`. Example config can be found in Config section. 192 | 193 | Deprecated loadPageByRoute and validateUrlByRoute methods were removed - pageUrlDate file isn't required anymore. It should be used on user side now - methods can be just copied from xsolve_wtf.js file. 194 | 195 | # Methods 196 | 197 | ## World 198 | 199 | World contains all basic methods needed for development of functional tests of websites. Driver is available too so it is possible to use all low-level Webdriver methods. All methods that need if contain build-in waits (timeouts configurable in config file), custom timeout can be set too. 200 | 201 | ### Actions 202 | 203 | #### click(xpath, customTimeout) 204 | Clicks element identified by xpath. 205 | 206 | `xpath` string with element xpath 207 | 208 | `customTimeout` (optional) - would be used instead of default config timeout. 209 | 210 | #### getCurrentUrl() 211 | Returns current Url 212 | 213 | #### hover(xpath, customTimeout) 214 | Hovers mouse cursor over element. 215 | 216 | `xpath` string with element xpath 217 | 218 | `customTimeout` (optional) - would be used instead of default config timeout. 219 | 220 | #### loadPage(url, customTimeout) 221 | Loads page by URL. 222 | 223 | `url` page URL 224 | `customTimeout` (optional) - would be used instead of default config timeout. 225 | 226 | #### fillInInput(xpath, value, blur, customTimeout) 227 | Fills in input with value. 228 | 229 | `xpath` string with element xpath 230 | 231 | `value` string with text value 232 | 233 | `blur` (optional) - blur support 234 | 235 | `customTimeout` (optional) - would be used instead of default config timeout. 236 | 237 | #### findElement(xpath, customTimeout) 238 | Just like Webdriver findElement with wait. 239 | 240 | `xpath` string with element xpath 241 | 242 | `customTimeout` (optional) - would be used instead of default config timeout. 243 | 244 | #### findElements(xpath, customTimeout) 245 | Just like Webdriver findElements with wait. 246 | 247 | `xpath` string with element xpath 248 | 249 | `customTimeout` (optional) - would be used instead of default config timeout. 250 | 251 | #### selectFileInputValue(inputXP, fileName, customTimeout) 252 | Sets file input value. 253 | 254 | `inputXP` string with file input element xpath. 255 | 256 | `fileName` fileName that would be set 257 | 258 | `customTimeout` (optional) - would be used instead of default config timeout. 259 | 260 | #### setCheckboxValue(xpath, value, customTimeout) 261 | Sets checkbox value. 262 | 263 | `xpath` string with element xpath 264 | 265 | `value` checkbox value 266 | 267 | `customTimeout` (optional) - would be used instead of default config timeout. 268 | 269 | ### Validators 270 | 271 | #### getCheckboxValue(xpath, customTimeout) 272 | Returns checkbox value. 273 | 274 | `xpath` string with element xpath 275 | 276 | `customTimeout` (optional) - would be used instead of default config timeout. 277 | 278 | #### getElementsNumber(xpath, customTimeout) 279 | Returns number of elements. 280 | 281 | `xpath` string with element xpath 282 | 283 | `customTimeout` (optional) - would be used instead of default config timeout. 284 | 285 | #### getElementText(xpath, customTimeout) 286 | Returns element text. 287 | 288 | `xpath` string with element xpath 289 | 290 | `customTimeout` (optional) - would be used instead of default config timeout. 291 | 292 | #### validateCheckboxValue(xpath, value, customTimeout) 293 | Validates if checkbox is set to expected value. 294 | 295 | `xpath` string with element xpath 296 | 297 | `value` expected value 298 | 299 | `customTimeout` (optional) - would be used instead of default config timeout. 300 | 301 | #### validateElementDisplayed(xpath, customTimeout) 302 | Validates if element is displayed (not only visible in page source - visible in sources AND displayed). 303 | May be browser-specific because of the driver differences. 304 | 305 | `xpath` string with element xpath 306 | 307 | `customTimeout` (optional) - would be used instead of default config timeout. 308 | 309 | #### validateElementNotDisplayed(xpath, customTimeout) 310 | Validates if element is not displayed (visible in sources and not displayed). 311 | May be browser-specific because of the driver differences. 312 | 313 | `xpath` string with element xpath 314 | 315 | `customTimeout` (optional) - would be used instead of default config timeout. 316 | 317 | #### validateElementText(xpath, text, customTimeout) 318 | Validates if text of element is equal to expected. 319 | 320 | `xpath` string with element xpath 321 | 322 | `text` expected text 323 | 324 | `customTimeout` (optional) - would be used instead of default config timeout. 325 | 326 | #### validateElementVisible(xpath, customTimeout) 327 | Checks if element is visible in page source. May not be displayed. 328 | 329 | `xpath` string with element xpath 330 | 331 | `customTimeout` (optional) - would be used instead of default config timeout. 332 | 333 | #### validateElementNotVisible(xpath, customTimeout) 334 | Checks if element is not visible in page source. 335 | 336 | `xpath` string with element xpath 337 | 338 | `customTimeout` (optional) - would be used instead of default config timeout. 339 | 340 | #### validateElementsNumber(xpath, number, customTimeout) 341 | Validates if number of elements is correct. 342 | 343 | `xpath` string with element xpath 344 | 345 | `number` expected number of elements 346 | 347 | `customTimeout` (optional) - would be used instead of default config timeout. 348 | 349 | #### validatePageReadyState() 350 | Validates if the page load is complete. `extendedPageReadyStateValidation` may be enabled in config file for extended validation (currently only Angular support implemented). 351 | 352 | #### validateUrl(url, customTimeout) 353 | Validates url by url text. 354 | 355 | `url` url string 356 | `customTimeout` (optional) - would be used instead of default config timeout. 357 | 358 | #### validateUrlByRegex(regex, customTimeout) 359 | Validates url by regex. 360 | 361 | `regex` regex 362 | `customTimeout` (optional) - would be used instead of default config timeout. 363 | 364 | ### Other 365 | #### getDriver() 366 | Returns current driver. May be used for direct driver access when low-level driver methods are needed. 367 | 368 | #### getInstanceNumber() (NOT YET IMPLEMENTED) 369 | Returns number of instance in case of parallel execution. 370 | 371 | #### logError(errorMessage, noThrow) 372 | Error logger. 373 | `errorMessage` message 374 | `noThrow` (optional) - can be set if error message should only be displayed and not thrown. 375 | 376 | #### logMessage(logMessage, detailedOnlyLog) 377 | Message logger - writes custom messages for steps. 378 | `logMessage` message 379 | `detailedOnlyLog` (optional) - can be set if message should be only shown if config `detailedTestLog` is enabled. 380 | 381 | #### sleep(sleepTime) 382 | Static sleep. 383 | 384 | `sleepTime` sleep time (ms) 385 | 386 | #### cleanBrowserState 387 | Cleans browser state - Cookies, localStorage, sessionStorage and console logs. 388 | 389 | #### takeScreenshot(fileName, directory) 390 | Takes screenshot. 391 | 392 | `fileName` screenshot file name (without extension - would be saved as .png). 393 | 394 | `directory` directory where screenshot would be saved. 395 | 396 | #### getCurrentDate() 397 | Returns current date in `YYYY-MM-DD_HH-MM-SS-mmm` format (for example `2017-06-26_13-53-51-122`) 398 | 399 | ### Angular-specific (NOT YET IMPLEMENTED) 400 | Angular specific methods. 401 | 402 | #### getAngularInputValue(xpath, customTimeout) 403 | Returns angular input value. 404 | 405 | `xpath` string with element xpath 406 | 407 | `customTimeout` (optional) - would be used instead of default config timeout. 408 | 409 | #### validateDynamicAngularInputValue(xpath, expectedValue, customTimeout) 410 | Validates if angular input value is correct. 411 | 412 | `xpath` string with element xpath 413 | 414 | `expectedValue` expected input value 415 | 416 | `customTimeout` (optional) - would be used instead of default config timeout. 417 | 418 | # Config 419 | 420 | Configuration options. `config.json` file must be created in main project directory. Framework settings are required. Also custom project-related settings may be added (`user` or `custom`) - more details in [Project specific settings](#project-specific-settings). 421 | 422 | Example config: 423 | 424 | ```javascript 425 | { 426 | "baseUrl": "http://localhost:82/", 427 | "seleniumServerHost": "localhost", 428 | "seleniumServerPort": 4444, 429 | "proxyHost": "localhost", 430 | "proxyPort": 8888, 431 | "proxyHttpPort": 8082, 432 | "platform": "CHROME", 433 | "runMaximized": true, 434 | "xvfbMode": false, 435 | "xvfbSettings": { 436 | "windowWidth": 1920, 437 | "windowHeight": 1080 438 | }, 439 | "defaultTimeout": 30000, 440 | "defaultStepTimeout": 45000, 441 | "seleniumDriverLogLevel": "SEVERE", 442 | "seleniumBrowserLogLevel": "ALL", 443 | "proxyCaptureHeaders": true, 444 | "proxyCaptureContent": false, 445 | "detailedTestLog": false, 446 | "enableScreenshotReports": false, 447 | "extendedPageReadyStateValidation": true, 448 | "pollingRate": 100 449 | } 450 | ``` 451 | 452 | ## Project related 453 | 454 | ### baseUrl 455 | 456 | Url of main page. Value not required - depends on Pages implementation. 457 | 458 | ## Selenium 459 | 460 | ### seleniumServerHost 461 | 462 | Host of Selenium Server. Remote server may be used. 463 | 464 | ### seleniumServerPort 465 | 466 | Port of Selenium Server. 467 | 468 | ## Proxy 469 | 470 | ### proxyHost 471 | 472 | Host of BrowserMob Proxy. 473 | 474 | ### proxyPort 475 | 476 | Port of BrowserMob Proxy. 477 | 478 | ### proxyHttpPort 479 | 480 | Proxy HTTP port. 481 | 482 | ## Browser 483 | 484 | ### platform 485 | 486 | Browser/Driver the tests would be run on. 487 | Chrome browser is recommended. Other browsers weren't tested. 488 | 489 | Possible values: 490 | 491 | - CHROME 492 | - FIREFOX 493 | - IE 494 | - EDGE 495 | - OPERA 496 | - SAFARI 497 | - PHANTOMJS 498 | - HTMLUNITWITHJS 499 | - ANDROID 500 | - IPHONE 501 | - IPAD 502 | 503 | ### runMaximized 504 | 505 | Set if browser should be maximized. In case of Chrome running inside Xvfb display `xvfbMode` may be required to correctly maximize. 506 | 507 | ### xvfbMode 508 | 509 | Sets static Browser window dimensions based on xvfbSettings values. 510 | 511 | ### xvfbSettings 512 | 513 | Defines Xvfb Browser window dimensions. 514 | 515 | `windowWidth` - width of browser window (px) 516 | `windowHeight` - height of browser window (px) 517 | 518 | ## Timeouts 519 | 520 | ### defaultTimeout 521 | 522 | Default timeout of specific action (for example click, validateElementVisible etc.) in ms. 523 | 524 | ### defaultStepTimeout 525 | 526 | Default timeout of Cucumber step in ms. 527 | 528 | ## Logs 529 | 530 | ### seleniumDriverLogLevel 531 | 532 | Sets log level for Selenium Driver. 533 | 534 | Possible values: 535 | 536 | - `OFF` 537 | - `SEVERE` 538 | - `WARNING` 539 | - `INFO` 540 | - `DEBUG` 541 | - `ALL` 542 | 543 | ### seleniumBrowserLogLevel 544 | 545 | Sets log level for Browser. 546 | 547 | Possible values: 548 | 549 | - `OFF` 550 | - `SEVERE` 551 | - `WARNING` 552 | - `INFO` 553 | - `DEBUG` 554 | - `ALL` 555 | 556 | ### proxyCaptureHeaders 557 | Controlls if request headers are logged by proxy. 558 | 559 | ### proxyCaptureContent 560 | Controlls if request content (body) is logged by proxy. 561 | 562 | ### detailedTestLog 563 | 564 | If enabled detailed logs are displayed. May be used for debugging. 565 | 566 | ### enableScreenshotReports 567 | 568 | Allows to enable/disable Screenshot reports functionality. If set to `true` screenshots will be made for every action. May be useful for for example for test reports. 569 | 570 | ## Other 571 | 572 | ### extendedPageReadyStateValidation 573 | 574 | By default actions are made after DOM is loaded (document.readystate == 'complete'). This option allows to enable additional validation for Angular apps - checking if requests are finished. 575 | 576 | ### pollingRate 577 | 578 | Polling rate isn't supported by Selenium JS bindings - on our test environment about 60-80 requests per second to Selenium Server were made. It isn't speeding up tests execution and it's causing additional CPU usage. This option allows to limit it to some reasonable value. 579 | 580 | ## Project specific settings 581 | ```javascript 582 | "user": { 583 | "test1": true, 584 | "test2": "aaa" 585 | }, 586 | "custom": { 587 | "test3": false, 588 | "test4": "bbb" 589 | } 590 | ``` 591 | 592 | Project-specific settings can also be added to config.json file. `user` or `custom` keywords may be used and may contain any needed values. 593 | 594 | # TODO 595 | Planned features: 596 | 597 | - Examples 598 | - Require wrapper 599 | - Runner 600 | - Proxy disable/enable config 601 | - Framework specific methods (for example for Angular) 602 | - Multiple devices support, Profiles 603 | - Parallel execution 604 | - Actions support 605 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "XSolve", 3 | "name": "xsolve_wtf-dev-tools", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/xsolve-pl/testing-framework" 8 | }, 9 | "devDependencies": { 10 | "babel-cli": "6.26.0", 11 | "babel-preset-es2015": "6.24.1", 12 | "eslint": "^4.16.0", 13 | "eslint-config-google": "^0.9.1" 14 | }, 15 | "scripts": { 16 | "babel": "babel src/src -d src/dist", 17 | "eslint": "eslint src/src/ bin/" 18 | }, 19 | "engines": { 20 | "node": ">=6.11.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 XSolve 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | **XSolve Web Testing Framework** is test framework above Selenium Webdriver and Cucumber written in JS (Node). 2 | 3 | Main goals: 4 | 5 | - Easy to use, high-level methods 6 | - All needed waits built-in 7 | - BDD Layer (Gherkin) 8 | - Reports useful from both business and developers point of view (Gherkin scenarios, Screenshots, Driver logs, Proxy logs) 9 | - Headless execution support 10 | - Parallel execution (not in first release) 11 | - Multiple driver configs and devices support (not in first release) 12 | 13 | Github page: https://github.com/xsolve-pl/testing-framework 14 | -------------------------------------------------------------------------------- /src/bin/xsolve_wtf.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const commandLineArgs = require('command-line-args'); 4 | const clu = require('command-line-usage'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | const Joi = require('joi'); 8 | const exec = require('child_process').exec; 9 | 10 | const version = require('../package.json').version; 11 | const cucumberPath = 'node_modules/cucumber/bin/cucumber.js' 12 | const cucumberRequireDirectories = '--require node_modules/xsolve_wtf/dist/ --require features/' 13 | const CHILD_MAX_BUFFER_SIZE = 10 * 1024 * 1024; 14 | 15 | const help = [ 16 | { 17 | header: 'XSolve Web Testing Framework (xsolve_wtf)', 18 | content: 'Help.' 19 | }, 20 | { 21 | header: 'Options', 22 | optionList: [ 23 | { 24 | name: 'help, -h', 25 | description: 'Displays this help.' 26 | }, 27 | { 28 | name: 'tags, -t', 29 | typeLabel: '[underline]{"tags"}', 30 | description: 'Tags' 31 | }, 32 | { 33 | name: 'cucumber, -c', 34 | typeLabel: '[underline]{"parameters"}', 35 | description: 'Cucumber parameters. If you need to use double quotes (") inside you have to escape them using backslash.' 36 | }, 37 | { 38 | name: 'version, -v', 39 | desctiption: 'Shows framework version.' 40 | } 41 | ] 42 | } 43 | ] 44 | 45 | const configSchema = Joi.object().keys({ 46 | baseUrl: Joi.string().required(), 47 | seleniumServerHost: Joi.string().required(), 48 | seleniumServerPort: Joi.number().integer().required(), 49 | proxyHost: Joi.string().required(), 50 | proxyPort: Joi.number().integer().required(), 51 | proxyHttpPort: Joi.number().integer().required(), 52 | platform: Joi.string().required(), 53 | runMaximized: Joi.boolean().required(), 54 | xvfbMode: Joi.boolean().required(), 55 | xvfbSettings: Joi.object().required(), 56 | defaultTimeout: Joi.number().integer().required(), 57 | defaultStepTimeout: Joi.number().integer().required(), 58 | seleniumDriverLogLevel: Joi.string().required(), 59 | seleniumBrowserLogLevel: Joi.string().required(), 60 | proxyCaptureHeaders: Joi.boolean().required(), 61 | proxyCaptureContent: Joi.boolean().required(), 62 | detailedTestLog: Joi.boolean().required(), 63 | enableScreenshotReports: Joi.boolean().required(), 64 | extendedPageReadyStateValidation: Joi.boolean().required(), 65 | pollingRate: Joi.number().integer().required(), 66 | user: Joi.object().optional(), 67 | custom: Joi.object().optional() 68 | }); 69 | 70 | const clu_help = clu(help); 71 | 72 | const cliOptionDefinitions = [ 73 | { name: 'help', alias: 'h', type: Boolean }, 74 | { name: 'tags', alias: 't', type: String, multiple: true }, 75 | { name: 'cucumber', alias: 'c', type: String, multiple: true }, 76 | { name: 'version', alias: 'v', type: Boolean } 77 | ] 78 | const cliOptions = commandLineArgs(cliOptionDefinitions); 79 | 80 | if(cliOptions.help) { 81 | console.log(clu_help); 82 | process.exit(0); 83 | } 84 | 85 | if(cliOptions.version) { 86 | console.log(`xsolve_wtf: "${ version }"\n`); 87 | process.exit(0); 88 | } 89 | 90 | console.log(`XSolve Web Testing Framework (xs_wtf), version ${ version }\n`); 91 | validateConfig(); 92 | 93 | let cucumberTags = cliOptions.tags ? `--tags "${ cliOptions.tags }"` : ''; 94 | var cucumberOptions = cliOptions.cucumber ? cliOptions.cucumber : 'features/'; 95 | var execValue = `${ cucumberPath } ${ cucumberTags } ${ cucumberOptions } ${ cucumberRequireDirectories }`; 96 | 97 | console.log(`Running Cucumber, command: "${ execValue }"`); 98 | 99 | var child = exec(execValue, { maxBuffer: CHILD_MAX_BUFFER_SIZE }); 100 | child.stdout.on('data', function(data) { 101 | console.log(data); 102 | }); 103 | child.stderr.on('data', function(data) { 104 | console.log('ERROR: ' + data); 105 | }); 106 | child.on('close', function(code) { 107 | console.log(`Cucumber finished with exit code "${ code }"`); 108 | process.exit(code); 109 | }); 110 | 111 | 112 | function validateConfig() { 113 | const configPath = path.join(__dirname, '../../..') + '/config.json';//TODO: temporary 114 | 115 | if(!fs.existsSync(configPath)) { 116 | console.log('Config validation failed - missing config.json file. \n') 117 | process.exit(1); 118 | } 119 | 120 | var config = JSON.parse(fs.readFileSync(configPath, 'utf8')); 121 | 122 | Joi.validate(config, configSchema).catch((error) => { 123 | console.log('Config validation failed. \n') 124 | console.log(error.details); 125 | process.exit(1); 126 | }); 127 | } 128 | -------------------------------------------------------------------------------- /src/dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boldare/testing-framework/7d41144632559dd563644ba1bfe89cfe1bd4efdc/src/dist/.gitkeep -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "XSolve", 3 | "name": "xsolve_wtf", 4 | "description": "XSolve Web Testing Framework", 5 | "version": "0.4.0", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/xsolve-pl/testing-framework" 10 | }, 11 | "main": "./dist/xsolve_wtf.js", 12 | "keywords": [ 13 | "XSolve", 14 | "Web Testing Framework", 15 | "WTF" 16 | ], 17 | "bin": { 18 | "xsolve_wtf": "./bin/xsolve_wtf.js" 19 | }, 20 | "dependencies": { 21 | "browsermob-proxy": "^1.0.9", 22 | "cucumber": "2.3.1", 23 | "sanitize-filename": "^1.6.1", 24 | "selenium-webdriver": "3.6.0", 25 | "sprintf-js": "^1.1.1", 26 | "superagent": "^3.8.2", 27 | "stringify": "^5.2.0", 28 | "command-line-args": "^5.0.1", 29 | "command-line-usage": "^4.1.0", 30 | "joi": "^13.1.1" 31 | }, 32 | "engines": { 33 | "node": ">=6.11.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/src/action.js: -------------------------------------------------------------------------------- 1 | import * as world from './xsolve_wtf'; 2 | import {By} from 'selenium-webdriver'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | const config = world.getConfig(); 6 | let driver; 7 | 8 | export default class Action { 9 | constructor(d) { 10 | this.driver = d; 11 | driver = d; 12 | } 13 | 14 | getAngularInputValue(xpath, customTimeout = config.defaultTimeout) { 15 | return world.findElement(xpath, customTimeout).then(function() { 16 | var script = `return document.evaluate('${ xpath }', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value`; 17 | 18 | return driver.executeScript(script, '') 19 | .then((result) => result); 20 | }); 21 | } 22 | 23 | validateAngularInputValue(xpath, expectedValue, customTimeout = config.defaultTimeout) { 24 | return driver.wait( 25 | function () { 26 | return world.getAngularInputValue(xpath, customTimeout).then(function(currentValue) { 27 | return currentValue === expectedValue; 28 | }); 29 | }, 30 | customTimeout 31 | ).catch(function(err){ 32 | throw(`validateAngularInputValue failed on element: "${ xpath }". Error message: "${ err.message }", error stack: "${ err.stack }`); 33 | }); 34 | } 35 | 36 | cleanBrowserState() { 37 | return driver.executeScript('return window.location.hostname.length > 0', '').then(function(result) {//data URLs 38 | if(result) { 39 | driver.executeScript('localStorage.clear()'); 40 | driver.executeScript('sessionStorage.clear()'); 41 | driver.executeScript('console.clear()'); 42 | } else { 43 | world.logError('Can\'t clean localStorage and sessionStorage'); 44 | } 45 | 46 | return driver.manage().deleteAllCookies(); 47 | }); 48 | } 49 | 50 | takeScreenshot(fileName, directory) { 51 | let screenshotFilePath = path.join(directory, `${ fileName }.png`); 52 | 53 | return driver.takeScreenshot().then(function(data){ 54 | let base64Data = data.replace(/^data:image\/png;base64,/,""); 55 | 56 | return fs.writeFile(screenshotFilePath, base64Data, 'base64', function(err) { 57 | if(err) { 58 | world.logError(`takeScreenshot eror: ${ err }`); 59 | } 60 | }); 61 | }); 62 | } 63 | 64 | selectFileInputValue(inputXP, fileName, customTimeout = config.defaultTimeout) { 65 | return world.findElement(inputXP, customTimeout) 66 | .then(function(el) { 67 | let filePath = `${ world.getProjectDir() }/data/test_files/${ fileName }`; 68 | world.logMessage(`Selecting ${ filePath } file.`); 69 | 70 | return el.sendKeys(filePath); 71 | }); 72 | } 73 | 74 | sleep(sleepTime) { 75 | return new Promise((resolve) => setTimeout(resolve, sleepTime)); 76 | } 77 | 78 | setCheckboxValue(xpath, value, customTimeout = config.defaultTimeout) { 79 | return world.getCheckboxValue(xpath, customTimeout).then(function(isChecked) { 80 | if(isChecked === value) { 81 | return true; 82 | } 83 | 84 | return world.click(xpath, customTimeout).then(() => true); 85 | }); 86 | } 87 | 88 | getElementText(xpath, customTimeout = config.defaultTimeout) { 89 | return world.findElement(xpath, customTimeout) 90 | .then((el) => el.getText()); 91 | } 92 | 93 | 94 | jsBasedClick(xpath) { 95 | //TODO2: timeout 96 | return world.findElement(xpath, 0) 97 | .then(function() { 98 | return driver.executeScript( 99 | `document.evaluate('${ xpath }', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click();` 100 | ).then(() => false); 101 | }); 102 | } 103 | 104 | click(xpath, customTimeout) { 105 | return world.validatePageReadyState() 106 | .then(function() { 107 | return world.findElement(xpath, customTimeout) 108 | .then(function(el) { 109 | el.click().catch(function(err) { 110 | world.logMessage(`Standard click failed with error message: "${ err.message }"`, true); 111 | return world.jsBasedClick(xpath); 112 | }); 113 | }); 114 | }); 115 | } 116 | 117 | hover(xpath, customTimeout) { 118 | return world.validatePageReadyState() 119 | .then(function() { 120 | return world.findElement(xpath, customTimeout) 121 | .then((el) => driver.actions().mouseMove(el).perform()); 122 | }); 123 | } 124 | 125 | fillInInput(xpath, value, blur, customTimeout) { 126 | let element; 127 | 128 | return world.findElement(xpath, customTimeout) 129 | .then(function(el) { 130 | element = el; 131 | 132 | return element.clear(); 133 | }) 134 | .then(() => element.sendKeys(typeof blur !== 'undefined' && blur ? value + '\t': value)); 135 | } 136 | 137 | getCheckboxValue(xpath, customTimeout) { 138 | return world.findElement(xpath, customTimeout) 139 | .then((el) => el.isSelected()); 140 | } 141 | 142 | findElement(xpath, customTimeout) { 143 | return world.waitForElement(xpath, customTimeout) 144 | .then(() => driver.findElement(By.xpath(xpath))); 145 | } 146 | 147 | findElements(xpath, customTimeout) { 148 | return world.waitForElement(xpath, customTimeout) 149 | .then(() => driver.findElements(By.xpath(xpath))); 150 | } 151 | 152 | getElementsNumber(xpath, customTimeout) { 153 | return driver.findElements(By.xpath(xpath), customTimeout) 154 | .then((el) => el.length); 155 | } 156 | 157 | 158 | waitForElement(xpath, customTimeout = config.defaultTimeout) {//internal only 159 | return driver.wait( 160 | () => driver.findElements(By.xpath(xpath)).then(function(el) { 161 | if(el.length > 0) { 162 | return true; 163 | } 164 | 165 | return world.sleep(config.pollingRate) 166 | .then(() => false); 167 | }), 168 | customTimeout 169 | ).catch(function(err){ 170 | throw(`waitForElement failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 171 | }); 172 | } 173 | 174 | getCurrentUrl() { 175 | return driver.getCurrentUrl(); 176 | } 177 | 178 | getDocumentReadyState() {//internal only 179 | return driver.executeScript( 180 | 'return document.readyState === \'complete\'', 181 | '' 182 | ).then((result) => result); 183 | } 184 | 185 | loadPage(page) { 186 | return driver.get(page); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/src/constants.js: -------------------------------------------------------------------------------- 1 | export const PLATFORM = { 2 | CHROME: 'CHROME', 3 | FIREFOX: 'FIREFOX', 4 | IE: 'IE', 5 | EDGE: 'EDGE', 6 | OPERA: 'OPERA', 7 | SAFARI: 'SAFARI', 8 | PHANTOMJS: 'PHANTOMJS', 9 | HTMLUNITWITHJS: 'HTMLUNITWITHJS', 10 | ANDROID: 'ANDROID', 11 | IPHONE: 'IPHONE', 12 | IPAD: 'IPAD' 13 | }; 14 | -------------------------------------------------------------------------------- /src/src/helpers.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | export const projectDir = path.join(__dirname, '../../..'); 4 | -------------------------------------------------------------------------------- /src/src/hooks.js: -------------------------------------------------------------------------------- 1 | import * as world from './xsolve_wtf'; 2 | const config = world.getConfig(); 3 | import {defineSupportCode} from 'cucumber'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | import superagent from 'superagent'; 7 | 8 | let driver; 9 | 10 | const proxyPortUrl = `http://${ config.proxyHost }:${ config.proxyPort }/proxy`; 11 | const proxyHarUrl = `http://${ config.proxyHost }:${ config.proxyPort }/proxy/${ config.proxyHttpPort }/har` + 12 | `?captureContent=${ config.proxyCaptureContent }&captureHeaders=${ config.proxyCaptureHeaders }`; 13 | const logsDirName = world.getCurrentDate(); 14 | 15 | const logsDir = `logs/execution_logs/${ logsDirName }`; 16 | const screenshotReportsDir = `logs/screenshot_reports/${ logsDirName }`; 17 | 18 | function isProxyHttpPortOpen() { 19 | return superagent 20 | .get(proxyPortUrl) 21 | .then(function(res) {//TODO: error support (browsermob not running etc.) 22 | if(res.status !== 200) { 23 | world.logError(`Proxy response error: "${ res.status }"`); 24 | return false; 25 | } 26 | 27 | let portsObj = JSON.parse(res.text); 28 | 29 | if( portsObj !== undefined && 30 | portsObj.proxyList !== undefined && 31 | portsObj.proxyList[0] !== undefined && 32 | portsObj.proxyList[0].port == config.proxyHttpPort 33 | ) { 34 | return true; 35 | } 36 | 37 | return false; 38 | }); 39 | } 40 | 41 | function openProxyHttpPort(proxyHttpPort) { 42 | return isProxyHttpPortOpen().then(function(isOpen) { 43 | if(!isOpen) { 44 | world.logMessage(`Opening proxy HTTP port: "${ config.proxyHttpPort }"`, true); 45 | 46 | return superagent 47 | .post(proxyPortUrl) 48 | .send('port=' + proxyHttpPort) 49 | .then(function(res) { 50 | if(res.status !== 200) { 51 | world.logError(`Proxy response error: "${ res.status }"`); 52 | } 53 | 54 | return true; 55 | }); 56 | } 57 | }); 58 | } 59 | 60 | function startHar() { 61 | return superagent 62 | .put(proxyHarUrl) 63 | .then(function(res) { 64 | if(res.status !== 200 && res.status !== 204) { 65 | world.logError(`Proxy start HAR error: "${ res.status }"`); 66 | } 67 | 68 | return true; 69 | }); 70 | } 71 | 72 | function saveHar(fileName, directory) { 73 | let harFilePath = path.join(directory, `${ fileName }.har`); 74 | 75 | return superagent 76 | .get(proxyHarUrl) 77 | .pipe(fs.createWriteStream(harFilePath)); 78 | } 79 | 80 | defineSupportCode(function({After, Before}) { 81 | let logFileName; 82 | 83 | createLogDirs(logsDir, screenshotReportsDir); 84 | 85 | Before(function(scenario, callback) { 86 | this.driver.then(function(d) { 87 | driver = d; 88 | 89 | openProxyHttpPort(config.proxyHttpPort).then(function() { 90 | startHar(); 91 | }) 92 | .catch(function (err) { 93 | world.logError(`Proxy response error: "${ err }"`); 94 | }); 95 | 96 | let featureName = scenario.scenario.feature.name; 97 | let scenarioName = scenario.scenario.name; 98 | logFileName = `${ world.getCurrentDate() }__${ featureName }-${ scenarioName }`; 99 | 100 | callback(); 101 | }); 102 | }); 103 | 104 | After(function(scenario) { 105 | if(scenario.isFailed()) { 106 | world.takeScreenshot(logFileName, logsDir); 107 | 108 | driver.manage().logs().get('driver').then(function(logs){ 109 | world.logMessage(`Driver logs: "${ JSON.stringify(logs) }"`); 110 | }); 111 | 112 | driver.manage().logs().get('browser').then(function(logs){ 113 | world.logMessage(`Browser logs: "${ JSON.stringify(logs) }"`); 114 | }); 115 | } 116 | 117 | saveHar(logFileName, logsDir); 118 | return driver.quit(); 119 | //return world.cleanBrowserState(); 120 | }); 121 | }); 122 | 123 | 124 | defineSupportCode(function({registerHandler}) { 125 | registerHandler('AfterStep', function(afterStepData) { 126 | if(!config.enableScreenshotReports) 127 | return; 128 | 129 | if(afterStepData.constructor.name !== 'Step') 130 | return; 131 | 132 | //screenshot reports 133 | let featureName = afterStepData.scenario.feature.name; 134 | let scenarioName = afterStepData.scenario.name; 135 | let stepName = afterStepData.name; 136 | let screenshotReportFileName = `${ world.getCurrentDate() }__${ featureName }-${ scenarioName }-${ stepName }`; 137 | world.takeScreenshot(screenshotReportFileName, screenshotReportsDir); 138 | }); 139 | }); 140 | 141 | function createLogDirs(logsDir, screenshotReportsDir) { 142 | if(!fs.existsSync(logsDir)) { 143 | fs.mkdirSync(logsDir); 144 | } 145 | 146 | if(config.enableScreenshotReports) { 147 | if(!fs.existsSync(screenshotReportsDir)) { 148 | fs.mkdirSync(screenshotReportsDir); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/src/logger.js: -------------------------------------------------------------------------------- 1 | import * as world from './xsolve_wtf'; 2 | const config = world.getConfig(); 3 | 4 | export default class Logger { 5 | logMessage(logMessage, detailedOnlyLog = false) { 6 | let displayDetailedLog = config.detailedTestLog !== undefined ? config.detailedTestLog : false; 7 | 8 | if(displayDetailedLog && detailedOnlyLog) { 9 | console.log(`LOG-info: ${ logMessage }`); 10 | } else if(!displayDetailedLog) { 11 | console.log(`LOG: ${ logMessage }`); 12 | } 13 | } 14 | 15 | logError(errorMessage, noThrow = false) { 16 | let message = `ERROR: ${ errorMessage }`; 17 | 18 | if(noThrow) { 19 | throw(message); 20 | } else { 21 | console.log(message); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/src/timeouts.js: -------------------------------------------------------------------------------- 1 | import {defineSupportCode} from 'cucumber'; 2 | import * as world from './xsolve_wtf'; 3 | const config = world.getConfig(); 4 | 5 | defineSupportCode(function({setDefaultTimeout}) { 6 | setDefaultTimeout(config.defaultStepTimeout); 7 | }); 8 | -------------------------------------------------------------------------------- /src/src/validator.js: -------------------------------------------------------------------------------- 1 | import * as world from './xsolve_wtf'; 2 | import {By} from 'selenium-webdriver'; 3 | const config = world.getConfig(); 4 | let driver; 5 | 6 | export default class Validator { 7 | constructor(d) { 8 | this.driver = d; 9 | driver = d; 10 | } 11 | 12 | validateExtendedPageState(customTimeout = config.defaultTimeout) { 13 | return driver.wait(function() { 14 | return world.checkExtendedPageState() 15 | .then(function(value) { 16 | if(value) { 17 | return true; 18 | } 19 | 20 | return world.sleep(config.pollingRate) 21 | .then(() => false); 22 | }); 23 | }, 24 | customTimeout 25 | ); 26 | } 27 | 28 | getDocumentReadyState() {//internal only 29 | return driver.executeScript( 30 | 'return document.readyState === \'complete\'', 31 | '' 32 | ).then((result) => result); 33 | } 34 | 35 | validatePageReadyState(customTimeout = config.defaultTimeout) { 36 | return driver.wait(function() { 37 | return world.getDocumentReadyState() 38 | .then(function(value) { 39 | if(value) { 40 | return true; 41 | } 42 | 43 | return world.sleep(config.pollingRate) 44 | .then(() => false); 45 | }); 46 | }, 47 | customTimeout 48 | ).then(function() { 49 | return world.validateExtendedPageState(customTimeout); 50 | }); 51 | } 52 | 53 | validateUrl(url, customTimeout = config.defaultTimeout) { 54 | return driver.wait(function() { 55 | return driver.getCurrentUrl().then(function(currentUrl) { 56 | world.logMessage(`validateUrl currentUrl ${ currentUrl }, expectedUrl ${ url }`, true); 57 | if(currentUrl.indexOf(url) !== -1) { 58 | return true; 59 | } 60 | 61 | return world.sleep(config.pollingRate).then(function() { 62 | return false; 63 | }); 64 | }); 65 | }, 66 | customTimeout 67 | ); 68 | } 69 | 70 | validateUrlByRegex(regex, customTimeout = config.defaultTimeout) { 71 | return driver.wait(function() { 72 | return driver.getCurrentUrl().then(function(currentUrl) { 73 | let r = new RegExp(regex); 74 | if(r.test(currentUrl)) { 75 | return true; 76 | } 77 | 78 | return world.sleep(config.pollingRate).then(() => false); 79 | }); 80 | }, 81 | customTimeout 82 | ); 83 | } 84 | 85 | validateElementText(xpath, text, customTimeout = config.defaultTimeout) { 86 | return driver.wait( 87 | function () { 88 | return world.getElementText(xpath, customTimeout).then(function(currentText) { 89 | return currentText === text; 90 | }); 91 | }, 92 | customTimeout 93 | ).catch(function(err){ 94 | throw(`validateElementText failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 95 | }); 96 | } 97 | 98 | validateCheckboxValue(xpath, value, customTimeout = config.defaultTimeout) { 99 | return driver.wait( 100 | function () { 101 | return world.getCheckboxValue(xpath, customTimeout) 102 | .then((currentValue) => currentValue === value); 103 | }, 104 | customTimeout 105 | ).catch(function(err){ 106 | throw(`validateCheckboxValue failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 107 | }); 108 | } 109 | 110 | validateElementVisible(xpath, customTimeout = config.defaultTimeout) {//element visible in sources and may be displayed or not 111 | return driver.wait( 112 | function () { 113 | return world.findElements(xpath).then(function(elem) { 114 | if(elem.length !== 0) { 115 | return true; 116 | } 117 | 118 | return world.sleep(config.pollingRate) 119 | .then(() => false); 120 | }); 121 | }, 122 | customTimeout 123 | ).catch(function(err){ 124 | throw(`validateElementVisible failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 125 | }); 126 | } 127 | 128 | validateElementNotVisible(xpath, customTimeout = config.defaultTimeout) {//not visible in sources and not displayed 129 | return world.validatePageReadyState().then(function() { 130 | return driver.wait( 131 | function () { 132 | return driver.findElements(By.xpath(xpath)).then(function(elem) { 133 | if(elem.length === 0) { 134 | return true; 135 | } 136 | 137 | return world.sleep(config.pollingRate) 138 | .then(() => false); 139 | }); 140 | }, 141 | customTimeout 142 | ).catch(function(err){ 143 | throw(`validateElementNotVisible failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 144 | }); 145 | }); 146 | } 147 | 148 | validateElementDisplayed(xpath, customTimeout = config.defaultTimeout) {//visible in sources AND displayed 149 | return driver.wait( 150 | function () { 151 | return world.findElements(xpath, customTimeout).then(function(elem) { 152 | if(elem[0].isDisplayed()) { 153 | return true; 154 | } 155 | 156 | return world.sleep(config.pollingRate) 157 | .then(() => false); 158 | }); 159 | }, 160 | customTimeout 161 | ).catch(function(err){ 162 | throw(`validateElementDisplayed failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 163 | }); 164 | } 165 | 166 | validateElementNotDisplayed(xpath, customTimeout = config.defaultTimeout) {//element visible in sources and not displayed 167 | return driver.wait( 168 | function () { 169 | return world.findElements(xpath, customTimeout).then(function(elem) { 170 | if(!elem[0].isDisplayed()) { 171 | return true; 172 | } 173 | 174 | return world.sleep(config.pollingRate) 175 | .then(() => false); 176 | }); 177 | }, 178 | customTimeout 179 | ).catch(function(err){ 180 | throw(`validateElementNotDisplayed failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 181 | }); 182 | } 183 | 184 | validateElementsNumber(xpath, number, customTimeout = config.defaultTimeout) { 185 | if(number === 0) { 186 | return world.validatePageReadyState() 187 | .then(() => world.validateElementNotVisible(xpath, customTimeout)); 188 | } else { 189 | return driver.wait( 190 | function () { 191 | return world.findElements(xpath, customTimeout).then(function(elem) { 192 | if(elem.length === number) { 193 | return true; 194 | } 195 | 196 | return world.sleep(config.pollingRate) 197 | .then(() => false); 198 | }); 199 | }, 200 | customTimeout 201 | ).catch(function(err){ 202 | throw(`validateElementsNumber failed on element: "${ xpath }" - error message: "${ err.message }", error stack: "${ err.stack }`); 203 | }); 204 | } 205 | } 206 | 207 | checkAngularPresence() { 208 | let script = 'return (window.angular !== undefined)'; 209 | 210 | return driver.executeScript(script, '') 211 | .then((result) => result); 212 | } 213 | 214 | checkExtendedPageState() { 215 | if(!config.extendedPageReadyStateValidation) { 216 | return world.boolPromiseResult(true); 217 | } 218 | 219 | return world.checkAngularPresence().then(function(present) { 220 | if(present) { 221 | //angular-based page - validation 222 | let script = 'return (angular.element(document.body).injector() !== undefined) && ' + 223 | '(angular.element(document.body).injector().get(\'$http\').pendingRequests.length === 0)'; 224 | 225 | return driver.executeScript(script, '') 226 | .then((result) => result); 227 | } 228 | 229 | return true;//currently only Angular 230 | }); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/src/xsolve_wtf.js: -------------------------------------------------------------------------------- 1 | import {projectDir} from './helpers'; 2 | import {PLATFORM} from './constants'; 3 | import {defineSupportCode} from 'cucumber'; 4 | import webdriver from 'selenium-webdriver'; 5 | import webdriverRemote from 'selenium-webdriver/remote'; 6 | import fs from 'fs'; 7 | import path from 'path'; 8 | 9 | //framework modules 10 | import Logger from './logger'; 11 | import Validator from './validator'; 12 | import Action from './action'; 13 | 14 | let driver; 15 | let config; 16 | let logger; 17 | let validator; 18 | let action; 19 | 20 | const seleniumServerUrl = `http://${ getConfig().seleniumServerHost }:${ getConfig().seleniumServerPort }/wd/hub`; 21 | 22 | export function CustomWorld({attach, parameters}) { 23 | this.attach = attach; 24 | this.parameters = parameters; 25 | 26 | this.driver = buildDriver(getConfig().platform).then(function(d) { 27 | driver = d; 28 | 29 | loadDriverOptions(d); 30 | 31 | logger = new Logger(d); 32 | validator = new Validator(d); 33 | action = new Action(d); 34 | 35 | return d; 36 | }); 37 | } 38 | 39 | defineSupportCode(function({setWorldConstructor}) { 40 | setWorldConstructor(CustomWorld); 41 | }); 42 | 43 | Function.prototype.curry = function() { 44 | let func = this; 45 | let slice = Array.prototype.slice; 46 | let appliedArgs = slice.call(arguments, 0); 47 | 48 | return function() { 49 | let leftoverArgs = slice.call(arguments, 0); 50 | return func.apply(this, appliedArgs.concat(leftoverArgs)); 51 | }; 52 | }; 53 | 54 | 55 | export function getConfig() { 56 | if (config) 57 | return config; 58 | 59 | config = JSON.parse(fs.readFileSync(path.join(__dirname, '../../..') + '/config.json', 'utf8'));//TODO: temporary solution 60 | 61 | return config; 62 | } 63 | 64 | export function getProjectDir() { 65 | return projectDir; 66 | } 67 | 68 | export function boolPromiseResult(value) { 69 | return new Promise((resolve) => { resolve(value); }); 70 | } 71 | 72 | export function getCurrentDate() { 73 | let date = new Date(); 74 | 75 | return `${ date.toJSON().slice(0,10) }_${ date.getHours() }-${ date.getMinutes() }-${ date.getSeconds() }-${ date.getMilliseconds() }`; 76 | } 77 | 78 | export function getDriver() { 79 | return driver; 80 | } 81 | 82 | function buildDriver(platform) { 83 | console.log(`Platform: ${ platform }`); 84 | let capabilities; 85 | 86 | switch(platform) { 87 | case PLATFORM.CHROME: 88 | capabilities = webdriver.Capabilities.chrome(); 89 | break; 90 | case PLATFORM.FIREFOX: 91 | capabilities = webdriver.Capabilities.firefox(); 92 | break; 93 | case PLATFORM.IE: 94 | capabilities = webdriver.Capabilities.ie(); 95 | break; 96 | case PLATFORM.EDGE: 97 | capabilities = webdriver.Capabilities.edge(); 98 | break; 99 | case PLATFORM.OPERA: 100 | capabilities = webdriver.Capabilities.opera(); 101 | break; 102 | case PLATFORM.SAFARI: 103 | capabilities = webdriver.Capabilities.safari(); 104 | break; 105 | case PLATFORM.PHANTOMJS: 106 | capabilities = webdriver.Capabilities.phantomjs(); 107 | break; 108 | case PLATFORM.HTMLUNITWITHJS: 109 | capabilities = webdriver.Capabilities.htmlunitwithjs(); 110 | break; 111 | case PLATFORM.ANDROID: 112 | capabilities = webdriver.Capabilities.android(); 113 | break; 114 | case PLATFORM.IPHONE: 115 | capabilities = webdriver.Capabilities.iphone(); 116 | break; 117 | case PLATFORM.IPAD: 118 | capabilities = webdriver.Capabilities.ipad(); 119 | break; 120 | } 121 | 122 | let logPreferences = new webdriver.logging.Preferences(); 123 | logPreferences.setLevel('driver', getConfig().seleniumDriverLogLevel); 124 | logPreferences.setLevel('browser', getConfig().seleniumBrowserLogLevel); 125 | 126 | let seleniumProxy = require('selenium-webdriver/proxy'); 127 | let proxyUrl = getConfig().proxyHost + ':' + getConfig().proxyHttpPort; 128 | 129 | return new webdriver.Builder() 130 | .usingServer(seleniumServerUrl) 131 | .withCapabilities(capabilities) 132 | .setLoggingPrefs(logPreferences) 133 | .setProxy(seleniumProxy.manual({ 134 | http: proxyUrl 135 | })) 136 | .build(); 137 | } 138 | 139 | function loadDriverOptions(driver) { 140 | if(config.runMaximized && !config.xvfbMode) { 141 | driver.manage().window().maximize(); 142 | } 143 | 144 | if(config.xvfbMode) { 145 | driver.manage().window().setSize(getConfig().xvfbSettings.windowWidth, getConfig().xvfbSettings.windowHeight); 146 | } 147 | 148 | return driver.setFileDetector(new webdriverRemote.FileDetector); 149 | } 150 | 151 | //loggers 152 | 153 | export function logMessage(...args) { 154 | return logger.logMessage(...args); 155 | } 156 | 157 | export function logError(...args) { 158 | return logger.logError(...args); 159 | } 160 | 161 | //validators 162 | export function checkAngularPresence(...args) {//internal 163 | return validator.checkAngularPresence(...args); 164 | } 165 | 166 | export function checkExtendedPageState(...args) {//internal 167 | return validator.checkExtendedPageState(...args); 168 | } 169 | 170 | export function getDocumentReadyState(...args) {//internal 171 | return validator.getDocumentReadyState(...args); 172 | } 173 | 174 | export function validateElementDisplayed(...args) { 175 | return validator.validateElementDisplayed(...args); 176 | } 177 | 178 | export function validateElementNotDisplayed(...args) { 179 | return validator.validateElementNotDisplayed(...args); 180 | } 181 | 182 | export function validateElementVisible(...args) { 183 | return validator.validateElementVisible(...args); 184 | } 185 | 186 | export function validateElementNotVisible(...args) { 187 | return validator.validateElementNotVisible(...args); 188 | } 189 | 190 | export function validateElementsNumber(...args) { 191 | return validator.validateElementsNumber(...args); 192 | } 193 | 194 | export function validateCheckboxValue(...args) { 195 | return validator.validateCheckboxValue(...args); 196 | } 197 | 198 | export function validateElementText(...args) { 199 | return validator.validateElementText(...args); 200 | } 201 | 202 | export function validatePageReadyState(...args) { 203 | return validator.validatePageReadyState(...args); 204 | } 205 | 206 | export function validateExtendedPageState(...args) { 207 | return validator.validateExtendedPageState(...args); 208 | } 209 | 210 | export function validateUrl(...args) { 211 | return validator.validateUrl(...args); 212 | } 213 | 214 | export function validateUrlByRegex(...args) { 215 | return validator.validateUrlByRegex(...args); 216 | } 217 | 218 | export function validateAngularInputValue(...args) { 219 | return validator.validateAngularInputValue(...args); 220 | } 221 | 222 | //actions 223 | 224 | export function waitForElement(...args) { 225 | return action.waitForElement(...args); 226 | } 227 | 228 | export function loadPage(...args) { 229 | return action.loadPage(...args); 230 | } 231 | 232 | export function findElement(...args) { 233 | return action.findElement(...args); 234 | } 235 | 236 | export function findElements(...args) { 237 | return action.findElements(...args); 238 | } 239 | 240 | export function click(...args) { 241 | return action.click(...args); 242 | } 243 | 244 | export function jsBasedClick(...args) { 245 | return action.jsBasedClick(...args); 246 | } 247 | 248 | export function getCheckboxValue(...args) { 249 | return action.getCheckboxValue(...args); 250 | } 251 | 252 | export function getElementText(...args) { 253 | return action.getElementText(...args); 254 | } 255 | 256 | export function setCheckboxValue(...args) { 257 | return action.setCheckboxValue(...args); 258 | } 259 | 260 | export function getCurrentUrl(...args) { 261 | return action.getCurrentUrl(...args); 262 | } 263 | 264 | export function hover(...args) { 265 | return action.hover(...args); 266 | } 267 | 268 | export function fillInInput(...args) { 269 | return action.fillInInput(...args); 270 | } 271 | 272 | export function selectFileInputValue(...args) { 273 | return action.selectFileInputValue(...args); 274 | } 275 | 276 | export function getElementsNumber(...args) { 277 | return action.getElementsNumber(...args); 278 | } 279 | 280 | export function sleep(...args) { 281 | return action.sleep(...args); 282 | } 283 | 284 | export function cleanBrowserState(...args) { 285 | return action.cleanBrowserState(...args); 286 | } 287 | 288 | export function takeScreenshot(...args) { 289 | return action.takeScreenshot(...args); 290 | } 291 | 292 | export function getAngularInputValue(...args) { 293 | return action.getAngularInputValue(...args); 294 | } 295 | --------------------------------------------------------------------------------