├── .coveragerc ├── .gitattributes ├── .github └── workflows │ ├── publish.yaml │ └── test.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── appveyor.yml ├── behave_webdriver ├── __init__.py ├── conditions.py ├── driver.py ├── fixtures.py ├── steps │ ├── __init__.py │ ├── actions.py │ ├── actions_re.py │ └── expectations.py ├── transformers.py └── utils.py ├── ci ├── bfcache.reg ├── install.bat └── runtests.bat ├── docs ├── Makefile ├── _static │ └── behave-webdriver.gif ├── api.rst ├── browsers.rst ├── conf.py ├── examples.rst ├── index.rst ├── installation.rst ├── make.bat ├── quickstart.rst ├── requirements.txt ├── roadmap.rst └── steps.rst ├── examples ├── minimal-project │ └── features │ │ ├── environment.py │ │ ├── myFeature.feature │ │ └── steps │ │ └── my_steps.py └── selenium-requests │ └── features │ ├── environment.py │ ├── requests.feature │ └── steps │ └── selenium_steps.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── demo-app ├── favicon.png ├── inc │ └── script.js ├── index.html ├── page.html └── runserver.py ├── experimental-features ├── elementPosition.feature ├── elementVisibility.feature ├── environment.py ├── githubSearch.feature.pending ├── login.feature.pending ├── pending.feature └── steps │ └── webdriver_steps.py ├── features ├── attribute.feature ├── baseUrl.feature ├── baseUrlWithParameterTransformation.feature ├── buttonPress.feature ├── checkTitle.feature ├── checkbox.feature ├── class.feature ├── click.feature ├── cookie.feature ├── drag.feature ├── elementExistence.feature ├── elementSize.feature ├── environment.py ├── focus.feature ├── form.feature ├── inputfield.feature ├── isEmpty.feature ├── isExisting.feature ├── modals.feature ├── moveTo.feature ├── multipleSelect.feature ├── sampleSnippets.feature ├── screenSize.feature ├── sctructure.feature ├── select.feature ├── steps │ └── webdriver_steps.py ├── textComparison.feature ├── title.feature ├── transformation_fixture.feature ├── urlValidation.feature ├── wait.feature ├── window.feature └── withinViewport.feature └── unittests ├── test_css_xpath_discerning.py ├── test_fixture.py ├── test_from.py └── test_parameter_trasformations.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = behave_webdriver 4 | 5 | [report] 6 | exclude_lines = 7 | raise NotImplementedError 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | tests/features/* linguist-vendored 2 | tests/experimental-features/* linguest-vendored 3 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: setup python 15 | uses: actions/setup-python@v2 16 | with: 17 | python-version: 3.9 18 | 19 | - name: build 20 | shell: bash 21 | run: | 22 | python -m pip install -r requirements.txt 23 | python -m pip install wheel 24 | python setup.py sdist bdist_wheel 25 | - name: Release PyPI 26 | shell: bash 27 | env: 28 | TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} 29 | TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} 30 | run: | 31 | pip install --upgrade twine 32 | twine upload dist/* 33 | - name: Release GitHub 34 | uses: softprops/action-gh-release@v1 35 | with: 36 | files: "dist/*" 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | jobs: 4 | test: 5 | name: test 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-latest, macos-latest, windows-latest] 11 | browser: [Chrome, Firefox, Safari, Edge] 12 | exclude: 13 | - os: macos-latest 14 | browser: Edge 15 | - os: windows-latest 16 | browser: Safari 17 | - os: ubuntu-latest 18 | browser: Safari 19 | - os: ubuntu-latest 20 | browser: Edge 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v2 25 | - uses: actions/setup-python@v2 26 | with: 27 | python-version: '3.9' 28 | 29 | - name: enable-safaridriver 30 | if: ${{ matrix.os == 'macos-latest' }} 31 | run: sudo safaridriver --enable 32 | 33 | - name: install-deps 34 | run: | 35 | python -m pip install -r ./requirements.txt 36 | python -m pip install coveralls pytest mock 37 | 38 | - name: run unittests 39 | env: 40 | BEHAVE_WEBDRIVER: ${{ matrix.browser }} 41 | BEHAVE_WEBDRIVER_HEADLESS: "1" 42 | ENV_BASE_URL: "http://127.0.0.1:8000/" 43 | run: | 44 | coverage run -m pytest tests/unittests 45 | 46 | - name: start-test-server 47 | shell: bash 48 | run: | 49 | cd tests/demo-app 50 | python ./runserver.py > /dev/null 2>&1 & 51 | cd ../.. 52 | 53 | - name: regedit 54 | if: ${{ matrix.os == 'windows-latest' }} 55 | shell: cmd 56 | run: | 57 | regedit /s .\ci\bfcache.reg 58 | 59 | - name: run-behave-tests 60 | env: 61 | BEHAVE_WEBDRIVER: ${{ matrix.browser }} 62 | BEHAVE_WEBDRIVER_HEADLESS: "1" 63 | ENV_BASE_URL: "http://127.0.0.1:8000/" 64 | run: | 65 | coverage run -a -m behave tests/features 66 | 67 | - name: coveralls 68 | if: ${{ success() }} 69 | env: 70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 71 | run: coveralls --service=github 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | env/ 8 | build/ 9 | develop-eggs/ 10 | dist/ 11 | downloads/ 12 | eggs/ 13 | .eggs/ 14 | lib/ 15 | lib64/ 16 | parts/ 17 | sdist/ 18 | var/ 19 | wheels/ 20 | *.egg-info/ 21 | .installed.cfg 22 | *.egg 23 | *.manifest 24 | *.spec 25 | pip-log.txt 26 | pip-delete-this-directory.txt 27 | htmlcov/ 28 | .tox/ 29 | .coverage 30 | .coverage.* 31 | .cache 32 | nosetests.xml 33 | coverage.xml 34 | *,cover 35 | .hypothesis/ 36 | *.mo 37 | *.pot 38 | *.log 39 | local_settings.py 40 | instance/ 41 | .webassets-cache 42 | .scrapy 43 | docs/_build/ 44 | target/ 45 | .ipynb_checkpoints 46 | .python-version 47 | celerybeat-schedule 48 | *.sage.py 49 | .env 50 | .venv 51 | venv/ 52 | ENV/ 53 | .spyderproject 54 | .ropeproject 55 | .idea/**/workspace.xml 56 | .idea/**/tasks.xml 57 | .idea/dictionaries 58 | .idea/**/dataSources/ 59 | .idea/**/dataSources.ids 60 | .idea/**/dataSources.xml 61 | .idea/**/dataSources.local.xml 62 | .idea/**/sqlDataSources.xml 63 | .idea/**/dynamic.xml 64 | .idea/**/uiDesigner.xml 65 | .idea/**/gradle.xml 66 | .idea/**/libraries 67 | .idea/**/mongoSettings.xml 68 | *.iws 69 | /out/ 70 | .idea_modules/ 71 | atlassian-ide-plugin.xml 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | - id: trailing-whitespace 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Spencer Young 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | behave-webdriver 2 | ================ 3 | 4 | behave-webdriver is a step library intended to allow users to easily write `selenium`_ webdriver tests with the 5 | behave BDD testing framework. 6 | Inspired by the `webdriverio/cucumber-boilerplate`_ project. 7 | 8 | |docs| |status| |version| |pyversions| |coverage| 9 | 10 | For more details, see the `behave-webdriver documentation`_ 11 | 12 | .. image:: https://raw.githubusercontent.com/spyoungtech/behave-webdriver/master/docs/_static/behave-webdriver.gif 13 | 14 | 15 | 16 | 17 | Installation 18 | ============ 19 | 20 | Installation is easy via pip. The install will require ``behave`` and ``selenium``.:: 21 | 22 | pip install behave-webdriver 23 | 24 | Using webdrivers 25 | ---------------- 26 | 27 | Selenium requires that you provide executables for the webdriver you want to use. Further, unless you specify the path to 28 | the binary explicitly, selenium expects that this executable is in PATH. See these 29 | `driver installation notes`_ for more details. 30 | 31 | 32 | Usage 33 | ===== 34 | 35 | Basic usage of this library with behave requires the following steps: 36 | 37 | 1. write your feature file 38 | 2. import the step implementations 39 | 3. set the ``behave_driver`` attribute on the behave ``context`` in your ``environment.py`` file. 40 | 4. run ``behave`` 41 | 42 | 43 | Writing the feature file 44 | ------------------------ 45 | 46 | .. code-block:: gherkin 47 | 48 | # my-minimal-project/features/myFeature.feature 49 | Feature: Sample Snippets test 50 | As a developer 51 | I should be able to use given text snippets 52 | 53 | Scenario: open URL 54 | Given the page url is not "http://webdriverjs.christian-bromann.com/" 55 | And I open the url "http://webdriverjs.christian-bromann.com/" 56 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" 57 | And I expect that the url is not "http://google.com" 58 | 59 | 60 | Scenario: click on link 61 | Given the title is not "two" 62 | And I open the url "http://webdriverjs.christian-bromann.com/" 63 | When I click on the link "two" 64 | Then I expect that the title is "two" 65 | 66 | 67 | Importing the step implementations 68 | ---------------------------------- 69 | 70 | In order for your feature file steps to match our step implementations, behave needs to find them in your project. 71 | This is as simple as importing our step definitions into your own step implementation file. 72 | 73 | .. code-block:: python 74 | 75 | # features/steps/webdriver_example.py 76 | from behave_webdriver.steps import * 77 | 78 | 79 | For more information about `step implementations`_, see the behave tutorial. 80 | 81 | 82 | Set behave_driver in the environment 83 | ------------------------------------ 84 | 85 | Our step implementations specifically look at the behave context for a ``behave_driver`` attribute to use to run your tests. 86 | In order for that to work, you'll have to provide this attribute in your ``environment.py`` file. 87 | 88 | .. code-block:: python 89 | 90 | # features/environment.py 91 | import behave_webdriver 92 | 93 | def before_all(context): 94 | context.behave_driver = behave_webdriver.Chrome() 95 | 96 | def after_all(context): 97 | # cleanup after tests run 98 | context.behave_driver.quit() 99 | 100 | 101 | The webdriver classes provided by behave-webdriver inherit from selenium's webdriver classes, so they will accept all 102 | same positional and keyword arguments that selenium accepts. 103 | 104 | Some webdrivers, such as Chrome, provide special classmethods like ``Chrome.headless`` which instantiates ``Chrome`` with 105 | options to run headless. This is useful, for example in headless testing environments. 106 | 107 | .. code-block:: python 108 | 109 | def before_all(context): 110 | context.behave_driver = behave_webdriver.Chrome.headless() 111 | 112 | 113 | Using a fixture 114 | ^^^^^^^^^^^^^^^ 115 | 116 | *New in 0.1.1* 117 | 118 | You may also find it convenient to use a fixture to setup your driver as well. For example, to use our fixture with Firefox 119 | 120 | .. code-block:: python 121 | 122 | from behave_webdriver.fixtures import fixture_browser 123 | def before_all(context): 124 | use_fixture(fixture_browser, context, webdriver='Firefox') 125 | 126 | This will also ensure that the browser is torn down at the corresponding `cleanup point`_. 127 | 128 | .. _cleanup point: http://behave.readthedocs.io/en/stable/fixtures.html#fixture-cleanup-points 129 | 130 | 131 | Run behave 132 | ---------- 133 | 134 | Then run the tests, just like any other behave test 135 | 136 | .. code-block:: bash 137 | 138 | behave 139 | 140 | You should then see an output as follows:: 141 | 142 | Feature: Sample Snippets test # features/myFeature.feature:2 143 | As a developer 144 | I should be able to use given text snippets 145 | Scenario: open URL # features/myFeature.feature:6 146 | Given the page url is not "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:136 0.012s 147 | And I open the url "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:10 1.414s 148 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/then.py:102 0.007s 149 | And I expect that the url is not "http://google.com" # ../../behave_webdriver/steps/then.py:102 0.007s 150 | 151 | Scenario: click on link # features/myFeature.feature:13 152 | Given the title is not "two" # ../../behave_webdriver/steps/given.py:81 0.006s 153 | And I open the url "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:10 0.224s 154 | When I click on the link "two" # ../../behave_webdriver/steps/when.py:21 0.622s 155 | Then I expect that the title is "two" # ../../behave_webdriver/steps/then.py:10 0.006s 156 | 157 | 1 feature passed, 0 failed, 0 skipped 158 | 2 scenarios passed, 0 failed, 0 skipped 159 | 8 steps passed, 0 failed, 0 skipped, 0 undefined 160 | Took 0m2.298s 161 | 162 | Advanced usage; extending behave-webdriver 163 | ========================================== 164 | 165 | behave-webdriver is designed with **you** in-mind. You are free to extend the behavior of our webdriver classes to suit your 166 | unique needs. You can subclass our webdriver classes, use a custom selenium webdriver, write your own mixin, or use 167 | a mixin somebody else provides for selenium. 168 | 169 | 170 | Example: selenium-requests 171 | -------------------------- 172 | 173 | `selenium-requests`_ is a preexisting project that adds functionality of the popular ``requests`` library to selenium. 174 | It is simple to use ``selenium-requests`` with behave-webdriver. 175 | The following, and other examples, are available in the repo ``examples`` directory and in the full documentation. 176 | 177 | .. code-block:: python 178 | 179 | # examples/selenium-requests/features/environment.py 180 | from selenium import webdriver # or any custom webdriver 181 | from behave_webdriver.driver import BehaveDriverMixin 182 | from seleniumrequests import RequestMixin # or your own mixin 183 | 184 | class BehaveRequestDriver(BehaveDriverMixin, RequestMixin, webdriver.Chrome): 185 | pass 186 | 187 | def before_all(context): 188 | context.behave_driver = BehaveRequestDriver() 189 | .. code-block:: python 190 | 191 | # examples/selenium-requests/features/steps/selenium_steps.py 192 | from behave import * 193 | from behave_webdriver.steps import * 194 | from urllib.parse import urljoin 195 | 196 | @given('I send a {method} request to the page "{page}"') 197 | def send_request_page(context, method, page): 198 | url = urljoin(context.base_url, page) 199 | context.response = context.behave_driver.request(method, url) 200 | 201 | @then('I expect the response text contains "{text}"') 202 | def check_response_text_contains(context, text): 203 | assert text in context.response.text 204 | .. code-block:: gherkin 205 | 206 | # examples/selenium-requests/features/selenium-requests.feature 207 | Feature: Using selenium-requests 208 | As a developer 209 | I should be able to extend behave-webdriver with selenium-requests 210 | 211 | Scenario: use selenium-requests with behave-webdriver 212 | # use a behave-webdriver step 213 | Given the base url is "http://127.0.0.1:8000" 214 | # use your own steps using selenium-requests features 215 | Given I send a GET request to the page "/" 216 | Then I expect the response text contains "

DEMO APP

" 217 | 218 | Assuming you're in the repository root (and have the demo app running) just run like any other project with ``behave`` 219 | 220 | Results ✨ 221 | ^^^^^^^^^^ 222 | 223 | .. code-block:: 224 | 225 | (behave-webdriver) $ behave examples/selenium-requests/features 226 | 227 | DevTools listening on ws://127.0.0.1:12646/devtools/browser/1fe75b44-1c74-49fa-8e77-36c54d50cd24 228 | Feature: Using selenium-requests # examples/selenium-requests/features/requests.feature:1 229 | As a developer 230 | I should be able to extend behave-webdriver with selenium-requests 231 | Scenario: use selenium-requests with behave-webdriver # examples/selenium-requests/features/requests.feature:6 232 | Given the base url is "http://127.0.0.1:8000" # behave_webdriver/steps/actions.py:162 233 | Given I send a GET request to the page "/" # examples/selenium-requests/features/steps/selenium_steps.py:11 234 | Then I expect the response text contains "

DEMO APP

