├── .coveragerc ├── .github ├── FUNDING.yml └── workflows │ ├── deploy-mkdocs-poetry.yml │ ├── linters.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── .pylint-disabled-rules ├── .run ├── build.sh ├── bump_build_publish.sh ├── bump_version.sh ├── lint_black.sh ├── lint_mypy.sh ├── lint_pycodestyle.sh ├── lint_pylint.sh ├── publish.sh └── serve_docs.sh ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── assets │ └── images │ │ ├── favicon.png │ │ ├── logo-icon.png │ │ ├── selene-for-page-objects-guide.md │ │ ├── with-autocomplete-py.png │ │ ├── with-quick-fix-py.png │ │ └── without-autocomplete-py.png │ │ ├── selene-in-action-tutorial.md │ │ ├── todomvc-app-inspect.png │ │ ├── todomvc-app.png │ │ └── view-page-source-of-todomvc-with-no-todos-in-chrome.png │ │ └── selene-quick-start-tutorial.md │ │ ├── autocomplete-browser.png │ │ ├── autocomplete-condition.png │ │ ├── autocomplete-selectors.jpg │ │ ├── autocomplete-selene-element.png │ │ ├── collapse-child-element.png │ │ ├── compose-css-selector.png │ │ ├── compose-selector-child-element.png │ │ ├── compose-selector-parent-element.png │ │ ├── configuring-test-runner.png │ │ ├── context-menu-inspect.png │ │ ├── context-menu-new-python-file.png │ │ ├── find-parent-element.png │ │ ├── html-element-highlighted.png │ │ ├── new-python-file-created.png │ │ ├── new-python-file-name.png │ │ ├── run-test-from-pycharm.png │ │ ├── search-for-html-element.png │ │ └── select-attribute.png ├── changelog.md ├── contribution │ ├── assets │ │ ├── github-actions-url-to-docs.png │ │ └── github-pages-via-actions.png │ ├── code-conventions-guide.md │ ├── documentation-for-project-tutorial.md │ ├── how-to-organize-docs-guide.md │ ├── how-to-write-docs-guide.md │ ├── index.md │ ├── release-workflow-guide.md │ ├── to-documentation-guide.md │ └── to-source-code-guide.md ├── faq │ ├── adding-chrome-extension-howto.md │ ├── clipboard-copy-and-paste-howto.md │ ├── custom-test-id-selectors-howto.md │ ├── custom-user-profile-howto.md │ ├── extending-selene-howto.md │ ├── iframes-howto.md │ ├── index.md │ └── shadow-dom-howto.md ├── index.md ├── license.md ├── reference │ ├── command.md │ ├── condition.md │ ├── configuration.md │ ├── exceptions.md │ ├── index.md │ ├── match.md │ ├── query.md │ └── web │ │ └── elements.md ├── selene-cheetsheet-howto.md ├── selene-for-page-objects-guide.md ├── selene-in-action-tutorial.md ├── selene-quick-start-tutorial.md └── warn-from-next-release.md ├── examples ├── __init__.py ├── custom_condition_be_visible_in_view_port │ ├── __init__.py │ ├── framework │ │ ├── __init__.py │ │ └── extensions │ │ │ ├── __init__.py │ │ │ └── selene │ │ │ ├── __init__.py │ │ │ └── be.py │ └── tests │ │ ├── __init__.py │ │ └── test_the_internet_app.py ├── custom_conditions.py ├── extend_selene_conditions__framework │ ├── __init__.py │ ├── demoqa_tests │ │ ├── __init__.py │ │ └── extensions │ │ │ ├── __init__.py │ │ │ └── selene │ │ │ ├── __init__.py │ │ │ └── conditions │ │ │ ├── __init__.py │ │ │ └── have.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_datepicker.py ├── log_all_selene_commands_with_wait.py ├── log_all_selene_commands_with_wait__framework │ ├── __init__.py │ ├── framework │ │ ├── __init__.py │ │ └── extensions │ │ │ ├── __init__.py │ │ │ ├── python │ │ │ ├── __init__.py │ │ │ └── logging.py │ │ │ └── selene.py │ └── tests │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_todomvc.py ├── run_cross_platform │ ├── .env.example │ ├── __init__.py │ ├── project.py │ ├── tests │ │ ├── __init__.py │ │ ├── acceptance_test.py │ │ └── conftest.py │ └── wikipedia_e2e_tests │ │ ├── __init__.py │ │ └── utils │ │ ├── __init__.py │ │ └── locators.py ├── run_cross_platform_android_ios │ ├── README.md │ ├── __init__.py │ ├── project.py │ ├── tests │ │ ├── __init__.py │ │ ├── acceptance_test.py │ │ └── conftest.py │ └── wikipedia_app_tests │ │ ├── __init__.py │ │ └── support │ │ ├── __init__.py │ │ ├── mobile_selectors.py │ │ └── path.py ├── run_cross_platform_with_fixture_and_custom_location_strategy │ ├── __init__.py │ └── todo_implement.py ├── run_local_android_app_with_options_for_appium │ ├── __init__.py │ └── test_wikipedia.py ├── run_local_android_chrome_with_options_for_appium │ ├── __init__.py │ ├── version_1__explicit_appium_driver_init_and_set │ │ ├── __init__.py │ │ └── test_todomvc.py │ ├── version_2__implicit_appium_driver_init_and_set_with_explicit_url │ │ ├── __init__.py │ │ └── test_todomvc.py │ └── version_3__implicit_and_most_minimal │ │ ├── __init__.py │ │ └── test_todomvc.py ├── run_local_in_chrome_with_options_for_headless │ ├── __init__.py │ └── test_todomvc.py ├── run_local_in_default_chrome │ ├── __init__.py │ └── test_todomvc.py ├── run_local_in_firefox_via_set_config_driver_options │ ├── __init__.py │ └── test_todomvc.py ├── run_local_in_firefox_via_set_config_name │ ├── __init__.py │ └── test_todomvc.py ├── run_local_in_safari_via_set_config_driver │ ├── __init__.py │ └── test_todomvc.py ├── run_remote_in_android_app_with_options_for_browserstack │ ├── __init__.py │ ├── version_1__original_selene_browser_style │ │ ├── __init__.py │ │ └── test_wikipedia.py │ ├── version_2__mobile_alias_style │ │ ├── __init__.py │ │ └── test_wikipedia.py │ ├── version_3__app_copy_style │ │ ├── __init__.py │ │ └── test_wikipedia.py │ └── version_4__app_fixture_style │ │ ├── __init__.py │ │ └── test_wikipedia.py ├── run_remote_in_android_chrome_with_options_for_browserstack │ ├── __init__.py │ └── test_todomvc.py ├── run_remote_in_ios_safari_with_options_for_browserstack │ ├── __init__.py │ └── test_todomvc.py ├── run_remote_in_web_chrome_with_options_for_selenoid │ ├── __init__.py │ └── test_todomvc.py └── select_from_table.py ├── mkdocs.yml ├── poetry.lock ├── pyproject.toml ├── selene ├── __init__.py ├── _managed.py ├── api │ ├── __init__.py │ ├── base │ │ └── __init__.py │ └── shared │ │ └── __init__.py ├── common │ ├── __init__.py │ ├── _typing_functions.py │ ├── appium_tools.py │ ├── data_structures │ │ ├── __init__.py │ │ └── persistent.py │ ├── fp.py │ ├── helpers.py │ ├── none_object.py │ └── predicate.py ├── core │ ├── __init__.py │ ├── _actions.py │ ├── _browser.py │ ├── _browser.pyi │ ├── _client.py │ ├── _element.py │ ├── _elements.py │ ├── _elements_context.py │ ├── _entity.py │ ├── command.py │ ├── condition.py │ ├── conditions.py │ ├── configuration.py │ ├── configuration.pyi │ ├── entity.py │ ├── exceptions.py │ ├── locator.py │ ├── match.py │ ├── query.py │ └── wait.py ├── py.typed ├── support │ ├── __init__.py │ ├── _extensions │ │ └── __init__.py │ ├── _logging.py │ ├── _mobile │ │ ├── __init__.py │ │ ├── context.py │ │ ├── elements.py │ │ └── locators.py │ ├── _pom.py │ ├── _wait.py │ ├── by.py │ ├── conditions │ │ ├── __init__.py │ │ ├── be.py │ │ ├── have.py │ │ └── not_.py │ ├── shared │ │ ├── __init__.py │ │ ├── browser.py │ │ ├── config.py │ │ └── jquery_style.py │ └── webdriver.py └── web │ ├── __init__.py │ ├── _context.py │ ├── _context.pyi │ ├── _elements.py │ └── _elements.pyi └── tests ├── __init__.py ├── acceptance ├── __init__.py ├── acceptance_straightforward_test.py ├── custom_driver_management_via_callable │ ├── __init__.py │ └── test_adding_todos.py ├── custom_driver_management_via_descriptor │ ├── __init__.py │ └── test_adding_todos.py ├── custom_driver_via_fixtures │ ├── conftest.py │ └── test_ecosia.py ├── driver_management_test.py ├── helpers │ ├── __init__.py │ ├── givenpage.py │ └── helper.py ├── legacy_shared_browser_support │ ├── __init__.py │ └── test_todomvc.py ├── speed_comparison_of_simple_element_actions_test.py └── test_search.py ├── adhoc ├── __init__.py ├── straightforward_style_test.py ├── test_logging_outer_html.py └── test_shared_config_base_url.py ├── base_test.py ├── bys_test.py ├── conftest.py ├── const.py ├── examples ├── __init__.py ├── pom │ ├── __init__.py │ ├── test_material_ui__react_select.py │ ├── test_material_ui__react_select__explicit_PO_browser_based__with_alias.py │ ├── test_material_ui__react_select__explicit_PO_browser_based__with_lambda_on_self.py │ └── test_material_ui__react_x_data_grid__mit.py ├── test_matching.py ├── todomvc │ ├── __init__.py │ ├── pagemodules_approach │ │ ├── __init__.py │ │ ├── pages │ │ │ ├── __init__.py │ │ │ └── tasks.py │ │ └── todomvc_test.py │ ├── straightforward_approach │ │ ├── __init__.py │ │ └── todomvc_test.py │ ├── straightforward_approach_with_manual_shared_driver_via_basetest │ │ ├── __init__.py │ │ └── todomvc_test.py │ └── straightforward_in_firefox │ │ ├── __init__.py │ │ └── todomvc_test.py └── widgets_aka_components_page_objects_style_for_spa_apps │ ├── __init__.py │ ├── model │ ├── __init__.py │ └── widgets.py │ └── order_test.py ├── helpers.py ├── integration ├── __init__.py ├── browser__actions_test.py ├── browser__execute_script_test.py ├── browser__open_test.py ├── browser__should_test.py ├── browser__switch_to__alert__local_test.py ├── browser__switch_to__alert__remote_test.py ├── by_text_test.py ├── collection__count_test.py ├── collection__element_by_condition__lazy_search_test.py ├── collection__element_by_condition__waiting_search_on_actions_like_click_test.py ├── collection__filtered_by_condition__lazy_search_test.py ├── collection__filtered_by_condition__len_test.py ├── collection__filtered_by_condition__no_waiting_search_test.py ├── collection__get__query__inner_htmls_test.py ├── collection__get__query__outer_htmls_test.py ├── collection__get__query__texts_content_test.py ├── collection__get__query__texts_test.py ├── collection__get__query__values_test.py ├── collection__indexed_element__lazy_search_test.py ├── collection__indexed_element__waiting_search_on_actions_like_click_test.py ├── collection__inner_collection__nowaiting_search_test.py ├── collection__inner_collection_lazy_search_test.py ├── collection__lazy_search_test.py ├── collection__len_test.py ├── collection__nowaiting_search_test.py ├── command__copy__paste_test.py ├── command__js__set_style_property__test.py ├── command__select_all_test.py ├── condition__browser__have_url_test.py ├── condition__collection__have_exact_texts_like__with_ellipsis_globbing_test.py ├── condition__collection__have_exact_texts_test.py ├── condition__collection__have_texts_like__with_items_globs_regex_patterns_and_wildcards_test.py ├── condition__collection__have_texts_test.py ├── condition__collection__have_values_test.py ├── condition__element__enabled__plus_inversions_and_aliases_test.py ├── condition__element__have_css_class_test.py ├── condition__element__have_exact_text_test.py ├── condition__element__have_text_matching__compared_test.py ├── condition__element__is_blank_test.py ├── condition__element__present__via_inline_Match_test.py ├── condition__element__present_visible__plus_inversions__compared_test.py ├── condition__element__selected__plus_inversions_test.py ├── condition__elements__have_attribute_and_co_test.py ├── condition__elements__have_property_and_co_test.py ├── condition__elements__have_text_and_co_test.py ├── condition__mixed_test.py ├── condition_each_test.py ├── conftest.py ├── core_wait_test.py ├── element__all__len_test.py ├── element__click_test.py ├── element__element__lazy_search_test.py ├── element__element__waiting_search_on_actions_like_click_test.py ├── element__get__query__frame_context__decorator_test.py ├── element__get__query__frame_context__element_test.py ├── element__get__query__frame_context__nested__element_test.py ├── element__get__query__frame_context__nested__with_test.py ├── element__get__query__frame_context__with_test.py ├── element__get__query__js__shadow_root__all_elements_test.py ├── element__get__query__js__shadow_root__element_test.py ├── element__lazy_search_test.py ├── element__perform__drag_and_drop_by_offset_test.py ├── element__perform__drag_and_drop_to_test.py ├── element__perform__js__click_test.py ├── element__perform__js__drag_and_drop_to_test.py ├── element__perform__js__drop_file_test.py ├── element__perform__select_all_test.py ├── element__scroll_to__test.py ├── element__type_test.py ├── element__waiting_search_on_actions_like_click_test.py ├── elements__shadow_root__element_test.py ├── error_messages_test.py ├── helpers │ ├── __init__.py │ └── givenpage.py ├── query_size_test.py ├── save_page_source_test.py └── shared_browser │ ├── __init__.py │ ├── browser__config__wait_decorator_test.py │ ├── browser__config__wait_decorator_with_decorator_from_support_logging_test.py │ ├── browser__open_test.py │ └── save_screenshot_test.py ├── resources ├── README.md ├── __init__.py ├── empty.html ├── orderapp │ ├── app.js │ ├── jquery.min.js │ └── order.html └── selenite.png └── unit ├── __init__.py ├── common ├── __init__.py ├── _typing_fucntions__query_test.py ├── data_structures │ ├── __init__.py │ └── test_persistent.py ├── fp__either_test.py ├── fp__threads_test.py └── test_predicate.py ├── core ├── __init__.py ├── _pom_test.py ├── command__js__click_test.py ├── condition_test.py └── test_wait.py └── support └── __init__.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | 4 | [report] 5 | exclude_lines = 6 | pass 7 | raise NoSuchElementException 8 | raise NotImplementedError 9 | if 0: 10 | def __str__ 11 | pragma: no cover -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: yashaka 2 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | on: 3 | release: 4 | types: [ published ] 5 | 6 | jobs: 7 | build_and_publish: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: abatilo/actions-poetry@v2 12 | 13 | - name: Bump version 14 | run: | 15 | version=${{ github.event.release.tag_name }} 16 | poetry version $version 17 | sed -i "s/__version__ = .*/__version__ = \'${version}\'/g" ./selene/__init__.py 18 | 19 | - name: Commit version 20 | uses: EndBug/add-and-commit@v9 21 | with: 22 | add: '["pyproject.toml", "selene/__init__.py"]' 23 | branch: 'master' 24 | message: 'bump version up to ${{ github.event.release.tag_name }}' 25 | 26 | - name: Build and publish to pypi 27 | uses: JRubics/poetry-publish@v1.16 28 | with: 29 | pypi_token: ${{ secrets.PYPI_TOKEN }} 30 | -------------------------------------------------------------------------------- /.pylint-disabled-rules: -------------------------------------------------------------------------------- 1 | missing-function-docstring, 2 | no-name-in-module, 3 | line-too-long, 4 | fixme, 5 | import-error, 6 | missing-module-docstring, 7 | invalid-name, 8 | missing-class-docstring, 9 | unused-argument, 10 | unused-wildcard-import, 11 | undefined-variable, 12 | duplicate-code, 13 | unused-import, 14 | redefined-outer-name, 15 | useless-object-inheritance, 16 | broad-except, 17 | wrong-import-position, 18 | trailing-newlines, 19 | wildcard-import, 20 | ungrouped-imports, 21 | protected-access, 22 | global-statement, 23 | redefined-builtin, 24 | unnecessary-lambda, 25 | too-many-instance-attributes, 26 | pointless-string-statement, 27 | no-member, 28 | missing-final-newline, 29 | keyword-arg-before-vararg, 30 | function-redefined, 31 | wrong-import-order, 32 | useless-super-delegation, 33 | unused-variable, 34 | unnecessary-pass, 35 | trailing-whitespace, 36 | too-many-arguments, 37 | arguments-differ, 38 | useless-return, 39 | superfluous-parens, 40 | super-init-not-called, 41 | reimported, 42 | multiple-statements, 43 | E1120, 44 | global-variable-not-assigned, 45 | consider-using-f-string, 46 | unsubscriptable-object, 47 | unnecessary-lambda-assignment, 48 | unnecessary-dunder-call, 49 | unnecessary-ellipsis, 50 | unused-private-member, 51 | typevar-name-incorrect-variance, 52 | anomalous-backslash-in-string, 53 | use-dict-literal, 54 | too-many-statements, 55 | attribute-defined-outside-init, 56 | too-many-return-statements, 57 | -------------------------------------------------------------------------------- /.run/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | poetry build 3 | -------------------------------------------------------------------------------- /.run/bump_build_publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | bash .run/bump_version.sh $1 3 | bash .run/build.sh 4 | bash .rub/publish.sh 5 | -------------------------------------------------------------------------------- /.run/bump_version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version=$1 3 | if [[ $1 == '' ]]; then 4 | echo -n 'please specify selene version: ' 5 | read text 6 | version=$text 7 | fi 8 | poetry version $version 9 | new_vers=$(cat pyproject.toml | grep "^version = \"*\"" | cut -d'"' -f2) 10 | sed -i "" "s/__version__ = .*/__version__ = \'${new_vers}\'/g" selene/__init__.py 11 | -------------------------------------------------------------------------------- /.run/lint_black.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | black . --check --diff 3 | -------------------------------------------------------------------------------- /.run/lint_mypy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mypy . --follow-imports=skip --ignore-missing-imports 4 | -------------------------------------------------------------------------------- /.run/lint_pycodestyle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pycodestyle $(pwd) --ignore=E501,W503,E402,E731,E203,E704,W605 --exclude=.venv 4 | -------------------------------------------------------------------------------- /.run/lint_pylint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | touch __init__.py 4 | pylint $(pwd) --disable="$(cat .pylint-disabled-rules)" --ignore-paths=\.venv --ignore-patterns=.*\.pyi -r n 5 | rm __init__.py 6 | -------------------------------------------------------------------------------- /.run/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | poetry publish 3 | -------------------------------------------------------------------------------- /.run/serve_docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdocs serve 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /docs/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/favicon.png -------------------------------------------------------------------------------- /docs/assets/images/logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/logo-icon.png -------------------------------------------------------------------------------- /docs/assets/images/selene-for-page-objects-guide.md/with-autocomplete-py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-for-page-objects-guide.md/with-autocomplete-py.png -------------------------------------------------------------------------------- /docs/assets/images/selene-for-page-objects-guide.md/with-quick-fix-py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-for-page-objects-guide.md/with-quick-fix-py.png -------------------------------------------------------------------------------- /docs/assets/images/selene-for-page-objects-guide.md/without-autocomplete-py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-for-page-objects-guide.md/without-autocomplete-py.png -------------------------------------------------------------------------------- /docs/assets/images/selene-in-action-tutorial.md/todomvc-app-inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-in-action-tutorial.md/todomvc-app-inspect.png -------------------------------------------------------------------------------- /docs/assets/images/selene-in-action-tutorial.md/todomvc-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-in-action-tutorial.md/todomvc-app.png -------------------------------------------------------------------------------- /docs/assets/images/selene-in-action-tutorial.md/view-page-source-of-todomvc-with-no-todos-in-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-in-action-tutorial.md/view-page-source-of-todomvc-with-no-todos-in-chrome.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/autocomplete-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/autocomplete-browser.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/autocomplete-condition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/autocomplete-condition.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/autocomplete-selectors.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/autocomplete-selectors.jpg -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/autocomplete-selene-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/autocomplete-selene-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/collapse-child-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/collapse-child-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/compose-css-selector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/compose-css-selector.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/compose-selector-child-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/compose-selector-child-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/compose-selector-parent-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/compose-selector-parent-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/configuring-test-runner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/configuring-test-runner.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/context-menu-inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/context-menu-inspect.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/context-menu-new-python-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/context-menu-new-python-file.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/find-parent-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/find-parent-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/html-element-highlighted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/html-element-highlighted.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/new-python-file-created.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/new-python-file-created.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/new-python-file-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/new-python-file-name.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/run-test-from-pycharm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/run-test-from-pycharm.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/search-for-html-element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/search-for-html-element.png -------------------------------------------------------------------------------- /docs/assets/images/selene-quick-start-tutorial.md/select-attribute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/assets/images/selene-quick-start-tutorial.md/select-attribute.png -------------------------------------------------------------------------------- /docs/changelog.md: -------------------------------------------------------------------------------- 1 | --8<-- "../CHANGELOG.md" 2 | -------------------------------------------------------------------------------- /docs/contribution/assets/github-actions-url-to-docs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/contribution/assets/github-actions-url-to-docs.png -------------------------------------------------------------------------------- /docs/contribution/assets/github-pages-via-actions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/docs/contribution/assets/github-pages-via-actions.png -------------------------------------------------------------------------------- /docs/contribution/code-conventions-guide.md: -------------------------------------------------------------------------------- 1 | # Code conventions 2 | 3 | We follow the principles of consistency and readability. 4 | Code-style is controlled by few linter jobs in GitHub Actions. 5 | 6 | 1. [Pycodestyle][pycodestyle] 7 | 2. [Pylint][pylint] 8 | 3. [Black][black] 9 | 10 | [pycodestyle]: https://github.com/PyCQA/pycodestyle 11 | [pylint]: https://github.com/PyCQA/pylint 12 | [black]: https://github.com/psf/black 13 | 14 | ## Pycodestyle 15 | 16 | - Protects the code from violations of agreed rules. 17 | - Ignores E402,E731 rules for now. 18 | 19 | ## Pycodestyle-full-report 20 | 21 | - Prints a full report of pycodestyle rule violations, 22 | including not agreed yet. 23 | 24 | ## Pylint 25 | 26 | - Protects the code from violations of agreed rules. 27 | - Lints all agreed rules configured in [.pylintrc][selene-pylintrc] 28 | - Ignores list of rules which are not agreed yet 29 | [.pylint-disabled-rules][selene-pylint-disabled-rules] 30 | 31 | [selene-pylintrc]: https://github.com/yashaka/selene/blob/master/.pylintrc 32 | [selene-pylint-disabled-rules]: https://github.com/yashaka/selene/blob/master/.pylint-disabled-rules 33 | 34 | ## Pylint-full-report 35 | 36 | - Lints all agreed rules configured in 37 | [.pylintrc][selene-pylintrc] 38 | - Prints a full report of pylint rule violations, 39 | including not agreed yet. 40 | 41 | ## Black 42 | 43 | - Lints default black rules except "string normalization". 44 | -------------------------------------------------------------------------------- /docs/contribution/index.md: -------------------------------------------------------------------------------- 1 | # Selene contribution guides 2 | 3 | ## Contribute to source code 4 | 5 | - [How to contribute](to-source-code-guide.md) 6 | - [Code conventions](code-conventions-guide.md) 7 | - [Release workflow](release-workflow-guide.md) 8 | 9 | ## Contribute to documentation 10 | 11 | - [How to contribute to documentation](to-documentation-guide.md) 12 | - [How to organize documentation](how-to-organize-docs-guide.md) 13 | - [How to write documentation](how-to-write-docs-guide.md) 14 | 15 | ## Project related documents 16 | 17 | - [How to add documentation to your project](documentation-for-project-tutorial.md) 18 | -------------------------------------------------------------------------------- /docs/contribution/release-workflow-guide.md: -------------------------------------------------------------------------------- 1 | # Release workflow 2 | 3 | First make sure you described release notes in CHANGELOG.md. 4 | 5 | ## Automatically 6 | 7 | 1. Create new release on GitHub. 8 | 2. Choose new tag or take not released yet. 9 | 10 | Name it without `v` prefix, like: `1.0.1`, `2.0.0a39` 11 | 12 | 3. Set "release title" to same value as tag name 13 | 14 | (in order to fully render at Releases section on main github project page, 15 | and be consistent with other common github projects) 16 | 17 | 4. Describe release notes. 18 | 19 | Give it a short summary as `# ` 20 | Provide details (usually copied from CHANGELOG) 21 | (if they are not the same as summary ;) 22 | 23 | 5. Select pre-release checkbox if not stable. 24 | 25 | (Currently the 2.0.0a* alpha versions can also be marked 26 | as stable) 27 | 28 | 6. Publish the release on GitHub. 29 | 30 | Then GitHub action will automatically build and publish release 31 | to PyPI with selected tag automatically. 32 | Also it will commit the tag semver 33 | into `__init__.py` and `pyproject.toml` 34 | before building if it has not been there yet. 35 | 36 | ## Old fashion manually 37 | 38 | (only if GitHub Actions CI is not available) 39 | 40 | 1. bump version via `bash .run/bump_version.sh x.x.x` 41 | 2. build via `bash .run/build.sh` 42 | 3. publish via `bash .run/publish.sh` 43 | 44 | or 45 | 46 | `bash .run/bump_build_publish.sh x.x.x` 47 | 48 | or if you want to control all by yourself 49 | 50 | 1. manually bump version in `pyproject.toml` and `selene/__init.py:__version__` 51 | 2. `poetry publish --build` 52 | 53 | Also don't forget to push a tag and describe release notes on GitHub! 54 | (If GitHub Actions works then publish job will fail 55 | because same version had already been published on pypi.org) 56 | -------------------------------------------------------------------------------- /docs/contribution/to-source-code-guide.md: -------------------------------------------------------------------------------- 1 | --8<-- "../CONTRIBUTING.md:githubSection" 2 | 3 | 4 | 5 | 6 | [code-conventions]: code-conventions-guide.md 7 | [release-workflow]: release-workflow-guide.md 8 | 9 | -------------------------------------------------------------------------------- /docs/faq/custom-test-id-selectors-howto.md: -------------------------------------------------------------------------------- 1 | # How to simplify search by Test IDs in Selene? 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | – By customizing [config.selector_to_by_strategy][selene.core.configuration.Config.selector_to_by_strategy] as simply as: 6 | 7 | ```python 8 | # tests/conftest.py 9 | import re 10 | import pytest 11 | import selene 12 | from selene.common.helpers import _HTML_TAGS 13 | 14 | 15 | @pytest.fixture(scope="function", autouse=True) 16 | def browser_management(): 17 | selene.browser.config.selector_to_by_strategy = lambda selector: ( 18 | # wrap into default strategy (to not redefine from scratch auto-xpath-detection) 19 | selene.browser.config.selector_to_by_strategy( 20 | # detected testid 21 | f"[data-testid={selector}]" # ⬅️ 22 | if re.match( 23 | # word_with_dashes_underscores_or_numbers 24 | r"^[a-zA-Z_\d\-]+$", 25 | selector, 26 | ) 27 | and selector not in _HTML_TAGS 28 | else selector 29 | ) 30 | ) 31 | 32 | yield 33 | 34 | selene.browser.quit() 35 | ``` 36 | 37 | You can adapt this code to the specific name of you test id attribute, e.g. `data-test-id`, `data-test`, `data-qa`, `test-id`, `testid`, etc. 38 | 39 | Thus, you can use: 40 | 41 | ```python 42 | # tests/test_duckduckgo.py 43 | from selene import browser, by, have 44 | 45 | 46 | def test_search(): 47 | browser.open('https://www.duckduckgo.org/') 48 | browser.element('[name=q]').type('github yashaka selene python').press_enter() 49 | 50 | browser.all('result').first.element('result-title-a').click() # 💡😇 51 | 52 | browser.should(have.title_containing('yashaka/selene')) 53 | ``` 54 | 55 | over: 56 | 57 | ```python 58 | # tests/test_duckduckgo.py 59 | from selene import browser, by, have 60 | 61 | 62 | def test_search(): 63 | browser.open('https://www.duckduckgo.org/') 64 | browser.element('[name=q]').type('github yashaka selene python').press_enter() 65 | 66 | browser.all('[data-testid=result]').first.element( # 🙈 67 | '[data-testid=result-title-a]' # 🙈 68 | ).click() 69 | 70 | browser.should(have.title_containing('yashaka/selene')) 71 | ``` 72 | 73 | See a bigger example of utilizing same technique with [Page Object Model pattern applied to the DataGrid React component](https://github.com/yashaka/selene/blob/master/tests/examples/pom/test_material_ui__react_x_data_grid__mit.py). 74 | -------------------------------------------------------------------------------- /docs/faq/extending-selene-howto.md: -------------------------------------------------------------------------------- 1 | # How to extend Selene? 2 | 3 | !!! note 4 | This document is a work in progress. In meantime, you can check this [example of "monkey patching"](https://github.com/qa-guru/mobile-tests-13-py/tree/main/mobile_tests_lesson_13/utils/selene) approach to extend Selene with custom commands for mobile context (like `tap` and `long_press`) and custom location strategy. 5 | -------------------------------------------------------------------------------- /docs/faq/index.md: -------------------------------------------------------------------------------- 1 | # Selene FAQ 2 | 3 | ## Browser configuration 4 | 5 | - [How to work with iFrames](iframes-howto.md) 6 | - [How to work with Shadow DOM](shadow-dom-howto.md) 7 | - [How to use custom profile](custom-user-profile-howto.md) 8 | - [Ho to extend Selene](extending-selene-howto.md) 9 | -------------------------------------------------------------------------------- /docs/faq/shadow-dom-howto.md: -------------------------------------------------------------------------------- 1 | # How to work with Shadow DOM in Selene? 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ## Via built-in WebDriver-based properties 6 | 7 | – As simply as: 8 | 9 | ```python 10 | from selene import browser, have 11 | 12 | # GIVEN 13 | paragraphs = browser.all('my-paragraph') 14 | 15 | # WHEN it's enough to access specific elements 16 | paragraph_2_shadow = paragraphs.second.shadow_root # 💡 17 | my_shadowed_text_2 = paragraph_2_shadow.element('[name=my-text]') 18 | # OR when you need all shadow roots 19 | my_shadowed_texts = paragraphs.shadow_roots.all('[name=my-text]') # 💡 20 | 21 | # As you can see these queries are lazy, 22 | # so you were able to store them in vars ↖️ 23 | # even before open ↙️ 24 | browser.open('https://the-internet.herokuapp.com/shadowdom') 25 | 26 | # THEN 27 | my_shadowed_text_2.should(have.exact_text("My default text")) # ⬅️ 28 | my_shadowed_texts.should(have.exact_texts("My default text", "My default text")) # ⬅️ 29 | ``` 30 | 31 | ## Via JavaScript queries at query.js.* 32 | 33 | – By using advanced [query.js.shadow_root][selene.core.query.js.shadow_root] and [query.js.shadow_roots][selene.core.query.js.shadow_roots] queries, as simply as: 34 | 35 | ```python 36 | from selene import browser, have, query 37 | 38 | # GIVEN 39 | paragraphs = browser.all('my-paragraph') 40 | 41 | # WHEN it's enough to access specific elements 42 | paragraph_2_shadow = paragraphs.second.get(query.js.shadow_root) # 💡 43 | my_shadowed_text_2 = paragraph_2_shadow.element('[name=my-text]') 44 | # OR when you need all shadow roots 45 | my_shadowed_texts = paragraphs.get(query.js.shadow_roots).all('[name=my-text]') # 💡 46 | 47 | # As you can see these queries are lazy, 48 | # so you were able to store them in vars ↖️ 49 | # even before open ↙️ 50 | browser.open('https://the-internet.herokuapp.com/shadowdom') 51 | 52 | # THEN 53 | my_shadowed_text_2.should(have.exact_text("My default text")) # ⬅️ 54 | my_shadowed_texts.should(have.exact_texts("My default text", "My default text")) # ⬅️ 55 | ``` 56 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --8<-- "../README.md:githubSection" 2 | 3 | 4 | 5 | 6 | [contribution]: contribution/to-source-code-guide.md 7 | [release-workflow]: contribution/release-workflow-guide.md 8 | [changelog]: changelog.md 9 | 10 | -------------------------------------------------------------------------------- /docs/license.md: -------------------------------------------------------------------------------- 1 | --8<-- "../LICENSE" 2 | -------------------------------------------------------------------------------- /docs/reference/command.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ::: selene.core.command 4 | options: 5 | show_root_toc_entry: false 6 | show_if_no_docstring: true 7 | members_order: alphabetical 8 | filters: 9 | - "!__.*" 10 | -------------------------------------------------------------------------------- /docs/reference/condition.md: -------------------------------------------------------------------------------- 1 | # Expected conditions 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ::: selene.core.condition 6 | options: 7 | show_root_toc_entry: false 8 | show_if_no_docstring: false 9 | members_order: alphabetical 10 | filters: 11 | - "!__.*" 12 | -------------------------------------------------------------------------------- /docs/reference/exceptions.md: -------------------------------------------------------------------------------- 1 | # Selene Exceptions 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ::: selene.core.exceptions 6 | options: 7 | show_root_toc_entry: false 8 | show_if_no_docstring: true 9 | members_order: alphabetical 10 | filters: 11 | - "!__.*" 12 | -------------------------------------------------------------------------------- /docs/reference/index.md: -------------------------------------------------------------------------------- 1 | # Selene reference 2 | 3 | ## Core 4 | 5 | - [Config](configuration.md) 6 | - [Expected Conditions](condition.md) 7 | - [Command](command.md) 8 | - [Query](query.md) 9 | -------------------------------------------------------------------------------- /docs/reference/match.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ::: selene.core.match 6 | options: 7 | show_root_toc_entry: false 8 | show_if_no_docstring: true 9 | members_order: alphabetical 10 | filters: 11 | - "!E$" 12 | - "!__.*" 13 | - "!_step" 14 | - "!_steps" 15 | - "!_inner" 16 | - "!_inside" 17 | - "!_content" 18 | -------------------------------------------------------------------------------- /docs/reference/query.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ::: selene.core.query 6 | options: 7 | show_root_toc_entry: false 8 | show_if_no_docstring: true 9 | members_order: alphabetical 10 | filters: 11 | - "!__.*" 12 | - "!_step" 13 | - "!_steps" 14 | - "!_inner" 15 | - "!_inside" 16 | - "!_content" 17 | -------------------------------------------------------------------------------- /docs/reference/web/elements.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | {% include-markdown 'warn-from-next-release.md' %} 4 | 5 | ::: selene.web._elements 6 | options: 7 | show_root_toc_entry: false 8 | show_if_no_docstring: true 9 | members_order: alphabetical 10 | filters: 11 | - "!__.*" 12 | - "!_step" 13 | - "!_steps" 14 | - "!_inner" 15 | - "!_inside" 16 | - "!_content" 17 | -------------------------------------------------------------------------------- /docs/warn-from-next-release.md: -------------------------------------------------------------------------------- 1 | !!! warning "This is the latest development version" 2 | 3 | Some features documented on this page may not yet be available 4 | in the published stable version. 5 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/custom_condition_be_visible_in_view_port/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/custom_condition_be_visible_in_view_port/framework/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/framework/extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/custom_condition_be_visible_in_view_port/framework/extensions/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/framework/extensions/selene/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/custom_condition_be_visible_in_view_port/framework/extensions/selene/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/framework/extensions/selene/be.py: -------------------------------------------------------------------------------- 1 | from selene import web 2 | from selene.core.condition import Condition, Match 3 | from selene.core.conditions import ElementCondition 4 | from selene.support.conditions.be import * # noqa 5 | from selene.support.conditions.be import not_ as _original_not_ # noqa 6 | 7 | not_ = _original_not_ 8 | 9 | visible_in_viewport: Condition[web.Element] = Match( 10 | 'is visible in view port', 11 | by=lambda element: element.execute_script( 12 | ''' 13 | var rect = element.getBoundingClientRect(); 14 | 15 | return ( 16 | rect.top >= 0 && 17 | rect.left >= 0 && 18 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 19 | rect.right <= (window.innerWidth || document.documentElement.clientWidth) 20 | ); 21 | ''' 22 | ), 23 | ) 24 | 25 | not_visible_in_viewport: Condition[web.Element] = visible_in_viewport.not_ 26 | -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/custom_condition_be_visible_in_view_port/tests/__init__.py -------------------------------------------------------------------------------- /examples/custom_condition_be_visible_in_view_port/tests/test_the_internet_app.py: -------------------------------------------------------------------------------- 1 | from examples.custom_condition_be_visible_in_view_port.framework.extensions.selene import ( 2 | be, 3 | ) 4 | from selene import Browser, Config 5 | 6 | 7 | def test_tables(): 8 | browser = Browser( 9 | Config( 10 | window_width=1000, 11 | window_height=600, 12 | ) 13 | ) 14 | browser.open('https://the-internet.herokuapp.com/tables') 15 | browser.element('#table1').should(be.visible) 16 | browser.element('#table1').should(be.visible_in_viewport) 17 | browser.element('#table2').should(be.visible) 18 | browser.element('#table2').should(be.not_visible_in_viewport) 19 | 20 | try: 21 | browser.element('#table2').with_(timeout=0.1).should(be.visible_in_viewport) 22 | except Exception as e: 23 | assert '''Timed out after 0.1s, while waiting for: 24 | browser.element(('css selector', '#table2')).is visible in view port 25 | 26 | Reason: ConditionNotMatchedError: condition not matched''' in str( 27 | e 28 | ) 29 | -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/extend_selene_conditions__framework/__init__.py -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/demoqa_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/extend_selene_conditions__framework/demoqa_tests/__init__.py -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/demoqa_tests/extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/extend_selene_conditions__framework/demoqa_tests/extensions/__init__.py -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/demoqa_tests/extensions/selene/__init__.py: -------------------------------------------------------------------------------- 1 | from .conditions import have as _extended_have 2 | 3 | have = _extended_have 4 | -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/demoqa_tests/extensions/selene/conditions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/extend_selene_conditions__framework/demoqa_tests/extensions/selene/conditions/__init__.py -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/demoqa_tests/extensions/selene/conditions/have.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import sys 3 | 4 | from selene import have 5 | from selene.support.conditions.have import * # noqa 6 | 7 | 8 | def date(value: datetime.date): # noqa 9 | """ 10 | match date according to the project specific format 11 | """ 12 | month_full_name = '%B' 13 | year_full = '%Y' 14 | 15 | is_unix_like = sys.platform in ('linux', 'linux2', 'darwin') 16 | day_without_leading_zero = '%-d' if is_unix_like else '%#d' 17 | hours_without_leading_zero = '%-I' if is_unix_like else '%#I' 18 | 19 | minutes_00_59 = '%M' 20 | am_or_pm = '%p' 21 | 22 | return have.value( 23 | value.strftime( 24 | f'{month_full_name} ' 25 | f'{day_without_leading_zero}, ' 26 | f'{year_full} ' 27 | f'{hours_without_leading_zero}:' 28 | f'{minutes_00_59} ' 29 | f'{am_or_pm}' 30 | ) 31 | ) 32 | 33 | 34 | ''' 35 | # Just another versions of implementation... 36 | 37 | 38 | def date(value: datetime.date): # noqa 39 | """ 40 | match date according to the project specific format 41 | """ 42 | month_full_name = '%B' 43 | year_full = '%Y' 44 | day_01_31 = '%d' 45 | hours_00_59 = '%I' 46 | minutes_00_59 = '%M' 47 | am_or_pm = '%p' 48 | 49 | return have.value( 50 | value.strftime( 51 | f'{month_full_name} ' 52 | f'{day_01_31}, ' 53 | f'{year_full} ' 54 | f'{hours_00_59}:' 55 | f'{minutes_00_59} ' 56 | f'{am_or_pm}' 57 | ).replace(' 0', ' ') 58 | ) 59 | 60 | 61 | def date(value: datetime.date): # noqa 62 | """ 63 | match date according to the project specific format 64 | """ 65 | month_full_name = '%B' 66 | year_full = '%Y' 67 | day_without_leading_zero = str(int(value.strftime('%d'))) 68 | hours_without_leading_zero = str(int(value.strftime('%I'))) 69 | minutes_00_59 = '%M' 70 | am_or_pm = '%p' 71 | 72 | return have.value( 73 | value.strftime( 74 | f'{month_full_name} ' 75 | f'{day_without_leading_zero}, ' 76 | f'{year_full} ' 77 | f'{hours_without_leading_zero}:' 78 | f'{minutes_00_59} ' 79 | f'{am_or_pm}' 80 | ) 81 | ) 82 | ''' 83 | -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/extend_selene_conditions__framework/tests/__init__.py -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene.support.shared import browser 4 | 5 | 6 | @pytest.fixture(scope='function', autouse=True) 7 | def manage_browser(): 8 | browser.config.base_url = 'https://demoqa.com' 9 | 10 | yield 11 | 12 | ... 13 | -------------------------------------------------------------------------------- /examples/extend_selene_conditions__framework/tests/test_datepicker.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from examples.extend_selene_conditions__framework.demoqa_tests.extensions.selene import ( 3 | have, 4 | ) 5 | from selene.support.shared import browser 6 | 7 | 8 | def test_date_and_time_control(): 9 | browser.open('/date-picker') 10 | 11 | browser.element('#dateAndTimePickerInput').should( 12 | have.date(datetime.datetime.now()) 13 | ) 14 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/log_all_selene_commands_with_wait__framework/__init__.py -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/framework/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | «framework» is kind of name for you project, 3 | so rename it as needed ;) 4 | 5 | E.g. if you write e2e tests for todomvc app, 6 | you can name your project as todomvc-e2e-test, 7 | and rename this framework py package to todomvc_e2e_test... 8 | """ 9 | 10 | from . import extensions 11 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/framework/extensions/__init__.py: -------------------------------------------------------------------------------- 1 | from . import python, selene 2 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/framework/extensions/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/log_all_selene_commands_with_wait__framework/framework/extensions/python/__init__.py -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/framework/extensions/python/logging.py: -------------------------------------------------------------------------------- 1 | from logging import Formatter, LogRecord # type: ignore 2 | from functools import reduce 3 | from typing import Tuple, List 4 | 5 | 6 | class TranslatingFormatter(Formatter): 7 | translations: List[Tuple[str, str]] = [] 8 | 9 | def formatMessage(self, record: LogRecord) -> str: 10 | original = super().formatMessage(record) 11 | 12 | def translate(initial: str, item: Tuple[str, str]): 13 | old, new = item 14 | return initial.replace(old, new) 15 | 16 | return reduce( 17 | translate, 18 | self.translations, 19 | original, 20 | ) 21 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/framework/extensions/selene.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Tuple, List, Optional 3 | 4 | from examples.log_all_selene_commands_with_wait__framework.framework.extensions.python.logging import ( 5 | TranslatingFormatter, 6 | ) 7 | 8 | 9 | def log_with( 10 | logger, 11 | *, 12 | added_handler_translations: Optional[List[Tuple[str, str]]] = None, 13 | ): 14 | """ 15 | returns decorator factory with logging to specified logger 16 | with added list of translations 17 | to decorate Selene's waiting via config._wait_decorator. 18 | Example: 19 | from selene.support.shared import browser 20 | from framework.extensions.selene import log_with 21 | import logging 22 | 23 | logger = logging.getLogger('SE') 24 | browser.config._wait_decorator = log_with( 25 | logger, 26 | added_handler_translations = [ 27 | ('browser.element', 'element'), 28 | ('browser.all', 'all'), 29 | ] 30 | ) 31 | 32 | ... 33 | """ 34 | if not added_handler_translations: 35 | added_handler_translations = [] 36 | 37 | TranslatingFormatter.translations = added_handler_translations 38 | 39 | handler = logging.StreamHandler() 40 | formatter = TranslatingFormatter("[%(name)s] - %(message)s") 41 | handler.setFormatter(formatter) 42 | logger.addHandler(handler) 43 | 44 | def decorator_factory(wait): 45 | def decorator(for_): 46 | def decorated(fn): 47 | entity = wait.entity 48 | logger.info('step: %s > %s: STARTED', entity, fn) 49 | try: 50 | res = for_(fn) 51 | logger.info('step: %s > %s: ENDED', entity, fn) 52 | return res 53 | except Exception as error: 54 | logger.info('step: %s > %s: FAILED: %s', entity, fn, error) 55 | raise error 56 | 57 | return decorated 58 | 59 | return decorator 60 | 61 | return decorator_factory 62 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/log_all_selene_commands_with_wait__framework/tests/__init__.py -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import logging 3 | 4 | from examples.log_all_selene_commands_with_wait__framework.framework import ( 5 | extensions, 6 | ) 7 | from selene import browser 8 | 9 | 10 | log = logging.getLogger('SE') 11 | log.setLevel(20) 12 | 13 | on_wait_report_to_log = extensions.selene.log_with( 14 | logger=log, 15 | added_handler_translations=[ 16 | ('browser.element', 'element'), 17 | ('browser.all', 'all'), 18 | ("'css selector', ", ""), 19 | (r"('\ue007',)", "Enter"), 20 | ('((', '('), 21 | ('))', ')'), 22 | (': has ', ': have '), 23 | (': have ', ': should have '), 24 | (': is ', ': should be'), 25 | ], 26 | ) 27 | 28 | 29 | @pytest.fixture(scope='session', autouse=True) 30 | def browser_management(): 31 | browser.config._wait_decorator = on_wait_report_to_log 32 | 33 | yield 34 | -------------------------------------------------------------------------------- /examples/log_all_selene_commands_with_wait__framework/tests/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from functools import reduce 2 | from typing import Tuple 3 | 4 | import pytest 5 | 6 | from examples.log_all_selene_commands_with_wait__framework.framework.extensions.python.logging import ( 7 | TranslatingFormatter, 8 | ) 9 | from selene import browser, have 10 | from selene.core.wait import Wait 11 | import logging 12 | 13 | from tests import resources 14 | 15 | 16 | def test_add_todos(): 17 | """ 18 | should log something like: 19 | 20 | [SE] - step: element('#new-todo') > type: a: STARTED 21 | [SE] - step: element('#new-todo') > type: a: ENDED 22 | [SE] - step: element('#new-todo') > press keys: Enter: STARTED 23 | [SE] - step: element('#new-todo') > press keys: Enter: ENDED 24 | [SE] - step: element('#new-todo') > type: b: STARTED 25 | [SE] - step: element('#new-todo') > type: b: ENDED 26 | [SE] - step: element('#new-todo') > press keys: Enter: STARTED 27 | [SE] - step: element('#new-todo') > press keys: Enter: ENDED 28 | [SE] - step: element('#new-todo') > type: c: STARTED 29 | [SE] - step: element('#new-todo') > type: c: ENDED 30 | [SE] - step: element('#new-todo') > press keys: Enter: STARTED 31 | [SE] - step: element('#new-todo') > press keys: Enter: ENDED 32 | [SE] - step: all('#todo-list>li') > has texts ('ab', 'b', 'c', 'd'): STARTED 33 | [SE] - step: all('#todo-list>li') > has texts ('ab', 'b', 'c', 'd'): FAILED: Message: 34 | 35 | Timed out after 4s, while waiting for: 36 | all('#todo-list>li').has texts ('ab', 'b', 'c', 'd') 37 | 38 | 39 | """ 40 | 41 | browser.open(resources.TODOMVC_URL) 42 | 43 | browser.element('#new-todo').type('a').press_enter() 44 | browser.element('#new-todo').type('b').press_enter() 45 | browser.element('#new-todo').type('c').press_enter() 46 | 47 | browser.all('#todo-list>li').should(have.texts('ab', 'b', 'c', 'd')) 48 | -------------------------------------------------------------------------------- /examples/run_cross_platform/.env.example: -------------------------------------------------------------------------------- 1 | selenoid_login=admin 2 | selenoid_password=admin 3 | bstack_userName=adminadminovych_8kIgeh 4 | bstack_accessKey=lb96XHN96ybsqSbl6My1 5 | app_package=com.android.chrome 6 | context=local_web 7 | -------------------------------------------------------------------------------- /examples/run_cross_platform/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform/project.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | import dotenv 4 | import pydantic 5 | 6 | 7 | class Context(Enum): 8 | bstack_android = 'bstack_android' 9 | # bstack_ios = 'bstack_ios' 10 | # bstack_web = 'bstack_web' 11 | # local_android = 'local_android' 12 | # local_ios = 'local_ios' 13 | local_web = 'local_web' 14 | # selenoid_web = 'selenoid_web' 15 | 16 | 17 | class Config(pydantic.BaseSettings): 18 | context: Context = Context.bstack_android 19 | bstack_accessKey: str 20 | bstack_userName: str = 'admin' 21 | app_package: str = 'org.wikipedia.alpha' 22 | 23 | @property 24 | def bstack_creds(self): 25 | return { 26 | 'userName': self.bstack_userName, 27 | 'accessKey': self.bstack_accessKey, 28 | } 29 | 30 | 31 | config = Config(dotenv.find_dotenv()) # type: ignore 32 | -------------------------------------------------------------------------------- /examples/run_cross_platform/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform/tests/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform/tests/acceptance_test.py: -------------------------------------------------------------------------------- 1 | from examples.run_cross_platform.wikipedia_e2e_tests.utils.locators import by 2 | from selene import browser, have, be 3 | 4 | 5 | def test_wikipedia_searches(): 6 | # GIVEN 7 | browser.open() 8 | 9 | # WHEN 10 | browser.element(by.name(drd='Search Wikipedia')).click() 11 | browser.element(by.id(web='searchInput', drd='search_src_text')).type('Appium') 12 | 13 | # THEN 14 | results = browser.all(by(web='.suggestion-link', drd='#page_list_item_title')) 15 | results.should(have.size_greater_than(0)) 16 | results.first.should(have.text('Appium')) 17 | 18 | # WHEN 19 | results.first.click() 20 | 21 | # THEN 22 | browser.element(by.text('Appium')).should(be.visible) 23 | -------------------------------------------------------------------------------- /examples/run_cross_platform/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from appium.options.android import UiAutomator2Options 3 | 4 | from examples.run_cross_platform import project 5 | from selene import browser 6 | 7 | 8 | @pytest.fixture(scope='function', autouse=True) 9 | def driver_management(): 10 | # TODO: move all values to project.config 11 | if project.config.context is project.Context.bstack_android: 12 | android_options = UiAutomator2Options() 13 | android_options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c' 14 | android_options.set_capability( 15 | 'bstack:options', 16 | { 17 | 'deviceName': 'Google Pixel 7', 18 | # 'platformVersion': '13.0', 19 | **project.config.bstack_creds, 20 | }, 21 | ) 22 | browser.config.driver_options = android_options 23 | browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub' 24 | elif project.config.context is project.Context.local_web: 25 | browser.config.base_url = 'https://www.wikipedia.org' 26 | # if not set, will load nothing on browser.open() for web, but just init driver: 27 | browser.config._get_base_url_on_open_with_no_args = True 28 | 29 | yield 30 | 31 | browser.quit() 32 | -------------------------------------------------------------------------------- /examples/run_cross_platform/wikipedia_e2e_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform/wikipedia_e2e_tests/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform/wikipedia_e2e_tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform/wikipedia_e2e_tests/utils/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/README.md: -------------------------------------------------------------------------------- 1 | See more complete example with full configuration management and pageobjects at [python-mobile-test-template](https://github.com/automician/python-mobile-test-template) 2 | -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform_android_ios/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform_android_ios/tests/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/tests/acceptance_test.py: -------------------------------------------------------------------------------- 1 | from selene import have, be 2 | from selene.support._mobile import device 3 | 4 | 5 | def test_wikipedia_searches(): 6 | # GIVEN 7 | device.element('fragment_onboarding_skip_button').tap() 8 | 9 | # WHEN 10 | device.element(drd='Search Wikipedia').tap() 11 | device.element('search_src_text').type('Appium') 12 | 13 | # THEN 14 | results = device.all('page_list_item_title') 15 | results.should(have.size_greater_than(0)) 16 | results.first.should(have.text('Appium')) 17 | 18 | # WHEN 19 | results.first.tap() 20 | 21 | # THEN 22 | device.element('text=Appium').should(be.visible) 23 | -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from examples.run_cross_platform_android_ios import project 4 | from examples.run_cross_platform_android_ios.wikipedia_app_tests import support 5 | from selene.support._mobile import device 6 | 7 | 8 | @pytest.fixture(scope='function', autouse=True) 9 | def driver_management(): 10 | device.config.driver_options = project.config.to_driver_options() 11 | device.config.driver_remote_url = project.config.driver_remote_url 12 | device.config.selector_to_by_strategy = support.mobile_selectors.to_by_strategy 13 | device.config.timeout = 8.0 14 | 15 | yield 16 | 17 | device.driver.quit() 18 | -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/wikipedia_app_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform_android_ios/wikipedia_app_tests/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/wikipedia_app_tests/support/__init__.py: -------------------------------------------------------------------------------- 1 | from . import mobile_selectors 2 | from . import path 3 | -------------------------------------------------------------------------------- /examples/run_cross_platform_android_ios/wikipedia_app_tests/support/path.py: -------------------------------------------------------------------------------- 1 | def relative_from_root(path: str): 2 | from examples.run_cross_platform_android_ios import wikipedia_app_tests 3 | from pathlib import Path 4 | 5 | return ( 6 | Path(wikipedia_app_tests.__file__) 7 | .parent.parent.joinpath(path) 8 | .absolute() 9 | .__str__() 10 | ) 11 | -------------------------------------------------------------------------------- /examples/run_cross_platform_with_fixture_and_custom_location_strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform_with_fixture_and_custom_location_strategy/__init__.py -------------------------------------------------------------------------------- /examples/run_cross_platform_with_fixture_and_custom_location_strategy/todo_implement.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_cross_platform_with_fixture_and_custom_location_strategy/todo_implement.py -------------------------------------------------------------------------------- /examples/run_local_android_app_with_options_for_appium/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_android_app_with_options_for_appium/__init__.py -------------------------------------------------------------------------------- /examples/run_local_android_app_with_options_for_appium/test_wikipedia.py: -------------------------------------------------------------------------------- 1 | from appium.options.android import UiAutomator2Options 2 | from appium.webdriver.common.appiumby import AppiumBy 3 | from selene import browser, have 4 | 5 | 6 | def abs_path_at_examples(relative_path): 7 | import examples # pylint: disable=import-outside-toplevel 8 | from pathlib import Path # pylint: disable=import-outside-toplevel 9 | 10 | return Path(examples.__file__).parent.joinpath(relative_path).absolute().__str__() 11 | 12 | 13 | def test_searches(): 14 | android_options = UiAutomator2Options() 15 | android_options.new_command_timeout = 60 16 | android_options.app = abs_path_at_examples('wikipedia-alpha-universal-release.apk') 17 | android_options.app_wait_activity = 'org.wikipedia.*' 18 | # android_options.app_package = 'org.wikipedia.alpha' # not mandatory 19 | 20 | # # Possible, but not needed, because of Selene's implicit driver init logic 21 | # browser.config.driver = webdriver.Remote( 22 | # command_executor='http://127.0.0.1:4723/wd/hub', 23 | # options=android_options, 24 | # ) 25 | browser.config.driver_options = android_options 26 | # # Possible, but not needed, because will be used by default: 27 | # browser.config.driver_remote_url = 'http://127.0.0.1:4723/wd/hub' 28 | 29 | by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}') 30 | 31 | # GIVEN 32 | browser.open() 33 | browser.element(by_id('fragment_onboarding_skip_button')).click() 34 | 35 | # WHEN 36 | browser.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click() 37 | browser.element(by_id('search_src_text')).type('Appium') 38 | 39 | # THEN 40 | browser.all(by_id('page_list_item_title')).should(have.size_greater_than(0)) 41 | -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_android_chrome_with_options_for_appium/__init__.py -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_1__explicit_appium_driver_init_and_set/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_android_chrome_with_options_for_appium/version_1__explicit_appium_driver_init_and_set/__init__.py -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_1__explicit_appium_driver_init_and_set/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from appium import webdriver 2 | from appium.options.common import AppiumOptions 3 | from selene import browser, have 4 | 5 | 6 | def test_complete_task(): 7 | mobile_options = AppiumOptions() 8 | mobile_options.new_command_timeout = 60 9 | # Mandatory, also tells Selene to build Appium driver: 10 | mobile_options.platform_name = 'android' 11 | mobile_options.set_capability('browserName', 'chrome') 12 | 13 | browser.config.driver = webdriver.Remote( 14 | command_executor='http://127.0.0.1:4723/wd/hub', 15 | options=mobile_options, 16 | ) 17 | browser.config.base_url = 'https://todomvc.com/examples/emberjs' 18 | 19 | # WHEN 20 | browser.open('/') 21 | 22 | browser.should(have.title_containing('TodoMVC')) 23 | 24 | # WHEN 25 | browser.element('#new-todo').type('a').press_enter() 26 | browser.element('#new-todo').type('b').press_enter() 27 | browser.element('#new-todo').type('c').press_enter() 28 | 29 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 30 | 31 | # WHEN 32 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 33 | '.toggle' 34 | ).click() 35 | 36 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 37 | have.exact_texts('b') 38 | ) 39 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 40 | have.exact_texts('a', 'c') 41 | ) 42 | -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_2__implicit_appium_driver_init_and_set_with_explicit_url/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_android_chrome_with_options_for_appium/version_2__implicit_appium_driver_init_and_set_with_explicit_url/__init__.py -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_2__implicit_appium_driver_init_and_set_with_explicit_url/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from appium.options.common import AppiumOptions 2 | from selene import browser, have 3 | 4 | 5 | def test_complete_task(): 6 | mobile_options = AppiumOptions() 7 | mobile_options.new_command_timeout = 60 8 | # Mandatory, also tells Selene to build Appium driver: 9 | mobile_options.platform_name = 'android' 10 | mobile_options.set_capability('browserName', 'chrome') 11 | 12 | browser.config.driver_options = mobile_options 13 | # Not mandatory (because same value in defaults), but let's be explicit now: 14 | browser.config.driver_remote_url = 'http://127.0.0.1:4723/wd/hub' 15 | browser.config.base_url = 'https://todomvc.com/examples/emberjs' 16 | 17 | # WHEN 18 | browser.open('/') 19 | 20 | browser.should(have.title_containing('TodoMVC')) 21 | 22 | # WHEN 23 | browser.element('#new-todo').type('a').press_enter() 24 | browser.element('#new-todo').type('b').press_enter() 25 | browser.element('#new-todo').type('c').press_enter() 26 | 27 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 28 | 29 | # WHEN 30 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 31 | '.toggle' 32 | ).click() 33 | 34 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 35 | have.exact_texts('b') 36 | ) 37 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 38 | have.exact_texts('a', 'c') 39 | ) 40 | -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_3__implicit_and_most_minimal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_android_chrome_with_options_for_appium/version_3__implicit_and_most_minimal/__init__.py -------------------------------------------------------------------------------- /examples/run_local_android_chrome_with_options_for_appium/version_3__implicit_and_most_minimal/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from appium.options.common import AppiumOptions 2 | from selene import browser, have 3 | 4 | 5 | # Normally to be stored in a separate file, 6 | # somewhere at project_package/utils/driver_options.py 7 | def android_chrome_options(): 8 | options = AppiumOptions() 9 | options.platform_name = 'android' # mandatory 10 | options.set_capability('browserName', 'chrome') 11 | return options 12 | 13 | 14 | def test_complete_task(): 15 | browser.config.driver_options = android_chrome_options() 16 | browser.config.base_url = 'https://todomvc.com/examples/emberjs' 17 | 18 | # WHEN 19 | browser.open('/') 20 | 21 | browser.should(have.title_containing('TodoMVC')) 22 | 23 | # WHEN 24 | browser.element('#new-todo').type('a').press_enter() 25 | browser.element('#new-todo').type('b').press_enter() 26 | browser.element('#new-todo').type('c').press_enter() 27 | 28 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 29 | 30 | # WHEN 31 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 32 | '.toggle' 33 | ).click() 34 | 35 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 36 | have.exact_texts('b') 37 | ) 38 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 39 | have.exact_texts('a', 'c') 40 | ) 41 | -------------------------------------------------------------------------------- /examples/run_local_in_chrome_with_options_for_headless/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_in_chrome_with_options_for_headless/__init__.py -------------------------------------------------------------------------------- /examples/run_local_in_chrome_with_options_for_headless/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | 3 | from selene import browser, have 4 | from tests import resources 5 | 6 | 7 | def test_complete_task(): 8 | options = webdriver.ChromeOptions() 9 | options.add_argument('--headless=new') 10 | # additional options: 11 | options.add_argument('--no-sandbox') 12 | options.add_argument('--disable-gpu') 13 | options.add_argument('--disable-notifications') 14 | options.add_argument('--disable-extensions') 15 | options.add_argument('--disable-infobars') 16 | options.add_argument('--enable-automation') 17 | options.add_argument('--disable-dev-shm-usage') 18 | options.add_argument('--disable-setuid-sandbox') 19 | browser.config.driver_options = options 20 | 21 | browser.open(resources.TODOMVC_URL) 22 | browser.should(have.title_containing('TodoMVC')) 23 | 24 | browser.element('#new-todo').type('a').press_enter() 25 | browser.element('#new-todo').type('b').press_enter() 26 | browser.element('#new-todo').type('c').press_enter() 27 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 28 | 29 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 30 | '.toggle' 31 | ).click() 32 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 33 | have.exact_texts('b') 34 | ) 35 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 36 | have.exact_texts('a', 'c') 37 | ) 38 | -------------------------------------------------------------------------------- /examples/run_local_in_default_chrome/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_in_default_chrome/__init__.py -------------------------------------------------------------------------------- /examples/run_local_in_default_chrome/test_todomvc.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import browser, have 23 | from tests import resources 24 | 25 | 26 | def test_completes_todo(): 27 | browser.open(resources.TODOMVC_URL) 28 | browser.should(have.title_containing('TodoMVC')) 29 | 30 | browser.element('#new-todo').type('a').press_enter() 31 | browser.element('#new-todo').type('b').press_enter() 32 | browser.element('#new-todo').type('c').press_enter() 33 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 34 | 35 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 36 | '.toggle' 37 | ).click() 38 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 39 | have.exact_texts('b') 40 | ) 41 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 42 | have.exact_texts('a', 'c') 43 | ) 44 | -------------------------------------------------------------------------------- /examples/run_local_in_firefox_via_set_config_driver_options/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_in_firefox_via_set_config_driver_options/__init__.py -------------------------------------------------------------------------------- /examples/run_local_in_firefox_via_set_config_driver_options/test_todomvc.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selenium.webdriver import FirefoxOptions 23 | from selene import browser, have 24 | from tests import resources 25 | 26 | 27 | def test_completes_todo(): 28 | browser.config.driver_options = FirefoxOptions() 29 | 30 | browser.open(resources.TODOMVC_URL) 31 | browser.should(have.title_containing('TodoMVC')) 32 | 33 | browser.element('#new-todo').type('a').press_enter() 34 | browser.element('#new-todo').type('b').press_enter() 35 | browser.element('#new-todo').type('c').press_enter() 36 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 37 | 38 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 39 | '.toggle' 40 | ).click() 41 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 42 | have.exact_texts('b') 43 | ) 44 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 45 | have.exact_texts('a', 'c') 46 | ) 47 | -------------------------------------------------------------------------------- /examples/run_local_in_firefox_via_set_config_name/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_in_firefox_via_set_config_name/__init__.py -------------------------------------------------------------------------------- /examples/run_local_in_firefox_via_set_config_name/test_todomvc.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import browser, have 23 | from tests import resources 24 | 25 | 26 | def test_completes_todo(): 27 | browser.config.driver_name = 'firefox' 28 | 29 | browser.open(resources.TODOMVC_URL) 30 | browser.should(have.title_containing('TodoMVC')) 31 | 32 | browser.element('#new-todo').type('a').press_enter() 33 | browser.element('#new-todo').type('b').press_enter() 34 | browser.element('#new-todo').type('c').press_enter() 35 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 36 | 37 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 38 | '.toggle' 39 | ).click() 40 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 41 | have.exact_texts('b') 42 | ) 43 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 44 | have.exact_texts('a', 'c') 45 | ) 46 | -------------------------------------------------------------------------------- /examples/run_local_in_safari_via_set_config_driver/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_local_in_safari_via_set_config_driver/__init__.py -------------------------------------------------------------------------------- /examples/run_local_in_safari_via_set_config_driver/test_todomvc.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selenium.webdriver.safari.service import Service as SafariService 23 | from selenium.webdriver.safari.options import Options as SafariOptions 24 | 25 | from selene import browser, by, have 26 | from selenium import webdriver 27 | 28 | from tests import resources 29 | 30 | 31 | def test_completes_todo(): 32 | browser.config.driver = webdriver.Safari(service=SafariService()) 33 | 34 | browser.open(resources.TODOMVC_URL) 35 | browser.should(have.title_containing('TodoMVC')) 36 | 37 | browser.element('#new-todo').type('a').press_enter() 38 | browser.element('#new-todo').type('b').press_enter() 39 | browser.element('#new-todo').type('c').press_enter() 40 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 41 | 42 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 43 | '.toggle' 44 | ).click() 45 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 46 | have.exact_texts('b') 47 | ) 48 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 49 | have.exact_texts('a', 'c') 50 | ) 51 | -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_app_with_options_for_browserstack/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_1__original_selene_browser_style/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_app_with_options_for_browserstack/version_1__original_selene_browser_style/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_1__original_selene_browser_style/test_wikipedia.py: -------------------------------------------------------------------------------- 1 | import dotenv 2 | import pydantic 3 | from appium.options.android import UiAutomator2Options 4 | from appium.webdriver.common.appiumby import AppiumBy 5 | from selene import browser, have 6 | 7 | 8 | def test_searches(): 9 | class ProjectConfig(pydantic.BaseSettings): 10 | bstack_accessKey: str 11 | bstack_userName: str = 'bob' 12 | 13 | @property 14 | def bstack_creds(self): 15 | return { 16 | 'userName': self.bstack_userName, 17 | 'accessKey': self.bstack_accessKey, 18 | } 19 | 20 | project_config = ProjectConfig(dotenv.find_dotenv()) 21 | 22 | options = UiAutomator2Options() 23 | options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c' 24 | options.set_capability( 25 | 'bstack:options', 26 | { 27 | 'deviceName': 'Google Pixel 7', 28 | # 'platformVersion': '13.0', 29 | **project_config.bstack_creds, 30 | }, 31 | ) 32 | browser.config.driver_options = options 33 | browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub' 34 | 35 | by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}') 36 | 37 | # GIVEN 38 | browser.open() # not needed, but to explicitly force appium to open app 39 | 40 | # WHEN 41 | browser.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click() 42 | browser.element(by_id('search_src_text')).type('Appium') 43 | 44 | # THEN 45 | browser.all(by_id('page_list_item_title')).should(have.size_greater_than(0)) 46 | -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_2__mobile_alias_style/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_app_with_options_for_browserstack/version_2__mobile_alias_style/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_2__mobile_alias_style/test_wikipedia.py: -------------------------------------------------------------------------------- 1 | import dotenv 2 | import pydantic 3 | from appium.options.android import UiAutomator2Options 4 | from appium.webdriver.common.appiumby import AppiumBy 5 | from selene import browser as mobile, have 6 | 7 | # one more option would be just to: 8 | # >>> from selene import browser 9 | # >>> mobile = browser 10 | 11 | 12 | def test_searches(): 13 | class ProjectConfig(pydantic.BaseSettings): 14 | bstack_accessKey: str 15 | bstack_userName: str = 'bob' 16 | 17 | @property 18 | def bstack_creds(self): 19 | return { 20 | 'userName': self.bstack_userName, 21 | 'accessKey': self.bstack_accessKey, 22 | } 23 | 24 | project_config = ProjectConfig(dotenv.find_dotenv()) 25 | 26 | options = UiAutomator2Options() 27 | options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c' 28 | options.set_capability( 29 | 'bstack:options', 30 | { 31 | 'deviceName': 'Google Pixel 7', 32 | # 'platformVersion': '13.0', 33 | **project_config.bstack_creds, 34 | }, 35 | ) 36 | mobile.config.driver_options = options 37 | mobile.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub' 38 | 39 | by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}') 40 | 41 | # GIVEN 42 | mobile.open() # not needed, but to explicitly force appium to open app 43 | 44 | # WHEN 45 | mobile.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click() 46 | mobile.element(by_id('search_src_text')).type('Appium') 47 | 48 | # THEN 49 | mobile.all(by_id('page_list_item_title')).should(have.size_greater_than(0)) 50 | -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_3__app_copy_style/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_app_with_options_for_browserstack/version_3__app_copy_style/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_3__app_copy_style/test_wikipedia.py: -------------------------------------------------------------------------------- 1 | import dotenv 2 | import pydantic 3 | from appium.options.android import UiAutomator2Options 4 | from appium.webdriver.common.appiumby import AppiumBy 5 | from selene import browser, have 6 | 7 | 8 | def test_searches(): 9 | class ProjectConfig(pydantic.BaseSettings): 10 | bstack_accessKey: str 11 | bstack_userName: str = 'bob' 12 | 13 | @property 14 | def bstack_creds(self): 15 | return { 16 | 'userName': self.bstack_userName, 17 | 'accessKey': self.bstack_accessKey, 18 | } 19 | 20 | project_config = ProjectConfig(dotenv.find_dotenv()) 21 | 22 | android_options = UiAutomator2Options() 23 | android_options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c' 24 | android_options.set_capability( 25 | 'bstack:options', 26 | { 27 | 'deviceName': 'Google Pixel 7', 28 | # 'platformVersion': '13.0', 29 | **project_config.bstack_creds, 30 | }, 31 | ) 32 | app = browser.with_( 33 | driver_options=android_options, 34 | driver_remote_url='http://hub.browserstack.com/wd/hub', 35 | ) 36 | 37 | by_id = lambda id: (AppiumBy.ID, f'org.wikipedia.alpha:id/{id}') 38 | 39 | # GIVEN 40 | app.open() # not needed, but to explicitly force appium to open app 41 | 42 | # WHEN 43 | app.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click() 44 | app.element(by_id('search_src_text')).type('Appium') 45 | 46 | # THEN 47 | app.all(by_id('page_list_item_title')).should(have.size_greater_than(0)) 48 | -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_4__app_fixture_style/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_app_with_options_for_browserstack/version_4__app_fixture_style/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_app_with_options_for_browserstack/version_4__app_fixture_style/test_wikipedia.py: -------------------------------------------------------------------------------- 1 | import dotenv 2 | import pydantic 3 | import pytest 4 | from appium.options.android import UiAutomator2Options 5 | from appium.webdriver.common.appiumby import AppiumBy 6 | from selene import browser as mobile, have 7 | 8 | 9 | class ProjectConfig(pydantic.BaseSettings): 10 | """ 11 | A project config class to store all project specific settings 12 | to be used both in tests and core part of the framework, 13 | like models, data, utils, etc. – i.e. serves as cross-cutting concern. 14 | Normally to be stored in a separate file. 15 | """ 16 | 17 | bstack_accessKey: str 18 | bstack_userName: str = 'admin' 19 | app_package: str = 'org.wikipedia.alpha' 20 | 21 | @property 22 | def bstack_creds(self): 23 | return { 24 | 'userName': self.bstack_userName, 25 | 'accessKey': self.bstack_accessKey, 26 | } 27 | 28 | 29 | project_config = ProjectConfig(dotenv.find_dotenv()) # type: ignore 30 | """ 31 | A project config instance that is a cross-cutting concern for all project, 32 | that can be used as it is, just via simple hard-code where it's needed, 33 | like in `by_id` helper below. 34 | """ 35 | 36 | 37 | def by_id(value): 38 | """ 39 | A helper to create a mobile locator based on id value 40 | Normally to be stored in a separate file, 41 | somewhere in `utils` package, 42 | probably than renamed to id(value) and stored in `utils/by.py` module, 43 | to be used as `by.id(value)` in tests. 44 | """ 45 | return AppiumBy.ID, f'{project_config.app_package}:id/{value}' 46 | 47 | 48 | @pytest.fixture(scope='function') 49 | def app(): 50 | android_options = UiAutomator2Options() 51 | android_options.app = 'bs://c700ce60cf13ae8ed97705a55b8e022f13c5827c' 52 | android_options.set_capability( 53 | 'bstack:options', 54 | { 55 | 'deviceName': 'Google Pixel 7', 56 | # 'platformVersion': '13.0', 57 | **project_config.bstack_creds, 58 | }, 59 | ) 60 | android = mobile.with_( 61 | driver_options=android_options, 62 | driver_remote_url='http://hub.browserstack.com/wd/hub', 63 | ) 64 | 65 | yield android 66 | 67 | android.quit() 68 | 69 | 70 | def test_searches(app): 71 | # GIVEN 72 | app.open() 73 | 74 | # WHEN 75 | app.element((AppiumBy.ACCESSIBILITY_ID, 'Search Wikipedia')).click() 76 | app.element(by_id('search_src_text')).type('Appium') 77 | 78 | # THEN 79 | app.all(by_id('page_list_item_title')).should(have.size_greater_than(0)) 80 | -------------------------------------------------------------------------------- /examples/run_remote_in_android_chrome_with_options_for_browserstack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_android_chrome_with_options_for_browserstack/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_android_chrome_with_options_for_browserstack/test_todomvc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import dotenv_values 4 | from selenium import webdriver 5 | from selene import browser, have 6 | from tests import resources 7 | 8 | 9 | class EnvField: 10 | dotenv = dotenv_values() 11 | 12 | def __init__(self, env_name=None, default=None): 13 | self.env_name = env_name 14 | self.default = default 15 | 16 | def __set_name__(self, owner, name): 17 | self.env_name = self.env_name or name 18 | 19 | def __get__(self, instance, owner): 20 | return os.getenv(self.env_name, self.dotenv.get(self.env_name, self.default)) 21 | 22 | 23 | def test_complete_task(): 24 | class ProjectConfig: 25 | bstack_userName = EnvField() 26 | bstack_accessKey = EnvField() 27 | 28 | project_config = ProjectConfig() 29 | 30 | options = webdriver.ChromeOptions() 31 | options.set_capability( 32 | 'bstack:options', 33 | { 34 | 'deviceName': 'Google Pixel 7', 35 | # 'browserName': 'chrome', # default for Pixel 36 | # 'deviceOrientation': 'landscape', 37 | 'deviceOrientation': 'portrait', 38 | 'userName': project_config.bstack_userName, 39 | 'accessKey': project_config.bstack_accessKey, 40 | }, 41 | ) 42 | browser.config.driver_options = options 43 | browser.config.driver_remote_url = 'http://hub.browserstack.com/wd/hub' 44 | 45 | # WHEN 46 | browser.open(resources.TODOMVC_URL) 47 | 48 | browser.should(have.title_containing('TodoMVC')) 49 | 50 | # WHEN 51 | browser.element('#new-todo').type('a').press_enter() 52 | browser.element('#new-todo').type('b').press_enter() 53 | browser.element('#new-todo').type('c').press_enter() 54 | 55 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 56 | 57 | # WHEN 58 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 59 | '.toggle' 60 | ).click() 61 | 62 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 63 | have.exact_texts('b') 64 | ) 65 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 66 | have.exact_texts('a', 'c') 67 | ) 68 | -------------------------------------------------------------------------------- /examples/run_remote_in_ios_safari_with_options_for_browserstack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_ios_safari_with_options_for_browserstack/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_web_chrome_with_options_for_selenoid/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/examples/run_remote_in_web_chrome_with_options_for_selenoid/__init__.py -------------------------------------------------------------------------------- /examples/run_remote_in_web_chrome_with_options_for_selenoid/test_todomvc.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import dotenv_values 4 | from selenium import webdriver 5 | from selene import browser, have 6 | from tests import resources 7 | 8 | 9 | def test_complete_task(): 10 | dotenv = dotenv_values() 11 | 12 | class ProjectConfig: 13 | selenoid_login = os.getenv('selenoid_login', dotenv.get('selenoid_login')) 14 | selenoid_password = os.getenv( 15 | 'selenoid_password', dotenv.get('selenoid_password') 16 | ) 17 | 18 | options = webdriver.ChromeOptions() 19 | options.browser_version = '100.0' 20 | options.set_capability( 21 | 'selenoid:options', 22 | { 23 | 'screenResolution': '1920x1080x24', 24 | 'enableVNC': True, 25 | 'enableVideo': True, 26 | 'enableLog': True, 27 | }, 28 | ) 29 | browser.config.driver_options = options 30 | browser.config.driver_remote_url = ( 31 | f'https://{ProjectConfig.selenoid_login}:{ProjectConfig.selenoid_password}@' 32 | f'selenoid.autotests.cloud/wd/hub' 33 | ) 34 | 35 | browser.open(resources.TODOMVC_URL) 36 | browser.should(have.title_containing('TodoMVC')) 37 | 38 | browser.element('#new-todo').type('a').press_enter() 39 | browser.element('#new-todo').type('b').press_enter() 40 | browser.element('#new-todo').type('c').press_enter() 41 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 42 | 43 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 44 | '.toggle' 45 | ).click() 46 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 47 | have.exact_texts('b') 48 | ) 49 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 50 | have.exact_texts('a', 'c') 51 | ) 52 | -------------------------------------------------------------------------------- /examples/select_from_table.py: -------------------------------------------------------------------------------- 1 | from selene import have, be 2 | import selene 3 | 4 | 5 | def test_remove_row_v1(): 6 | """ 7 | use collection.element_by(condition) 8 | to filter proper row to find action element inside 9 | """ 10 | browser = selene.browser.with_(window_width=1400) 11 | browser.open('https://demoqa.com/webtables') 12 | fired_employee_email = 'alden@example.com' 13 | rows = browser.all('.rt-tbody [role=row]').by(have.no.exact_text('')) 14 | rows.should(have.size(3)) 15 | 16 | rows.element_by(have.text(fired_employee_email)).element( 17 | '[id^=delete-record]' 18 | ).click() 19 | 20 | rows.should(have.size(2)) 21 | rows.should(have.no.text(fired_employee_email).each) 22 | 23 | 24 | def test_remove_row_v2(): 25 | """ 26 | use collection.element_by(more_precise_lambda_condition) 27 | to filter proper row to find action element inside 28 | """ 29 | browser = selene.browser.with_(window_width=1400) 30 | browser.open('https://demoqa.com/webtables') 31 | fired_employee_email = 'alden@example.com' 32 | rows = browser.all('.rt-tbody [role=row]').by(have.no.exact_text('')) 33 | rows.should(have.size(3)) 34 | 35 | rows.element_by( 36 | lambda its: have.exact_text(fired_employee_email)( 37 | its.element('[role=gridcell]:nth-of-type(4)') 38 | ) 39 | ).element('[id^=delete-record]').click() 40 | 41 | rows.should(have.size(2)) 42 | rows.should(have.no.text(fired_employee_email).each) 43 | 44 | 45 | def test_remove_row_v3(): 46 | """ 47 | use collection.element_by_its(selector, more_precise_condition) 48 | to filter proper row to find action element inside 49 | 50 | element_by_its is a shortcut to same receipt from v2 51 | """ 52 | browser = selene.browser.with_(window_width=1400) 53 | browser.open('https://demoqa.com/webtables') 54 | fired_employee_email = 'alden@example.com' 55 | rows = browser.all('.rt-tbody [role=row]').by(have.no.exact_text('')) 56 | rows.should(have.size(3)) 57 | 58 | rows.element_by_its( 59 | '[role=gridcell]:nth-of-type(4)', have.exact_text(fired_employee_email) 60 | ).element('[id^=delete-record]').click() 61 | 62 | rows.should(have.size(2)) 63 | rows.should(have.no.text(fired_employee_email).each) 64 | -------------------------------------------------------------------------------- /selene/_managed.py: -------------------------------------------------------------------------------- 1 | from selene.core.configuration import Config 2 | 3 | # from selene.core._browser import Browser 4 | from selene.web import Browser 5 | 6 | config = Config() 7 | browser = Browser(config) 8 | -------------------------------------------------------------------------------- /selene/api/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | # --- BASE -- # 24 | 25 | from selene.core._browser import Browser 26 | from selene.core.configuration import Config 27 | 28 | from selene.support import by 29 | 30 | from selene.support.conditions import be, have 31 | 32 | # --- ADVANCED --- # 33 | 34 | from selene.core import query, command, Collection 35 | from selene.core.condition import not_ # just in case 36 | 37 | # --- SHARED --- # 38 | 39 | from selene.support.shared import browser, config 40 | from selene.support.shared.jquery_style import s, ss 41 | 42 | 43 | # --- probably just for Type Hints --- # 44 | 45 | from selene.core._element import Element 46 | from selene.core.condition import Condition 47 | from selene.core.conditions import ( 48 | ElementCondition, 49 | CollectionCondition, 50 | BrowserCondition, 51 | ) 52 | -------------------------------------------------------------------------------- /selene/api/base/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene.core._browser import Browser 24 | from selene.core.configuration import Config 25 | 26 | from selene.support import by 27 | 28 | from selene.support.conditions import be, have 29 | 30 | # TODO: probably we don't need it... it's currently removed from distribution 31 | -------------------------------------------------------------------------------- /selene/api/shared/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene.api.base import * 24 | 25 | from selene.support.shared import browser, config, jquery_style 26 | -------------------------------------------------------------------------------- /selene/common/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: consider renaming this package to _common 2 | -------------------------------------------------------------------------------- /selene/common/appium_tools.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2024 Iakiv Kramarenko 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 | 23 | 24 | def _is_mobile_element(element): 25 | try: 26 | from appium.webdriver import WebElement as MobileElement 27 | except ImportError: 28 | return False 29 | return isinstance(element, MobileElement) 30 | -------------------------------------------------------------------------------- /selene/common/data_structures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/selene/common/data_structures/__init__.py -------------------------------------------------------------------------------- /selene/common/none_object.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | 24 | class _NoneObject: 25 | def __init__(self, description): 26 | # type: (str) -> None 27 | self.description = description 28 | 29 | def __getattr__(self, item): 30 | raise AttributeError( 31 | f"'{self.__class__.__name__}' for «'{self.description}'» has no attribute '{item}'" 32 | ) 33 | 34 | def __bool__(self): 35 | return False 36 | -------------------------------------------------------------------------------- /selene/core/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015 Iakiv Kramarenko 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 | from __future__ import annotations 23 | 24 | from selene.core._element import Element 25 | from selene.core._elements import All 26 | from selene.core.entity import Collection 27 | from selene.core._elements_context import _ElementsContext 28 | from selene.core._client import Client 29 | 30 | # TODO: should we break core.* into [core.]model.* and [core.]webdriver.*? 31 | -------------------------------------------------------------------------------- /selene/core/conditions.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene.core.condition import Condition 23 | from selene.core import Collection 24 | from selene.core._element import Element 25 | from selene.core._browser import Browser 26 | 27 | # todo: consider deprecating ElementCondition & Co... 28 | 29 | 30 | class ElementCondition(Condition[Element]): 31 | pass 32 | 33 | 34 | class CollectionCondition(Condition[Collection]): 35 | pass 36 | 37 | 38 | class BrowserCondition(Condition[Browser]): 39 | pass 40 | -------------------------------------------------------------------------------- /selene/core/locator.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from __future__ import annotations 23 | from typing import TypeVar, Generic, Callable 24 | 25 | T = TypeVar('T') 26 | 27 | 28 | class Locator(Generic[T]): 29 | def __init__(self, description: str | Callable[[], str], locate: Callable[[], T]): 30 | self._description = description 31 | self._locate = locate 32 | 33 | def __call__(self) -> T: 34 | return self._locate() 35 | 36 | def __str__(self): 37 | return self._description() if callable(self._description) else self._description 38 | -------------------------------------------------------------------------------- /selene/py.typed: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /selene/support/__init__.py: -------------------------------------------------------------------------------- 1 | # TODO: consider renaming support to _support to emphasize its experimental nature 2 | 3 | from . import _logging, _extensions, _wait 4 | -------------------------------------------------------------------------------- /selene/support/_extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/selene/support/_extensions/__init__.py -------------------------------------------------------------------------------- /selene/support/_mobile/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015 Iakiv Kramarenko 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 | from selene import _managed # noqa 23 | 24 | 25 | # """ 26 | # Just types... 27 | # """ 28 | from selene.support._mobile.elements import Element, AllElements # noqa 29 | from selene.support._mobile.context import Device # noqa 30 | 31 | # device 32 | from selene._managed import config 33 | 34 | device = Device(config) 35 | -------------------------------------------------------------------------------- /selene/support/_wait.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from typing import ContextManager 3 | 4 | 5 | # TODO: consider adding examples to docstring 6 | # TODO: should we also accept a context_factory? 7 | # TODO: should we name it as decorated_with? 8 | def with_( 9 | *, 10 | context: ContextManager, 11 | ): 12 | """ 13 | :return: 14 | Decorator factory to pass to Selene's config._wait_decorator 15 | to wrap functions (to be waited for) into passed context manager 16 | :param context: 17 | a normal context manager under each function to be waited for 18 | """ 19 | 20 | def decorator_factory(wait): # noqa 21 | def decorator(for_): 22 | @functools.wraps(for_) 23 | def wrapper(fn): 24 | with context: 25 | return for_(fn) 26 | 27 | return wrapper 28 | 29 | return decorator 30 | 31 | return decorator_factory 32 | 33 | 34 | # TODO: consider adding alias to _logging.wait_with 35 | # so that usage is 36 | # support._wait.with_logging(context=allure_commons._allure.StepContext) 37 | # instead of or in addition to 38 | # support._logging.wait_with_(context=allure_commons._allure.StepContext) 39 | -------------------------------------------------------------------------------- /selene/support/conditions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/selene/support/conditions/__init__.py -------------------------------------------------------------------------------- /selene/support/conditions/be.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene.core import match 23 | from selene.support.conditions import not_ as __not 24 | 25 | not_ = __not 26 | 27 | present_in_dom = match.present_in_dom 28 | in_dom = match.present_in_dom # TODO: do we need both present_in_dom and in_dom? 29 | absent_in_dom = match.absent_in_dom 30 | hidden_in_dom = match.hidden_in_dom 31 | hidden = match.hidden 32 | visible = match.visible 33 | 34 | selected = match.selected 35 | 36 | enabled = match.enabled 37 | disabled = match.disabled 38 | 39 | checked = match.checked 40 | 41 | clickable = match.clickable 42 | 43 | blank = match.blank 44 | 45 | 46 | _empty = match._empty 47 | 48 | 49 | # --- Deprecated --- # 50 | 51 | empty = match.empty 52 | """Deprecated 'is empty' condition. Use 53 | [size(0)][selene.support.conditions.have.size] instead. 54 | """ 55 | 56 | 57 | present = match.present 58 | """Deprecated 'is present' condition. Use 59 | [present_in_dom][selene.support.conditions.be.present_in_dom] instead. 60 | """ 61 | 62 | absent = match.absent 63 | """Deprecated 'is absent' condition. Use 64 | [absent_in_dom][selene.support.conditions.not_.absent_in_dom] instead. 65 | """ 66 | 67 | existing = match.existing 68 | """Deprecated 'is existing' condition. Use 69 | [present_in_dom][selene.support.conditions.be.present_in_dom] instead. 70 | """ 71 | -------------------------------------------------------------------------------- /selene/support/shared/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from __future__ import annotations 24 | from selene import _managed 25 | 26 | 27 | config = _managed.config 28 | browser = _managed.browser 29 | -------------------------------------------------------------------------------- /selene/support/shared/browser.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | import warnings 23 | 24 | from selene.core._browser import Browser 25 | 26 | 27 | class SharedBrowser(Browser): 28 | warnings.warn( 29 | 'SharedBrowser is deprecated, use Browser instead', DeprecationWarning 30 | ) 31 | -------------------------------------------------------------------------------- /selene/support/shared/config.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | import warnings 23 | 24 | from selene.core.configuration import Config 25 | 26 | 27 | class SharedConfig(Config): 28 | warnings.warn('SharedConfig is deprecated. Use Config instead', DeprecationWarning) 29 | -------------------------------------------------------------------------------- /selene/support/shared/jquery_style.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from typing import Union, Tuple 23 | 24 | from selene.web._elements import Element, Collection 25 | from selene import browser 26 | 27 | 28 | def s(css_or_xpath_or_by: Union[str, Tuple[str, str]]) -> Element: 29 | return browser.element(css_or_xpath_or_by) 30 | 31 | 32 | def ss(css_or_xpath_or_by: Union[str, Tuple[str, str]]) -> Collection: 33 | return browser.all(css_or_xpath_or_by) 34 | -------------------------------------------------------------------------------- /selene/web/__init__.py: -------------------------------------------------------------------------------- 1 | from ._elements import Element, AllElements, Collection 2 | from ._context import Browser 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /tests/acceptance/custom_driver_management_via_callable/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/acceptance/custom_driver_management_via_callable/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/custom_driver_management_via_descriptor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/acceptance/custom_driver_management_via_descriptor/__init__.py -------------------------------------------------------------------------------- /tests/acceptance/custom_driver_via_fixtures/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.service import Service 4 | 5 | from selene import Config, Browser, support 6 | 7 | 8 | @pytest.fixture(scope='function') 9 | def driver_per_test(): 10 | chrome_driver = webdriver.Chrome(service=Service()) 11 | 12 | yield chrome_driver 13 | 14 | chrome_driver.quit() 15 | 16 | 17 | @pytest.fixture(scope='function') 18 | def browser(driver_per_test): 19 | yield Browser( 20 | Config( 21 | driver=driver_per_test, 22 | ) 23 | ) 24 | -------------------------------------------------------------------------------- /tests/acceptance/custom_driver_via_fixtures/test_ecosia.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import by, have 23 | 24 | 25 | def test_search(browser): 26 | browser.open('https://www.ecosia.org/') 27 | browser.element(by.name('q')).type( 28 | 'github yashaka/selene python User-oriented API' 29 | ).press_enter() 30 | 31 | browser.all('.web-result').first.element('.result__link').click() 32 | 33 | browser.should(have.title_containing('yashaka/selene')) 34 | -------------------------------------------------------------------------------- /tests/acceptance/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yashaka' 2 | -------------------------------------------------------------------------------- /tests/acceptance/helpers/helper.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selenium import webdriver 24 | from selenium.webdriver.chrome.service import Service 25 | 26 | 27 | def get_test_driver(): 28 | return webdriver.Chrome(service=Service()) 29 | -------------------------------------------------------------------------------- /tests/acceptance/legacy_shared_browser_support/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /tests/acceptance/legacy_shared_browser_support/test_todomvc.py: -------------------------------------------------------------------------------- 1 | from selene import have 2 | from selene.support.shared import browser 3 | from tests import resources 4 | 5 | 6 | def test_complete_task(): 7 | browser.open(resources.TODOMVC_URL) 8 | 9 | browser.element('#new-todo').type('a').press_enter() 10 | browser.element('#new-todo').type('b').press_enter() 11 | browser.element('#new-todo').type('c').press_enter() 12 | browser.all('#todo-list>li').should(have.exact_texts('a', 'b', 'c')) 13 | 14 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 15 | '.toggle' 16 | ).click() 17 | browser.all('#todo-list>li').by(have.css_class('completed')).should( 18 | have.exact_texts('b') 19 | ) 20 | browser.all('#todo-list>li').by(have.no.css_class('completed')).should( 21 | have.exact_texts('a', 'c') 22 | ) 23 | -------------------------------------------------------------------------------- /tests/acceptance/test_search.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import browser, by, have 23 | 24 | 25 | def x_test_ecosia(): 26 | browser.open('https://www.ecosia.org/') 27 | browser.element(by.name('q')).type('github yashaka selene python').press_enter() 28 | 29 | browser.all('.web-result').first.element('.result__link').click() 30 | 31 | browser.should(have.title_containing('yashaka/selene')) 32 | 33 | 34 | def test_duckduckgo(): 35 | browser.open('https://www.duckduckgo.org/') 36 | browser.element(by.name('q')).type( 37 | 'github yashaka selene python User-oriented' 38 | ).press_enter() 39 | 40 | browser.all('[data-testid=result]').first.element( 41 | '[data-testid=result-title-a]' 42 | ).click() 43 | 44 | browser.should(have.title_containing('yashaka/selene')) 45 | -------------------------------------------------------------------------------- /tests/adhoc/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /tests/adhoc/test_logging_outer_html.py: -------------------------------------------------------------------------------- 1 | from selene import browser, have, be 2 | from tests import resources 3 | 4 | 5 | def test_logging_outer_html__enabled__on_one_element(): 6 | browser.config.timeout = 0.5 7 | browser.config.log_outer_html_on_failure = True 8 | browser.open(resources.TODOMVC_URL) 9 | 10 | message = None 11 | try: 12 | browser.element('#new-todo').should(have.attribute('wrong_attr')) 13 | except Exception as e: 14 | message = str(e) 15 | 16 | assert message is not None 17 | assert 'Actual webelement:' in message 18 | 19 | 20 | def test_logging_outer_html__disabled__on_one_element(): 21 | browser.config.timeout = 0.5 22 | browser.config.log_outer_html_on_failure = False 23 | browser.open('http://todomvc.com/examples/emberjs/') 24 | 25 | message = None 26 | try: 27 | browser.element('#new-todo').should(have.attribute('wrong_attr')) 28 | except Exception as e: 29 | message = str(e) 30 | 31 | assert message is not None 32 | assert 'Actual webelement:' not in message 33 | 34 | 35 | def test_logging_outer_html__enabled__on_collection(): 36 | browser.config.timeout = 0.5 37 | browser.config.log_outer_html_on_failure = True 38 | browser.open(resources.TODOMVC_URL) 39 | 40 | message = None 41 | try: 42 | browser.all('footer p').element_by(have.attribute('wrong_attr')).should( 43 | be.visible 44 | ) 45 | except Exception as e: 46 | message = str(e) 47 | 48 | assert message is not None 49 | assert 'Actual webelements collection:' in message 50 | 51 | 52 | def test_logging_outer_html__disabled__on_collection(): 53 | browser.config.timeout = 0.5 54 | browser.config.log_outer_html_on_failure = False 55 | browser.open('http://todomvc.com/examples/emberjs/') 56 | 57 | message = None 58 | try: 59 | browser.all('footer p').element_by(have.attribute('wrong_attr')).should( 60 | be.visible 61 | ) 62 | except Exception as e: 63 | message = str(e) 64 | 65 | assert message is not None 66 | assert 'Actual webelements collection:' not in message 67 | -------------------------------------------------------------------------------- /tests/adhoc/test_shared_config_base_url.py: -------------------------------------------------------------------------------- 1 | from selene import browser, be 2 | from tests import resources 3 | 4 | 5 | def test_opening_relative_url(): 6 | browser.config.base_url = resources.TODOMVC_URL 7 | browser.open('/') 8 | 9 | browser.element('#new-todo').should(be.visible) 10 | -------------------------------------------------------------------------------- /tests/base_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selenium import webdriver 23 | from selenium.webdriver.chrome.service import Service 24 | 25 | from selene.support.shared import browser 26 | 27 | 28 | class BaseTest: 29 | def setup_method(self): 30 | browser.config.driver = webdriver.Chrome(service=Service()) 31 | 32 | def teardown_method(self): 33 | browser.quit() 34 | browser.config.driver = ... 35 | -------------------------------------------------------------------------------- /tests/bys_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import by 23 | 24 | 25 | def test_by_id(): 26 | assert by.id('c') == ('id', 'c') 27 | 28 | 29 | def test_by_css(): 30 | assert by.css("a") == ('css selector', 'a') 31 | 32 | 33 | def test_by_name(): 34 | assert by.name("test") == ('name', 'test') 35 | 36 | 37 | def test_by_link_text(): 38 | assert by.link_text("text") == ('link text', 'text') 39 | 40 | 41 | def test_by_partial_link_text(): 42 | assert by.partial_link_text("text") == ("partial link text", "text") 43 | 44 | 45 | def test_by_xpath(): 46 | assert by.xpath("//a") == ('xpath', "//a") 47 | 48 | 49 | def test_by_text(): 50 | assert by.text("test") == ( 51 | "xpath", 52 | './/*[text()[normalize-space(.) = concat("", "test")]]', 53 | ) 54 | 55 | 56 | def test_by_partial_text(): 57 | assert by.partial_text("text") == ( 58 | "xpath", 59 | './/*[text()[contains(normalize-space(.), concat("", "text"))]]', 60 | ) 61 | 62 | 63 | def test_by_escape_text_quotes_for_xpath(): 64 | assert by._escape_text_quotes_for_xpath('test') == 'concat("", "test")' 65 | 66 | 67 | def test_by_class_name(): 68 | assert by.class_name("test") == ("class name", "test") 69 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene.support import shared 4 | 5 | 6 | def pytest_addoption(parser): 7 | parser.addoption( 8 | '--headless', 9 | help='headless mode', 10 | default=False, 11 | ) 12 | 13 | 14 | @pytest.fixture(scope='function') 15 | def quit_shared_browser_afterwards(): 16 | shared.browser.config.driver = ... 17 | 18 | yield 19 | 20 | shared.browser.quit() 21 | -------------------------------------------------------------------------------- /tests/const.py: -------------------------------------------------------------------------------- 1 | # TINYMCE_URL = 'https://www.tiny.cloud/docs/tinymce/latest/cloud-quick-start/' 2 | from pathlib import Path 3 | 4 | import tests 5 | 6 | TINYMCE_URL = 'https://autotest.how/demo/tinymce' 7 | # SELENOID_HOST = 'selenoid.autotests.cloud' 8 | SELENOID_HOST = 'selenoid.autotest.how' 9 | LOGO_PATH = str(Path(tests.__file__).parent.parent / 'docs/assets/images/logo-icon.png') 10 | -------------------------------------------------------------------------------- /tests/examples/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | TODO: refactor examples tests to have the following structure: 3 | examples 4 | 5 | .py 6 | """ 7 | -------------------------------------------------------------------------------- /tests/examples/pom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/pom/__init__.py -------------------------------------------------------------------------------- /tests/examples/pom/test_material_ui__react_select.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene import browser, have 4 | from selene.support._pom import element, all_ 5 | 6 | 7 | class MUIBasicSelect: 8 | label = element('label') 9 | selected_text = element('.MuiSelect-select') 10 | input = element('input') 11 | items = all_('[role=option]').within(browser) 12 | 13 | def __init__(self, context): 14 | self.context = context 15 | 16 | @staticmethod 17 | def by_id(value): 18 | return MUIBasicSelect( 19 | browser.element(f'#{value}').element( 20 | './ancestor::*[contains(concat(" ", normalize-space(@class), " "), " ' 21 | 'MuiFormControl-root' 22 | ' ")]' 23 | ) 24 | ) 25 | 26 | def open(self): 27 | self.context.click() 28 | return self 29 | 30 | def choose(self, text): 31 | self.items.element_by(have.exact_text(text)).click() 32 | return self 33 | 34 | def select(self, text): 35 | self.open().choose(text) 36 | 37 | 38 | @pytest.mark.parametrize( 39 | 'age', 40 | [ 41 | MUIBasicSelect(browser.element('#BasicSelect+* .MuiFormControl-root')), 42 | MUIBasicSelect.by_id('demo-simple-select'), 43 | ], 44 | ) 45 | def test_material_ui__react_select__basic_select(age): 46 | browser.driver.refresh() 47 | 48 | # WHEN 49 | browser.open('https://mui.com/material-ui/react-select/#basic-select') 50 | 51 | # THEN 52 | age.label.should(have.exact_text('Age')) 53 | age.selected_text.should(have.exact_text('')) 54 | age.input.should(have.value('')) 55 | 56 | # WHEN 57 | age.select('Twenty') 58 | 59 | # THEN 60 | age.selected_text.should(have.exact_text('Twenty')) 61 | age.input.should(have.value('20')) 62 | -------------------------------------------------------------------------------- /tests/examples/pom/test_material_ui__react_select__explicit_PO_browser_based__with_alias.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene import browser, have 4 | from selene.support._pom import element, all_ 5 | 6 | 7 | class MUIBasicSelect: 8 | label = element('label') 9 | selected_text = element('.MuiSelect-select') 10 | input = element('input') 11 | items = all_('[role=option]').within_browser 12 | 13 | def __init__(self, context, browser): # noqa 14 | self.browser = browser 15 | self.context = context 16 | 17 | @staticmethod 18 | def by_id(value): 19 | return MUIBasicSelect( 20 | browser.element(f'#{value}').element( 21 | './ancestor::*[contains(concat(" ", normalize-space(@class), " "), " ' 22 | 'MuiFormControl-root' 23 | ' ")]' 24 | ), 25 | browser, 26 | ) 27 | 28 | def open(self): 29 | self.context.click() 30 | return self 31 | 32 | def choose(self, text): 33 | self.items.element_by(have.exact_text(text)).click() 34 | return self 35 | 36 | def select(self, text): 37 | self.open().choose(text) 38 | 39 | 40 | @pytest.mark.parametrize( 41 | 'age', 42 | [ 43 | MUIBasicSelect(browser.element('#BasicSelect+* .MuiFormControl-root'), browser), 44 | MUIBasicSelect.by_id('demo-simple-select'), 45 | ], 46 | ) 47 | def test_material_ui__react_select__basic_select(age): 48 | browser.driver.refresh() 49 | 50 | # WHEN 51 | browser.open('https://mui.com/material-ui/react-select/#basic-select') 52 | 53 | # THEN 54 | age.label.should(have.exact_text('Age')) 55 | age.selected_text.should(have.exact_text('')) 56 | age.input.should(have.value('')) 57 | 58 | # WHEN 59 | age.select('Twenty') 60 | 61 | # THEN 62 | age.selected_text.should(have.exact_text('Twenty')) 63 | age.input.should(have.value('20')) 64 | -------------------------------------------------------------------------------- /tests/examples/pom/test_material_ui__react_select__explicit_PO_browser_based__with_lambda_on_self.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene import browser, have 4 | from selene.support._pom import element, all_ 5 | 6 | 7 | class MUIBasicSelect: 8 | label = element('label') 9 | selected_text = element('.MuiSelect-select') 10 | input = element('input') 11 | items = all_('[role=option]').within(lambda self: self.browser) 12 | 13 | def __init__(self, context, browser): # noqa 14 | self.browser = browser 15 | self.context = context 16 | 17 | @staticmethod 18 | def by_id(value): 19 | return MUIBasicSelect( 20 | browser.element(f'#{value}').element( 21 | './ancestor::*[contains(concat(" ", normalize-space(@class), " "), " ' 22 | 'MuiFormControl-root' 23 | ' ")]' 24 | ), 25 | browser, 26 | ) 27 | 28 | def open(self): 29 | self.context.click() 30 | return self 31 | 32 | def choose(self, text): 33 | self.items.element_by(have.exact_text(text)).click() 34 | return self 35 | 36 | def select(self, text): 37 | self.open().choose(text) 38 | 39 | 40 | @pytest.mark.parametrize( 41 | 'age', 42 | [ 43 | MUIBasicSelect(browser.element('#BasicSelect+* .MuiFormControl-root'), browser), 44 | MUIBasicSelect.by_id('demo-simple-select'), 45 | ], 46 | ) 47 | def test_material_ui__react_select__basic_select(age): 48 | browser.driver.refresh() 49 | 50 | # WHEN 51 | browser.open('https://mui.com/material-ui/react-select/#basic-select') 52 | 53 | # THEN 54 | age.label.should(have.exact_text('Age')) 55 | age.selected_text.should(have.exact_text('')) 56 | age.input.should(have.value('')) 57 | 58 | # WHEN 59 | age.select('Twenty') 60 | 61 | # THEN 62 | age.selected_text.should(have.exact_text('Twenty')) 63 | age.input.should(have.value('20')) 64 | -------------------------------------------------------------------------------- /tests/examples/test_matching.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | 24 | def x_test_working_with_kind_of_unexpected_dialogs(): 25 | """ 26 | implement test for page with alert-like-window that appears each second time 27 | after click on a button 28 | test should use wait_until in if clause to check alert appeared 29 | if yes 30 | wait for it to disappear 31 | else 32 | do nothing 33 | :return: 34 | """ 35 | -------------------------------------------------------------------------------- /tests/examples/todomvc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/todomvc/__init__.py -------------------------------------------------------------------------------- /tests/examples/todomvc/pagemodules_approach/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/todomvc/pagemodules_approach/__init__.py -------------------------------------------------------------------------------- /tests/examples/todomvc/pagemodules_approach/pages/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yashaka' 2 | -------------------------------------------------------------------------------- /tests/examples/todomvc/pagemodules_approach/pages/tasks.py: -------------------------------------------------------------------------------- 1 | from selene import by, be, have, browser 2 | 3 | __author__ = 'yashaka' 4 | 5 | _elements = browser.all("#todo-list>li") 6 | 7 | app_url = 'https://todomvc4tasj.herokuapp.com/' 8 | 9 | 10 | def visit(): 11 | browser.open(app_url) 12 | clear_completed_js_loaded = ( 13 | "return $._data($('#clear-completed').get(0), 'events').hasOwnProperty('click')" 14 | ) 15 | browser.with_(timeout=browser.config.timeout * 3).wait.for_( 16 | have.script_returned(True, clear_completed_js_loaded) 17 | ) 18 | 19 | 20 | def filter_active(): 21 | browser.element(by.link_text("Active")).click() 22 | 23 | 24 | def filter_completed(): 25 | browser.element(by.link_text("Completed")).click() 26 | 27 | 28 | def add(*task_texts): 29 | for text in task_texts: 30 | browser.element("#new-todo").should(be.enabled).set_value(text).press_enter() 31 | 32 | 33 | def toggle(task_text): 34 | _elements.element_by(have.exact_text(task_text)).element(".toggle").click() 35 | 36 | 37 | def should_be(*task_texts): 38 | _elements.by(be.visible).should(have.exact_texts(*task_texts)) 39 | 40 | 41 | def clear_completed(): 42 | browser.element('#clear-completed').click() 43 | -------------------------------------------------------------------------------- /tests/examples/todomvc/pagemodules_approach/todomvc_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | import pytest 23 | 24 | from selene import browser, command 25 | from tests.examples.todomvc.pagemodules_approach.pages import tasks 26 | 27 | 28 | @pytest.fixture(scope='function') 29 | def with_clean_storage_teardown(): 30 | # ... 31 | 32 | yield 33 | 34 | browser.perform(command.js.clear_local_storage) 35 | 36 | 37 | def test_filter_tasks(with_clean_storage_teardown): 38 | tasks.visit() 39 | 40 | tasks.add('a', 'b', 'c') 41 | tasks.should_be('a', 'b', 'c') 42 | 43 | tasks.toggle('b') 44 | 45 | tasks.filter_active() 46 | tasks.should_be('a', 'c') 47 | 48 | tasks.filter_completed() 49 | tasks.should_be('b') 50 | 51 | 52 | def test_clear_completed(with_clean_storage_teardown): 53 | tasks.visit() 54 | 55 | tasks.add('a', 'b', 'c') 56 | tasks.toggle('b') 57 | tasks.clear_completed() 58 | 59 | tasks.should_be('a', 'c') 60 | -------------------------------------------------------------------------------- /tests/examples/todomvc/straightforward_approach/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/todomvc/straightforward_approach/__init__.py -------------------------------------------------------------------------------- /tests/examples/todomvc/straightforward_approach_with_manual_shared_driver_via_basetest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/todomvc/straightforward_approach_with_manual_shared_driver_via_basetest/__init__.py -------------------------------------------------------------------------------- /tests/examples/todomvc/straightforward_approach_with_manual_shared_driver_via_basetest/todomvc_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import have, be, by, browser 23 | from tests import resources 24 | from tests.base_test import BaseTest 25 | 26 | APP_URL = resources.TODOMVC_URL 27 | 28 | 29 | def setup_module(): 30 | browser.config.timeout = 4 31 | 32 | 33 | class TestTodoMVC(BaseTest): 34 | def test_filter_tasks(self): 35 | browser.open(APP_URL) 36 | 37 | browser.element('#new-todo').should(be.enabled).set_value('a').press_enter() 38 | browser.element('#new-todo').should(be.enabled).set_value('b').press_enter() 39 | browser.element('#new-todo').should(be.enabled).set_value('c').press_enter() 40 | 41 | browser.all('#todo-list>li').should(have.texts('a', 'b', 'c')) 42 | 43 | browser.all('#todo-list>li').element_by(have.exact_text('b')).element( 44 | '.toggle' 45 | ).click() 46 | 47 | browser.element(by.link_text('Active')).click() 48 | browser.all('#todo-list>li').should(have.texts('a', 'c')) 49 | 50 | browser.element(by.link_text('Completed')).click() 51 | browser.all('#todo-list>li').should(have.texts('b')) 52 | -------------------------------------------------------------------------------- /tests/examples/todomvc/straightforward_in_firefox/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/todomvc/straightforward_in_firefox/__init__.py -------------------------------------------------------------------------------- /tests/examples/todomvc/straightforward_in_firefox/todomvc_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import have, browser 23 | from tests import resources 24 | 25 | 26 | def setup_function(): 27 | browser.config.browser_name = 'firefox' 28 | 29 | 30 | def teardown_function(): 31 | browser.quit() 32 | browser.config.browser_name = 'chrome' 33 | 34 | 35 | def test_add_todos(): 36 | browser.open(resources.TODOMVC_URL) 37 | 38 | browser.element('#new-todo').set_value('a').press_enter() 39 | browser.element('#new-todo').set_value('b').press_enter() 40 | browser.element('#new-todo').set_value('c').press_enter() 41 | browser.all('#todo-list li').should(have.exact_texts('a', 'b', 'c')) 42 | -------------------------------------------------------------------------------- /tests/examples/widgets_aka_components_page_objects_style_for_spa_apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/examples/widgets_aka_components_page_objects_style_for_spa_apps/__init__.py -------------------------------------------------------------------------------- /tests/examples/widgets_aka_components_page_objects_style_for_spa_apps/model/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'yashaka' 2 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | import time 24 | from selenium import webdriver 25 | 26 | 27 | def time_spent(function, *args, **kwargs): 28 | start_time = time.time() 29 | function(*args, **kwargs) 30 | end_time = time.time() 31 | 32 | return end_time - start_time 33 | 34 | 35 | def convert_sec_to_ms(timeout): 36 | return timeout * 1000 37 | 38 | 39 | def headless_chrome_options(): 40 | options = webdriver.ChromeOptions() 41 | options.add_argument("--no-sandbox") 42 | options.add_argument("--disable-gpu") 43 | options.add_argument("--disable-notifications") 44 | options.add_argument("--disable-extensions") 45 | options.add_argument("--disable-infobars") 46 | options.add_argument("--enable-automation") 47 | options.add_argument("--headless") 48 | options.add_argument("--disable-dev-shm-usage") 49 | options.add_argument("--disable-setuid-sandbox") 50 | return options 51 | -------------------------------------------------------------------------------- /tests/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/integration/__init__.py -------------------------------------------------------------------------------- /tests/integration/browser__execute_script_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from tests.integration.helpers.givenpage import GivenPage 23 | 24 | 25 | def test_can_scroll_to_via_js(function_browser): 26 | function_browser.driver.set_window_size(300, 200) 27 | GivenPage(function_browser.driver).opened_with_body( 28 | ''' 29 |
30 |
31 | 32 |