" # examples/selenium-requests/features/steps/selenium_steps.py:17 235 | 236 | 1 feature passed, 0 failed, 0 skipped 237 | 1 scenario passed, 0 failed, 0 skipped 238 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 239 | Took 0m1.385s 240 | 241 | 242 | Getting help ⛑ 243 | -------------- 244 | 245 | If you have any unanswered questions or encounter any issues, please feel welcome to raise an issue. We recognize that 246 | testers come in all different shapes, sizes, and backgrounds. We welcome any and all questions that may arise from using 247 | this library. 248 | 249 | Contributing 250 | ------------ 251 | 252 | Contributions are very much welcomed! If you have ideas or suggestions, please raise an issue or submit a PR. 253 | 254 | List of step definitions 📝 255 | =========================== 256 | 257 | We support all the steps supported by webdriverio/cucumber-boilerplate. 258 | We also support some additional niceties and plan to add more step definitions. 259 | 260 | 261 | Given Steps 👷 262 | -------------- 263 | 264 | - ``I open the site "([^"]*)?"`` 265 | - ``I open the url "([^"]*)?"`` 266 | - ``I have a screen that is ([\d]+) by ([\d]+) pixels`` 267 | - ``I have a screen that is ([\d]+) pixels (broad|tall)`` 268 | - ``I have closed all but the first (window|tab)`` 269 | - ``I pause for (\d+)*ms`` 270 | - ``a (alertbox|confirmbox|prompt) is( not)* opened`` 271 | - ``the base url is "([^"]*)?"`` 272 | - ``the checkbox "([^"]*)?" is( not)* checked`` 273 | - ``the cookie "([^"]*)?" contains( not)* the value "([^"]*)?"`` 274 | - ``the cookie "([^"]*)?" does( not)* exist`` 275 | - ``the element "([^"]*)?" contains( not)* the same text as element "([^"]*)?"`` 276 | - ``the element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)`` 277 | - ``the element "([^"]*)?" is( not)* empty`` 278 | - ``the element "([^"]*)?" is( not)* enabled`` 279 | - ``the element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis`` 280 | - ``the element "([^"]*)?" is( not)* selected`` 281 | - ``the element "([^"]*)?" is( not)* visible`` 282 | - ``the element "([^"]*)?"( not)* contains any text`` 283 | - ``the element "([^"]*)?"( not)* contains the text "([^"]*)?"`` 284 | - ``the element "([^"]*)?"( not)* matches the text "([^"]*)?"`` 285 | - ``the page url is( not)* "([^"]*)?"`` 286 | - ``the title is( not)* "([^"]*)?"`` 287 | - ``the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"`` 288 | - ``there is (an|no) element "([^"]*)?" on the page`` 289 | 290 | 291 | 292 | When Steps ▶️ 293 | ------------- 294 | 295 | - ``I open the site "([^"]*)?"`` 296 | - ``I open the url "([^"]*)?"`` 297 | - ``I accept the (alertbox|confirmbox|prompt)`` 298 | - ``I add "{value}" to the inputfield "{element}"`` 299 | - ``I clear the inputfield "{element}"`` 300 | - ``I click on the button "{element}"`` 301 | - ``I click on the element "{element}"`` 302 | - ``I click on the link "{link_text}"`` 303 | - ``I close the last opened (tab|window)`` 304 | - ``I delete the cookie "{cookie_key}"`` 305 | - ``I dismiss the (alertbox|confirmbox|prompt)`` 306 | - ``I doubleclick on the element "{element}"`` 307 | - ``I drag element "{from_element}" to element "{to_element}"`` 308 | - ``I enter "([^"]*)?" into the (alertbox|confirmbox|prompt)`` 309 | - ``I focus the last opened (tab|window)`` 310 | - ``I move to element "{element}" with an offset of {x_offset:d},{y_offset:d}`` 311 | - ``I move to element "{element}"`` 312 | - ``I pause for {milliseconds:d}ms`` 313 | - ``I press "{key}"`` 314 | - ``I scroll to element "{element}"`` 315 | - ``I select the option with the (text|value|name) "([^"]*)?" for element "([^"]*)?"`` 316 | - ``I select the {nth} option for element "{element}"`` 317 | - ``I set "{value}" to the inputfield "{element}"`` 318 | - ``I set a cookie "{cookie_key}" with the content "{value}"`` 319 | - ``I submit the form "{element}"`` 320 | 321 | Then Steps ✔️ 322 | ------------- 323 | 324 | - ``I expect the screen is ([\d]+) by ([\d]+) pixels`` 325 | - ``I expect a new (window|tab) has( not)* been opened`` 326 | - ``I expect that a (alertbox|confirmbox|prompt) is( not)* opened`` 327 | - ``I expect that a (alertbox|confirmbox|prompt)( not)* contains the text "([^"]*)?"`` 328 | - ``I expect that checkbox "([^"]*)?" is( not)* checked`` 329 | - ``I expect that cookie "([^"]*)?"( not)* contains "([^"]*)?"`` 330 | - ``I expect that cookie "([^"]*)?"( not)* exists`` 331 | - ``I expect that element "([^"]*)?" (has|does not have) the class "([^"]*)?"`` 332 | - ``I expect that element "([^"]*)?" becomes( not)* visible`` 333 | - ``I expect that element "([^"]*)?" does( not)* exist`` 334 | - ``I expect that element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)`` 335 | - ``I expect that element "([^"]*)?" is( not)* empty`` 336 | - ``I expect that element "([^"]*)?" is( not)* enabled`` 337 | - ``I expect that element "([^"]*)?" is( not)* focused`` 338 | - ``I expect that element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis`` 339 | - ``I expect that element "([^"]*)?" is( not)* selected`` 340 | - ``I expect that element "([^"]*)?" is( not)* visible`` 341 | - ``I expect that element "([^"]*)?" is( not)* within the viewport`` 342 | - ``I expect that element "([^"]*)?"( not)* contains any text`` 343 | - ``I expect that element "([^"]*)?"( not)* contains the same text as element "([^"]*)?"`` 344 | - ``I expect that element "([^"]*)?"( not)* contains the text "([^"]*)?"`` 345 | - ``I expect that element "([^"]*)?"( not)* matches the text "([^"]*)?"`` 346 | - ``I expect that the path is( not)* "([^"]*)?"`` 347 | - ``I expect that the title is( not)* "([^"]*)?"`` 348 | - ``I expect that the url is( not)* "([^"]*)?"`` 349 | - ``I expect that the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"`` 350 | - ``I expect the url "([^"]*)?" is opened in a new (tab|window)`` 351 | - ``I expect the url to( not)* contain "([^"]*)?"`` 352 | - ``I wait on element "([^"]*)?"(?: for (\d+)ms)*(?: to( not)* (be checked|be enabled|be selected|be visible|contain a text|contain a value|exist))*`` 353 | 354 | 355 | Acknowledgements ❤️ 356 | =================== 357 | 358 | Special thanks to the authors and contributors of the `webdriverio/cucumber-boilerplate`_ project 359 | 360 | Special thanks to the authors and contributors of `behave`_ 361 | 362 | 363 | 364 | 365 | .. _selenium-requests: https://github.com/cryzed/Selenium-Requests 366 | 367 | .. _environment controls: http://behave.readthedocs.io/en/stable/tutorial.html#environmental-controls 368 | 369 | .. _fixtures: http://behave.readthedocs.io/en/stable/fixtures.html 370 | 371 | .. _step implementations: http://behave.readthedocs.io/en/stable/tutorial.html#python-step-implementations 372 | 373 | .. _driver installation notes: http://selenium-python.readthedocs.io/installation.html#drivers 374 | 375 | .. _behave-webdriver documentation: http://behave-webdriver.readthedocs.io/en/stable/ 376 | 377 | .. _selenium: https://github.com/SeleniumHQ/selenium 378 | 379 | .. _behave: https://github.com/behave/behave 380 | 381 | .. _webdriverio/cucumber-boilerplate: https://github.com/webdriverio/cucumber-boilerplate 382 | 383 | 384 | 385 | .. |docs| image:: https://readthedocs.org/projects/behave-webdriver/badge/?version=stable 386 | :target: http://behave-webdriver.readthedocs.io/en/stable/ 387 | 388 | .. |status| image:: https://travis-ci.org/spyoungtech/behave-webdriver.svg?branch=master 389 | :target: https://travis-ci.org/spyoungtech/behave-webdriver 390 | 391 | .. |version| image:: https://img.shields.io/pypi/v/behave-webdriver.svg?colorB=blue 392 | :target: https://pypi.org/project/behave-webdriver/ 393 | 394 | .. |pyversions| image:: https://img.shields.io/pypi/pyversions/behave-webdriver.svg? 395 | :target: https://pypi.org/project/behave-webdriver/ 396 | 397 | .. |coverage| image:: https://coveralls.io/repos/github/spyoungtech/behave-webdriver/badge.svg 398 | :target: https://coveralls.io/github/spyoungtech/behave-webdriver 399 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | 3 | environment: 4 | BEHAVE_WEBDRIVER: IE 5 | 6 | matrix: 7 | allow_failures: 8 | - BEHAVE_WEBDRIVER: IE 9 | 10 | services: 11 | - iis 12 | 13 | install: 14 | - regedit /s .\ci\bfcache.reg 15 | - cmd: .\ci\install.bat 16 | 17 | build: off 18 | 19 | test_script: 20 | - cmd: .\ci\runtests.bat 21 | - ps: | 22 | $wc = New-Object 'System.Net.WebClient'; 23 | Get-ChildItem .\reports | 24 | Foreach-Object { 25 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $_.FullName)) 26 | } 27 | -------------------------------------------------------------------------------- /behave_webdriver/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | 'Chrome', 3 | 'Firefox', 4 | 'Ie', 5 | 'Edge', 6 | 'Opera', 7 | 'Safari', 8 | 'BlackBerry', 9 | 'PhantomJS', 10 | 'Android', 11 | 'Remote', 12 | 'from_env', 13 | 'from_string', 14 | ] 15 | from behave_webdriver.driver import (Chrome, 16 | Firefox, 17 | Ie, 18 | Edge, 19 | Opera, 20 | Safari, 21 | BlackBerry, 22 | PhantomJS, 23 | Android, 24 | Remote) 25 | from behave_webdriver.utils import (from_env, 26 | from_string) 27 | from behave_webdriver.fixtures import (fixture_browser, 28 | before_all_factory, 29 | before_feature_factory, 30 | before_scenario_factory) 31 | from behave_webdriver.fixtures import use_fixture_tag 32 | from behave_webdriver import transformers 33 | -------------------------------------------------------------------------------- /behave_webdriver/conditions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides additional expected conditions as well as *negatable* versions of selenium's expected conditions. 3 | """ 4 | 5 | from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException 6 | from selenium.webdriver.support import expected_conditions as EC 7 | 8 | 9 | class NegationMixin(object): 10 | """ 11 | Provides the ability to test the negation of any existing expected condition (EC). 12 | Currently, there is an unsolved problem in certain ECs due to the way exceptions are caught. 13 | """ 14 | def __init__(self, *args, **kwargs): 15 | negative = kwargs.pop('negative', False) 16 | super(NegationMixin, self).__init__(*args, **kwargs) 17 | self.negative = negative 18 | 19 | def __call__(self, *args, **kwargs): 20 | try: 21 | result = super(NegationMixin, self).__call__(*args, **kwargs) 22 | except StaleElementReferenceException: 23 | return False # Stale elements are unreliable, always try to regrab the element 24 | if self.negative: 25 | return not result 26 | return result 27 | 28 | 29 | class AnyTextMixin(object): 30 | """ 31 | Provides default for text_ arguments when the EC expects it. An empty value will test true when 32 | tested against any other string. For example, with selenium's ``text_to_be_present_in_element`` that checks 33 | >>> if element_text: 34 | ... return self.text in element_text 35 | In effect, to the desired behavior of accepting just any text because ``'' in any_string`` is always ``True`` 36 | >>> if element_text: 37 | ... return True 38 | 39 | This behavior only applies if the text_ keyword argument is not provided. 40 | This may cause problems if you try to provide text_ as a positional argument, so don't do that. 41 | """ 42 | def __init__(self, *args, **kwargs): 43 | if 'text_' not in kwargs: 44 | kwargs['text_'] = '' 45 | super(AnyTextMixin, self).__init__(*args, **kwargs) 46 | 47 | 48 | class element_is_selected(NegationMixin, EC.element_located_to_be_selected): 49 | """ 50 | Like selenium's element_located_to_be_selected but with the :ref:`~behave_webdriver.conditions.NegationMixin`. 51 | """ 52 | pass 53 | 54 | 55 | class element_is_visible(NegationMixin, EC.visibility_of_element_located): 56 | """ 57 | Like selenium's visibility_of_element_located but with the :ref:`~behave_webdriver.conditions.NegationMixin`. 58 | """ 59 | pass 60 | 61 | 62 | class element_is_present(NegationMixin, EC.presence_of_element_located): 63 | """ 64 | Like selenium's presence_of_element_located but with the :ref:`~behave_webdriver.conditions.NegationMixin`. 65 | """ 66 | def __call__(self, driver): 67 | """ 68 | extends __call__ to catch NoSuchElementException errors to support negation of element existing. 69 | """ 70 | try: 71 | return super(element_is_present, self).__call__(driver) 72 | except NoSuchElementException: 73 | result = False 74 | if self.negative: 75 | return not result 76 | return result 77 | 78 | 79 | class element_is_enabled(object): 80 | """ 81 | A new EC that checks a webelement's ``is_enabled`` method. 82 | Negation is supplied manually, rather than the usual mixin. 83 | """ 84 | def __init__(self, locator, negative=False): 85 | self.locator = locator 86 | self.negative = negative 87 | 88 | def __call__(self, driver): 89 | try: 90 | element = driver.find_element(*self.locator) 91 | except StaleElementReferenceException: 92 | return False 93 | result = element.is_enabled() 94 | if self.negative: 95 | return not result 96 | return result 97 | 98 | 99 | class element_contains_text(NegationMixin, AnyTextMixin, EC.text_to_be_present_in_element): 100 | """ 101 | Like selenium's text_to_be_present_in_element but with the :ref:`~behave_webdriver.conditions.NegationMixin`. 102 | and :ref:`~behave_webdriver.conditions.AnyTextMixin`. 103 | """ 104 | def __call__(self, driver): 105 | """ 106 | Same logic as in EC.text_to_be_present_in_element except StaleElementReferenceException is not caught 107 | this, for now, is needed to distinguish if a False return value is the result of this exception. 108 | """ 109 | try: 110 | element = driver.find_element(*self.locator) 111 | result = bool(element.text) 112 | except StaleElementReferenceException: 113 | return False 114 | 115 | if self.negative: 116 | return not result 117 | return result 118 | 119 | 120 | class element_contains_value(NegationMixin, AnyTextMixin, EC.text_to_be_present_in_element_value): 121 | """ 122 | Like selenium's text_to_be_present_in_element_value but with the :ref:`~behave_webdriver.conditions.NegationMixin`. 123 | and :ref:`~behave_webdriver.conditions.AnyTextMixin`. 124 | """ 125 | pass 126 | -------------------------------------------------------------------------------- /behave_webdriver/fixtures.py: -------------------------------------------------------------------------------- 1 | """ 2 | Provides fixtures to initialize the web driver. 3 | """ 4 | 5 | from behave import fixture, use_fixture 6 | from behave_webdriver.utils import _from_string, _from_env 7 | from behave_webdriver.driver import BehaveDriverMixin 8 | from functools import partial 9 | from behave_webdriver import transformers 10 | import six 11 | _env_webdriver_name = 'env' 12 | 13 | class DriverNotSet: 14 | pass 15 | 16 | @fixture 17 | def fixture_browser(context, *args, **kwargs): 18 | """ 19 | webdriver setup fixture for behave context; sets ``context.behave_driver``. 20 | 21 | Will destroy the driver at the end of this fixture usage. 22 | 23 | :param webdriver: the webdriver to use -- can be a string (e.g. ``"Chrome"``) or a webdriver class. If omitted, will attempt to use the BEHAVE_WEBDRIVER environment variable 24 | 25 | :param default_driver: a fallback driver if webdriver keyword is not provided AND the BEHAVE_WEBDRIVER environment variable is not set. Defaults to 'Chrome.headless' 26 | 27 | :param args: arguments that will be passed as is to the webdriver. 28 | :param kwargs: keywords arguments that will be passed as is to the webdriver. 29 | 30 | Basic usage: 31 | 32 | >>> from behave import use_fixture 33 | >>> from behave_webdriver.fixtures import fixture_browser 34 | >>> def before_all(context): 35 | ... use_fixture(fixture_browser, context, webdriver='firefox') 36 | 37 | You may also provide webdriver class. Just be sure it inherits (or otherwise has method from) BehaveDriverMixin 38 | 39 | >>> from behave import use_fixture 40 | >>> from behave_webdriver.fixtures import fixture_browser 41 | >>> from behave_webdriver.driver import BehaveDriverMixin 42 | >>> from selenium.webdriver import Firefox 43 | >>> class FirefoxDriver(BehaveDriverMixin, Firefox): 44 | ... pass 45 | >>> def before_all(context): 46 | ... use_fixture(fixture_browser, context, webdriver=FirefoxDriver) 47 | 48 | positional arguments and additional keyword arguments are passed to the webdriver init: 49 | 50 | >>> from behave import use_fixture 51 | >>> from behave_webdriver.fixtures import fixture_browser 52 | >>> from behave_webdriver.driver import ChromeOptions 53 | >>> def before_all(context): 54 | ... options = ChromeOptions() 55 | ... options.add_argument('--ignore-gpu-blacklist') 56 | ... use_fixture(fixture_browser, context, webdriver='chrome', options=options) 57 | 58 | If the ``webdriver`` keyword is omitted, will attampt to get the driver from BEHAVE_WEBDRIVER or will use headless chrome as a final fallback if environment is not set and there is no ``default_driver`` specified 59 | 60 | >>> from behave import use_fixture 61 | >>> from behave_webdriver.fixtures import fixture_browser 62 | >>> def before_all(context): 63 | ... # try to use driver from BEHAVE_WEBDRIVER environment variable; use firefox as a fallback when env not set 64 | ... use_fixture(fixture_browser, context, default_driver='firefox') 65 | 66 | """ 67 | 68 | webdriver = kwargs.pop('webdriver', None) 69 | default_driver = kwargs.pop('default_driver', 'Chrome.headless') 70 | if isinstance(webdriver, six.string_types): 71 | webdriver = _from_string(webdriver) 72 | if webdriver is None: 73 | webdriver = _from_env(default_driver=default_driver) 74 | old_driver_class = context.BehaveDriver if 'BehaveDriver' in context else DriverNotSet 75 | old_driver = context.behave_driver if 'behave_driver' in context else DriverNotSet 76 | context.behave_driver = webdriver(*args, **kwargs) 77 | 78 | def cleanup_driver(ctx, old_driver, old_driver_class): 79 | try: 80 | ctx.behave_driver.quit() 81 | finally: 82 | if old_driver_class is DriverNotSet and 'BehaveDriver' in ctx: 83 | del ctx.BehaveDriver 84 | else: 85 | ctx.BehaveDriver = old_driver_class 86 | if old_driver is DriverNotSet and 'behave_driver' in ctx: 87 | del ctx.behave_driver 88 | else: 89 | ctx.behave_driver = old_driver 90 | 91 | cleanup = partial(cleanup_driver, context, old_driver, old_driver_class) 92 | context.add_cleanup(cleanup) 93 | 94 | 95 | def before_all_factory(*args, **kwargs): 96 | """ 97 | Create and return a ``before_all`` function that use the ``fixture_browser`` fixture with the corresponding arguments 98 | :param args: positional arguments of ``fixture_browser`` 99 | :param kwargs: keywords arguments of ``fixture_browser`` 100 | 101 | >>> from behave_webdriver.fixtures import before_all_factory 102 | >>> before_all = before_all_factory(webdriver='firefox') 103 | """ 104 | def before_all(context): 105 | use_fixture(fixture_browser, context, *args, **kwargs) 106 | return before_all 107 | 108 | 109 | def before_feature_factory(*args, **kwargs): 110 | """ 111 | Create and return a ``before_feature` function that use the ``fixture_browser`` fixture with the corresponding arguments 112 | :param args: positional arguments of ``fixture_browser`` 113 | :param kwargs: keywords arguments of ``fixture_browser`` 114 | 115 | >>> from behave_webdriver.fixtures import before_feature_factory 116 | >>> before_feature = before_feature_factory(webdriver='firefox') 117 | """ 118 | def before_feature(context, feature): 119 | use_fixture(fixture_browser, context, *args, **kwargs) 120 | return before_feature 121 | 122 | 123 | def before_scenario_factory(*args, **kwargs): 124 | """ 125 | Create and return a ``before_scenario`` function that use the ``fixture_browser`` fixture with the corresponding arguments 126 | :param args: positional arguments of ``fixture_browser`` 127 | :param kwargs: keywords arguments of ``fixture_browser`` 128 | 129 | >>> from behave_webdriver.fixtures import before_scenario_factory 130 | >>> before_scenario = before_scenario_factory(webdriver='firefox') 131 | """ 132 | def before_scenario(context, scenario): 133 | use_fixture(fixture_browser, context, *args, **kwargs) 134 | return before_scenario 135 | 136 | 137 | class TransformerNotSet: 138 | pass 139 | 140 | 141 | 142 | @fixture 143 | def transformation_fixture(context, transformer_class, *args, **kwargs): 144 | old_transformer = context.transformer_class if 'transformer_class' in context else TransformerNotSet 145 | transformer_class = partial(transformer_class, *args, **kwargs) 146 | context.transformer_class = transformer_class 147 | 148 | def cleanup(context, old): 149 | if old is TransformerNotSet: 150 | del context.transformer_class 151 | else: 152 | context.transformer_class = old 153 | cleanup_transformer = partial(cleanup, context, old_transformer) 154 | context.add_cleanup(cleanup_transformer) 155 | 156 | 157 | def use_fixture_tag(context, tag, *args, **kwargs): 158 | if not tag.startswith('fixture'): 159 | return 160 | if tag.startswith('fixture.webdriver'): 161 | browser_name = '.'.join(tag.split('.')[2:]) 162 | if browser_name == 'browser': 163 | browser_name = 'Chrome.headless' 164 | use_fixture(fixture_browser, context, *args, **kwargs) 165 | 166 | elif tag.startswith('fixture.transformer'): 167 | transformer_name = tag.split('.')[-1] 168 | transformer_class = getattr(transformers, transformer_name) 169 | use_fixture(transformation_fixture, context, transformer_class, **kwargs) 170 | -------------------------------------------------------------------------------- /behave_webdriver/steps/__init__.py: -------------------------------------------------------------------------------- 1 | from .actions import * 2 | from .actions_re import * 3 | from .expectations import * 4 | -------------------------------------------------------------------------------- /behave_webdriver/steps/actions.py: -------------------------------------------------------------------------------- 1 | import string 2 | from behave import * 3 | from behave_webdriver.transformers import matcher_mapping 4 | try: 5 | from urllib.parse import urljoin 6 | except ImportError: 7 | from urlparse import urljoin # Python 2 8 | 9 | 10 | if 'transform-parse' not in matcher_mapping: 11 | use_step_matcher('parse') 12 | else: 13 | use_step_matcher('transform-parse') 14 | 15 | 16 | @when('I pause for {milliseconds:d}ms') 17 | def sleep_ms(context, milliseconds): 18 | context.behave_driver.pause(milliseconds) 19 | 20 | 21 | @when('I click on the element "{element}"') 22 | def click_element(context, element): 23 | context.behave_driver.click_element(element) 24 | 25 | 26 | @when('I doubleclick on the element "{element}"') 27 | def doubleclick_element(context, element): 28 | context.behave_driver.doubleclick_element(element) 29 | 30 | 31 | @when('I click on the link "{link_text}"') 32 | def click_link(context, link_text): 33 | context.behave_driver.click_link_text(link_text) 34 | 35 | 36 | @when('I click on the button "{element}"') 37 | def click_button(context, element): 38 | context.behave_driver.click_element(element) 39 | 40 | 41 | @when('I set "{value}" to the inputfield "{element}"') 42 | def set_input(context, value, element): 43 | elem = context.behave_driver.get_element(element) 44 | elem.clear() 45 | elem.send_keys(value) 46 | 47 | 48 | @when('I add "{value}" to the inputfield "{element}"') 49 | def add_input(context, value, element): 50 | elem = context.behave_driver.get_element(element) 51 | elem.send_keys(value) 52 | 53 | 54 | @when('I clear the inputfield "{element}"') 55 | def clear_input(context, element): 56 | elem = context.behave_driver.get_element(element) 57 | elem.clear() 58 | 59 | 60 | @when('I drag element "{from_element}" to element "{to_element}"') 61 | def drag_element(context, from_element, to_element): 62 | context.behave_driver.drag_element(from_element, to_element) 63 | 64 | 65 | @when('I submit the form "{element}"') 66 | def submit_form(context, element): 67 | context.behave_driver.submit(element) 68 | 69 | 70 | @when('I set a cookie "{cookie_key}" with the content "{value}"') 71 | def set_cookie(context, cookie_key, value): 72 | context.behave_driver.add_cookie({'name': cookie_key, 'value': value}) 73 | 74 | 75 | @when('I delete the cookie "{cookie_key}"') 76 | def delete_cookie(context, cookie_key): 77 | context.behave_driver.delete_cookie(cookie_key) 78 | 79 | 80 | @when('I press "{key}"') 81 | def press_button(context, key): 82 | context.behave_driver.press_button(key) 83 | 84 | 85 | @when('I scroll to element "{element}"') 86 | def scroll_to(context, element): 87 | context.behave_driver.scroll_to_element(element) 88 | 89 | 90 | @when('I select the {nth} option for element "{element}"') 91 | def select_nth_option(context, nth, element): 92 | index = int(''.join(char for char in nth if char in string.digits)) 93 | context.behave_driver.select_option(element, 94 | by='index', 95 | by_arg=index) 96 | 97 | 98 | @when('I move to element "{element}" with an offset of {x_offset:d},{y_offset:d}') 99 | def move_to_element_offset(context, element, x_offset, y_offset): 100 | context.behave_driver.move_to_element(element, (x_offset, y_offset)) 101 | 102 | 103 | @when('I move to element "{element}"') 104 | def move_to_element(context, element): 105 | context.behave_driver.move_to_element(element) 106 | 107 | 108 | use_step_matcher('parse') 109 | -------------------------------------------------------------------------------- /behave_webdriver/steps/actions_re.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from behave_webdriver.transformers import matcher_mapping 3 | try: 4 | from urllib.parse import urljoin 5 | except ImportError: 6 | from urlparse import urljoin # Python 2 7 | 8 | 9 | if 'transform-parse' not in matcher_mapping: 10 | use_step_matcher('re') 11 | else: 12 | use_step_matcher('transform-re') 13 | 14 | 15 | @when('I close the last opened (tab|window)') 16 | def close_last_tab(context, _): 17 | context.behave_driver.switch_to_window(context.behave_driver.last_opened_handle) 18 | context.behave_driver.close() 19 | context.behave_driver.switch_to_window(context.behave_driver.primary_handle) 20 | 21 | 22 | @when('I focus the last opened (tab|window)') 23 | def focus_last_tab(context, _): 24 | context.behave_driver.switch_to_window(context.behave_driver.last_opened_handle) 25 | 26 | 27 | @when('I select the option with the (text|value|name) "([^"]*)?" for element "([^"]*)?"') 28 | def select_option_by(context, attr, attr_value, element): 29 | attr_map = {'text': 'visible_text'} 30 | attr = attr_map.get(attr, attr) 31 | context.behave_driver.select_option(select_element=element, 32 | by=attr, 33 | by_arg=attr_value) 34 | 35 | 36 | @when('I accept the (alertbox|confirmbox|prompt)') 37 | def accept_alert(context, modal_type): 38 | context.behave_driver.alert.accept() 39 | 40 | 41 | @when('I dismiss the (alertbox|confirmbox|prompt)') 42 | def dismiss_alert(context, modal_type): 43 | context.behave_driver.alert.dismiss() 44 | 45 | 46 | @when('I enter "([^"]*)?" into the (alertbox|confirmbox|prompt)') 47 | def handle_prompt(context, text, modal_type): 48 | context.behave_driver.alert.send_keys(text) 49 | 50 | 51 | @given('I have closed all but the first (window|tab)') 52 | def close_secondary_windows(context, window_or_tab): 53 | if len(context.behave_driver.window_handles) > 1: 54 | for handle in context.behave_driver.window_handles[1:]: 55 | context.behave_driver.switch_to_window(handle) 56 | context.behave_driver.close() 57 | context.behave_driver.switch_to_window(context.behave_driver.primary_handle) 58 | 59 | 60 | @step('I open the url "([^"]*)?"') 61 | def open_url(context, url): 62 | context.behave_driver.open_url(url) 63 | 64 | 65 | @step('I open the site "([^"]*)?"') 66 | def open_site(context, url): 67 | base_url = getattr(context, 'base_url', 'http://localhost:8000') 68 | destination = urljoin(base_url, url) 69 | context.behave_driver.open_url(destination) 70 | 71 | 72 | @given('the base url is "([^"]*)?"') 73 | def set_base_url(context, url): 74 | if url.endswith('/'): 75 | url = url[:-1] 76 | context.base_url = url 77 | 78 | 79 | @given('I pause for (\d+)*ms') 80 | def pause(context, milliseconds): 81 | milliseconds = int(milliseconds) 82 | context.behave_driver.pause(milliseconds) 83 | 84 | 85 | @given('I have a screen that is ([\d]+) by ([\d]+) pixels') 86 | def set_screen_size(context, x, y): 87 | context.behave_driver.screen_size = (x, y) 88 | 89 | 90 | @given('I have a screen that is ([\d]+) pixels (broad|tall)') 91 | def set_screen_dimension(context, size, how): 92 | size = int(size) 93 | if how == 'tall': 94 | context.behave_driver.screen_size = (None, size) 95 | else: 96 | context.behave_driver.screen_size = (size, None) 97 | -------------------------------------------------------------------------------- /behave_webdriver/steps/expectations.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from behave_webdriver.transformers import matcher_mapping 3 | try: 4 | from urllib.parse import urlparse 5 | except ImportError: 6 | from urlparse import urlparse 7 | 8 | if 'transform-parse' not in matcher_mapping: 9 | use_step_matcher('re') 10 | else: 11 | use_step_matcher('transform-re') 12 | 13 | 14 | @given('the element "([^"]*)?" is( not)* visible') 15 | @then('I expect that element "([^"]*)?" becomes( not)* visible') 16 | @then('I expect that element "([^"]*)?" is( not)* visible') 17 | def check_element_visibility(context, element, negative): 18 | element_is_visible = context.behave_driver.element_visible(element) 19 | if negative: 20 | assert not element_is_visible, 'Expected element to not be visible, but it was' 21 | else: 22 | assert element_is_visible, 'Expected element to be visible, but it was not visible' 23 | 24 | 25 | @given('the title is( not)* "([^"]*)?"') 26 | @then('I expect that the title is( not)* "([^"]*)?"') 27 | def title(context, negative, value): 28 | if negative: 29 | assert context.behave_driver.title != value, 'Title was "{}"'.format(context.behave_driver.title) 30 | else: 31 | assert context.behave_driver.title == value, 'Title was "{}"'.format(context.behave_driver.title) 32 | 33 | 34 | @then('I expect that element "([^"]*)?" is( not)* within the viewport') 35 | def check_element_within_viewport(context, element, negative): 36 | element_in_viewport = context.behave_driver.element_in_viewport(element) 37 | if negative: 38 | assert not element_in_viewport, 'Element was completely within the viewport' 39 | else: 40 | assert element_in_viewport, 'Element was not completely within viewport' 41 | 42 | 43 | @given('the element "([^"]*)?" is( not)* enabled') 44 | @then('I expect that element "([^"]*)?" is( not)* enabled') 45 | def element_enabled(context, element, negative): 46 | enabled = context.behave_driver.element_enabled(element) 47 | if negative: 48 | assert not enabled 49 | else: 50 | assert enabled 51 | 52 | 53 | @given('the element "([^"]*)?" is( not)* selected') 54 | @then('I expect that element "([^"]*)?" is( not)* selected') 55 | def element_selected(context, element, negative): 56 | selected = context.behave_driver.element_selected(element) 57 | if negative: 58 | assert not selected 59 | else: 60 | assert selected 61 | 62 | 63 | @given('the checkbox "([^"]*)?" is( not)* checked') 64 | @then('I expect that checkbox "([^"]*)?" is( not)* checked') 65 | def element_checked(context, element, negative): 66 | checked = context.behave_driver.element_selected(element) 67 | if negative: 68 | assert not checked 69 | else: 70 | assert checked 71 | 72 | 73 | @given('there is (an|no) element "([^"]*)?" on the page') 74 | def element_exists(context, an_no, element): 75 | negative = an_no == 'no' 76 | exists = context.behave_driver.element_exists(element) 77 | if negative: 78 | assert not exists 79 | else: 80 | assert exists 81 | 82 | 83 | @then('I expect that element "([^"]*)?" does( not)* exist') 84 | def check_element_exists(context, element, negative): 85 | exists = context.behave_driver.element_exists(element) 86 | if negative: 87 | assert not exists, 'Expected the element does not exist, but element "{}" was located'.format(element) 88 | else: 89 | assert exists, 'Expected element to exist, but no element "{}" was located'.format(element) 90 | 91 | 92 | @given('the element "([^"]*)?" contains( not)* the same text as element "([^"]*)?"') 93 | @then('I expect that element "([^"]*)?"( not)* contains the same text as element "([^"]*)?"') 94 | def elements_same_text(context, first_element, negative, second_element): 95 | first_elem_text = context.behave_driver.get_element_text(first_element) 96 | second_elem_text = context.behave_driver.get_element_text(second_element) 97 | same = first_elem_text == second_elem_text 98 | if negative: 99 | assert not same, 'Element "{}" text "{}" is same as element "{}"'.format(first_element, 100 | first_elem_text, 101 | second_element) 102 | else: 103 | assert same, 'Element "{}" text "{}" is not same as element "{}" text "{}"'.format(first_element, 104 | first_elem_text, 105 | second_element, 106 | second_elem_text) 107 | 108 | 109 | @given('the element "([^"]*)?"( not)* matches the text "([^"]*)?"') 110 | @then('I expect that element "([^"]*)?"( not)* matches the text "([^"]*)?"') 111 | def element_matches_text(context, element, negative, text): 112 | elem_text = context.behave_driver.get_element_text(element) 113 | matches = elem_text == text 114 | if negative: 115 | assert not matches, 'Element "{}" text matches "{}"'.format(element, 116 | text) 117 | else: 118 | assert matches, 'The text "{}" did not match the element text "{}"'.format(text, elem_text) 119 | 120 | 121 | @given('the element "([^"]*)?"( not)* contains the text "([^"]*)?"') 122 | @then('I expect that element "([^"]*)?"( not)* contains the text "([^"]*)?"') 123 | def check_element_contains_text(context, element, negative, text): 124 | contains = context.behave_driver.element_contains(element, text) 125 | if negative: 126 | assert not contains, 'Element text does contain "{}"'.format(text) 127 | else: 128 | assert contains, 'Element text does not contain "{}"'.format(text) 129 | 130 | 131 | @given('the element "([^"]*)?"( not)* contains any text') 132 | @then('I expect that element "([^"]*)?"( not)* contains any text') 133 | def element_any_text(context, element, negative): 134 | any_text = bool(context.behave_driver.get_element_text(element)) 135 | if negative: 136 | assert not any_text 137 | else: 138 | assert any_text 139 | 140 | 141 | @given('the element "([^"]*)?" is( not)* empty') 142 | @then('I expect that element "([^"]*)?" is( not)* empty') 143 | def check_element_empty(context, element, negative): 144 | elem_text = context.behave_driver.get_element_text(element) 145 | any_text = bool(elem_text) 146 | if negative: 147 | assert any_text is True 148 | else: 149 | assert any_text is False 150 | 151 | 152 | @given('the page url is( not)* "([^"]*)?"') 153 | @then('I expect that the url is( not)* "([^"]*)?"') 154 | def check_url(context, negative, value): 155 | current_url = context.behave_driver.current_url 156 | if negative: 157 | assert current_url != value, 'The url was "{}"'.format(current_url) 158 | else: 159 | assert current_url == value, 'Expected url to be "{}", but saw the url was "{}"'.format(value, current_url) 160 | 161 | 162 | @then('I expect the url to( not)* contain "([^"]*)?"') 163 | def check_url_contains(context, negative, value): 164 | current_url = context.behave_driver.current_url 165 | if negative: 166 | assert value not in current_url, 'url was "{}"'.format(current_url) 167 | else: 168 | assert value in current_url, 'url was "{}"'.format(current_url) 169 | 170 | 171 | @given('the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"') 172 | @then('I expect that the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"') 173 | def check_element_attribute(context, is_css, attr, element, negative, value): 174 | if is_css: 175 | attribute_value, value = context.behave_driver.get_element_attribute(element, attr, is_css, value) 176 | else: 177 | attribute_value = context.behave_driver.get_element_attribute(element, attr) 178 | 179 | if negative: 180 | assert attribute_value != value, 'Attribute value was "{}"'.format(attribute_value) 181 | else: 182 | assert attribute_value == value, 'Attribute value was "{}"'.format(attribute_value) 183 | 184 | 185 | @given('the cookie "([^"]*)?" contains( not)* the value "([^"]*)?"') 186 | @then('I expect that cookie "([^"]*)?"( not)* contains "([^"]*)?"') 187 | def check_cookie_value(context, cookie_key, negative, value): 188 | cookie = context.behave_driver.get_cookie(cookie_key) 189 | cookie_value = cookie.get('value') 190 | if negative: 191 | assert cookie_value != value, 'Cookie value was "{}"'.format(cookie_value) 192 | else: 193 | assert cookie_value == value, 'Cookie value was "{}"'.format(cookie_value) 194 | 195 | 196 | @given('the cookie "([^"]*)?" does( not)* exist') 197 | def cookie_exists(context, cookie_key, negative): 198 | cookie = context.behave_driver.get_cookie(cookie_key) 199 | if negative: 200 | assert cookie is None, 'Cookie exists: {}'.format(cookie) 201 | else: 202 | assert cookie is not None 203 | 204 | 205 | @then('I expect that cookie "([^"]*)?"( not)* exists') 206 | def check_cookie_exists(context, cookie_key, negative): 207 | cookie = context.behave_driver.get_cookie(cookie_key) 208 | if negative: 209 | assert cookie is None, u'Cookie was present: "{}"'.format(cookie) 210 | else: 211 | assert cookie is not None, 'Cookie was not found' 212 | 213 | 214 | @given('the element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)') 215 | @then('I expect that element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)') 216 | def check_element_size(context, element, negative, pixels, how): 217 | elem_size = context.behave_driver.get_element_size(element) 218 | if how == 'tall': 219 | axis = 'height' 220 | else: 221 | axis = 'width' 222 | if negative: 223 | assert elem_size[axis] != int(pixels), 'Element size was "{}"'.format(elem_size) 224 | else: 225 | assert elem_size[axis] == int(pixels), 'Element size was "{}"'.format(elem_size) 226 | 227 | 228 | @given('the element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis') 229 | @then('I expect that element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis') 230 | def check_element_position(context, element, negative, pos, axis): 231 | element_position = context.behave_driver.get_element_location(element) 232 | if negative: 233 | assert element_position[axis] != int(pos), 'Position was {} on the {} axis'.format(element_position[axis], axis) 234 | else: 235 | assert element_position[axis] == int(pos), 'Position was {} on the {} axis'.format(element_position[axis], axis) 236 | 237 | 238 | @given('a (alertbox|confirmbox|prompt) is( not)* opened') 239 | @then('I expect that a (alertbox|confirmbox|prompt) is( not)* opened') 240 | def check_modal(context, modal, negative): 241 | if negative: 242 | assert context.behave_driver.has_alert is False 243 | else: 244 | assert context.behave_driver.has_alert is True 245 | 246 | 247 | @then('I expect that the path is( not)* "([^"]*)?"') 248 | def check_path(context, negative, value): 249 | current_url = context.behave_driver.current_url 250 | path = urlparse(current_url).path 251 | if negative: 252 | assert path != value, 'The path was "{}"'.format(path) 253 | else: 254 | assert path == value, 'Expected the path to be "{}", but saw the path "{}"'.format(value, path) 255 | 256 | 257 | @then('I expect that element "([^"]*)?" (has|does not have) the class "([^"]*)?"') 258 | def check_element_has_class(context, element, has, classname): 259 | if 'not' in has: 260 | negative = True 261 | else: 262 | negative = False 263 | 264 | has_class = context.behave_driver.element_has_class(element, classname) 265 | if negative: 266 | assert not has_class, 'Classes were {}'.format(context.behave_driver.get_element_attribute(element, 'class')) 267 | else: 268 | assert has_class, 'Classes were {}'.format(context.behave_driver.get_element_attribute(element, 'class')) 269 | 270 | 271 | @then('I expect a new (window|tab) has( not)* been opened') 272 | def check_window_opened(context, _, negative): 273 | if negative: 274 | assert not context.behave_driver.secondary_handles 275 | else: 276 | assert bool(context.behave_driver.secondary_handles) 277 | 278 | 279 | @then('I expect the url "([^"]*)?" is opened in a new (tab|window)') 280 | def check_url_new_window(context, url, _): 281 | current_handle = context.behave_driver.primary_handle 282 | for handle in context.behave_driver.secondary_handles: 283 | context.behave_driver.switch_to_window(handle) 284 | if context.behave_driver.current_url == url: 285 | context.behave_driver.switch_to_window(current_handle) 286 | break 287 | else: 288 | context.behave_driver.switch_to_window(current_handle) 289 | if len(context.behave_driver.secondary_handles) < 1: 290 | raise AssertionError('No secondary handles found!') 291 | raise AssertionError("The url '{}' was not found in any handle") 292 | 293 | 294 | @then('I expect that element "([^"]*)?" is( not)* focused') 295 | def check_element_focused(context, element, negative): 296 | element_focused = context.behave_driver.element_focused(element) 297 | if negative: 298 | assert not element_focused 299 | else: 300 | assert element_focused 301 | 302 | 303 | @then('I expect that a (alertbox|confirmbox|prompt)( not)* contains the text "([^"]*)?"') 304 | def check_modal_text_contains(context, modal_type, negative, text): 305 | alert_text = context.behave_driver.alert.text 306 | if negative: 307 | assert not text in alert_text 308 | else: 309 | assert text in alert_text 310 | 311 | 312 | @then('I wait on element "([^"]*)?"(?: for (\d+)ms)*(?: to( not)* (be checked|be enabled|be selected|be visible|contain a text|contain a value|exist))*') 313 | def wait_for_element_condition(context, element, milliseconds, negative, condition): 314 | if milliseconds: 315 | digits = ''.join(char for char in milliseconds if char.isdigit()) 316 | milliseconds = int(digits) 317 | 318 | result = context.behave_driver.wait_for_element_condition(element, milliseconds, negative, condition) 319 | if not negative: 320 | negative = '' 321 | assert result, 'was expecting element "{element}" to {negative} {condition}, but the result was {result}'.format( 322 | element=element, 323 | negative=negative, 324 | condition=condition, 325 | result=result) 326 | 327 | 328 | @then("I expect the screen is ([\d]+) by ([\d]+) pixels") 329 | def check_screen_size(context, x, y): 330 | screen_x, screen_y = context.behave_driver.screen_size 331 | 332 | 333 | use_step_matcher('parse') 334 | -------------------------------------------------------------------------------- /behave_webdriver/transformers.py: -------------------------------------------------------------------------------- 1 | import os 2 | from behave.matchers import Match, ParseMatcher, RegexMatcher, MatchWithError 3 | from behave.matchers import matcher_mapping 4 | from collections import defaultdict 5 | import six 6 | from functools import partial 7 | 8 | class TransformerBase(object): 9 | """ 10 | Defines the basic functions of a Transformer 11 | As implemented, it does effectively nothing. You are meant to subclass and override the methods. 12 | 13 | Don't forget to call ``super`` when extending ``__init__`` 14 | """ 15 | def __init__(self, context=None, func=None, **kwargs): 16 | """ 17 | 18 | :param context: behave context 19 | :param func: the matched step function currently being executed 20 | :param kwargs: Not doing anything with these, but allowing us to swallow them. 21 | """ 22 | self.context = context 23 | self.func = func 24 | 25 | def transform_value(self, value): 26 | return value 27 | 28 | def transform_args(self, args): 29 | return [self.transform_value(arg) for arg in args] 30 | 31 | def transform_kwargs(self, kwargs): 32 | return {key: self.transform_value(value) for key, value in kwargs.items()} 33 | 34 | def transform(self, args, kwargs): 35 | return self.transform_args(args), self.transform_kwargs(kwargs), self.func 36 | 37 | 38 | class FormatTransformer(TransformerBase): 39 | """ 40 | Implements basic interpolation transformation startegy. 41 | Parameter value is transformed through .format method 42 | using named placeholders and values supplied as 43 | keyword arguments passed at the time of initialization. 44 | """ 45 | 46 | def __init__(self, context=None, func=None, **kwargs): 47 | """ 48 | 49 | :param context: behave context 50 | :param func: the matched step function currently being executed 51 | :param kwargs: keyword-value pairs used for formatting step strings. 52 | """ 53 | suppress_missing = kwargs.pop('suppress_missing', False) 54 | if context is not None: 55 | kwargs.update(context=context) 56 | if func is not None: 57 | kwargs.update(func=func) 58 | super(FormatTransformer, self).__init__(**kwargs) 59 | self.transformations = kwargs 60 | if suppress_missing: 61 | self.transformations = defaultdict(lambda key: '', self.transformations) 62 | 63 | def transform_value(self, value): 64 | if not isinstance(value, six.string_types): 65 | return value # non-string arguments should be returned unadulterated 66 | 67 | return value.format(**self.transformations) 68 | 69 | 70 | class EnvironmentTransformer(FormatTransformer): 71 | """ 72 | Like FormatTransformer, but additionally provides items from ``os.environ`` as keyword arguments 73 | """ 74 | def __init__(self, *args, **kwargs): 75 | kwargs.update(os.environ) 76 | super(EnvironmentTransformer, self).__init__(*args, **kwargs) 77 | 78 | 79 | class FuncTransformer(TransformerBase): 80 | """ 81 | Replaces the step function with a supplied new function! 82 | """ 83 | def __init__(self, new_func, *args, **kwargs): 84 | self.new_func = new_func 85 | super(FuncTransformer, self).__init__(*args, **kwargs) 86 | 87 | def transform(self, *transform_args, **transform_kwargs): 88 | args, kwargs, _old_func = super(FuncTransformer, self).transform(*transform_args, **transform_kwargs) 89 | return args, kwargs, self.new_func 90 | 91 | 92 | class TransformingMatch(Match): 93 | """ 94 | Tweak of the normal Match object 95 | When the ``transformer_class`` attribute, a subclass of ``behave_webdriver.transformers.TrransformerBase``, 96 | is present on the context, that class will be called with the context and decorated step function for the step 97 | currently being executed. This class has the ability to 'transform' the parsed arguments and the function itself. 98 | """ 99 | def run(self, context): 100 | args = [] 101 | kwargs = {} 102 | for arg in self.arguments: 103 | if arg.name is not None: 104 | kwargs[arg.name] = arg.value 105 | else: 106 | args.append(arg.value) 107 | 108 | with context.use_with_user_mode(): 109 | # the above is a COPY/PASTE of the original `run` implementation, 110 | transformer_class = context.transformer_class if 'transformer_class' in context else None 111 | if transformer_class and ((isinstance(transformer_class, partial) and issubclass(transformer_class.func, TransformerBase)) or issubclass(transformer_class, TransformerBase)): 112 | transformer = transformer_class(context=context, func=self.func) 113 | args, kwargs, func = transformer.transform(args, kwargs) 114 | else: 115 | func = self.func 116 | func(context, *args, **kwargs) 117 | 118 | 119 | class TransformMixin(object): 120 | """ 121 | Replaces the usual Match object with a TransformingMatch 122 | This can be mixed in with any matcher class and added to the mapping; you could even override existing matchers 123 | 124 | >>> from behave.matchers import RegexMatcher, matcher_mapping # any matcher will work 125 | >>> class TransformRegexMatcher(TransformMixin, RegexMatcher): pass 126 | >>> matcher_mapping['re'] = TransformRegexMatcher 127 | """ 128 | def match(self, step): 129 | # -- PROTECT AGAINST: Type conversion errors (with ParseMatcher). 130 | try: 131 | result = self.check_match(step) 132 | except Exception as e: # pylint: disable=broad-except 133 | return MatchWithError(self.func, e) 134 | 135 | if result is None: 136 | return None # -- NO-MATCH 137 | # the above is a COPY/PASTE of original implementation; only the following line is changed 138 | return TransformingMatch(self.func, result) 139 | 140 | 141 | # behave-webdriver uses both ParseMatcher ('parse') and RegexMatcher ('re'); so we need a transforming version of each 142 | class TransformParseMatcher(TransformMixin, ParseMatcher): 143 | pass 144 | 145 | 146 | class TransformRegexMatcher(TransformMixin, RegexMatcher): 147 | pass 148 | 149 | 150 | # add the transforming matchers to the mapping so they can be used by ``use_step_matcher``. 151 | matcher_mapping['transform-parse'] = TransformParseMatcher 152 | matcher_mapping['transform-re'] = TransformRegexMatcher 153 | -------------------------------------------------------------------------------- /behave_webdriver/utils.py: -------------------------------------------------------------------------------- 1 | from os import getenv 2 | import six 3 | from behave_webdriver.driver import (Chrome, 4 | Firefox, 5 | Ie, 6 | Edge, 7 | Opera, 8 | Safari, 9 | BlackBerry, 10 | PhantomJS, 11 | Android, 12 | Remote) 13 | 14 | 15 | def _from_string(webdriver_string): 16 | def get_driver_name(driver): 17 | return driver.__name__.upper() 18 | drivers = [Chrome, Firefox, Ie, Edge, Opera, Safari, BlackBerry, PhantomJS, Android, Remote] 19 | driver_map = {get_driver_name(d): d for d in drivers} 20 | driver_map['CHROME.HEADLESS'] = Chrome.headless 21 | Driver = driver_map.get(webdriver_string.upper(), None) 22 | if Driver is None: 23 | raise ValueError('No such driver "{}". Valid options are: {}'.format(webdriver_string, 24 | ', '.join(driver_map.keys()))) 25 | return Driver 26 | 27 | 28 | def from_string(webdriver_string, *args, **kwargs): 29 | Driver = _from_string(webdriver_string) 30 | return Driver(*args, **kwargs) 31 | 32 | 33 | def _from_env(default_driver=None): 34 | browser_env = getenv('BEHAVE_WEBDRIVER', default_driver) 35 | if browser_env is None: 36 | raise ValueError('No driver found in environment variables and no default driver selection') 37 | if isinstance(browser_env, six.string_types): 38 | Driver = _from_string(browser_env) 39 | else: 40 | # if not a string, assume we have a webdriver instance 41 | Driver = browser_env 42 | return Driver 43 | 44 | 45 | def from_env(*args, **kwargs): 46 | default_driver = kwargs.pop('default_driver', None) 47 | if default_driver is None: 48 | default_driver = 'Chrome.headless' 49 | Driver = _from_env(default_driver=default_driver) 50 | 51 | return Driver(*args, **kwargs) 52 | -------------------------------------------------------------------------------- /ci/bfcache.reg: -------------------------------------------------------------------------------- 1 | Windows Registry Editor Version 5.00 2 | 3 | [HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BFCACHE] 4 | "iexplore.exe"=dword:00000000 5 | -------------------------------------------------------------------------------- /ci/install.bat: -------------------------------------------------------------------------------- 1 | C:\Python36\python.exe -m venv venv 2 | 3 | call venv\Scripts\activate.bat 4 | 5 | pip install -r .\requirements.txt 6 | 7 | pip install pytest mock coverage 8 | 9 | call deactivate 10 | -------------------------------------------------------------------------------- /ci/runtests.bat: -------------------------------------------------------------------------------- 1 | call venv\Scripts\activate.bat 2 | 3 | powershell -Command "Import-Module WebAdministration;New-WebSite -Name demoapp -Port 8000 -PhysicalPath C:\projects\behave-webdriver\tests\demo-app" 4 | 5 | coverage run -m behave tests\features --format=progress2 --junit 6 | 7 | coverage run -a -m pytest tests\unittests --junitxml=reports\pytestresults.xml 8 | 9 | call deactivate 10 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = behave-webdriver 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/behave-webdriver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyoungtech/behave-webdriver/9e660bcae5b1b345a32142070973476f71da8d04/docs/_static/behave-webdriver.gif -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | .. _api: 2 | 3 | ============================== 4 | behave-webdriver API Reference 5 | ============================== 6 | 7 | This reference is meant for those who want to develop upon, extend, or alter the behavior of behave-webdriver. This 8 | will contain information regarding the implementation of various methods. Many aspects of the BehaveDriver class deal 9 | closely with selenium webdriver instances, but this document will refrain from duplicating information that should be 10 | contained in the selenium documentation. 11 | 12 | behave-webdriver is designed with **you** in-mind. You are free to extend the behavior of our webdriver classes to suit your 13 | unique needs. You can subclass our webdriver classes, use a custom selenium webdriver, write your own mixin, or use 14 | a mixin somebody else provides for selenium. 15 | 16 | .. warning:: 17 | 18 | While every effort is made to not make breaking changes, until a stable release, expect some things here to change, including breaking changes. 19 | 20 | 21 | 22 | The webdriver classes 23 | --------------------- 24 | 25 | behave-webdriver provides each of the same webdriver classes provided in ``selenium.webdriver``. Each class inherits from the :py:class:`~behave_webdriver.driver.BehaveDriverMixin` 26 | mixin as well as the respective ``selenium`` counterpart class. 27 | 28 | .. autoclass:: behave_webdriver.Chrome 29 | :members: 30 | 31 | .. autoclass:: behave_webdriver.Firefox 32 | :members: 33 | 34 | .. autoclass:: behave_webdriver.Ie 35 | :members: 36 | 37 | .. autoclass:: behave_webdriver.Safari 38 | :members: 39 | 40 | .. autoclass:: behave_webdriver.PhantomJS 41 | :members: 42 | 43 | .. autoclass:: behave_webdriver.Edge 44 | :members: 45 | 46 | .. autoclass:: behave_webdriver.Opera 47 | :members: 48 | 49 | .. autoclass:: behave_webdriver.BlackBerry 50 | :members: 51 | 52 | .. autoclass:: behave_webdriver.Android 53 | :members: 54 | 55 | .. autoclass:: behave_webdriver.Remote 56 | :members: 57 | 58 | 59 | The BehaveDriverMixin 60 | --------------------- 61 | 62 | The mixin class implements all of the general logic. If you want to alter how behave-webdriver behaves, this is probably the place to do it. 63 | 64 | 65 | .. autoclass:: behave_webdriver.driver.BehaveDriverMixin 66 | :members: 67 | -------------------------------------------------------------------------------- /docs/browsers.rst: -------------------------------------------------------------------------------- 1 | Browser Support 2 | =============== 3 | 4 | behave-webdriver is designed so that you *can* use any of the webdriver classes you would normally use with Selenium, 5 | e.g. ``Chrome``, ``Firefox``, ``Remote``, etc... However, not all browsers were made equal and attempting to get identical 6 | behavior across browsers is... complicated, if not impossible. 7 | 8 | This document will aim to describe the status of support with the various webdrivers supported by Selenium. Where there 9 | are known issues or quirks related to this step library, there will be an effort to document them here, too. Be sure to also checkout the 10 | github issues and projects. browser-specific issues should be tagged accordingly. 11 | 12 | More specifics may be revealed in the :doc:`api` and in the source. The ecosystem around selenium/webdrivers is huge. 13 | This is not a repository or body of knowledge for all driver-related issues; just the ones that most directly affect this library. 14 | 15 | Unless otherwise noted, we are referring to the latest stable release of Selenium and each respective browser and driver. 16 | Keep in mind, this documentation may not necessarily be up-to-date with very recent releases. 17 | 18 | 19 | Chrome (recommended) 20 | -------------------- 21 | 22 | Currently, Chrome is essentially the reference implementation. We primarily discuss issues with other webdrivers with 23 | respect to how Chrome behaves. In our experience so far, Chrome is the fastest and most well-behaving driver. 24 | 25 | We recommend Chrome and fully support the use of the Chrome webdriver with the latest versions of selenium and chrome/chromedriver. 26 | At the time of this writing (March 2018) that's selenium 3.10, Chrome 65, and chromedriver 2.36 27 | While earlier versions should work fine and we are willing to support them, they are not tested. 28 | 29 | 30 | 31 | 32 | Firefox (beta) 33 | -------------- 34 | 35 | Firefox is officially supported as of v0.1.1 36 | 37 | 38 | 39 | 40 | Known issues 41 | ^^^^^^^^^^^^ 42 | 43 | - ``submit`` on form elements is implemented by a (Selenium) JS shim and will not block for page load. Clicking the form button should block properly, however. 44 | - support for window handles is somewhat problematic 45 | - clicking elements requires they are in the viewport (we compensate for this by scrolling to an element before any click) 46 | - moving to an element *with an offset* that is bigger than the viewport is not (yet) supported 47 | - slower than Chrome 48 | 49 | Workarounds/Shims 50 | ^^^^^^^^^^^^^^^^^ 51 | 52 | Shims and other workarounds for some known issues are implemented in the Firefox class. 53 | 54 | See :doc:`api` for more details. 55 | 56 | 57 | Ie 58 | -- 59 | 60 | We have some preliminary support for Internet Explorer. It is tested in our `appveyor CI build`_. 61 | 62 | .. _appveyor CI build: https://ci.appveyor.com/project/spyoungtech/behave-webdriver 63 | 64 | 65 | Safari 66 | ------ 67 | 68 | We have some preliminary support for Safari on OSX/Mac OS. It is tested as part of our `Travis CI build`_ (failures currently allowed). 69 | 70 | .. _Travis CI build: https://travis-ci.org/spyoungtech/behave-webdriver/ 71 | 72 | 73 | 74 | PhantomJS 75 | --------- 76 | 77 | 78 | .. danger:: 79 | Selenium support for PhantomJS has been deprecated and the `PhantomJS development has been suspended`_. As such, 80 | users are recommended to NOT use PhantomJS to begin with. 81 | 82 | PhantomJS is a low priority (see above). Users should expect issues with PhantomJS when using modern versions of selenium, 83 | and 84 | 85 | 86 | Known issues 87 | ^^^^^^^^^^^^ 88 | 89 | - No support for alerts/modals 90 | - Cookies are problematic (cookies must have domain (and expiry?); setting cookies for localdomain not supported) 91 | - Memory-hungry 92 | - Unsupported (see above) 93 | 94 | 95 | .. _phantomJS development has been suspended: https://github.com/ariya/phantomjs/issues/15344 96 | 97 | 98 | Remote 99 | ------ 100 | 101 | Remote is untested at this time. 102 | 103 | 104 | 105 | Edge 106 | ---- 107 | 108 | Edge is untested at this time. 109 | 110 | Opera 111 | ----- 112 | 113 | Opera is untested at this time. 114 | 115 | 116 | BlackBerry 117 | ---------- 118 | 119 | BlackBerry is untested at this time. 120 | 121 | Android 122 | ------- 123 | 124 | Android is untested at this time. 125 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # behave-webdriver documentation build configuration file, created by 5 | # sphinx-quickstart on Tue Oct 17 09:40:19 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = ['sphinx.ext.autodoc', 35 | 'sphinx.ext.coverage', 36 | 'sphinx.ext.viewcode'] 37 | #autodoc_member_order = 'bysource' 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix(es) of source filenames. 43 | # You can specify multiple suffix as a list of string: 44 | # 45 | # source_suffix = ['.rst', '.md'] 46 | source_suffix = '.rst' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = 'behave-webdriver' 53 | copyright = '2017, Spencer Young' 54 | author = 'Spencer Young' 55 | 56 | # The version info for the project you're documenting, acts as replacement for 57 | # |version| and |release|, also used in various other places throughout the 58 | # built documents. 59 | # 60 | # The short X.Y version. 61 | version = '0.0.1' 62 | # The full version, including alpha/beta/rc tags. 63 | release = '0.0.1a' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = None 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This patterns also effect to html_static_path and html_extra_path 75 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 76 | 77 | # The name of the Pygments (syntax highlighting) style to use. 78 | pygments_style = 'sphinx' 79 | 80 | # If true, `todo` and `todoList` produce output, else they produce nothing. 81 | todo_include_todos = False 82 | 83 | 84 | # -- Options for HTML output ---------------------------------------------- 85 | 86 | # The theme to use for HTML and HTML Help pages. See the documentation for 87 | # a list of builtin themes. 88 | # 89 | html_theme = 'sphinx_rtd_theme' # 'alabaster' 90 | 91 | # Theme options are theme-specific and customize the look and feel of a theme 92 | # further. For a list of options available for each theme, see the 93 | # documentation. 94 | # 95 | # html_theme_options = {} 96 | 97 | # Add any paths that contain custom static files (such as style sheets) here, 98 | # relative to this directory. They are copied after the builtin static files, 99 | # so a file named "default.css" will overwrite the builtin "default.css". 100 | html_static_path = ['_static'] 101 | 102 | # Custom sidebar templates, must be a dictionary that maps document names 103 | # to template names. 104 | # 105 | # This is required for the alabaster theme 106 | # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars 107 | html_sidebars = { 108 | '**': [ 109 | 'about.html', 110 | 'navigation.html', 111 | 'relations.html', # needs 'show_related': True theme option to display 112 | 'searchbox.html', 113 | 'donate.html', 114 | ] 115 | } 116 | 117 | 118 | # -- Options for HTMLHelp output ------------------------------------------ 119 | 120 | # Output file base name for HTML help builder. 121 | htmlhelp_basename = 'behave-webdriverdoc' 122 | 123 | 124 | # -- Options for LaTeX output --------------------------------------------- 125 | 126 | latex_elements = { 127 | # The paper size ('letterpaper' or 'a4paper'). 128 | # 129 | # 'papersize': 'letterpaper', 130 | 131 | # The font size ('10pt', '11pt' or '12pt'). 132 | # 133 | # 'pointsize': '10pt', 134 | 135 | # Additional stuff for the LaTeX preamble. 136 | # 137 | # 'preamble': '', 138 | 139 | # Latex figure (float) alignment 140 | # 141 | # 'figure_align': 'htbp', 142 | } 143 | 144 | # Grouping the document tree into LaTeX files. List of tuples 145 | # (source start file, target name, title, 146 | # author, documentclass [howto, manual, or own class]). 147 | latex_documents = [ 148 | (master_doc, 'behave-webdriver.tex', 'behave-webdriver Documentation', 149 | 'Spencer Young', 'manual'), 150 | ] 151 | 152 | 153 | # -- Options for manual page output --------------------------------------- 154 | 155 | # One entry per manual page. List of tuples 156 | # (source start file, name, description, authors, manual section). 157 | man_pages = [ 158 | (master_doc, 'behave-webdriver', 'behave-webdriver Documentation', 159 | [author], 1) 160 | ] 161 | 162 | 163 | # -- Options for Texinfo output ------------------------------------------- 164 | 165 | # Grouping the document tree into Texinfo files. List of tuples 166 | # (source start file, target name, title, author, 167 | # dir menu entry, description, category) 168 | texinfo_documents = [ 169 | (master_doc, 'behave-webdriver', 'behave-webdriver Documentation', 170 | author, 'behave-webdriver', 'One line description of project.', 171 | 'Miscellaneous'), 172 | ] 173 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Advanced usage; extending behave-webdriver 2 | ========================================== 3 | 4 | behave-webdriver is designed with **you** in-mind. You are free to extend the behavior of our webdriver classes to suit your 5 | unique needs. You can subclass our webdriver classes, use a custom selenium webdriver, write your own mixin, or use 6 | a mixin somebody else provides for selenium. 7 | 8 | 9 | Example: selenium-requests 10 | -------------------------- 11 | 12 | `selenium-requests`_ is a preexisting project that adds functionality of the popular ``requests`` library to selenium. 13 | It is simple to use ``selenium-requests`` with behave-webdriver. 14 | The following, and other examples, are available in the repo ``examples`` directory and in the full documentation. 15 | 16 | .. code-block:: python 17 | 18 | # examples/selenium-requests/features/environment.py 19 | from selenium import webdriver # or any custom webdriver 20 | from behave_webdriver.driver import BehaveDriverMixin 21 | from seleniumrequests import RequestMixin # or your own mixin 22 | 23 | class BehaveRequestDriver(BehaveDriverMixin, RequestMixin, webdriver.Chrome): 24 | pass 25 | 26 | def before_all(context): 27 | context.behave_driver = BehaveRequestDriver() 28 | .. code-block:: python 29 | 30 | # examples/selenium-requests/features/steps/selenium_steps.py 31 | from behave import * 32 | from behave_webdriver.steps import * 33 | from urllib.parse import urljoin 34 | 35 | @given('I send a {method} request to the page "{page}"') 36 | def send_request_page(context, method, page): 37 | url = urljoin(context.base_url, page) 38 | context.response = context.behave_driver.request(method, url) 39 | 40 | @then('I expect the response text contains "{text}"') 41 | def check_response_text_contains(context, text): 42 | assert text in context.response.text 43 | .. code-block:: gherkin 44 | 45 | # examples/selenium-requests/features/selenium-requests.feature 46 | Feature: Using selenium-requests 47 | As a developer 48 | I should be able to extend behave-webdriver with selenium-requests 49 | 50 | Scenario: use selenium-requests with behave-webdriver 51 | # use a behave-webdriver step 52 | Given the base url is "http://127.0.0.1:8000" 53 | # use your own steps using selenium-requests features 54 | Given I send a GET request to the page "/" 55 | Then I expect the response text contains "