Heading 1

33 | ''' 34 | ) 35 | link = function_browser.element("#not-viewable-link") 36 | # browser.driver().execute_script("arguments[0].scrollIntoView();", link) 37 | # - this code does not work because SeleneElement is not JSON serializable, and I don't know the way to fix it 38 | # - because all available in python options needs a change to json.dumps call - adding a second parameter to it 39 | # and specify a custom encoder, but we can't change this call inside selenium webdriver implementation 40 | 41 | function_browser.driver.execute_script("arguments[0].scrollIntoView();", link()) 42 | link.click() 43 | # actually, selene .click() scrolls to any element in dom, so it's not an option fo 44 | # in this case we should find another way to check page is scrolled down or to choose another script. 45 | 46 | assert "header" in function_browser.driver.current_url 47 | -------------------------------------------------------------------------------- /tests/integration/browser__open_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import Browser, Config 23 | from tests.integration.helpers import givenpage 24 | 25 | 26 | def test_changes_window_size_on_open_according_to_config(chrome_driver): 27 | browser = Browser( 28 | Config( 29 | driver=chrome_driver, 30 | window_width=640, 31 | window_height=480, 32 | ) 33 | ) 34 | 35 | browser.open(givenpage.EMPTY_PAGE_URL) 36 | 37 | assert browser.driver.get_window_size()['width'] == 640 38 | assert browser.driver.get_window_size()['height'] == 480 39 | -------------------------------------------------------------------------------- /tests/integration/browser__should_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | import os 24 | 25 | import pytest 26 | 27 | from selene import have 28 | from selene.core.exceptions import TimeoutException 29 | 30 | 31 | def x_test_waiting_for_conditions_like_url_containing(session_browser): 32 | """ 33 | TODO: GivenPage(...).opened_with_body(...)\ 34 | .execute_script('script should click on link after 500ms' 35 | 'link should follow to #second') 36 | browser.should(have.url_containing('#second')) 37 | assert 'second' in browser.driver.current_url 38 | """ 39 | -------------------------------------------------------------------------------- /tests/integration/by_text_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import by 23 | from tests.integration.helpers.givenpage import GivenPage 24 | 25 | 26 | def test_nested_elements_search(session_browser): 27 | page = GivenPage(session_browser.driver) 28 | page.opened_with_body( 29 | ''' 30 |
31 |
32 |
33 | 34 |
35 |
38 |
39 |
40 |
41 | 42 | 46 |
47 |
48 |
49 |
50 |
51 |