DEMO APP

" 56 | 57 | Assuming you're in the repository root (and have the demo app running) just run like any other project with ``behave`` 58 | 59 | Results ✨ 60 | ^^^^^^^^^^ 61 | 62 | .. code-block:: guess 63 | 64 | (behave-webdriver) $ behave examples/selenium-requests/features 65 | 66 | DevTools listening on ws://127.0.0.1:12646/devtools/browser/1fe75b44-1c74-49fa-8e77-36c54d50cd24 67 | Feature: Using selenium-requests # examples/selenium-requests/features/requests.feature:1 68 | As a developer 69 | I should be able to extend behave-webdriver with selenium-requests 70 | Scenario: use selenium-requests with behave-webdriver # examples/selenium-requests/features/requests.feature:6 71 | Given the base url is "http://127.0.0.1:8000" # behave_webdriver/steps/actions.py:162 72 | Given I send a GET request to the page "/" # examples/selenium-requests/features/steps/selenium_steps.py:11 73 | Then I expect the response text contains "

DEMO APP

" # examples/selenium-requests/features/steps/selenium_steps.py:17 74 | 75 | 1 feature passed, 0 failed, 0 skipped 76 | 1 scenario passed, 0 failed, 0 skipped 77 | 3 steps passed, 0 failed, 0 skipped, 0 undefined 78 | Took 0m1.385s 79 | 80 | .. _selenium-requests: https://github.com/cryzed/Selenium-Requests 81 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. behave-webdriver documentation master file, created by 2 | sphinx-quickstart on Tue Oct 17 09:40:19 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to behave-webdriver's documentation! 7 | ============================================ 8 | 9 | behave-webdriver 10 | ---------------- 11 | 12 | behave-webdriver is a step library intended to allow users to easily run browser automation tests (via `selenium`_) 13 | with the `behave`_ BDD testing framework. 14 | 15 | .. _selenium: https://github.com/SeleniumHQ/selenium 16 | .. _behave: https://github.com/behave/behave 17 | 18 | Inspired by, the webdriverio `cucumber-boilerplate`_ project. 19 | 20 | .. _cucumber-boilerplate: https://github.com/webdriverio/cucumber-boilerplate 21 | 22 | .. toctree:: 23 | :maxdepth: 2 24 | 25 | installation 26 | quickstart 27 | api 28 | steps 29 | examples 30 | browsers 31 | roadmap 32 | 33 | 34 | 35 | Indices and tables 36 | ================== 37 | 38 | * :ref:`genindex` 39 | * :ref:`modindex` 40 | * :ref:`search` 41 | 42 | 43 | 44 | Goals 45 | ----- 46 | 47 | * Make writing readable browser automation tests as Gherkin features easy. 48 | * Provide an easily extensible interface to the selenium driver (``BehaveDriver``) 49 | * To be (at least mostly) compatible with feature files written for `webdriverio/cucumber-boilerplate`_ 50 | 51 | 52 | Status 53 | ------ 54 | 55 | |version| |pyversions| |status| 56 | 57 | We currently test against Python2.7 and Python3.5+ using headless chrome. While all selenium's webdrivers are provided, 58 | they are not all supported at this time. We plan to add support for additional browsers in the future. 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | .. _raise an issue: https://github.com/spyoungtech/behave-webdriver/issues/new 67 | 68 | 69 | .. _selenium-requests: https://github.com/cryzed/Selenium-Requests 70 | 71 | .. _environment controls: http://behave.readthedocs.io/en/latest/tutorial.html#environmental-controls 72 | 73 | .. _fixtures: http://behave.readthedocs.io/en/latest/fixtures.html 74 | 75 | .. _step implementations: http://behave.readthedocs.io/en/latest/tutorial.html#python-step-implementations 76 | 77 | .. _driver installation notes: http://selenium-python.readthedocs.io/installation.html#drivers 78 | 79 | .. _behave-webdriver documentation: http://behave-webdriver.readthedocs.io/en/latest/ 80 | 81 | .. _selenium: https://github.com/SeleniumHQ/selenium 82 | 83 | .. _behave: https://github.com/behave/behave 84 | 85 | .. _webdriverio/cucumber-boilerplate: https://github.com/webdriverio/cucumber-boilerplate 86 | 87 | 88 | 89 | .. |docs| image:: https://readthedocs.org/projects/behave-webdriver/badge/?version=latest 90 | :target: http://behave-webdriver.readthedocs.io/en/latest/ 91 | 92 | .. |status| image:: https://travis-ci.org/spyoungtech/behave-webdriver.svg?branch=master 93 | :target: https://travis-ci.org/spyoungtech/behave-webdriver 94 | 95 | .. |version| image:: https://img.shields.io/pypi/v/behave-webdriver.svg?colorB=blue 96 | :target: https://pypi.org/project/behave-webdriver/ 97 | 98 | .. |pyversions| image:: https://img.shields.io/pypi/pyversions/behave-webdriver.svg? 99 | :target: https://pypi.org/project/behave-webdriver/ 100 | 101 | .. |coverage| image:: https://coveralls.io/repos/github/spyoungtech/behave-webdriver/badge.svg 102 | :target: https://coveralls.io/github/spyoungtech/behave-webdriver 103 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | behave-webdriver can be installed via ``pip``. 5 | 6 | .. code:: bash 7 | 8 | pip install behave-webdriver 9 | 10 | Using webdrivers 11 | ---------------- 12 | 13 | Selenium requires that you provide executables for the webdriver you want to use. Further, unless you specify the path to 14 | the binary explicitly, selenium expects that this executable is in PATH. See these 15 | `driver installation notes`_ for more details. 16 | 17 | .. _driver installation notes: http://selenium-python.readthedocs.io/installation.html#drivers 18 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=behave-webdriver 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | Ready to get started testing? This page will give you a quick introduction to behave-webdriver and how to use it. This 5 | assumes you have installed behave-webdriver and a webdriver on PATH. We also assume you got at least some familiarity 6 | with BDD/behave. If you're brand new to BDD in Python, you may want to check out the `behave docs`_ first. 7 | 8 | .. _behave docs: http://behave.readthedocs.io/en/latest/ 9 | 10 | Basic usage of this library with behave requires the following steps: 11 | 12 | 1. import the step implementations 13 | 2. set the ``behave_driver`` attribute on the behave ``context`` in your ``environment.py`` file. 14 | 3. write your feature file 15 | 4. run ``behave`` 16 | 17 | Importing the step implementations 18 | ---------------------------------- 19 | 20 | In order for your feature file steps to match our step implementations, behave needs to find them in your project. 21 | This is as simple as importing our step definitions into your own step implementation file. 22 | 23 | .. code-block:: python 24 | 25 | # features/steps/webdriver_example.py 26 | from behave_webdriver.steps import * 27 | 28 | 29 | For more information about `step implementations`_, see the behave tutorial. 30 | 31 | 32 | Set behave_driver in the environment 33 | ------------------------------------ 34 | 35 | Our step implementations specifically look at the behave context for a ``behave_driver`` attribute to use to run your tests. 36 | In order for that to work, you'll have to provide this attribute in your ``environment.py`` file. 37 | 38 | .. code-block:: python 39 | 40 | # features/environment.py 41 | import behave_webdriver 42 | 43 | def before_all(context): 44 | context.behave_driver = behave_webdriver.Chrome() 45 | 46 | def after_all(context): 47 | # cleanup after tests run 48 | context.behave_driver.quit() 49 | 50 | 51 | The webdriver classes provided by behave-webdriver inherit from selenium's webdriver classes, so they will accept all 52 | same positional and keyword arguments that selenium accepts. 53 | 54 | Some webdrivers, such as Chrome, provide special classmethods like ``Chrome.headless`` which instantiates ``Chrome`` with 55 | options to run headless. This is useful, for example in headless testing environments. 56 | 57 | .. code-block:: python 58 | 59 | def before_all(context): 60 | context.behave_driver = behave_webdriver.Chrome.headless() 61 | 62 | In the future, behave-webdriver will provide `fixtures`_ for the setup and teardown of webdrivers. 63 | See the behave tutorial for more information about `environment controls`_ . 64 | 65 | Writing the feature file 66 | ------------------------ 67 | 68 | .. code-block:: gherkin 69 | 70 | # my-minimal-project/features/myFeature.feature 71 | Feature: Sample Snippets test 72 | As a developer 73 | I should be able to use given text snippets 74 | 75 | Scenario: open URL 76 | Given the page url is not "http://webdriverjs.christian-bromann.com/" 77 | And I open the url "http://webdriverjs.christian-bromann.com/" 78 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" 79 | And I expect that the url is not "http://google.com" 80 | 81 | 82 | Scenario: click on link 83 | Given the title is not "two" 84 | And I open the url "http://webdriverjs.christian-bromann.com/" 85 | When I click on the link "two" 86 | Then I expect that the title is "two" 87 | 88 | Run behave 89 | ---------- 90 | 91 | Then run the tests, just like any other behave test 92 | 93 | .. code-block:: bash 94 | 95 | behave 96 | 97 | You should then see an output as follows:: 98 | 99 | Feature: Sample Snippets test # features/myFeature.feature:2 100 | As a developer 101 | I should be able to use given text snippets 102 | Scenario: open URL # features/myFeature.feature:6 103 | Given the page url is not "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:136 0.012s 104 | And I open the url "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:10 1.414s 105 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/then.py:102 0.007s 106 | And I expect that the url is not "http://google.com" # ../../behave_webdriver/steps/then.py:102 0.007s 107 | 108 | Scenario: click on link # features/myFeature.feature:13 109 | Given the title is not "two" # ../../behave_webdriver/steps/given.py:81 0.006s 110 | And I open the url "http://webdriverjs.christian-bromann.com/" # ../../behave_webdriver/steps/given.py:10 0.224s 111 | When I click on the link "two" # ../../behave_webdriver/steps/when.py:21 0.622s 112 | Then I expect that the title is "two" # ../../behave_webdriver/steps/then.py:10 0.006s 113 | 114 | 1 feature passed, 0 failed, 0 skipped 115 | 2 scenarios passed, 0 failed, 0 skipped 116 | 8 steps passed, 0 failed, 0 skipped, 0 undefined 117 | Took 0m2.298s 118 | 119 | Congratulations, you've just implemented a behavior-driven test without having to write a single step implementation! 120 | 121 | .. _environment controls: http://behave.readthedocs.io/en/latest/tutorial.html#environmental-controls 122 | 123 | .. _fixtures: http://behave.readthedocs.io/en/latest/fixtures.html 124 | 125 | .. _step implementations: http://behave.readthedocs.io/en/latest/tutorial.html#python-step-implementations 126 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | alabaster==0.7.10 2 | Babel==2.5.3 3 | behave==1.2.6 4 | certifi==2022.12.7 5 | chardet==3.0.4 6 | colorama==0.3.9 7 | coverage==4.5 8 | docutils==0.14 9 | idna==2.6 10 | imagesize==0.7.1 11 | Jinja2>=2.10.1 12 | MarkupSafe==1.0 13 | parse==1.8.2 14 | parse-type==0.4.2 15 | Pygments==2.2.0 16 | pytz==2017.3 17 | requests>=2.21.0 18 | selenium==3.9.0 19 | six==1.11.0 20 | snowballstemmer==1.2.1 21 | Sphinx==1.6.7 22 | sphinx-rtd-theme==0.2.4 23 | sphinxcontrib-websupport==1.0.1 24 | urllib3>=1.23 25 | -------------------------------------------------------------------------------- /docs/roadmap.rst: -------------------------------------------------------------------------------- 1 | Roadmap 2 | ======= 3 | 4 | Loosely organized collection of goals/milestones and ideas. Nothing here is necessarily concrete, but should give you an 5 | idea of where our heads are at for the development of behave-webdriver. 6 | 7 | You can help steer our roadmap in the right direction with suggestions, feedback, and other contributions. Please don't 8 | hesitate to `raise an issue`_ on Github. 9 | 10 | 11 | Immediate and Short Term 12 | ------------------------ 13 | 14 | Immediate and short term goals are some milestones that we are actively working on or in our immediate forefront for development. 15 | Ideally, these things have clearly defined requirements and some work in progress. 16 | 17 | 18 | 19 | Documentation; recipes & tutorials 20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 21 | 22 | While documentation is something we'll be working on perpetually through development, 23 | we are particularly motivated on immediately providing at least some brief tutorials and recipes. 24 | 25 | 26 | 27 | Medium Term 28 | ----------- 29 | 30 | Medium term goals are things we are committed to working on and implementing in the not-so-distant future. 31 | Ideally, this means we're working on these things passively and have at least a basic plan for the implementation. 32 | 33 | 34 | Device emulation 35 | ^^^^^^^^^^^^^^^^ 36 | 37 | We want to provide support for steps that use device emulation features of drivers that support this. E.g. steps like 38 | ``Given I am using an iPhone 6``, ``Given I am using a Pixel 2``, etc. 39 | 40 | 41 | 42 | More step definitions 43 | ^^^^^^^^^^^^^^^^^^^^^ 44 | 45 | We plan to implement additional step definitions to perform more actions with selenium and provide more robust 46 | interfaces for testing and automation. This will include things like taking & saving screenshots, retrieving/saving page source, and more. 47 | 48 | If you have ideas for step definitions you'd like to see implemented, `raise an issue`_ on Github. These contributions 49 | are welcomed and very much appreciated. 50 | 51 | Browser support (others) 52 | ^^^^^^^^^^^^^^^^^^^^^^^^ 53 | 54 | Chrome and Firefox are in our forefront for browser support. We do however plan to test and provide best-effort 55 | support for all the webdrivers supported by selenium. 56 | 57 | We hope to get all browsers tested (but not necessarily passing) and attempt to make note of compatibility, behavior differences, and other browser-specific quirks. 58 | 59 | Would be nice to have more browsers tested in the CI builds as well. 60 | 61 | See :doc:`browsers` for more information. 62 | 63 | 64 | 65 | Long Term, ongoing, and Ideas 66 | ----------------------------- 67 | 68 | These are some loose long-term milestones or ideas (which may or may not materialize) we have for the future. 69 | These are things that we would probably like to do, but have probably not put much effort into implementation or detailed plans. 70 | Anything we are remotely considering, but have not committed to, will be here, too. 71 | 72 | Assertion matcher 73 | ^^^^^^^^^^^^^^^^^ 74 | 75 | Currently, standard python assertions are used. In the future, we may opt to use an assertion matching library such as 76 | pyhamcrest. Some time and effort will need to be put in to research a good choice in this area. 77 | 78 | 79 | Parallel support 80 | ^^^^^^^^^^^^^^^^ 81 | 82 | While behave itself is planning to add parallel runner support in the future, its unlikely this will work well for 83 | browser testing. As such, we have this parallel support in the back of our minds, but it will probably be some time before 84 | it is introduced in a stable release. 85 | 86 | 87 | Use of other selenium libraries 88 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 89 | 90 | It may be possible for us to take advantage of previous work in this area, for example requestium, to enhance behave-webdriver. 91 | We want to explore these possibilities. 92 | 93 | 94 | Survey & reflection - path to a stable release 95 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 96 | 97 | While a stable (LTS) release itself is more of a long-term goal, we are constantly surveying the behave landscape and reviewing our API. 98 | We've made (what we feel are good) decisions in the design of behave-webdriver, but there's always room for improvement. 99 | Now particularly is a good time for us to ensure we are laying down a solid foundation to build upon for the future. 100 | 101 | Your feedback is immensely valuable in this regard and is sincerely appreciated. 102 | The best way to make suggestions or general comments is to `raise an issue`_ on Github. 103 | 104 | 105 | 106 | 107 | Better tests (ongoing) 108 | ^^^^^^^^^^^^^^^^^^^^^^ 109 | 110 | Our github README boasts its coverage with a shiny badge from coveralls. The truth is that coverage isn't everything. There's undoubtedly cases 111 | where functionality is broken or doesn't work quite as expected. We want to find those to build better test cases, and 112 | improve the functionality of the library as a whole. 113 | 114 | 115 | 116 | 117 | Completed 118 | --------- 119 | 120 | Browser support (Firefox) 121 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 122 | 123 | Firefox is officially supported as of v0.1.1 124 | 125 | v0.1.0 126 | ^^^^^^ 127 | 128 | Complete™ support with Google Chrome. We use the feature files (with modifications or additons in some cases) from 129 | cucumber-boilerplate as acceptance tests. While this is bound to be imperfect, it's a great start for v0.1 130 | 131 | 132 | Deferred 133 | -------- 134 | 135 | Deferred items are things we previously comitted to but, for some reason or another, have placed on the backburner or 136 | suspended entirely. 137 | 138 | PhantomJS support 139 | ^^^^^^^^^^^^^^^^^ 140 | 141 | While we will continue to provide best-effort support for all browsers, including PhantomJS, because PhantomJS has been 142 | deprecated for selenium and `phantomJS development has been suspended`_, PhantomJS is now a low priority. 143 | 144 | .. _raise an issue: https://github.com/spyoungtech/behave-webdriver/issues/new 145 | 146 | 147 | .. _phantomJS development has been suspended: https://github.com/ariya/phantomjs/issues/15344 148 | -------------------------------------------------------------------------------- /docs/steps.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | List of predefined steps 3 | ======================== 4 | 5 | 6 | 7 | Given Steps 👷 8 | -------------- 9 | 10 | - ``I open the site "([^"]*)?"`` 11 | - ``I open the url "([^"]*)?"`` 12 | - ``I have a screen that is ([\d]+) by ([\d]+) pixels`` 13 | - ``I have a screen that is ([\d]+) pixels (broad|tall)`` 14 | - ``I have closed all but the first (window|tab)`` 15 | - ``I pause for (\d+)*ms`` 16 | - ``a (alertbox|confirmbox|prompt) is( not)* opened`` 17 | - ``the base url is "([^"]*)?"`` 18 | - ``the checkbox "([^"]*)?" is( not)* checked`` 19 | - ``the cookie "([^"]*)?" contains( not)* the value "([^"]*)?"`` 20 | - ``the cookie "([^"]*)?" does( not)* exist`` 21 | - ``the element "([^"]*)?" contains( not)* the same text as element "([^"]*)?"`` 22 | - ``the element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)`` 23 | - ``the element "([^"]*)?" is( not)* empty`` 24 | - ``the element "([^"]*)?" is( not)* enabled`` 25 | - ``the element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis`` 26 | - ``the element "([^"]*)?" is( not)* selected`` 27 | - ``the element "([^"]*)?" is( not)* visible`` 28 | - ``the element "([^"]*)?"( not)* contains any text`` 29 | - ``the element "([^"]*)?"( not)* contains the text "([^"]*)?"`` 30 | - ``the element "([^"]*)?"( not)* matches the text "([^"]*)?"`` 31 | - ``the page url is( not)* "([^"]*)?"`` 32 | - ``the title is( not)* "([^"]*)?"`` 33 | - ``the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"`` 34 | - ``there is (an|no) element "([^"]*)?" on the page`` 35 | 36 | 37 | 38 | When Steps ▶️ 39 | ------------- 40 | 41 | - ``I open the site "([^"]*)?"`` 42 | - ``I open the url "([^"]*)?"`` 43 | - ``I accept the (alertbox|confirmbox|prompt)`` 44 | - ``I add "{value}" to the inputfield "{element}"`` 45 | - ``I clear the inputfield "{element}"`` 46 | - ``I click on the button "{element}"`` 47 | - ``I click on the element "{element}"`` 48 | - ``I click on the link "{link_text}"`` 49 | - ``I close the last opened (tab|window)`` 50 | - ``I delete the cookie "{cookie_key}"`` 51 | - ``I dismiss the (alertbox|confirmbox|prompt)`` 52 | - ``I doubleclick on the element "{element}"`` 53 | - ``I drag element "{from_element}" to element "{to_element}"`` 54 | - ``I enter "([^"]*)?" into the (alertbox|confirmbox|prompt)`` 55 | - ``I focus the last opened (tab|window)`` 56 | - ``I move to element "{element}" with an offset of {x_offset:d},{y_offset:d}`` 57 | - ``I move to element "{element}"`` 58 | - ``I pause for {milliseconds:d}ms`` 59 | - ``I press "{key}"`` 60 | - ``I scroll to element "{element}"`` 61 | - ``I select the option with the (text|value|name) "([^"]*)?" for element "([^"]*)?"`` 62 | - ``I select the {nth} option for element "{element}"`` 63 | - ``I set "{value}" to the inputfield "{element}"`` 64 | - ``I set a cookie "{cookie_key}" with the content "{value}"`` 65 | - ``I submit the form "{element}"`` 66 | 67 | Then Steps ✔️ 68 | ------------- 69 | 70 | - ``I expect the screen is ([\d]+) by ([\d]+) pixels`` 71 | - ``I expect a new (window|tab) has( not)* been opened`` 72 | - ``I expect that a (alertbox|confirmbox|prompt) is( not)* opened`` 73 | - ``I expect that a (alertbox|confirmbox|prompt)( not)* contains the text "([^"]*)?"`` 74 | - ``I expect that checkbox "([^"]*)?" is( not)* checked`` 75 | - ``I expect that cookie "([^"]*)?"( not)* contains "([^"]*)?"`` 76 | - ``I expect that cookie "([^"]*)?"( not)* exists`` 77 | - ``I expect that element "([^"]*)?" (has|does not have) the class "([^"]*)?"`` 78 | - ``I expect that element "([^"]*)?" becomes( not)* visible`` 79 | - ``I expect that element "([^"]*)?" does( not)* exist`` 80 | - ``I expect that element "([^"]*)?" is( not)* ([\d]+)px (broad|tall)`` 81 | - ``I expect that element "([^"]*)?" is( not)* empty`` 82 | - ``I expect that element "([^"]*)?" is( not)* enabled`` 83 | - ``I expect that element "([^"]*)?" is( not)* focused`` 84 | - ``I expect that element "([^"]*)?" is( not)* positioned at ([\d]+)px on the (x|y) axis`` 85 | - ``I expect that element "([^"]*)?" is( not)* selected`` 86 | - ``I expect that element "([^"]*)?" is( not)* visible`` 87 | - ``I expect that element "([^"]*)?" is( not)* within the viewport`` 88 | - ``I expect that element "([^"]*)?"( not)* contains any text`` 89 | - ``I expect that element "([^"]*)?"( not)* contains the same text as element "([^"]*)?"`` 90 | - ``I expect that element "([^"]*)?"( not)* contains the text "([^"]*)?"`` 91 | - ``I expect that element "([^"]*)?"( not)* matches the text "([^"]*)?"`` 92 | - ``I expect that the path is( not)* "([^"]*)?"`` 93 | - ``I expect that the title is( not)* "([^"]*)?"`` 94 | - ``I expect that the url is( not)* "([^"]*)?"`` 95 | - ``I expect that the( css)* attribute "([^"]*)?" from element "([^"]*)?" is( not)* "([^"]*)?"`` 96 | - ``I expect the url "([^"]*)?" is opened in a new (tab|window)`` 97 | - ``I expect the url to( not)* contain "([^"]*)?"`` 98 | - ``I wait on element "([^"]*)?"(?: for (\d+)ms)*(?: to( not)* (be checked|be enabled|be selected|be visible|contain a text|contain a value|exist))*`` 99 | -------------------------------------------------------------------------------- /examples/minimal-project/features/environment.py: -------------------------------------------------------------------------------- 1 | from behave_webdriver import Chrome 2 | 3 | def before_all(context): 4 | context.behave_driver = Chrome() 5 | 6 | def after_all(context): 7 | context.behave_driver.quit() 8 | -------------------------------------------------------------------------------- /examples/minimal-project/features/myFeature.feature: -------------------------------------------------------------------------------- 1 | # minimal-project/features/myFeature.feature 2 | Feature: Sample Snippets test 3 | As a developer 4 | I should be able to use given text snippets 5 | 6 | Scenario: open URL 7 | Given the page url is not "http://webdriverjs.christian-bromann.com/" 8 | And I open the url "http://webdriverjs.christian-bromann.com/" 9 | Then I expect that the url is "http://webdriverjs.christian-bromann.com/" 10 | And I expect that the url is not "http://google.com" 11 | 12 | 13 | Scenario: click on link 14 | Given the title is not "two" 15 | And I open the url "http://webdriverjs.christian-bromann.com/" 16 | When I click on the link "two" 17 | Then I expect that the title is "two" 18 | -------------------------------------------------------------------------------- /examples/minimal-project/features/steps/my_steps.py: -------------------------------------------------------------------------------- 1 | from behave_webdriver.steps import * 2 | -------------------------------------------------------------------------------- /examples/selenium-requests/features/environment.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from behave_webdriver.driver import BehaveDriverMixin 3 | from seleniumrequests import RequestMixin 4 | 5 | class BehaveRequestDriver(BehaveDriverMixin, RequestMixin, webdriver.Chrome): 6 | pass 7 | 8 | def before_all(context): 9 | context.behave_driver = BehaveRequestDriver() 10 | 11 | def after_all(context): 12 | context.behave_driver.quit() 13 | -------------------------------------------------------------------------------- /examples/selenium-requests/features/requests.feature: -------------------------------------------------------------------------------- 1 | Feature: Using selenium-requests 2 | As a developer 3 | I should be able to extend behave-webdriver with selenium-requests 4 | 5 | 6 | Scenario: use selenium-requests with behave-webdriver 7 | Given the base url is "http://127.0.0.1:8000" 8 | Given I send a GET request to the page "/" 9 | Then I expect the response text contains "

DEMO APP

" 10 | -------------------------------------------------------------------------------- /examples/selenium-requests/features/steps/selenium_steps.py: -------------------------------------------------------------------------------- 1 | try: 2 | from urllib.parse import urljoin 3 | except ImportError: 4 | from urlparse import urljoin 5 | 6 | from behave import * 7 | from behave_webdriver.steps import * 8 | 9 | 10 | @given('I send a {method} request to the url "{url}"') 11 | def send_request(context, method, url): 12 | context.response = context.behave_driver.request(method, url) 13 | 14 | 15 | @given('I send a {method} request to the page "{page}"') 16 | def send_request_page(context, method, page): 17 | url = urljoin(context.base_url, page) 18 | context.response = context.behave_driver.request(method, url) 19 | 20 | 21 | @then('I expect the response text contains "{text}"') 22 | def check_response_text_contains(context, text): 23 | assert text in context.response.text 24 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | behave 2 | selenium < 4 3 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [metadata] 5 | license_file = LICENSE 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | from io import open 3 | 4 | 5 | with open('README.rst', 'rt', encoding='utf8') as f: 6 | readme = f.read() 7 | 8 | setup( 9 | name='behave-webdriver', 10 | version='0.3.1', 11 | url='https://github.com/spyoungtech/behave-webdriver/', 12 | license='MIT', 13 | author='Spencer Young', 14 | author_email='spencer.young@spyoung.com', 15 | description='Selenium webdriver step library for behave BDD testing', 16 | long_description=readme, 17 | packages=['behave_webdriver', 'behave_webdriver.steps'], 18 | platforms='any', 19 | install_requires=[ 20 | 'selenium < 4', 21 | 'behave' 22 | ], 23 | classifiers=[ 24 | 'Intended Audience :: Developers', 25 | 'Topic :: Software Development :: Testing', 26 | 'Topic :: Software Development :: Quality Assurance', 27 | 'Topic :: Internet :: WWW/HTTP :: Browsers', 28 | 'Topic :: Software Development :: Testing :: BDD', 29 | 'Operating System :: OS Independent', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python', 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | 'Programming Language :: Python :: 3.5', 35 | 'Programming Language :: Python :: 3.6', 36 | 'Programming Language :: Python :: 3.7', 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /tests/demo-app/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spyoungtech/behave-webdriver/9e660bcae5b1b345a32142070973476f71da8d04/tests/demo-app/favicon.png -------------------------------------------------------------------------------- /tests/demo-app/inc/script.js: -------------------------------------------------------------------------------- 1 | function onClickToggleElement() { 2 | var $el = $(this), 3 | $showTarget = $($el.data('show')), 4 | $hideTarget = $($el.data('hide')), 5 | timeout = $el.data('timeout') || 0; 6 | 7 | setTimeout( 8 | function toggleHiddenClass() { 9 | $showTarget.removeClass('hidden'); 10 | $hideTarget.addClass('hidden'); 11 | }, 12 | timeout 13 | ); 14 | } 15 | 16 | function displayFirstMessage() { 17 | $('#message1').removeClass('hidden'); 18 | $('#message2').addClass('hidden'); 19 | } 20 | 21 | function displaySecondMessage() { 22 | $('#message1').addClass('hidden'); 23 | $('#message2').removeClass('hidden'); 24 | } 25 | 26 | function makeBgRed() { 27 | $(this).css('background-color', 'red'); 28 | } 29 | 30 | function makeBgBlue() { 31 | $(this).css('background-color', 'blue'); 32 | } 33 | 34 | function detectDrop($el) { 35 | var $dropZone = $($el.data('dropzone')), 36 | dragOffset = $el.offset(), 37 | dropOffset = $dropZone.offset(), 38 | dragTop = dragOffset.top, 39 | dragRight = (dragOffset.left + $el.outerWidth()), 40 | dragBottom = (dragOffset.top + $el.outerHeight()), 41 | dragLeft = dragOffset.left, 42 | dropTop = dropOffset.top, 43 | dropRight = (dropOffset.left + $dropZone.outerWidth()), 44 | dropBottom = (dropOffset.top + $dropZone.outerHeight()), 45 | dropLeft = dropOffset.left; 46 | 47 | if ( 48 | ( 49 | (dragBottom > dropTop) && 50 | (dragTop < dropBottom) 51 | ) && 52 | ( 53 | (dragRight > dropLeft) && 54 | (dragLeft < dropRight) 55 | ) 56 | ) { 57 | $dropZone.text('Dropped!'); 58 | } else { 59 | $dropZone.text('Dropzone'); 60 | } 61 | } 62 | 63 | function handleFormSubmit(event) { 64 | event.preventDefault(); 65 | 66 | $(this).find('.message').removeClass('hidden'); 67 | } 68 | 69 | function handleDataAction(event) { 70 | var $target = $($(this).data('target')), 71 | data = $(this).data('action'), 72 | action = Object.keys(data)[0], 73 | val = data[action], 74 | state = $(this).data('state') ? !$(this).data('state') : true; 75 | 76 | event.preventDefault(); 77 | 78 | setTimeout(function delayedHandle() { 79 | switch (action) { 80 | case 'select': { 81 | if (state === true) { 82 | $target.val(val).change(); 83 | } else { 84 | $target.val('1').change(); 85 | } 86 | 87 | break; 88 | } 89 | 90 | case 'toggleClass': { 91 | $target.toggleClass(val); 92 | break; 93 | } 94 | 95 | case 'text': { 96 | if (state) { 97 | $target.text(val); 98 | } else { 99 | $target.text(''); 100 | } 101 | break; 102 | } 103 | 104 | case 'value': { 105 | if (state) { 106 | $target.val(val); 107 | } else { 108 | $target.val(''); 109 | } 110 | break; 111 | } 112 | 113 | case 'create': { 114 | if (state) { 115 | $(val).appendTo($target); 116 | } else { 117 | $target.empty(); 118 | } 119 | 120 | break; 121 | } 122 | 123 | default: { 124 | if (state) { 125 | $target.prop(action, (val === 'true')); 126 | } else { 127 | $target.prop(action, (val === 'false')); 128 | } 129 | 130 | break; 131 | } 132 | } 133 | }, 500); 134 | 135 | $(this).data('state', state); 136 | } 137 | 138 | function handleMousedown(event) { 139 | window.dragging = { 140 | pageX0: event.pageX, 141 | pageY0: event.pageY, 142 | elem: this, 143 | offset0: $(this).offset() 144 | }; 145 | 146 | function handleDragging(event) { 147 | var left = dragging.offset0.left + (event.pageX - dragging.pageX0), 148 | top = dragging.offset0.top + (event.pageY - dragging.pageY0); 149 | 150 | $(dragging.elem) 151 | .offset({ 152 | top: top, 153 | left: left 154 | }); 155 | 156 | detectDrop($(dragging.elem)); 157 | } 158 | 159 | function handleMouseup(event) { 160 | $('body') 161 | .off('mousemove', handleDragging) 162 | .off('mouseup', handleMouseup); 163 | } 164 | 165 | $('body') 166 | .on('mouseup', handleMouseup) 167 | .on('mousemove', handleDragging); 168 | } 169 | 170 | function handleKeydown(event) { 171 | var $target = $('#testKeyResponse'); 172 | 173 | $target.text(event.keyCode); 174 | } 175 | 176 | function openAlert(event) { 177 | event.preventDefault(); 178 | 179 | window.alert('I am a alert box!'); 180 | } 181 | 182 | function openConfirm(event) { 183 | var $result = $('#confirmResult'), 184 | result; 185 | 186 | event.preventDefault(); 187 | 188 | result = window.confirm('I am a confirm box!'); 189 | 190 | $result.text(result); 191 | } 192 | 193 | function openPrompt(event) { 194 | var $result = $('#promptResult'), 195 | result; 196 | 197 | event.preventDefault(); 198 | 199 | result = window.prompt('I am a prompt!'); 200 | 201 | $result.text(result); 202 | } 203 | 204 | function toggleMoveToElement(event) { 205 | $(this).toggleClass('moveToClass'); 206 | } 207 | 208 | $(function () { 209 | $('.jsToggleElement') 210 | .on('click', onClickToggleElement); 211 | 212 | $.cookie('test', 'yumyum'); 213 | 214 | $('#toggleMessage') 215 | .on('click', displayFirstMessage) 216 | .on('dblclick', displaySecondMessage); 217 | 218 | $('#toggleBackground') 219 | .on('click', makeBgRed) 220 | .on('dblclick', makeBgBlue); 221 | 222 | $('#draggable') 223 | .on('mousedown', handleMousedown); 224 | 225 | $('#formSubmitTest') 226 | .on('submit', handleFormSubmit); 227 | 228 | $('[data-action]') 229 | .on('click', handleDataAction); 230 | 231 | $('body') 232 | .on('keydown', handleKeydown); 233 | 234 | $('#openAlert') 235 | .on('click', openAlert); 236 | 237 | $('#openConfirm') 238 | .on('click', openConfirm); 239 | 240 | $('#openPrompt') 241 | .on('click', openPrompt); 242 | 243 | $('#moveTo') 244 | .on('mouseenter mouseleave', toggleMoveToElement); 245 | }); 246 | -------------------------------------------------------------------------------- /tests/demo-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DEMO APP 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 |
24 |