Heading 1

52 |

Heading 2

53 |

Heading 3

54 | ''' 55 | ) 56 | 57 | session_browser.element('#container').element(by.text('Second')).element( 58 | './following-sibling::*' 59 | ).element(by.partial_text('''"Heading 'Third'"''')).click() 60 | 61 | assert "third" in session_browser.driver.current_url 62 | -------------------------------------------------------------------------------- /tests/integration/collection__count_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from tests.integration.helpers.givenpage import GivenPage 23 | 24 | 25 | def test_counts_invisible_tasks(session_browser): 26 | page = GivenPage(session_browser.driver) 27 | page.opened_empty() 28 | 29 | elements = session_browser.all('.will-appear') 30 | 31 | page.load_body( 32 | ''' 33 |
    Hello to: 34 |
  • Bob
  • 35 | 36 |
''' 37 | ) 38 | 39 | assert len(elements) == 2 40 | -------------------------------------------------------------------------------- /tests/integration/collection__filtered_by_condition__len_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import have 23 | from tests.integration.helpers.givenpage import GivenPage 24 | 25 | 26 | def test_counts_invisible_tasks(session_browser): 27 | page = GivenPage(session_browser.driver) 28 | page.opened_empty() 29 | elements = session_browser.all('li').by(have.css_class('will-appear')) 30 | page.load_body( 31 | ''' 32 |
    Hello to: 33 |
  • Anonymous
  • 34 |
  • Bob
  • 35 | 36 |