DEMO APP

25 |
DEMO APP
26 | 27 |
28 | 29 |
30 |
Drag me!
31 |
Dropzone
32 |
33 | 34 |
35 | 36 | 37 |
I'm a visible element
38 | 39 |
40 | 41 |
I'm a existing element
42 | 43 |
44 | 45 |
46 | 47 |
48 |
I'm only hidden 2 seconds after the button was clicked
49 | 50 |
51 | 52 |
I'm the same as the first string
53 |
I'm the same as the second string
54 |
I'm the same as the first string
55 |
56 |
57 |
I'm the same as the first string
58 |
I'm the same as the first string
59 |
I'm a string containing € © >
60 |
I'm a string containing € © >
61 | 62 |
63 | 64 |
This element contains strawberry
65 |
This element contains cucumber
66 |
67 |
68 | 69 |
70 | 71 |
This element has a role attribute with the value "note"
72 |
This element has a color css attribute with the value "rgba(255,0,0,1)"
73 | 74 |
75 | 76 | 77 | 78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 |
I have cthe class class1 and class2 but not class3
86 | 87 |
88 | 89 |
This link opens example.com in the same window
90 |
This link opens example.com in a new window
91 | 92 |
93 | 94 | Navigate to example.com 95 | 96 | 97 | 98 |
Change the background of this element based on click or double click
99 | 100 |
101 | 102 |
103 | 104 |
105 | 106 |
107 | 110 | 111 |
112 | 113 |
114 | 115 |
116 | 117 | 118 |
119 |
120 | 121 | 122 |
123 |
124 | 125 | 129 |
130 |
131 | 132 | 133 |
134 |
135 | 136 | 137 |
138 |
139 | 140 | 141 |
142 |
143 | 144 | 145 |
146 | 147 |
148 | 149 |
150 | 151 |
152 | 153 |
154 |
155 |
156 |
157 |
158 | 159 |
160 | 161 |
162 | 168 |
169 | 170 |
171 | 172 |
173 |
174 | Move to me please 175 |
176 |
177 | 178 |
179 | 180 |
181 | 182 | 185 |
186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /tests/demo-app/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