37 | ''' 38 | ) 39 | 40 | count = len(elements) 41 | 42 | assert count == 2 43 | -------------------------------------------------------------------------------- /tests/integration/collection__filtered_by_condition__no_waiting_search_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import have 23 | from tests.integration.helpers.givenpage import GivenPage 24 | 25 | 26 | def test_waits_nothing(session_browser): 27 | page = GivenPage(session_browser.driver) 28 | page.opened_empty() 29 | elements = session_browser.all('li').by(have.css_class('will-appear')) 30 | page.load_body( 31 | ''' 32 |
    Hello to: 33 |
  • Anonymous
  • 34 |
  • Bob
  • 35 | 36 |
37 | ''' 38 | ) 39 | original_count = len(elements) 40 | page.load_body_with_timeout( 41 | ''' 42 |
    Hello to: 43 |
  • Anonymous
  • 44 |
  • Bob
  • 45 | 46 |
  • Joe
  • 47 |
48 | ''', 49 | 0.5, 50 | ) 51 | 52 | updated_count = len(elements) 53 | 54 | assert updated_count == original_count == 2 55 | -------------------------------------------------------------------------------- /tests/integration/collection__get__query__inner_htmls_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene import query 24 | from tests.integration.helpers.givenpage import GivenPage 25 | 26 | 27 | def test_query_inner_htmls(session_browser): 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
    Hello: 31 |
  • Alex!
  • 32 |
  • Yakov! \n
  • 33 |
34 | ''' 35 | ) 36 | 37 | assert session_browser.all('li').get(query.inner_htmls) == [ 38 | 'Alex!', 39 | ' Yakov! ', 40 | ] 41 | 42 | 43 | # TODO: check query is logged properly 44 | -------------------------------------------------------------------------------- /tests/integration/collection__get__query__outer_htmls_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene import query 24 | from tests.integration.helpers.givenpage import GivenPage 25 | 26 | 27 | def test_query_outer_htmls(session_browser): 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
    Hello: 31 |
  • Alex!
  • 32 |
  • Yakov! \n
  • 33 |