This is a page

9 | 10 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/demo-app/runserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """launch small http server 4 | """ 5 | 6 | import sys 7 | 8 | try: 9 | from SimpleHTTPServer import SimpleHTTPRequestHandler 10 | except ImportError: 11 | from http.server import SimpleHTTPRequestHandler 12 | 13 | try: 14 | from SocketServer import TCPServer as HTTPServer 15 | except ImportError: 16 | from http.server import HTTPServer 17 | 18 | # simple web server 19 | # serves files relative to the current directory. 20 | 21 | server_port = 8000 22 | try: 23 | server_port = int(sys.argv[1]) 24 | except: 25 | pass 26 | 27 | httpd = HTTPServer(("", server_port), SimpleHTTPRequestHandler) 28 | print("serving at port {0}".format(server_port)) 29 | httpd.serve_forever() 30 | -------------------------------------------------------------------------------- /tests/experimental-features/elementPosition.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the position of a given element 2 | As a developer 3 | I want to be able to test if a element has a certain position 4 | 5 | Background: 6 | Given I open the site "/" 7 | And I have a screen that is 800 by 600 pixels 8 | And the element "#square100x100" is 100px broad 9 | And the element "#square100x100" is 100px tall 10 | When I scroll to element "#square100x100" 11 | 12 | Scenario: The element #square100x100 is at XX pixels on the X axis 13 | Then I expect that element "#square100x100" is positioned at 40px on the x axis 14 | 15 | @Pending 16 | Scenario: The element #square100x100 is at XX pixels on the Y axis 17 | Then I expect that element "#square100x100" is positioned at 843px on the y axis 18 | 19 | Scenario: The element #square100x100 is not at YY pixels on the X axis 20 | Then I expect that element "#square100x100" is not positioned at 101px on the x axis 21 | 22 | Scenario: The element #square100x100 is not at YY pixels on the y axis 23 | Then I expect that element "#square100x100" is not positioned at 99px on the y axis 24 | -------------------------------------------------------------------------------- /tests/experimental-features/elementVisibility.feature: -------------------------------------------------------------------------------- 1 | Feature: Test visibility of elements 2 | As a developer 3 | I want to be able to test the visibillity of a element 4 | 5 | Background: 6 | Given I open the url "http://localhost:8000/" 7 | And I pause for 1000ms 8 | 9 | Scenario: Invisible elements to be invisible 10 | Then I expect that element "#hidden" is not visible 11 | 12 | Scenario: Visible elements to be visible 13 | Then I expect that element "#visible" is visible 14 | 15 | Scenario: Element should become visible 16 | Given the element "#makeVisible" is not visible 17 | When I click on the element "#btnMakeVisible" 18 | Then I expect that element "#makeVisible" becomes visible 19 | 20 | Scenario: Element should become invisible 21 | Given the element "#makeInvisible" is visible 22 | When I click on the element "#btnMakeInvisible" 23 | Then I expect that element "#makeInvisible" becomes not visible 24 | 25 | Scenario: Element in the viewport 26 | Then I expect that element "h1" is within the viewport 27 | 28 | Scenario: Element outside the viewport 29 | When I scroll to element "#footer" 30 | Then I expect that element "h1" is not within the viewport 31 | -------------------------------------------------------------------------------- /tests/experimental-features/environment.py: -------------------------------------------------------------------------------- 1 | from behave_webdriver import BehaveDriver 2 | 3 | def before_all(context): 4 | context.behave_driver = BehaveDriver.headless_chrome() 5 | def after_all(context): 6 | context.behave_driver.quit() 7 | -------------------------------------------------------------------------------- /tests/experimental-features/githubSearch.feature.pending: -------------------------------------------------------------------------------- 1 | Feature: Github test 2 | As a Developer in Test 3 | I want to search for webdriverio repository 4 | So that I can use it in my future tests 5 | 6 | Scenario: open URL 7 | Given I open the url "https://github.com/" 8 | Then I expect that the url is "https://github.com/" 9 | And I expect that the title is "How people build software · GitHub" 10 | 11 | Scenario: search for webdriverio repository 12 | Given I open the url "https://github.com/search" 13 | And the inputfield ".input-block" not contains any text 14 | And I set "webdriverio" to the inputfield ".input-block" 15 | And I press "Space" 16 | And I add "selenium" to the inputfield ".input-block" 17 | When I submit the form "#search_form" 18 | Then I expect that element ".input-block" contains the text "webdriverio selenium" 19 | And I expect that element ".repo-list-item:first-child > .repo-list-description" contains the text "Webdriver/Selenium 2.0 JavaScript bindings for Node.js" 20 | 21 | Scenario: login with fake credentials 22 | Given I open the url "https://github.com/" 23 | When I log in to site with username "marketionist" and password "1234" 24 | Then I expect that element "#js-flash-container .flash-error" is visible 25 | -------------------------------------------------------------------------------- /tests/experimental-features/login.feature.pending: -------------------------------------------------------------------------------- 1 | Feature: Github test 2 | As a Developer in Test 3 | I want to test if the github.com failed login screen displays a error 4 | 5 | Scenario: open URL 6 | Given I open the url "https://github.com/" 7 | Then I expect that the url is "https://github.com/" 8 | And I expect that the title is "How people build software · GitHub" 9 | 10 | Scenario: login with fake credentials 11 | Given I open the url "https://github.com/" 12 | When I log in to site with username "marketionist" and password "1234" 13 | Then I expect that element "#js-flash-container .flash-error" is visible 14 | -------------------------------------------------------------------------------- /tests/experimental-features/pending.feature: -------------------------------------------------------------------------------- 1 | @Pending 2 | Feature: Pending scenario 3 | As a test framework 4 | I should be able skip all these scenarios 5 | 6 | Scenario: do somethimg 7 | Given I open the site "/" 8 | Then this will fail since this does not exist 9 | -------------------------------------------------------------------------------- /tests/experimental-features/steps/webdriver_steps.py: -------------------------------------------------------------------------------- 1 | from behave_webdriver.steps import * 2 | -------------------------------------------------------------------------------- /tests/features/attribute.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the attributes of a given element 2 | As a developer 3 | I want to be able to test the attributes of a given element 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: The attribute "role" of a element should be "note" 9 | Then I expect that the attribute "role" from element "#attributeComparison" is "note" 10 | 11 | Scenario: The attribute "role" of a element should not be "main" 12 | Then I expect that the attribute "role" from element "#attributeComparison" is not "main" 13 | 14 | Scenario: The CSS attribute "color" of a element should be "red" 15 | Then I expect that the css attribute "color" from element "#cssAttributeComparison" is "rgba(255, 0, 0, 1)" 16 | 17 | Scenario: The CSS attribute "color" of a element should not be "blue" 18 | Then I expect that the css attribute "color" from element "#cssAttributeComparison" is not " rgba(0, 255, 0, 1)" 19 | 20 | Scenario: The (missing) CSS attribute "border" of a element should not be "0" 21 | Then I expect that the css attribute "border" from element "#cssAttributeComparison" is not "0" 22 | -------------------------------------------------------------------------------- /tests/features/baseUrl.feature: -------------------------------------------------------------------------------- 1 | Feature: Base URL configuration 2 | As a developer 3 | I should be able to change the base URL for opening pages. 4 | 5 | Scenario: Change the base url to http://127.0.0.1:8000/ 6 | Given the base url is "http://127.0.0.1:8000/" 7 | When I open the site "/page.html" 8 | Then I expect that the url is "http://127.0.0.1:8000/page.html" 9 | And I expect that the url is not "http://localhost:8000/page.html" 10 | -------------------------------------------------------------------------------- /tests/features/baseUrlWithParameterTransformation.feature: -------------------------------------------------------------------------------- 1 | Feature: Base URL configuration supports parameter transformation 2 | As a developer 3 | I should be able to change the base URL for opening pages 4 | And I should be able to replace hardcoded values with parameters. 5 | 6 | # {BASE_URL} / {ALT_BASE_URL} are transformed by transformer provided in environment.py 7 | # As long the URLs are valid, this test should still work even if the test url is not available at those urls. 8 | Scenario: Default base is http://localhost:8000/ 9 | When I open the site "/" 10 | Then I expect that the url is "{BASE_URL}/" 11 | 12 | Scenario: Change the base url to http://127.0.0.1:8000/ 13 | Given the base url is "{ALT_BASE_URL}/" 14 | When I open the site "/page.html" 15 | Then I expect that the url is "{ALT_BASE_URL}/page.html" 16 | And I expect that the url is not "{BASE_URL}/page.html" 17 | -------------------------------------------------------------------------------- /tests/features/buttonPress.feature: -------------------------------------------------------------------------------- 1 | Feature: Test button press 2 | As a developer 3 | I want to be able to test if a certain action is performed when a certain 4 | button is pressed 5 | 6 | Background: 7 | Given I open the site "/" 8 | 9 | Scenario: Test if element responds to button press 10 | Given the element "#testKeyResponse" not contains any text 11 | When I press "a" 12 | Then I expect that element "#testKeyResponse" contains the text "65" 13 | 14 | Scenario: Test if element responds to button press 15 | Given the element "#testKeyResponse" not contains any text 16 | When I press "b" 17 | Then I expect that element "#testKeyResponse" not contains the text "65" 18 | 19 | # Escape key 20 | Scenario: Test if element responds to button press 21 | Given the element "#testKeyResponse" not contains any text 22 | When I press "Escape" 23 | Then I expect that element "#testKeyResponse" contains the text "27" 24 | -------------------------------------------------------------------------------- /tests/features/checkTitle.feature: -------------------------------------------------------------------------------- 1 | Feature: Local server test 2 | As a developer 3 | I want the demo app have the correct title 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: Is not Google 9 | Then I expect that the title is not "Google" 10 | 11 | Scenario: Is correct 12 | Then I expect that the title is "DEMO APP" 13 | -------------------------------------------------------------------------------- /tests/features/checkbox.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the selected state of a checkbox 2 | As a developer 3 | I want to be able to test the selected state of a checkbox 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: The checkbox should not be selected by default 9 | Then I expect that checkbox "#checkbox" is not checked 10 | 11 | Scenario: The checkbox should be checked when clicked 12 | Given the checkbox "#checkbox" is not checked 13 | When I click on the element "#checkbox" 14 | Then I expect that checkbox "#checkbox" is checked 15 | 16 | Scenario: The checkbox should deselect when clicked twice 17 | Given the checkbox "#checkbox" is not checked 18 | When I click on the element "#checkbox" 19 | And I click on the element "#checkbox" 20 | Then I expect that checkbox "#checkbox" is not checked 21 | -------------------------------------------------------------------------------- /tests/features/class.feature: -------------------------------------------------------------------------------- 1 | # I expect that element "$string" (has|does not have) the class "$string" 2 | 3 | Feature: Test if a given element has a certain CSS class 4 | As a developer 5 | I want to be able to test if a element has a certain CSS class 6 | 7 | Background: 8 | Given I open the site "/" 9 | 10 | Scenario: Element #classTest should have the class "class1" 11 | Then I expect that element "#classTest" has the class "class1" 12 | 13 | Scenario: Element #classTest should also have the class "class2" 14 | Then I expect that element "#classTest" has the class "class2" 15 | 16 | Scenario: Element #classTest should not have the class "class3" 17 | Then I expect that element "#classTest" does not have the class "class3" 18 | -------------------------------------------------------------------------------- /tests/features/click.feature: -------------------------------------------------------------------------------- 1 | Feature: Test how clicks are handled on a certain element 2 | As a developer 3 | I want to be able to test how (double) clicks are handled by certain elements 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | @skip_safari 9 | Scenario: Single click on a link should navigate to another page 10 | When I click on the link "Navigate to example.com" 11 | Then I expect the url to contain "https://example.com" 12 | 13 | @Isolate 14 | Scenario: Single click on the button #toggleMessage should display an message 15 | When I click on the button "#toggleMessage" 16 | Then I expect that element "#message1" is visible 17 | And I expect that element "#message2" is not visible 18 | 19 | @firefox_bug 20 | @skip_safari 21 | Scenario: Double click on the button #toggleMessage should display another message 22 | When I doubleclick on the element "#toggleMessage" 23 | Then I expect that element "#message1" is not visible 24 | And I expect that element "#message2" is visible 25 | 26 | @skip_safari 27 | Scenario: Single click on the element #toggleBackground should make the elemnt red 28 | When I click on the element "#toggleBackground" 29 | @firefox_bug 30 | Scenario: Double click on the element #toggleBackground should make the elemnt blue 31 | When I doubleclick on the element "#toggleBackground" 32 | -------------------------------------------------------------------------------- /tests/features/cookie.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the existens and content of cookies 2 | As a developer 3 | I want to be able to test the existence and/or the content of cookies 4 | 5 | Background: 6 | Given I open the site "/" 7 | And I pause for 500ms 8 | 9 | Scenario: The cookie "test" should exist 10 | Then I expect that cookie "test" exists 11 | 12 | Scenario: The cookie "test2" should not exist 13 | Given the cookie "test" contains the value "yumyum" 14 | Then I expect that cookie "test2" not exists 15 | 16 | Scenario: The cookie "test" should contain the value "yumyum" 17 | Given the cookie "test" contains not the value "out of date" 18 | Then I expect that cookie "test" contains "yumyum" 19 | 20 | Scenario: The cookie "test" should not contain the value "out of date" 21 | Then I expect that cookie "test" not contains "out of date" 22 | 23 | Scenario: The cookie "test3" should be created 24 | When I set a cookie "test3" with the content "more cookies" 25 | Then I expect that cookie "test3" exists 26 | And I expect that cookie "test3" contains "more cookies" 27 | 28 | Scenario: The cookie "test3" should be deletable 29 | Then I expect that cookie "test3" exists 30 | When I delete the cookie "test3" 31 | Then I expect that cookie "test3" not exists 32 | -------------------------------------------------------------------------------- /tests/features/drag.feature: -------------------------------------------------------------------------------- 1 | Feature: Test draggable elements 2 | As a developer 3 | I want to be able to test a given draggable element 4 | 5 | Background: 6 | Given I open the site "/" 7 | And I have a screen that is 1024 by 768 pixels 8 | When I scroll to element "#draggable" 9 | 10 | Scenario: Drag to dropzone 11 | When I drag element "#draggable" to element "#droppable" 12 | Then I expect that element "#droppable" contains the text "Dropped!" 13 | -------------------------------------------------------------------------------- /tests/features/elementExistence.feature: -------------------------------------------------------------------------------- 1 | Feature: Test existence of elements 2 | As a developer 3 | I want to be able to test the existence of a element 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: None existing element check 9 | Then I expect that element "#noneExisting" does not exist 10 | 11 | Scenario: Existing element check 12 | Then I expect that element "#exisiting" does exist 13 | -------------------------------------------------------------------------------- /tests/features/elementSize.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the width and height of a given element 2 | As a developer 3 | I want to be able to test if a element has a certain width and/or height 4 | 5 | Background: 6 | Given I open the site "/" 7 | @skip_safari 8 | Scenario: The element #square100x100 whould have a width of 100px 9 | Then I expect that element "#square100x100" is 100px broad 10 | 11 | Scenario: The element #square100x100 whould have a height of 100px 12 | Then I expect that element "#square100x100" is 100px tall 13 | @skip_safari 14 | Scenario: The element #square100x100 whould not have a width of 101px 15 | Then I expect that element "#square100x100" is not 101px broad 16 | 17 | Scenario: The element #square100x100 whould not have a height of 99px 18 | Then I expect that element "#square100x100" is not 99px tall 19 | -------------------------------------------------------------------------------- /tests/features/environment.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import shutil 4 | from os import getcwd 5 | from os.path import abspath, join 6 | from sys import version_info 7 | from behave_webdriver import before_all_factory, use_fixture_tag 8 | from behave_webdriver.driver import Chrome, ChromeOptions 9 | from functools import partial 10 | from behave_webdriver.fixtures import transformation_fixture, fixture_browser 11 | from behave_webdriver.transformers import FormatTransformer 12 | from behave.fixture import use_fixture 13 | import behave_webdriver 14 | 15 | 16 | def get_driver(**kwargs): 17 | args = [] 18 | kwargs.setdefault('default_wait', 5) 19 | Driver = behave_webdriver.utils._from_env(default_driver='Chrome') 20 | if Driver == behave_webdriver.Chrome: 21 | opts = ChromeOptions() 22 | opts.add_argument('--no-sandbox') # for travis build 23 | kwargs['chrome_options'] = opts 24 | 25 | pwd_driver_path = os.path.abspath(os.path.join(os.getcwd(), Driver._driver_name)) 26 | if sys.version_info[0] < 3: 27 | ex_path = pwd_driver_path 28 | else: 29 | ex_path = shutil.which(Driver._driver_name) or pwd_driver_path 30 | kwargs['executable_path'] = ex_path 31 | if os.environ.get('BEHAVE_WEBDRIVER_HEADLESS', None) and hasattr(Driver, 'headless'): 32 | Driver = Driver.headless 33 | return Driver, kwargs 34 | 35 | #context.behave_driver = context.BehaveDriver() 36 | 37 | def before_all(context): 38 | driver, kwargs = get_driver() 39 | context.BehaveDriver = partial(driver, **kwargs) 40 | use_fixture(fixture_browser, context, webdriver=driver, **kwargs) 41 | use_fixture(transformation_fixture, context, FormatTransformer, BASE_URL='http://localhost:8000', ALT_BASE_URL='http://127.0.0.1:8000') 42 | 43 | 44 | def before_tag(context, tag): 45 | use_fixture_tag(context, tag) 46 | 47 | 48 | def before_feature(context, feature): 49 | if "skip_safari" in feature.tags and os.environ.get("BEHAVE_WEBDRIVER", '').lower() == 'safari': 50 | feature.skip() 51 | return 52 | if "fresh_driver" in feature.tags: 53 | context.behave_driver.quit() 54 | context.behave_driver = context.BehaveDriver() 55 | context.behave_driver.default_wait = 5 56 | 57 | 58 | def before_scenario(context, scenario): 59 | if "skip_firefox" in scenario.effective_tags and os.environ.get("BEHAVE_WEBDRIVER", '').lower() == 'firefox': 60 | scenario.skip("Skipping because @skip_firefox tag (usually this is because of a known-issue with firefox)") 61 | return 62 | if "skip_safari" in scenario.effective_tags and os.environ.get("BEHAVE_WEBDRIVER", '').lower() == 'safari': 63 | scenario.skip("Skipping because safari has issues we're not dealing with right now") 64 | return 65 | -------------------------------------------------------------------------------- /tests/features/focus.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the focus state of a given element 2 | As a developer 3 | I want to be able to test if a element has a certain focus state 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: The element #textinput should not have the focus by default 9 | Then I expect that element "#textinput" is not focused 10 | 11 | Scenario: The element #textinput should have the focus when selected 12 | When I click on the element "#textinput" 13 | Then I expect that element "#textinput" is focused 14 | -------------------------------------------------------------------------------- /tests/features/form.feature: -------------------------------------------------------------------------------- 1 | Feature: Test form submission 2 | As a developer 3 | I want to be able to test form submission 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: Test if a message is shown when the form is submitted 9 | Given the element "#formSubmitTest .message" is not visible 10 | When I submit the form "#formSubmitTest" 11 | Then I expect that element "#formSubmitTest .message" is visible 12 | -------------------------------------------------------------------------------- /tests/features/inputfield.feature: -------------------------------------------------------------------------------- 1 | Feature: Test input fields on a page 2 | As a developer 3 | I want to be able to test input fields on a certain page 4 | 5 | Background: 6 | Given I open the site "/" 7 | Then I expect that element "#testInput" not contains any text 8 | 9 | Scenario: Set the content of a input field 10 | When I set "test" to the inputfield "#testInput" 11 | Then I expect that element "#testInput" contains any text 12 | And I expect that element "#testInput" contains the text "test" 13 | 14 | Scenario: Add content to a input field 15 | When I set "test" to the inputfield "#testInput" 16 | Then I expect that element "#testInput" contains any text 17 | When I add " more tests" to the inputfield "#testInput" 18 | Then I expect that element "#testInput" contains the text "test more tests" 19 | 20 | Scenario: Clear the content of a input field 21 | When I set "test" to the inputfield "#testInput" 22 | Then I expect that element "#testInput" contains any text 23 | And I expect that element "#testInput" contains the text "test" 24 | When I clear the inputfield "#testInput" 25 | Then I expect that element "#testInput" not contains any text 26 | -------------------------------------------------------------------------------- /tests/features/isEmpty.feature: -------------------------------------------------------------------------------- 1 | Feature: Test input fields on a page 2 | As a developer 3 | I want to be able to test input fields on a certain page 4 | 5 | Background: 6 | Given I open the site "/" 7 | Then I expect that element "#testInput" is empty 8 | 9 | Scenario: Set the content of a input field 10 | When I set "test" to the inputfield "#testInput" 11 | Then I expect that element "#testInput" is not empty 12 | And I expect that element "#testInput" contains the text "test" 13 | 14 | Scenario: Add content to a input field 15 | When I set "test" to the inputfield "#testInput" 16 | Then I expect that element "#testInput" is not empty 17 | When I add " more tests" to the inputfield "#testInput" 18 | Then I expect that element "#testInput" contains the text "test more tests" 19 | 20 | Scenario: Clear the content of a input field 21 | When I set "test" to the inputfield "#testInput" 22 | Then I expect that element "#testInput" is not empty 23 | And I expect that element "#testInput" contains the text "test" 24 | When I clear the inputfield "#testInput" 25 | Then I expect that element "#testInput" is empty 26 | -------------------------------------------------------------------------------- /tests/features/isExisting.feature: -------------------------------------------------------------------------------- 1 | Feature: Github test 2 | As a Developer in Test 3 | I want to search for webdriverio repository 4 | And check if some elements are existing and others are not 5 | 6 | Scenario: open URL 7 | Given I open the url "https://github.com/spyoungtech/behave-webdriver" 8 | Then I expect that element ".octicon-mark-github" does exist 9 | And I expect that element ".some-other-element" does not exist 10 | -------------------------------------------------------------------------------- /tests/features/modals.feature: -------------------------------------------------------------------------------- 1 | Feature: Test modals 2 | As a developer 3 | I want to be able to test the onening, closing and contens of modal windows 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: Test if alert is opened accepted 9 | Given a alertbox is not opened 10 | When I click on the element "#openAlert" 11 | Then I expect that a alertbox is opened 12 | And I expect that a alertbox contains the text "I am a alert box!" 13 | And I expect that a alertbox not contains the text "Other Text" 14 | When I accept the alertbox 15 | Then I expect that a alertbox is not opened 16 | 17 | Scenario: Test if alert is opened & dismissed 18 | Given a alertbox is not opened 19 | When I click on the element "#openAlert" 20 | Then I expect that a alertbox is opened 21 | When I dismiss the alertbox 22 | Then I expect that a alertbox is not opened 23 | 24 | Scenario: Test if confirm is canceled 25 | Given a confirmbox is not opened 26 | And the element "#confirmResult" not contains any text 27 | When I click on the element "#openConfirm" 28 | Then I expect that a confirmbox is opened 29 | And I expect that a alertbox contains the text "I am a confirm box!" 30 | When I dismiss the confirmbox 31 | Then I expect that a confirmbox is not opened 32 | And I expect that element "#confirmResult" contains the text "false" 33 | 34 | Scenario: Test if confirm is accepted 35 | Given a confirmbox is not opened 36 | And the element "#confirmResult" not contains any text 37 | When I click on the element "#openConfirm" 38 | Then I expect that a confirmbox is opened 39 | When I accept the confirmbox 40 | Then I expect that a confirmbox is not opened 41 | And I expect that element "#confirmResult" contains the text "true" 42 | 43 | Scenario: Test if prompt is opened & dismissed 44 | Given a prompt is not opened 45 | And the element "#promptResult" not contains any text 46 | When I click on the element "#openPrompt" 47 | Then I expect that a prompt is opened 48 | And I expect that a alertbox contains the text "I am a prompt!" 49 | When I dismiss the prompt 50 | Then I expect that a prompt is not opened 51 | And I expect that element "#promptResult" contains the text "null" 52 | 53 | Scenario: Test if prompt is accepted 54 | Given a prompt is not opened 55 | And the element "#promptResult" not contains any text 56 | When I click on the element "#openPrompt" 57 | Then I expect that a prompt is opened 58 | When I accept the prompt 59 | Then I expect that a prompt is not opened 60 | And I expect that element "#promptResult" not contains any text 61 | 62 | Scenario: Test if prompt has text entered 63 | Given a prompt is not opened 64 | And the element "#promptResult" not contains any text 65 | When I click on the element "#openPrompt" 66 | Then I expect that a prompt is opened 67 | When I enter "test 1 2 3" into the prompt 68 | And I accept the prompt 69 | Then I expect that a prompt is not opened 70 | And I expect that element "#promptResult" contains the text "test 1 2 3" 71 | -------------------------------------------------------------------------------- /tests/features/moveTo.feature: -------------------------------------------------------------------------------- 1 | Feature: Test moveTo elements 2 | As a developer 3 | I want to be able to test if I can move to a element 4 | with an optional relative X and Y position 5 | 6 | Background: 7 | Given I open the site "/" 8 | And I have a screen that is 1024 by 768 pixels 9 | When I scroll to element "#moveTo" 10 | 11 | Scenario: Move to just the element 12 | When I move to element "#moveTo" 13 | Then I expect that element "#moveTo" has the class "moveToClass" 14 | When I move to element "body" 15 | Then I expect that element "#moveTo" does not have the class "moveToClass" 16 | 17 | Scenario: Move to the element with a X and Y offset 18 | When I move to element "#moveTo" with an offset of 15,5 19 | Then I expect that element "#moveTo" has the class "moveToClass" 20 | @skip_firefox 21 | Scenario: Move to the element with a too large offset 22 | When I move to element "#moveTo" with an offset of 941,21 23 | Then I expect that element "#moveTo" does not have the class "moveToClass" 24 | -------------------------------------------------------------------------------- /tests/features/multipleSelect.feature: -------------------------------------------------------------------------------- 1 | # Created by spenceryoung at 3/2/2018 2 | Feature: Test Multiple Select elements 3 | As a developer 4 | I want to be able to test if certain elements in a multiple select field are selected 5 | 6 | Background: 7 | Given I open the site "/page.html" 8 | Then I expect that element "#yes" is not selected 9 | Then I expect that element "#yes2" is not selected 10 | Then I expect that element "#no" is not selected 11 | Then I expect that element "#affirmative" is not selected 12 | Then I expect that element "#negative" is not selected 13 | 14 | Scenario: Test if multiple values are selected by text 15 | When I select the option with the text "Yes" for element "#selectElementTest" 16 | Then I expect that element "#yes" is selected 17 | And I expect that element "#yes2" is selected 18 | And I expect that element "#no" is not selected 19 | And I expect that element "#affirmative" is not selected 20 | And I expect that element "#negative" is not selected 21 | 22 | Scenario: Test is multiple values are selected by value 23 | When I select the option with the value "1" for element "#selectElementTest" 24 | Then I expect that element "#yes" is selected 25 | And I expect that element "#affirmative" is selected 26 | And I expect that element "#yes2" is not selected 27 | And I expect that element "#no" is not selected 28 | And I expect that element "#negative" is not selected 29 | 30 | Scenario: Trying to select non-existant elements raises an error 31 | Then I expect that executing the step 'When I select the option with the name "x" for element "#selectElementTest"' raises an exception 32 | -------------------------------------------------------------------------------- /tests/features/sampleSnippets.feature: -------------------------------------------------------------------------------- 1 | Feature: Sample Snippets test 2 | As a developer 3 | I should be able to use given text snippets 4 | 5 | Scenario: open URL 6 | Given the page url is not "http://guinea-pig.webdriver.io/" 7 | And I open the url "http://guinea-pig.webdriver.io/" 8 | Then I expect that the url is "http://guinea-pig.webdriver.io/" 9 | And I expect that the url is not "http://google.com" 10 | 11 | Scenario: open sub page of weburl 12 | Given the page url is not "http://guinea-pig.webdriver.io/two.html" 13 | And I open the url "http://guinea-pig.webdriver.io/" 14 | Then I expect that the url is "http://guinea-pig.webdriver.io/" 15 | And I expect that the url is not "http://google.com" 16 | @skip_safari 17 | Scenario: click on link 18 | Given the title is not "two" 19 | And I open the url "http://guinea-pig.webdriver.io/" 20 | When I click on the link "two" 21 | Then I expect that the title is "two" 22 | @skip_safari 23 | Scenario: click on button 24 | Given I open the url "http://guinea-pig.webdriver.io/" 25 | And the element ".btn1_clicked" is not visible 26 | When I click on the button ".btn1" 27 | Then I expect that element ".btn1_clicked" is visible 28 | 29 | @skip_safari 30 | @firefox_bug 31 | Scenario: double click on a button 32 | Given I open the url "http://guinea-pig.webdriver.io/" 33 | And the element ".btn1_dblclicked" is not visible 34 | When I doubleclick on the element ".btn1" 35 | Then I expect that element ".btn1_dblclicked" is visible 36 | 37 | Scenario: click on element 38 | Given I open the url "http://guinea-pig.webdriver.io/" 39 | And the element ".btn1_clicked" is not visible 40 | When I click on the element ".btn1" 41 | Then I expect that element ".btn1_clicked" is visible 42 | 43 | @skip_safari 44 | Scenario: add value to an input element 45 | Given I open the url "http://guinea-pig.webdriver.io/" 46 | And the element "//html/body/section/form/input[1]" not contains the text "abc" 47 | When I add "bc" to the inputfield "//html/body/section/form/input[1]" 48 | Then I expect that element "//html/body/section/form/input[1]" contains the text "abc" 49 | 50 | Scenario: set value to an input element 51 | Given I open the url "http://guinea-pig.webdriver.io/" 52 | And the element "//html/body/section/form/input[1]" not contains the text "bc" 53 | When I set "bc" to the inputfield "//html/body/section/form/input[1]" 54 | Then I expect that element "//html/body/section/form/input[1]" contains the text "bc" 55 | 56 | Scenario: clear value of input element 57 | Given I open the url "http://guinea-pig.webdriver.io/" 58 | When I set "test" to the inputfield "//html/body/section/form/input[1]" 59 | And I clear the inputfield "//html/body/section/form/input[1]" 60 | Then I expect that element "//html/body/section/form/input[1]" not contains any text 61 | 62 | Scenario: drag n drop 63 | Given I open the url "http://guinea-pig.webdriver.io/" 64 | And the element ".searchinput" not contains the text "Dropped!" 65 | When I drag element "#overlay" to element ".red" 66 | Then I expect that element ".searchinput" contains the text "Dropped!" 67 | @firefox_bug 68 | Scenario: submit form 69 | Given I open the url "http://guinea-pig.webdriver.io/" 70 | And there is no element ".gotDataA" on the page 71 | When I submit the form ".send" 72 | Then I wait on element ".gotDataA" for 5000ms to be visible 73 | 74 | Scenario: wait for element 75 | Given I open the url "http://guinea-pig.webdriver.io/" 76 | And there is no element ".lateElem" on the page 77 | Then I wait on element ".lateElem" for 5000ms to be visible 78 | 79 | Scenario: wait for element using default wait time 80 | Given I open the url "http://guinea-pig.webdriver.io/" 81 | And there is no element ".lateElem" on the page 82 | Then I wait on element ".lateElem" to be visible 83 | 84 | Scenario: pause 85 | Given I open the url "http://guinea-pig.webdriver.io/" 86 | And there is no element ".lateElem" on the page 87 | When I pause for 3000ms 88 | Then I expect that element ".lateElem" is visible 89 | 90 | Scenario: query title 91 | Given I open the url "http://guinea-pig.webdriver.io/" 92 | And the title is "WebdriverJS Testpage" 93 | And the title is not "Other title" 94 | Then I expect that the title is "WebdriverJS Testpage" 95 | And I expect that the title is not "Other title" 96 | 97 | Scenario: check visibility 98 | Given I open the url "http://guinea-pig.webdriver.io/" 99 | And the element ".btn1" is visible 100 | And the element ".btn1_clicked" is not visible 101 | Then I expect that element ".btn1" is visible 102 | And I expect that element ".btn1_clicked" is not visible 103 | 104 | Scenario: compare texts 105 | Given I open the url "http://guinea-pig.webdriver.io/" 106 | And the element "#secondPageLink" contains the same text as element "#secondPageLink" 107 | And the element "#secondPageLink" contains not the same text as element "#githubRepo" 108 | Then I expect that element "#secondPageLink" contains the same text as element "#secondPageLink" 109 | And I expect that element "#secondPageLink" not contains the same text as element "#githubRepo" 110 | 111 | Scenario: check text content 112 | Given I open the url "http://guinea-pig.webdriver.io/" 113 | And the element "#secondPageLink" contains the text "two" 114 | And the element "#secondPageLink" not contains the text "andere linktext" 115 | Then I expect that element "#secondPageLink" contains the text "two" 116 | And I expect that element "#secondPageLink" not contains the text "anderer linktext" 117 | 118 | Scenario: check input content 119 | Given I open the url "http://guinea-pig.webdriver.io/" 120 | And the element "//html/body/section/form/input[1]" contains the text "a" 121 | And the element "//html/body/section/form/input[1]" not contains the text "aa" 122 | Then I expect that element "//html/body/section/form/input[1]" contains the text "a" 123 | And I expect that element "//html/body/section/form/input[1]" not contains the text "aa" 124 | 125 | Scenario: check attribut 126 | Given I open the url "http://guinea-pig.webdriver.io/" 127 | And the attribute "data-foundby" from element "#newWindow" is "partial link text" 128 | And the attribute "data-foundby" from element "#newWindow" is not "something else" 129 | Then I expect that the attribute "data-foundby" from element "#newWindow" is "partial link text" 130 | And I expect that the attribute "data-foundby" from element "#newWindow" is not "something else" 131 | 132 | Scenario: check css attribut 133 | Given I open the url "http://guinea-pig.webdriver.io/" 134 | And the css attribute "background-color" from element ".red" is "rgba(255, 0, 0, 1)" 135 | And the css attribute "background-color" from element ".red" is not "rgba(0, 255, 0, 1)" 136 | Then I expect that the css attribute "background-color" from element ".red" is "rgba(255, 0, 0, 1)" 137 | And I expect that the css attribute "background-color" from element ".red" is not "rgba(0, 255, 0, 1)" 138 | @firefox_bug 139 | Scenario: check width and height 140 | Given I open the url "http://guinea-pig.webdriver.io/" 141 | And the element ".red" is 102px broad 142 | And the element ".red" is 102px tall 143 | And the element ".red" is not 103px broad 144 | And the element ".red" is not 103px tall 145 | Then I expect that element ".red" is 102px broad 146 | And I expect that element ".red" is 102px tall 147 | And I expect that element ".red" is not 103px broad 148 | And I expect that element ".red" is not 103px tall 149 | 150 | @skip_safari 151 | Scenario: check offset 152 | Given I open the url "http://guinea-pig.webdriver.io/" 153 | And the element ".red" is positioned at 15px on the x axis 154 | And the element ".red" is positioned at 268px on the y axis 155 | And the element ".red" is not positioned at 16px on the x axis 156 | And the element ".red" is not positioned at 246px on the y axis 157 | Then I expect that element ".red" is positioned at 15px on the x axis 158 | And I expect that element ".red" is positioned at 268px on the y axis 159 | And I expect that element ".red" is not positioned at 16px on the x axis 160 | And I expect that element ".red" is not positioned at 246px on the y axis 161 | 162 | Scenario: check selected 163 | Given I open the url "http://guinea-pig.webdriver.io/" 164 | And the checkbox ".checkbox_notselected" is not checked 165 | When I click on the element ".checkbox_notselected" 166 | Then I expect that checkbox ".checkbox_notselected" is checked 167 | 168 | Scenario: set / read cookie 169 | Given I open the url "http://guinea-pig.webdriver.io/" 170 | And the cookie "test" does not exist 171 | When I set a cookie "test" with the content "test123" 172 | Then I expect that cookie "test" exists 173 | And I expect that cookie "test" contains "test123" 174 | And I expect that cookie "test" not contains "test1234" 175 | 176 | Scenario: delete cookie 177 | Given I open the url "http://guinea-pig.webdriver.io/" 178 | And the cookie "test" does exist 179 | When I delete the cookie "test" 180 | Then I expect that cookie "test" not exists 181 | -------------------------------------------------------------------------------- /tests/features/screenSize.feature: -------------------------------------------------------------------------------- 1 | Feature: Screen Size 2 | As a developer 3 | I want to be able to check and modify the screen size 4 | 5 | Background: 6 | Given I have a screen that is 600 by 500 pixels 7 | 8 | Scenario: change the screen size 9 | Given I have a screen that is 500 by 420 pixels 10 | Then I expect the screen is 500 by 420 pixels 11 | 12 | Scenario: Change just Y dimension 13 | Given I have a screen that is 700 pixels tall 14 | Then I expect the screen is 600 by 700 pixels 15 | 16 | Scenario: Change just X dimension 17 | Given I have a screen that is 700 pixels broad 18 | Then I expect the screen is 700 by 500 pixels 19 | -------------------------------------------------------------------------------- /tests/features/sctructure.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the page structure 2 | As a developer 3 | I want to be able to test if a page has a certain structure 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: Test if the page has a H1 I expect its at the top of the page 9 | Given there is an element "h1" on the page 10 | When I scroll to element "h1" 11 | Then I expect that element "h1" is visible 12 | 13 | Scenario: Test if the page has only one H1 element 14 | Given there is no element "h1:nth-child(n+2)" on the page 15 | -------------------------------------------------------------------------------- /tests/features/select.feature: -------------------------------------------------------------------------------- 1 | @skip_safari 2 | Feature: Test select elements 3 | As a developer 4 | I want to be able to test if a certain value is selected for a certain 5 | select element 6 | 7 | Background: 8 | Given I open the site "/" 9 | 10 | Scenario: Test if we can select the second option of a select element 11 | Then I expect that element "#selectElementTest option:nth-child(2)" is not selected 12 | When I select the 1st option for element "#selectElementTest" 13 | Then I expect that element "#selectElementTest option:nth-child(2)" is selected 14 | 15 | Scenario: Test if we can select the third option of a select element 16 | Then I expect that element "#selectElementTest option:nth-child(3)" is not selected 17 | When I select the 2nd option for element "#selectElementTest" 18 | Then I expect that element "#selectElementTest option:nth-child(3)" is selected 19 | 20 | Scenario: Test if we can select the fourth option of a select element 21 | Then I expect that element "#selectElementTest option:nth-child(4)" is not selected 22 | When I select the 3th option for element "#selectElementTest" 23 | Then I expect that element "#selectElementTest option:nth-child(4)" is selected 24 | 25 | Scenario: Test if we can select the first option of a select element 26 | When I select the 1st option for element "#selectElementTest" 27 | Then I expect that element "#selectElementTest option:nth-child(1)" is not selected 28 | When I select the 0th option for element "#selectElementTest" 29 | Then I expect that element "#selectElementTest option:nth-child(1)" is selected 30 | 31 | Scenario: Test if we can select a option by its name 32 | Then I expect that element "#selectElementTest option:nth-child(2)" is not selected 33 | When I select the option with the name "secondOption" for element "#selectElementTest" 34 | Then I expect that element "#selectElementTest option:nth-child(2)" is selected 35 | 36 | Scenario: Test if we can select a option by its value 37 | Then I expect that element "#selectElementTest option:nth-child(3)" is not selected 38 | When I select the option with the value "third" for element "#selectElementTest" 39 | Then I expect that element "#selectElementTest option:nth-child(3)" is selected 40 | 41 | Scenario: Test if we can select a option by its visible text 42 | Then I expect that element "#selectElementTest option:nth-child(4)" is not selected 43 | When I select the option with the text "Option #4" for element "#selectElementTest" 44 | Then I expect that element "#selectElementTest option:nth-child(4)" is selected 45 | 46 | Scenario: Test if we can select a option 47 | by its value using XPath selectors only 48 | Then I expect that element "#selectElementTest option:nth-child(2)" is not selected 49 | When I select the option with the value "second" for element "//select[@id='selectElementTest']" 50 | Then I expect that element "#selectElementTest option:nth-child(2)" is selected 51 | 52 | Scenario: Test if we can select a option by its index using XPath selectors 53 | Then I expect that element "#selectElementTest option:nth-child(4)" is not selected 54 | When I select the 3rd option for element "//select[@id='selectElementTest']" 55 | Then I expect that element "#selectElementTest option:nth-child(4)" is selected 56 | 57 | Scenario: Test if we can select a option by its visible text using XPath selectors 58 | Then I expect that element "#selectElementTest option:nth-child(3)" is not selected 59 | When I select the option with the text "Option #3" for element "//select[@id='selectElementTest']" 60 | Then I expect that element "#selectElementTest option:nth-child(3)" is selected 61 | 62 | Scenario: Test if we can select a option by its name using XPath selectors 63 | Then I expect that element "#selectElementTest option:nth-child(2)" is not selected 64 | When I select the option with the name "secondOption" for element "//select[@id='selectElementTest']" 65 | Then I expect that element "#selectElementTest option:nth-child(2)" is selected 66 | -------------------------------------------------------------------------------- /tests/features/steps/webdriver_steps.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from behave_webdriver.steps import * 3 | 4 | use_step_matcher('re') 5 | 6 | @then("""I expect that executing the step '([^']*)?' raises an exception""") 7 | def test_step_raises_exception(context, step_text): 8 | try: 9 | context.execute_steps(step_text) 10 | except Exception as e: 11 | print(e) 12 | else: 13 | raise AssertionError('Step did not raise exception') 14 | -------------------------------------------------------------------------------- /tests/features/textComparison.feature: -------------------------------------------------------------------------------- 1 | Feature: Test text contents of elements 2 | As a developer 3 | I want to be able to test the text inside a element against the text inside 4 | another element 5 | 6 | Background: 7 | Given I open the site "/" 8 | 9 | Scenario: Elements containing different text 10 | Then I expect that element "#textComparison1" not contains the same text as element "#textComparison2" 11 | 12 | Scenario: Elements containing the same text 13 | Then I expect that element "#textComparison1" contains the same text as element "#textComparison3" 14 | 15 | Scenario: Elements containing no text 16 | Then I expect that element "#textComparison4" contains the same text as element "#textComparison5" 17 | 18 | Scenario: Elements containing text and elements 19 | Then I expect that element "#textComparison1" contains the same text as element "#textComparison6" 20 | 21 | Scenario: Elements containing text inside a child element 22 | Then I expect that element "#textComparison1" contains the same text as element "#textComparison7" 23 | 24 | Scenario: Elements containing text with encoded strings 25 | Then I expect that element "#textComparison8" contains the same text as element "#textComparison9" 26 | 27 | Scenario: Element containing different text 28 | Then I expect that element "#textDoesNotContainCucumber" not contains the text "This element contains cucumber" 29 | 30 | Scenario: Element containing the same text 31 | Then I expect that element "#textDoesContainCucumber" contains the text "This element contains cucumber" 32 | 33 | Scenario: Input containing different text 34 | Then I expect that element "#valueDoesNotContainCucumber" not contains the text "This input contains cucumber" 35 | 36 | Scenario: Input containing the same text 37 | Then I expect that element "#valueDoesContainCucumber" contains the text "This input contains cucumber" 38 | 39 | Scenario: Test element matches text 40 | Then I expect that element "#textDoesContainCucumber" matches the text "This element contains cucumber" 41 | And I expect that element "#textDoesContainCucumber" not matches the text "Something else" 42 | 43 | Scenario: Test Input matches text 44 | Then I expect that element "#valueDoesContainCucumber" matches the text "This input contains cucumber" 45 | And I expect that element "#valueDoesContainCucumber" not matches the text "Something else" 46 | -------------------------------------------------------------------------------- /tests/features/title.feature: -------------------------------------------------------------------------------- 1 | Feature: Test the page title 2 | As a developer 3 | I want to be able to test if a page has a certain title 4 | 5 | Background: 6 | Given I open the site "/" 7 | 8 | Scenario: Test if the demo app has the title "DEMO APP" 9 | Given the title is "DEMO APP" 10 | Then I expect that element "h1" contains the same text as element ".subtitle" 11 | 12 | Scenario: Test if the demo app does not have the title "Google" 13 | Given the title is not "Google" 14 | And the page url is not "https://www.google.com/" 15 | Then I expect that element "h1" not contains the text "Google" 16 | -------------------------------------------------------------------------------- /tests/features/transformation_fixture.feature: -------------------------------------------------------------------------------- 1 | Feature: Using a transformation fixture from feature file 2 | 3 | @fixture.transformer.EnvironmentTransformer 4 | Scenario: transform step from environment variable 5 | Given the base url is "{ENV_BASE_URL}" 6 | When I open the site "/page.html" 7 | Then I expect that the url is "{ENV_BASE_URL}page.html" 8 | -------------------------------------------------------------------------------- /tests/features/urlValidation.feature: -------------------------------------------------------------------------------- 1 | Feature: Test if the url is a certain value 2 | As a developer 3 | I want to be able to test if the url is a certain value 4 | 5 | Scenario: The url should not be http://www.google.com/ 6 | Given I open the site "/" 7 | Then I expect that the url is not "http://www.google.com/" 8 | 9 | Scenario: The url should be http://localhost:8000/ 10 | Given I open the site "/" 11 | Then I expect that the url is "http://localhost:8000/" 12 | 13 | Scenario: The path should not be /index.html 14 | Given I open the site "/" 15 | Then I expect that the path is not "/index.html" 16 | 17 | Scenario: The path should be /index.html 18 | Given I open the site "/index.html" 19 | Then I expect that the path is "/index.html" 20 | 21 | Scenario: The url should not contain "google" 22 | Given I open the site "/" 23 | Then I expect the url to not contain "google" 24 | 25 | Scenario: The url should not contain "index" 26 | Given I open the site "/index.html" 27 | Then I expect the url to contain "index" 28 | -------------------------------------------------------------------------------- /tests/features/wait.feature: -------------------------------------------------------------------------------- 1 | Feature: Test waiting for actions 2 | As a developer 3 | I want to be able to test if delayed actions are being performed 4 | 5 | Background: 6 | Given I open the site "/" 7 | And I pause for 1000ms 8 | 9 | Scenario: Test if element becomes checked after 2000 ms 10 | Given the checkbox "#waitForCheckedElement" is not checked 11 | When I click on the button "#waitForCheckedBtn" 12 | Then I wait on element "#waitForCheckedElement" for 2000ms to be checked 13 | 14 | Scenario: Test if element becomes checked 15 | Given the checkbox "#waitForCheckedElement" is not checked 16 | When I click on the button "#waitForCheckedBtn" 17 | Then I wait on element "#waitForCheckedElement" for 1000ms to be checked 18 | 19 | Scenario: Test if element becomes enabled 20 | Given the element "#waitForEnabledElement" is not enabled 21 | When I click on the button "#waitForEnabledBtn" 22 | Then I wait on element "#waitForEnabledElement" for 1000ms to be enabled 23 | 24 | Scenario: Test if element becomes selected 25 | Given the element "#waitForSelectedElement option:nth-child(2)" is not selected 26 | When I click on the button "#waitForSelectedBtn" 27 | Then I wait on element "#waitForSelectedElement option:nth-child(2)" for 1000ms to be selected 28 | 29 | Scenario: Test if element becomes visible 30 | Given the element "#waitForVisibleElement" is not visible 31 | When I click on the button "#waitForVisibleBtn" 32 | Then I wait on element "#waitForVisibleElement" for 1000ms to be visible 33 | 34 | Scenario: Test if element to contain a text 35 | Given the element "#waitForContainsTextElement" not contains any text 36 | When I click on the button "#waitForContainsTextBtn" 37 | Then I wait on element "#waitForContainsTextElement" for 1000ms to contain a text 38 | 39 | Scenario: Test if element to contain a value 40 | Given the element "#waitForContainsValueElement" not contains any text 41 | When I click on the button "#waitForContainsValueBtn" 42 | Then I wait on element "#waitForContainsValueElement" for 1000ms to contain a value 43 | 44 | Scenario: Test if element to exist 45 | Given there is no element "#waitForCreateElement > span" on the page 46 | When I click on the button "#waitForCreateBtn" 47 | Then I wait on element "#waitForCreateElement > span" for 1000ms 48 | 49 | Scenario: Test if element exists 50 | Given there is no element "#waitForCreateElement > span" on the page 51 | When I click on the button "#waitForCreateBtn" 52 | Then I wait on element "#waitForCreateElement > span" for 1000ms to exist 53 | 54 | Scenario: Test if element becomes unchecked 55 | When I click on the button "#waitForCheckedBtn" 56 | And I pause for 1000ms 57 | Then I expect that checkbox "#waitForCheckedElement" is checked 58 | When I click on the button "#waitForCheckedBtn" 59 | Then I wait on element "#waitForCheckedElement" for 1000ms to not be checked 60 | 61 | Scenario: Test if element becomes disabled 62 | When I click on the button "#waitForEnabledBtn" 63 | And I pause for 1000ms 64 | Then I expect that element "#waitForEnabledElement" is enabled 65 | When I click on the button "#waitForEnabledBtn" 66 | Then I wait on element "#waitForEnabledElement" for 1000ms to not be enabled 67 | 68 | Scenario: Test if element becomes not selected 69 | When I click on the button "#waitForSelectedBtn" 70 | And I pause for 1000ms 71 | Then I expect that element "#waitForSelectedElement option:nth-child(2)" is selected 72 | When I click on the button "#waitForSelectedBtn" 73 | Then I wait on element "#waitForSelectedElement option:nth-child(2)" for 1000ms to not be selected 74 | 75 | Scenario: Test if element becomes not visible 76 | When I click on the button "#waitForVisibleBtn" 77 | And I pause for 1000ms 78 | Then I expect that element "#waitForVisibleElement" is visible 79 | When I click on the button "#waitForVisibleBtn" 80 | Then I wait on element "#waitForVisibleElement" for 1000ms to not be visible 81 | 82 | Scenario: Test if element to not contain a text 83 | When I click on the button "#waitForContainsTextBtn" 84 | And I pause for 1000ms 85 | Then I expect that element "#waitForContainsTextElement" contains any text 86 | When I click on the button "#waitForContainsTextBtn" 87 | Then I wait on element "#waitForContainsTextElement" for 1000ms to not contain a text 88 | 89 | Scenario: Test if element to not contain a value 90 | When I click on the button "#waitForContainsValueBtn" 91 | And I pause for 1000ms 92 | Then I expect that element "#waitForContainsValueElement" contains any text 93 | When I click on the button "#waitForContainsValueBtn" 94 | Then I wait on element "#waitForContainsValueElement" for 2000ms to not contain a value 95 | 96 | Scenario: Test if element not exists 97 | When I click on the button "#waitForCreateBtn" 98 | And I pause for 1000ms 99 | Then I expect that element "#waitForCreateElement > span" does exist 100 | When I click on the button "#waitForCreateBtn" 101 | Then I wait on element "#waitForCreateElement > span" for 1000ms to not exist 102 | -------------------------------------------------------------------------------- /tests/features/window.feature: -------------------------------------------------------------------------------- 1 | Feature: Test if new windows/tabs are being opened 2 | As a developer 3 | I want to be able to test if a element opens a new window/tab 4 | 5 | Background: 6 | Given I have closed all but the first tab 7 | And I open the site "/" 8 | 9 | Scenario: Test if a new window/tab is not being opened 10 | Given the page url is "http://localhost:8000/" 11 | Then I expect a new window has not been opened 12 | 13 | Scenario: Test if a default link does not open a new window/tab 14 | When I click on the element "#linkSameWindow" 15 | Then I expect a new window has not been opened 16 | 17 | Scenario: Test if a link with target="_blank" does open a new window/tab 18 | When I click on the element "#linkNewWindow" 19 | Then I expect a new window has been opened 20 | 21 | Scenario: Test if a window/tab from "google.com" has the correct url 22 | When I click on the element "#linkNewWindow" 23 | Then I expect the url "http://example.com/" is opened in a new window 24 | 25 | Scenario: Test all opened windows/tabs are now closed 26 | Given the page url is "http://localhost:8000/" 27 | Then I expect a new window has not been opened 28 | 29 | @skip_safari 30 | Scenario: Test if we can close the last opened window/tab 31 | When I click on the element "#linkNewWindow" 32 | Then I expect a new window has been opened 33 | When I close the last opened window 34 | Then I expect a new window has not been opened 35 | 36 | Scenario: Test if we can focus the last opened window/tab 37 | When I click on the element "#linkNewWindow" 38 | Then I expect a new window has been opened 39 | When I focus the last opened window 40 | Then I expect that the url is "http://example.com/" 41 | When I close the last opened window 42 | Then I expect that the url is "http://localhost:8000/" 43 | 44 | Scenario: Test checking for new window without new widow raises an error 45 | Then I expect that executing the step 'Then I expect the url "/" is opened in a new window' raises an exception 46 | 47 | Scenario: Test checking for new window with nonexistent url fails 48 | When I click on the element "#linkNewWindow" 49 | Then I expect that executing the step 'Then I expect the url "foo" is opened in a new window' raises an exception 50 | -------------------------------------------------------------------------------- /tests/features/withinViewport.feature: -------------------------------------------------------------------------------- 1 | @fresh_driver 2 | Feature: Viewport test 3 | As a Developer in Test 4 | I want to visit the Google result page for the term "test" 5 | And make sure I have the logo within the viewport and make sure the footer is not 6 | 7 | Scenario: Header in viewport, footer outside viewport 8 | Given I open the site "/" 9 | And I have a screen that is 1024 by 768 pixels 10 | And I pause for 1000ms 11 | Then I expect that element "h1" is within the viewport 12 | And I expect that element "footer" is not within the viewport 13 | -------------------------------------------------------------------------------- /tests/unittests/test_css_xpath_discerning.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | import sys 4 | import os 5 | present_dir = os.path.dirname(os.path.realpath(__file__)) 6 | root_dir = os.path.abspath(os.path.join(present_dir, '..', '..')) 7 | sys.path.insert(0, root_dir) 8 | from behave_webdriver.driver import BehaveDriverMixin 9 | from selenium.webdriver.common.by import By 10 | 11 | 12 | def _init_get_element_mocks(): 13 | class DriverTest(BehaveDriverMixin): 14 | pass 15 | mock_el = mock.MagicMock(name='Html element') 16 | DriverTest.find_element = mock.MagicMock(name='find_element', return_value=mock_el) 17 | DriverTest.find_element_by_xpath = mock.MagicMock(name='find_element_by_xpath', return_value=mock_el) 18 | DriverTest.find_element_by_css_selector = mock.MagicMock(name='find_element_by_css_selector', return_value=mock_el) 19 | return DriverTest, mock_el 20 | 21 | 22 | def test_get_element_with_by(): 23 | DriverTest, mock_el = _init_get_element_mocks() 24 | el = DriverTest().get_element('/my_weird_id', by=By.ID) 25 | assert el is mock_el 26 | assert DriverTest.find_element.called 27 | assert DriverTest.find_element_by_xpath.not_called 28 | assert DriverTest.find_element_by_css_selector.not_called 29 | 30 | 31 | def test_get_element_with_xpath_expression(): 32 | DriverTest, mock_el = _init_get_element_mocks() 33 | el = DriverTest().get_element('/my_xpath/expression') 34 | assert el is mock_el 35 | assert DriverTest.find_element.not_called 36 | assert DriverTest.find_element_by_xpath.called 37 | assert DriverTest.find_element_by_css_selector.not_called 38 | 39 | 40 | def test_get_element_with_css_selector(): 41 | DriverTest, mock_el = _init_get_element_mocks() 42 | el = DriverTest().get_element('div.specific-class[title="tooltip"]') 43 | assert el is mock_el 44 | assert DriverTest.find_element.not_called 45 | assert DriverTest.find_element_by_xpath.not_called 46 | assert DriverTest.find_element_by_css_selector.called 47 | 48 | 49 | def _init_wait_for_element_condition_mocks(): 50 | with mock.patch('behave_webdriver.driver.element_is_present') as mock_element_is_present: 51 | with mock.patch('behave_webdriver.driver.WebDriverWait') as mock_WebDriverWait: 52 | mock_el = mock.MagicMock(name='Html element') 53 | mock_web_driver_wait = mock.MagicMock(name='web_driver_wait') 54 | mock_web_driver_wait.until.return_value = mock_el 55 | mock_WebDriverWait.return_value = mock_web_driver_wait 56 | class DriverTest(BehaveDriverMixin): 57 | pass 58 | yield DriverTest, mock_el, mock_WebDriverWait, mock_web_driver_wait, mock_element_is_present 59 | 60 | 61 | def test_wait_for_element_condition_with_xpath_expression(): 62 | for DriverTest, mock_el, mock_WebDriverWait, mock_web_driver_wait, mock_element_is_present \ 63 | in _init_wait_for_element_condition_mocks(): 64 | driver_test = DriverTest() 65 | el = driver_test.wait_for_element_condition('/my_xpath/expression', None, None, None) 66 | assert el is mock_el 67 | assert mock_WebDriverWait.called_with(driver_test, driver_test.default_wait) 68 | assert mock_web_driver_wait.until.called 69 | assert mock_element_is_present.called_with((By.XPATH, '/my_xpath/expression'), False) 70 | 71 | 72 | def test_wait_for_element_condition_with_css_selector(): 73 | for DriverTest, mock_el, mock_WebDriverWait, mock_web_driver_wait, mock_element_is_present \ 74 | in _init_wait_for_element_condition_mocks(): 75 | driver_test = DriverTest() 76 | el = driver_test.wait_for_element_condition('div.specific-class[title="tooltip"]', None, None, None) 77 | assert el is mock_el 78 | assert mock_WebDriverWait.called_with(driver_test, driver_test.default_wait) 79 | assert mock_web_driver_wait.until.called 80 | assert mock_element_is_present.called_with((By.CSS_SELECTOR, '/my_xpath/expression'), False) 81 | -------------------------------------------------------------------------------- /tests/unittests/test_fixture.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | import sys 4 | import os 5 | present_dir = os.path.dirname(os.path.realpath(__file__)) 6 | root_dir = os.path.abspath(os.path.join(present_dir, '..', '..')) 7 | sys.path.insert(0, root_dir) 8 | import behave_webdriver.fixtures 9 | 10 | 11 | def test_before_all_factory(): 12 | with mock.patch('behave_webdriver.fixtures.use_fixture') as mock_use_fixture: 13 | args = (4, True, "test") 14 | kwargs = { 15 | 'webdriver_name': 'Firefox', 16 | 'options': {'param': 'value'}, 17 | } 18 | before_all = behave_webdriver.fixtures.before_all_factory(*args, **kwargs) 19 | ctx = mock.MagicMock() 20 | before_all(ctx) 21 | assert mock_use_fixture.called_with(ctx, *args, **kwargs) 22 | 23 | 24 | def test_before_feature_factory(): 25 | with mock.patch('behave_webdriver.fixtures.use_fixture') as mock_use_fixture: 26 | args = (4, True, "test") 27 | kwargs = { 28 | 'webdriver_name': 'Firefox', 29 | 'options': {'param': 'value'}, 30 | } 31 | before_feature = behave_webdriver.fixtures.before_feature_factory(*args, **kwargs) 32 | ctx = mock.MagicMock() 33 | feature = mock.MagicMock() 34 | before_feature(ctx, feature) 35 | assert mock_use_fixture.called_with(ctx, *args, **kwargs) 36 | 37 | 38 | def test_before_scenario_factory(): 39 | with mock.patch('behave_webdriver.fixtures.use_fixture') as mock_use_fixture: 40 | args = (4, True, "test") 41 | kwargs = { 42 | 'webdriver_name': 'Firefox', 43 | 'options': {'param': 'value'}, 44 | } 45 | before_scenario = behave_webdriver.fixtures.before_scenario_factory(*args, **kwargs) 46 | ctx = mock.MagicMock() 47 | scenario = mock.MagicMock() 48 | before_scenario(ctx, scenario) 49 | assert mock_use_fixture.called_with(ctx, *args, **kwargs) 50 | -------------------------------------------------------------------------------- /tests/unittests/test_from.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import mock 3 | import sys 4 | import os 5 | present_dir = os.path.dirname(os.path.realpath(__file__)) 6 | root_dir = os.path.abspath(os.path.join(present_dir, '..', '..')) 7 | sys.path.insert(0, root_dir) 8 | import behave_webdriver.utils 9 | 10 | driver_names = ['Chrome', 'Firefox', 'Ie', 'Edge', 'Opera', 'Safari', 'BlackBerry', 'PhantomJS', 'Android', 'Remote'] 11 | 12 | 13 | @pytest.mark.parametrize("driver_name", driver_names) 14 | def test_browser_from_string(driver_name): 15 | driver_qual_name = 'behave_webdriver.utils.' + driver_name 16 | with mock.patch(driver_qual_name) as mock_driver: 17 | mock_driver.__name__ = driver_name 18 | driver = behave_webdriver.utils.from_string(driver_name) 19 | assert mock_driver.called 20 | 21 | 22 | @pytest.mark.parametrize("driver_name", driver_names) 23 | def test_browser_from_env(driver_name): 24 | driver_qual_name = 'behave_webdriver.utils.' + driver_name 25 | with mock.patch.dict(os.environ, {'BEHAVE_WEBDRIVER': driver_name}), mock.patch(driver_qual_name) as mock_driver: 26 | mock_driver.__name__ = driver_name 27 | driver = behave_webdriver.utils.from_env() 28 | assert mock_driver.called 29 | 30 | 31 | def test_default_from_env_driver_as_driver(): 32 | with mock.patch.dict(os.environ, clear=True): 33 | def_driver = behave_webdriver.utils.Chrome 34 | Driver = behave_webdriver.utils._from_env(default_driver=def_driver) 35 | assert Driver is def_driver 36 | 37 | 38 | def test_default_from_env_driver_as_string(): 39 | with mock.patch.dict(os.environ, clear=True): 40 | expected_driver = behave_webdriver.utils.Chrome 41 | Driver = behave_webdriver.utils._from_env(default_driver='Chrome') 42 | assert Driver is expected_driver 43 | 44 | 45 | def test_env_raises_for_absent_drivername(): 46 | 47 | with pytest.raises(ValueError) as excinfo, mock.patch.dict(os.environ, clear=True): 48 | driver = behave_webdriver.utils._from_env() 49 | assert "No driver found in environment variables and no default" in str(excinfo.value) 50 | 51 | 52 | def test_string_raises_for_invalid_drivername_and_contains_options(): 53 | with pytest.raises(ValueError) as excinfo: 54 | driver = behave_webdriver.utils._from_string('foo') 55 | assert 'No such driver "foo"' in str(excinfo.value) 56 | assert all(dname.upper() in str(excinfo.value) for dname in driver_names) 57 | -------------------------------------------------------------------------------- /tests/unittests/test_parameter_trasformations.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import sys 3 | import os 4 | present_dir = os.path.dirname(os.path.realpath(__file__)) 5 | root_dir = os.path.abspath(os.path.join(present_dir, '..', '..')) 6 | sys.path.insert(0, root_dir) 7 | from behave_webdriver.transformers import FormatTransformer 8 | --------------------------------------------------------------------------------