34 | ''' 35 | ) 36 | 37 | assert session_browser.all('li').get(query.outer_htmls) == [ 38 | '
  • Alex!
  • ', 39 | '
  • Yakov!
  • ', 40 | ] 41 | 42 | 43 | # TODO: check query is logged properly 44 | -------------------------------------------------------------------------------- /tests/integration/collection__get__query__texts_content_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene import query 24 | from tests.integration.helpers.givenpage import GivenPage 25 | 26 | 27 | def test_query_texts_content_as_almost_not_normalized(session_browser): 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
      Hello: 31 |
    • Alex!
    • 32 |
    • Yakov! \n
    • 33 |
    34 | ''' 35 | ) 36 | 37 | assert session_browser.all('li').get(query.texts_content) == [ 38 | 'Alex!', 39 | ' Yakov! ', 40 | ] 41 | 42 | 43 | # TODO: check query is logged properly 44 | -------------------------------------------------------------------------------- /tests/integration/collection__get__query__texts_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene import query 24 | from tests.integration.helpers.givenpage import GivenPage 25 | 26 | 27 | def test_query_texts_as_normalized(session_browser): 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
      Hello: 31 |
    • Alex!
    • 32 |
    • Yakov! \n
    • 33 |
    34 | ''' 35 | ) 36 | 37 | assert session_browser.all('li').get(query.texts) == ['Alex!', 'Yakov!'] 38 | 39 | 40 | # TODO: check query is logged properly 41 | -------------------------------------------------------------------------------- /tests/integration/collection__get__query__values_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | from selene import query 24 | from tests.integration.helpers.givenpage import GivenPage 25 | 26 | 27 | def test_query_values(session_browser): 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
      Hello: 31 |
    • 32 |
    • 33 |
    34 | ''' 35 | ) 36 | 37 | assert session_browser.all('input').get(query.values) == [ 38 | 'Alex!', 39 | ' Yakov! ', 40 | ] 41 | 42 | 43 | def test_query_values_after_retyped(session_browser): 44 | GivenPage(session_browser.driver).opened_with_body( 45 | ''' 46 |
      Hello: 47 |
    • 48 |
    • 49 |
    50 | ''' 51 | ) 52 | 53 | session_browser.all('input').first.clear().type(' 1 \n ') 54 | session_browser.all('input').second.clear().type('2') 55 | 56 | assert session_browser.all('input').get(query.values) == [ 57 | ' 1 ', 58 | '2', 59 | ] 60 | 61 | 62 | # TODO: check query is logged properly 63 | -------------------------------------------------------------------------------- /tests/integration/collection__len_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from tests.integration.helpers.givenpage import GivenPage 23 | 24 | 25 | def test_counts_invisible_tasks(session_browser): 26 | page = GivenPage(session_browser.driver) 27 | page.opened_empty() 28 | elements = session_browser.all('.will-appear') 29 | page.load_body( 30 | ''' 31 |
      Hello to: 32 |
    • Bob
    • 33 | 34 |
    35 | ''' 36 | ) 37 | 38 | count = len(elements) 39 | 40 | assert count == 2 41 | -------------------------------------------------------------------------------- /tests/integration/collection__nowaiting_search_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from tests.integration.helpers.givenpage import GivenPage 23 | 24 | 25 | def test_waits_nothing(session_browser): 26 | page = GivenPage(session_browser.driver) 27 | page.opened_empty() 28 | elements = session_browser.all('.will-appear') 29 | 30 | page.load_body( 31 | ''' 32 |
      Hello to: 33 |
    • Bob
    • 34 | 35 |
    ''' 36 | ) 37 | assert len(elements) == 2 38 | 39 | page.load_body_with_timeout( 40 | ''' 41 |
      Hello to: 42 |
    • Bob
    • 43 | 44 |
    • Joe
    • 45 |
    ''', 46 | 0.5, 47 | ) 48 | assert len(elements) == 2 49 | -------------------------------------------------------------------------------- /tests/integration/command__select_all_test.py: -------------------------------------------------------------------------------- 1 | from selene import have, command 2 | import time 3 | 4 | from selene.core.exceptions import TimeoutException 5 | 6 | from tests.integration.helpers.givenpage import GivenPage 7 | 8 | 9 | def test_select_all_allows_to_overwrite_text_via_type(session_browser): 10 | browser = session_browser.with_(timeout=1) 11 | page = GivenPage(browser.driver) 12 | page.opened_with_body( 13 | ''' 14 | 15 | ''' 16 | ) 17 | 18 | browser.element('#text-field').perform(command.select_all).type('new text') 19 | 20 | browser.element('#text-field').should(have.value('new text')) 21 | -------------------------------------------------------------------------------- /tests/integration/condition__collection__have_values_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | 23 | import pytest 24 | from selene import have 25 | from selene.core.exceptions import TimeoutException 26 | from tests.integration.helpers.givenpage import GivenPage 27 | 28 | 29 | def test_should_have_exact_values(session_browser): 30 | GivenPage(session_browser.driver).opened_with_body( 31 | ''' 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
    40 | ''' 41 | ) 42 | 43 | session_browser.all('.cell input').should(have.values('A1', 'A2', 'B1', 'B2')) 44 | 45 | session_browser.all('.cell input').should(have.values(['A1', 'A2', 'B1', 'B2'])) 46 | 47 | session_browser.all('.cell input').should(have.values(('A1', 'A2', 'B1', 'B2'))) 48 | 49 | session_browser.all('.cell input').should( 50 | have.values( 51 | ('A1', 'A2'), 52 | ('B1', 'B2'), 53 | ) 54 | ) 55 | -------------------------------------------------------------------------------- /tests/integration/core_wait_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.webdriver.common.by import By 4 | 5 | from selene import be 6 | from tests.integration.helpers.givenpage import GivenPage 7 | 8 | 9 | def test_waits_for_visibility_minimum_needed_time(session_browser): 10 | page = GivenPage(session_browser.driver) 11 | page.opened_with_body( 12 | ''' 13 | go to Heading 2 14 |

    Heading 2

    15 | ''' 16 | ).execute_script_with_timeout( 17 | 'document.getElementsByTagName("a")[0].style = "display:block";', 1 18 | ) 19 | stamp_before = time.time_ns() 20 | session_browser.element('a').wait.at_most(2).for_(be.visible) 21 | 22 | session_browser.driver.find_element(By.CSS_SELECTOR, 'a').click() 23 | 24 | stamp_after = time.time_ns() 25 | deviation_sec = 0.2 26 | assert stamp_after - stamp_before < (1 + deviation_sec) * pow(10, 9) 27 | assert "second" in session_browser.driver.current_url 28 | 29 | 30 | def x_test_fails_on_timeout_during_waits_first_for_present_in_dom_then_visibility( 31 | session_browser, 32 | ): 33 | # TODO: fix the test, it does not reflect its test goal reflected in summary 34 | page = GivenPage(session_browser.driver) 35 | page.opened_with_body( 36 | ''' 37 | go to Heading 2 38 |

    Heading 2

    39 | ''' 40 | ).execute_script_with_timeout( 41 | 'document.getElementsByTagName("a")[0].style = "display:block";', 1.1 42 | ) 43 | stamp_before = time.time_ns() 44 | session_browser.element('a').wait.at_most(1).for_(be.visible) 45 | 46 | session_browser.driver.find_element(By.CSS_SELECTOR, 'a').click() 47 | 48 | stamp_after = time.time_ns() 49 | deviation_sec = 0.2 50 | assert stamp_after - stamp_before < (1 + deviation_sec) * pow(10, 9) 51 | assert "second" in session_browser.driver.current_url 52 | -------------------------------------------------------------------------------- /tests/integration/element__all__len_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from tests.integration.helpers.givenpage import GivenPage 23 | 24 | 25 | def test_counts_invisible_tasks(session_browser): 26 | GivenPage(session_browser.driver).opened_with_body( 27 | ''' 28 |
      Hello to: 29 |
    • Bob
    • 30 | 31 |
    32 | ''' 33 | ) 34 | 35 | collection = session_browser.element('ul').all('.will-appear') 36 | 37 | assert len(collection) == 2 38 | -------------------------------------------------------------------------------- /tests/integration/element__click_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selene import have 4 | from tests.integration.helpers.givenpage import GivenPage 5 | 6 | 7 | def test_click_waits_for_no_overlay(session_browser): 8 | browser = session_browser.with_(timeout=0.5) 9 | page = GivenPage(browser.driver) 10 | page.opened_with_body( 11 | ''' 12 |
    29 |
    30 | go to Heading 2 31 |

    Heading 2

    32 | ''' 33 | ) 34 | before_call = time.time() 35 | page.execute_script_with_timeout( 36 | ''' 37 | document.getElementById('overlay').style.display='none' 38 | ''', 39 | 0.25, 40 | ) 41 | 42 | browser.element('a').click() 43 | 44 | time_diff = time.time() - before_call 45 | assert 0.25 < time_diff < 0.5 46 | assert "second" in browser.driver.current_url 47 | 48 | 49 | # TODO: cover yoffset too 50 | def test_command_js_click__with_xoffset(session_browser): 51 | browser = session_browser.with_(timeout=0.5) 52 | page = GivenPage(browser.driver) 53 | page.opened_with_body( 54 | ''' 55 | 56 | ''' 57 | ) 58 | 59 | browser.element('input').click(xoffset=-10) 60 | 61 | browser.element('input').should(have.value('2')) 62 | -------------------------------------------------------------------------------- /tests/integration/element__lazy_search_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import be 23 | from tests.integration.helpers.givenpage import GivenPage 24 | 25 | 26 | def test_search_does_not_start_on_creation(session_browser): 27 | page = GivenPage(session_browser.driver) 28 | page.opened_empty() 29 | 30 | non_existent_element = session_browser.element('#not-existing-element-id') 31 | 32 | assert str(non_existent_element) 33 | 34 | 35 | def test_search_is_postponed_until_actual_action_like_questioning_displayed( 36 | session_browser, 37 | ): 38 | page = GivenPage(session_browser.driver) 39 | page.opened_empty() 40 | 41 | element = session_browser.element('#will-be-existing-element-id') 42 | page.load_body('

    Hello kitty:*

    ') 43 | 44 | assert element().is_displayed() is True 45 | 46 | 47 | def test_search_is_updated_on_next_actual_action_like_questioning_displayed( 48 | session_browser, 49 | ): 50 | page = GivenPage(session_browser.driver) 51 | page.opened_empty() 52 | 53 | element = session_browser.element('#will-be-existing-element-id') 54 | page.load_body('

    Hello kitty:*

    ') 55 | assert element().is_displayed() is True 56 | 57 | page.load_body( 58 | '

    Hello kitty:*

    ' 59 | ) 60 | assert element().is_displayed() is False 61 | -------------------------------------------------------------------------------- /tests/integration/element__perform__js__drop_file_test.py: -------------------------------------------------------------------------------- 1 | from selene import command, have 2 | from tests import resources 3 | from tests.integration.helpers.givenpage import GivenPage 4 | 5 | 6 | def x_test_drops_file_to_self(session_browser): 7 | browser = session_browser.with_(timeout=2) 8 | page = GivenPage(browser.driver) 9 | page.opened_empty() 10 | page.add_style_to_head( 11 | """ 12 | TODO: implement this 13 | """ 14 | ) 15 | page.add_script_to_head( 16 | """ 17 | TODO: implement this 18 | """ 19 | ) 20 | page.load_body( 21 | ''' 22 | TODO: implement this 23 | ''' 24 | ) 25 | 26 | # TODO: implement 27 | 28 | 29 | # todo: find an alternative web page to test this 30 | def x_test_drops_file_to_self_in_react_mui(session_browser): 31 | browser = session_browser 32 | browser.open('https://app.qa.guru/automation-practice-form/') 33 | browser.element('[data-testid=ClearIcon]').click() 34 | browser.element('[role=presentation]').perform(command.js.scroll_into_view) 35 | 36 | browser.element('[role=presentation]').drop_file(resources.path('selenite.png')) 37 | 38 | browser.element('[role=presentation]+*').all('.MuiTypography-body1').should( 39 | have.exact_texts('selenite.png') 40 | ) 41 | -------------------------------------------------------------------------------- /tests/integration/element__perform__select_all_test.py: -------------------------------------------------------------------------------- 1 | from selene import have, command 2 | 3 | from tests.integration.helpers.givenpage import GivenPage 4 | 5 | 6 | def test_select_all_called_on_element_makes_type_to_reset_text(session_browser): 7 | browser = session_browser.with_(timeout=1) 8 | page = GivenPage(browser.driver) 9 | page.opened_with_body( 10 | ''' 11 | 12 | ''' 13 | ) 14 | 15 | browser.element('#text-field').select_all().type('reset') 16 | 17 | browser.element('#text-field').should(have.value('reset')) 18 | 19 | 20 | # TODO: should we move it to browser__perform__select_all_test.py? 21 | def test_select_all_called_on_browser_makes_type_to_reset_text_on_focused_element( 22 | session_browser, 23 | ): 24 | browser = session_browser.with_(timeout=1) 25 | page = GivenPage(browser.driver) 26 | page.opened_with_body( 27 | ''' 28 | 29 | ''' 30 | ) 31 | browser.element('#text-field').click() # <- GIVEN 32 | 33 | browser.perform(command.select_all) 34 | browser.element('#text-field').type('reset') 35 | 36 | browser.element('#text-field').should(have.value('reset')) 37 | 38 | 39 | def test_select_all_called_on_browser_makes_type_to_append_text_on_not_focused_element( 40 | session_browser, 41 | ): 42 | browser = session_browser.with_(timeout=1) 43 | page = GivenPage(browser.driver) 44 | page.opened_with_body( 45 | ''' 46 | 47 | ''' 48 | ) 49 | # browser.element('#text-field').click() # <- GIVEN 50 | 51 | browser.perform(command.select_all) 52 | browser.element('#text-field').type(' to append') 53 | 54 | browser.element('#text-field').should(have.value('text to append')) 55 | -------------------------------------------------------------------------------- /tests/integration/element__scroll_to__test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | from selene import be, command 23 | from tests.integration.helpers.givenpage import GivenPage 24 | 25 | 26 | def test_can_scroll_to_element_manually(session_browser): 27 | session_browser.driver.set_window_size(1000, 100) 28 | GivenPage(session_browser.driver).opened_with_body( 29 | ''' 30 |
    31 |
    32 | 33 |

    Heading 1

    34 | ''' 35 | ) 36 | element = session_browser.element("#not-viewable-link") 37 | 38 | element.scroll_to_top() 39 | 40 | element.click() # we can click even if we did not make the scrolling 41 | # TODO: find the way to assert that scroll worked! 42 | assert "header" in session_browser.driver.current_url 43 | 44 | 45 | def test_can_scroll_to_element_automatically(session_browser): 46 | session_browser.driver.set_window_size(1000, 100) 47 | GivenPage(session_browser.driver).opened_with_body( 48 | ''' 49 |
    50 |
    51 |
    52 |

    Heading 1

    53 | ''' 54 | ) 55 | 56 | session_browser.element("#not-viewable-link").click() 57 | 58 | assert "header" in session_browser.driver.current_url 59 | -------------------------------------------------------------------------------- /tests/integration/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/integration/helpers/__init__.py -------------------------------------------------------------------------------- /tests/integration/query_size_test.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from selene import Browser, query, by 4 | from tests.integration.helpers.givenpage import GivenPage 5 | 6 | 7 | def test_query_size_of_browser(session_browser): 8 | size = session_browser.get(query.size) 9 | 10 | assert size == session_browser.driver.get_window_size() 11 | 12 | 13 | def test_query_size_of_collection(session_browser): 14 | page = GivenPage(session_browser.driver) 15 | page.opened_empty() 16 | elements = session_browser.all('.will-appear') 17 | page.load_body( 18 | ''' 19 |
      Hello to: 20 |
    • Bob
    • 21 | 22 |
    23 | ''' 24 | ) 25 | 26 | count = elements.get(query.size) 27 | 28 | assert count == 2 29 | 30 | 31 | def test_query_size_of_element(session_browser): 32 | page = GivenPage(session_browser.driver) 33 | page.opened_empty() 34 | page.load_body( 35 | ''' 36 |
      Hello to: 37 |
    • Bob
    • 38 | 39 |
    40 | ''' 41 | ) 42 | 43 | size = session_browser.element('ul').get(query.size) 44 | 45 | assert size == session_browser.driver.find_element(*by.css('ul')).size 46 | -------------------------------------------------------------------------------- /tests/integration/shared_browser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/integration/shared_browser/__init__.py -------------------------------------------------------------------------------- /tests/integration/shared_browser/browser__open_test.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | import pytest 23 | 24 | from selene import Browser, Config 25 | from tests.integration.helpers import givenpage 26 | 27 | 28 | def test_changes_window_size_on_open_according_to_config(chrome_driver): 29 | browser = Browser(Config()) 30 | browser.config.window_width = 640 31 | browser.config.window_height = 480 32 | 33 | browser.open(givenpage.EMPTY_PAGE_URL) 34 | 35 | assert browser.driver.get_window_size()['width'] == 640 36 | assert browser.driver.get_window_size()['height'] == 480 37 | 38 | 39 | @pytest.fixture(scope='function') 40 | def reset_window_size_afterwards(): 41 | yield 42 | 43 | from selene import browser, Config 44 | 45 | browser.config.window_width = Config().window_width 46 | browser.config.window_height = Config().window_height 47 | 48 | 49 | def test_changes_window_size_on_shared_browser_open_according_to_config( 50 | chrome_driver, reset_window_size_afterwards 51 | ): 52 | from selene import browser 53 | 54 | browser.config.window_width = 640 55 | browser.config.window_height = 480 56 | 57 | browser.open(givenpage.EMPTY_PAGE_URL) 58 | 59 | assert browser.driver.get_window_size()['width'] == 640 60 | assert browser.driver.get_window_size()['height'] == 480 61 | -------------------------------------------------------------------------------- /tests/resources/README.md: -------------------------------------------------------------------------------- 1 | The `selenite.png` photo inside this folder was made by
    Renee Kiffin and available for free on Unsplash 2 | -------------------------------------------------------------------------------- /tests/resources/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | # TODOMVC_URL = 'https://todomvc.com/examples/emberjs/' 4 | TODOMVC_URL = 'https://todomvc.com/examples/jquery/dist' 5 | 6 | 7 | def path(relative: str): 8 | return str(Path(__file__).parent.joinpath(relative).absolute()) 9 | 10 | 11 | def url(relative_path: str): 12 | return f'file://{path(relative_path)}' 13 | -------------------------------------------------------------------------------- /tests/resources/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Selene Test Page 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/resources/selenite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/resources/selenite.png -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /tests/unit/common/__init__.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2015-2022 Iakiv Kramarenko 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 | -------------------------------------------------------------------------------- /tests/unit/common/_typing_fucntions__query_test.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from selene.core.exceptions import ConditionMismatch 6 | from selene.common._typing_functions import Query 7 | 8 | 9 | def test_query_name_and_application(): 10 | ... 11 | 12 | is_positive = Query('is positive', lambda x: x > 0) 13 | 14 | assert 'is positive' == str(is_positive) 15 | assert is_positive(1) is True 16 | assert is_positive(0) is False 17 | 18 | 19 | def test_query_recomposition(): 20 | ... 21 | 22 | is_positive_increented_by = lambda number: Query( 23 | f'is positive incremented by {number}', lambda x: x + number > 0 24 | ) 25 | 26 | assert 'is positive incremented by 0' == str(is_positive_increented_by(0)) 27 | assert is_positive_increented_by(0)(0) is False 28 | assert is_positive_increented_by(1)(0) is True 29 | assert is_positive_increented_by(1)(-1) is False 30 | assert is_positive_increented_by(2)(-1) is True 31 | 32 | # TODO: consider the is_positive_increented_by(1).as('is increment positive') syntax 33 | is_increment_positive = Query('is increment positive', is_positive_increented_by(1)) 34 | 35 | assert 'is increment positive' == str(is_increment_positive) 36 | assert is_increment_positive(0) is True 37 | assert is_increment_positive(-1) is False 38 | -------------------------------------------------------------------------------- /tests/unit/common/data_structures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/unit/common/data_structures/__init__.py -------------------------------------------------------------------------------- /tests/unit/common/fp__either_test.py: -------------------------------------------------------------------------------- 1 | def test_either__returns_failure(): 2 | from selene.common.fp import _either 3 | 4 | result, maybe_failure = _either( 5 | lambda numbers: numbers[3], 6 | or_=IndexError, 7 | )([1, 2, 3]) 8 | 9 | assert isinstance(maybe_failure, IndexError) 10 | assert str(maybe_failure) == 'list index out of range' 11 | assert not result 12 | 13 | 14 | def test_either__returns_result(): 15 | from selene.common.fp import _either 16 | 17 | result, maybe_failure = _either( 18 | lambda numbers: numbers[2], 19 | or_=IndexError, 20 | )([1, 2, 3]) 21 | 22 | assert result == 3 23 | assert not maybe_failure 24 | -------------------------------------------------------------------------------- /tests/unit/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/unit/core/__init__.py -------------------------------------------------------------------------------- /tests/unit/core/_pom_test.py: -------------------------------------------------------------------------------- 1 | from selene.support._pom import element, all_ 2 | 3 | 4 | def test__pom__element_is_unique_for_each_object(): 5 | class Page: 6 | selector = '.element' 7 | element = element(selector) 8 | elements = all_(selector) 9 | 10 | page1 = Page() 11 | page2 = Page() 12 | 13 | assert page1.selector is page2.selector 14 | # but 15 | assert page1.element is not page2.element 16 | assert page1.elements is not page2.elements 17 | # while 18 | assert page1.element is page1.element 19 | assert page1.elements is page1.elements 20 | assert page2.element is page2.element 21 | assert page2.elements is page2.elements 22 | -------------------------------------------------------------------------------- /tests/unit/core/command__js__click_test.py: -------------------------------------------------------------------------------- 1 | from selene import command, Element 2 | from selene.common._typing_functions import Command 3 | import inspect 4 | 5 | 6 | def test_command_js_click__is_a_command_on_element_instance(): 7 | 8 | assert Command in inspect.getmro(command.js.click.__class__) 9 | # TODO: what else can we check? How can we check that it is Command[Element]? 10 | -------------------------------------------------------------------------------- /tests/unit/support/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yashaka/selene/f7b6d055bb41313f6fdb51221370f23f0428f55f/tests/unit/support/__init__.py --------------------------------------------------------------------------------