├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── release.yml │ ├── tests.yml │ └── version-bump.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── celeryconfig.py ├── cli.py ├── configs └── test │ ├── common.yml │ └── environments │ ├── dev.yml │ ├── prod.yml │ ├── qa.yml │ └── stage.yml ├── docker-compose.yml ├── images ├── uaf.svg └── uaf.webp ├── pytest.ini ├── ruff.toml ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── amazon │ ├── __init__.py │ ├── test_mobile_amazon.py │ └── test_web_amazon.py ├── fixtures │ ├── __init__.py │ └── conftest.py ├── pages │ ├── __init__.py │ ├── base_page.py │ ├── mobile │ │ ├── __init__.py │ │ └── amazon │ │ │ ├── __init__.py │ │ │ ├── composition │ │ │ ├── __init__.py │ │ │ ├── category_content.py │ │ │ ├── header_content.py │ │ │ └── product_content.py │ │ │ ├── home_page.py │ │ │ └── product_page.py │ └── web │ │ ├── __init__.py │ │ └── amazon │ │ ├── __init__.py │ │ ├── composition │ │ ├── __init__.py │ │ ├── filter_content.py │ │ ├── header_content.py │ │ └── product_content.py │ │ ├── home_page.py │ │ └── product_page.py ├── parabank │ ├── __init__.py │ └── test_fund_transfer.py ├── test_data │ ├── __init__.py │ ├── api │ │ └── __init__.py │ ├── appium │ │ ├── __init__.py │ │ └── capabilities.py │ └── web │ │ └── __init__.py └── unit │ ├── __init__.py │ ├── test_crypt.py │ ├── test_device_tasks.py │ ├── test_element_utils.py │ ├── test_enums.py │ ├── test_helper.py │ ├── test_locator_utils.py │ ├── test_mobile_driver_factory.py │ ├── test_mobile_native_swipe_utils.py │ ├── test_mongo_utils.py │ ├── test_postgres_utility.py │ ├── test_waits.py │ ├── test_web_driver_factory.py │ └── test_yaml_parser_utils.py ├── tox.ini └── uaf ├── __init__.py ├── ai ├── __init__.py └── bot.py ├── common ├── __init__.py └── helper.py ├── cryptic ├── __init__.py └── crypt.py ├── decorators ├── __init__.py ├── loggers │ ├── __init__.py │ └── logger.py └── pytest │ ├── __init__.py │ └── pytest_ordering.py ├── device_farming ├── __init__.py └── device_tasks.py ├── enums ├── __init__.py ├── appium_automation_name.py ├── browser_make.py ├── device_status.py ├── direction.py ├── driver_executable_paths.py ├── environments.py ├── execution_mode.py ├── file_paths.py ├── mobile_app_status.py ├── mobile_app_type.py ├── mobile_device_environment_type.py └── mobile_os.py ├── factories ├── __init__.py └── driver │ ├── __init__.py │ ├── abstract_factory │ ├── __init__.py │ ├── abstract_factory.py │ └── abstract_products │ │ ├── __init__.py │ │ ├── abstract_mobile │ │ ├── __init__.py │ │ ├── abstract_android.py │ │ ├── abstract_ios.py │ │ └── abstract_mobile.py │ │ └── abstract_web │ │ ├── __init__.py │ │ ├── abstract_brave.py │ │ ├── abstract_chrome.py │ │ ├── abstract_chromium.py │ │ ├── abstract_firefox.py │ │ ├── abstract_ie.py │ │ ├── abstract_msedge.py │ │ └── abstract_web_driver.py │ └── concrete_factory │ ├── __init__.py │ ├── concrete_factory.py │ └── concrete_products │ ├── __init__.py │ ├── mobile │ ├── __init__.py │ ├── concrete_android_driver.py │ ├── concrete_ios_driver.py │ └── concrete_mobile_driver.py │ └── web │ ├── __init__.py │ ├── concrete_brave_driver.py │ ├── concrete_chrome_driver.py │ ├── concrete_chromium_driver.py │ ├── concrete_firefox_driver.py │ ├── concrete_ie_driver.py │ ├── concrete_msedge_driver.py │ └── concrete_web_driver.py ├── py.typed ├── utilities ├── __init__.py ├── database │ ├── __init__.py │ ├── mongo_utils.py │ └── postgres_utility.py ├── faker │ ├── __init__.py │ └── faker_utils.py ├── parser │ ├── __init__.py │ └── yaml_parser_utils.py └── ui │ ├── __init__.py │ ├── appium_core │ ├── __init__.py │ ├── appium_core_utils.py │ └── appium_service.py │ ├── element │ ├── __init__.py │ └── element_utils.py │ ├── locator │ ├── __init__.py │ └── locator_utils.py │ ├── scroller │ ├── __init__.py │ └── scroll.py │ ├── swipe │ ├── __init__.py │ └── swipe_utils.py │ ├── waiter │ ├── __init__.py │ └── waits.py │ └── window │ ├── __init__.py │ └── window_utils.py └── version.py /.dockerignore: -------------------------------------------------------------------------------- 1 | allure-reports 2 | allure-results 3 | .secrets 4 | .env 5 | Pipfile.lock 6 | **/node_modules/ 7 | **/dist 8 | .git 9 | npm-debug.log 10 | __pycache__ 11 | *.pyc 12 | *.pyo 13 | *.pyd 14 | .Python 15 | env 16 | pip-log.txt 17 | pip-delete-this-directory.txt 18 | .tox 19 | .coverage 20 | .coverage.* 21 | .cache 22 | nosetests.xml 23 | coverage.xml 24 | *.cover 25 | *.log 26 | .git 27 | .mypy_cache 28 | .pytest_cache 29 | .hypothesis 30 | .mypy_cache 31 | .tox 32 | build 33 | celerybeat-schedule.bak 34 | celerybeat-schedule.dat 35 | celerybeat-schedule.dir 36 | celerybeat-schedule.db 37 | logs 38 | .vscode 39 | .idea 40 | *.egg-info/ -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Type of change 5 | 6 | - [ ] Bug fix (non-breaking change which fixes an issue) 7 | - [ ] New feature (non-breaking change which adds functionality) 8 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 9 | - [ ] This change requires a documentation update 10 | 11 | ## How Has This Been Tested? 12 | 13 | 14 | ## Checklist: 15 | 16 | - [ ] My code follows the style guidelines of this project 17 | - [ ] I have performed a self-review of my own code 18 | - [ ] I have commented my code, particularly in hard-to-understand areas 19 | - [ ] I have made corresponding changes to the documentation 20 | - [ ] My changes generate no new warnings 21 | - [ ] I have added tests that prove my fix is effective or that my feature works 22 | - [ ] New and existing unit tests pass locally with my changes 23 | - [ ] Any dependent changes have been merged and published in downstream modules 24 | 25 | ## Conventional Commits 26 | 27 | - [ ] My commit messages follow the Conventional Commits specification 28 | - [ ] I understand how my commits will affect the version bump (if applicable) 29 | 30 | ## Additional context 31 | 32 | 33 | ## Related Issue 34 | 35 | Fixes #(issue) 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_run: 5 | workflows: ["Version Bump"] 6 | types: 7 | - completed 8 | 9 | jobs: 10 | check_version_bump: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 13 | outputs: 14 | version_bumped: ${{ steps.check_version_bump.outputs.version_bumped }} 15 | steps: 16 | - name: Check if version was bumped 17 | id: check_version_bump 18 | run: | 19 | version_bumped=$(curl -sS -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ 20 | "https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}" \ 21 | | jq -r '.jobs[0].outputs.version_bumped') 22 | echo "version_bumped=${version_bumped}" >> $GITHUB_OUTPUT 23 | 24 | build: 25 | needs: check_version_bump 26 | if: ${{ needs.check_version_bump.outputs.version_bumped == 'true' }} 27 | runs-on: ubuntu-latest 28 | strategy: 29 | fail-fast: false 30 | matrix: 31 | python-version: ["3.11.4", "3.11.5", "3.11.6", "3.11.7", "3.11.8", "3.11.9"] 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 0 37 | 38 | - name: Get new version 39 | id: get_version 40 | run: echo "new_version=$(grep -oP '(?<=__version__ = ")[^"]*' uaf/version.py)" >> $GITHUB_OUTPUT 41 | 42 | - name: Setup python for release ${{ matrix.python-version }} 43 | uses: actions/setup-python@v5 44 | with: 45 | python-version: ${{ matrix.python-version }} 46 | 47 | - name: Update pip 48 | run: pip install --upgrade pip 49 | 50 | - name: Install dependencies 51 | run: | 52 | sudo apt-get update 53 | sudo apt-get install -y make 54 | pip install build pre-commit tox tox-gh-actions cryptography 55 | 56 | - name: Upgrade pip 57 | run: make upgrade-pip 58 | 59 | - name: Set PYTHON_VERSION environment variable 60 | run: | 61 | PATCH_VERSION=$(echo "${{ matrix.python-version }}" | awk -F. '{print $3}') 62 | echo "PYTHON_VERSION=311${PATCH_VERSION}" >> $GITHUB_ENV 63 | 64 | - name: Decrypt configs 65 | run: make decrypt security_key=${{ secrets.SECURITY_KEY }} 66 | 67 | - name: Build package 68 | run: make tox PYTHON_VERSION=$PYTHON_VERSION 69 | env: 70 | PYTHON_VERSION: ${{ env.PYTHON_VERSION }} 71 | 72 | - name: Upload artifact 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: dist-${{ matrix.python-version }} 76 | path: dist/*.whl 77 | 78 | release: 79 | needs: [check_version_bump, build] 80 | if: ${{ needs.check_version_bump.outputs.version_bumped == 'true' }} 81 | runs-on: ubuntu-latest 82 | steps: 83 | - name: Checkout code 84 | uses: actions/checkout@v4 85 | with: 86 | fetch-depth: 0 87 | 88 | - name: Get new version 89 | id: get_version 90 | run: | 91 | git pull 92 | echo "new_version=$(grep -oP '(?<=__version__ = ")[^"]*' uaf/version.py)" >> $GITHUB_OUTPUT 93 | 94 | - name: Download all artifacts 95 | uses: actions/download-artifact@v4 96 | with: 97 | path: dist 98 | 99 | - name: Release 100 | uses: softprops/action-gh-release@v2 101 | with: 102 | files: dist/**/*.whl 103 | tag_name: v${{ steps.get_version.outputs.new_version }} 104 | name: Release ${{ steps.get_version.outputs.new_version }} 105 | draft: false 106 | prerelease: false 107 | env: 108 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 109 | 110 | debug: 111 | needs: check_version_bump 112 | runs-on: ubuntu-latest 113 | if: always() 114 | steps: 115 | - name: Debug Information 116 | run: | 117 | echo "Version bumped: ${{ needs.check_version_bump.outputs.version_bumped }}" 118 | echo "Workflow run conclusion: ${{ github.event.workflow_run.conclusion }}" 119 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build-uaf: 10 | name: Python ${{ matrix.python-version }} on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: [ubuntu-latest] 16 | python-version: 17 | ["3.11.4", "3.11.5", "3.11.6", "3.11.7", "3.11.8", "3.11.9"] 18 | steps: 19 | - name: Checkout code 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Setup python for test ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | 29 | - name: Update pip 30 | run: pip install --upgrade pip 31 | 32 | - name: Install dependencies 33 | run: | 34 | sudo apt-get update 35 | sudo apt-get install -y make 36 | pip install build pre-commit tox tox-gh-actions cryptography 37 | 38 | - name: Upgrade pip 39 | run: make upgrade-pip 40 | 41 | - name: Set PYTHON_VERSION environment variable 42 | run: | 43 | PATCH_VERSION=$(echo "${{ matrix.python-version }}" | awk -F. '{print $3}') 44 | echo "PYTHON_VERSION=311${PATCH_VERSION}" >> $GITHUB_ENV 45 | 46 | - name: Decrypt configs 47 | run: make decrypt security_key=${{ secrets.SECURITY_KEY }} 48 | 49 | - name: Build package 50 | run: make tox PYTHON_VERSION=$PYTHON_VERSION 51 | env: 52 | PYTHON_VERSION: ${{ env.PYTHON_VERSION }} 53 | 54 | - name: Unit test 55 | run: | 56 | source .tox/py$PYTHON_VERSION/bin/activate 57 | make test 58 | env: 59 | PYTHON_VERSION: ${{ env.PYTHON_VERSION }} 60 | 61 | - name: Clean up 62 | run: make clean 63 | 64 | tests-passed: 65 | needs: build-uaf 66 | runs-on: ubuntu-latest 67 | steps: 68 | - run: echo "All tests passed successfully" 69 | outputs: 70 | success: ${{ job.status == 'success' }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | allure-reports 2 | allure-results 3 | .secrets 4 | .env 5 | Pipfile.lock 6 | **/node_modules/ 7 | **/dist 8 | .git 9 | npm-debug.log 10 | __pycache__ 11 | *.pyc 12 | *.pyo 13 | *.pyd 14 | .Python 15 | env 16 | pip-log.txt 17 | pip-delete-this-directory.txt 18 | .tox 19 | .coverage 20 | .coverage.* 21 | .cache 22 | nosetests.xml 23 | coverage.xml 24 | *.cover 25 | *.log 26 | .git 27 | .mypy_cache 28 | .pytest_cache 29 | .hypothesis 30 | .mypy_cache 31 | .tox 32 | build 33 | celerybeat-schedule.bak 34 | celerybeat-schedule.dat 35 | celerybeat-schedule.dir 36 | celerybeat-schedule.db 37 | logs 38 | .vscode 39 | .idea 40 | *.egg-info/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v5.0.0 4 | hooks: 5 | - id: check-yaml 6 | 7 | - repo: https://github.com/asottile/pyupgrade 8 | rev: v3.18.0 9 | hooks: 10 | - id: pyupgrade 11 | args: [--py310-plus] 12 | 13 | - repo: https://github.com/pre-commit/mirrors-mypy 14 | rev: v1.12.0 15 | hooks: 16 | - id: mypy 17 | entry: mypy uaf/ tests 18 | pass_filenames: false 19 | additional_dependencies: 20 | - types-requests 21 | - types-PyYAML 22 | - types-dataclasses 23 | # Add other specific types if required 24 | 25 | - repo: https://github.com/psf/black 26 | rev: 24.10.0 27 | hooks: 28 | - id: black 29 | language_version: python3.11 30 | 31 | - repo: https://github.com/astral-sh/ruff-pre-commit 32 | rev: v0.6.9 33 | hooks: 34 | - id: ruff 35 | args: [--fix, --config=ruff.toml] 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to This Project 2 | 3 | Thank you for your interest in contributing to our project. To maintain consistency and ensure our automated version bumping works correctly, please follow these guidelines. 4 | 5 | ## Commit Message Standards 6 | 7 | We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification for our commit messages. This helps us automatically determine version bumps and generate changelogs. 8 | 9 | ### Commit Message Format 10 | 11 | Each commit message should be structured as follows: 12 | 13 | ``` 14 | [optional scope]: 15 | 16 | [optional body] 17 | 18 | [optional footer(s)] 19 | ``` 20 | 21 | ### Types 22 | 23 | - `feat`: A new feature (triggers a minor version bump) 24 | - `fix`: A bug fix (triggers a patch version bump) 25 | - `docs`: Documentation only changes 26 | - `style`: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 27 | - `refactor`: A code change that neither fixes a bug nor adds a feature 28 | - `perf`: A code change that improves performance 29 | - `test`: Adding missing tests or correcting existing tests 30 | - `chore`: Changes to the build process or auxiliary tools and libraries such as documentation generation 31 | 32 | ### Breaking Changes 33 | 34 | For commits that introduce breaking changes, add `BREAKING CHANGE:` in the commit body or footer. This will trigger a major version bump. 35 | 36 | ### Examples 37 | 38 | ``` 39 | feat: add user authentication feature 40 | 41 | BREAKING CHANGE: `auth` function now requires an API key 42 | ``` 43 | 44 | ``` 45 | fix: correct calculation in billing module 46 | ``` 47 | 48 | ``` 49 | docs: update README with new build instructions 50 | ``` 51 | 52 | ## How This Affects Version Bumping 53 | 54 | Our automated version bump process works as follows: 55 | 56 | 1. If any commit since the last release contains `BREAKING CHANGE`, the major version is bumped. 57 | 2. Otherwise, if any commit contains `feat:`, the minor version is bumped. 58 | 3. If neither of the above conditions are met, but there are `fix:` commits, the patch version is bumped. 59 | 4. If there are only `chore:`, `docs:`, `style:`, `refactor:`, `perf:`, or `test:` commits, no version bump occurs. 60 | 61 | ### Multiple Commit Types in a Single Release 62 | 63 | When a release includes multiple commit types: 64 | 65 | - The highest-priority change determines the version bump (major > minor > patch). 66 | - Commits that don't trigger a version bump (like `chore:`) are included in the release but don't affect the version number. 67 | 68 | For example, if you have commits with `feat:`, `fix:`, and `chore:` since the last release, it will result in a minor version bump (due to `feat:`), and all changes (including those from `fix:` and `chore:`) will be included in the release notes. 69 | 70 | ## Pull Request Process 71 | 72 | 1. Ensure your commits follow the standards outlined above. 73 | 2. Update the README.md or relevant documentation with details of changes, if applicable. 74 | 3. Use the pull request template provided when creating your pull request. This template is automatically loaded when you create a new pull request and includes checkboxes for ensuring your contribution meets our standards. 75 | 4. Fill out the pull request template completely, checking off each item as you complete it. 76 | 5. In the pull request description, provide a clear explanation of the changes and the rationale behind them. 77 | 6. If your pull request addresses an existing issue, reference that issue in the pull request description using the syntax `Fixes #123` (where 123 is the issue number). 78 | 7. You may merge the Pull Request once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you. 79 | 80 | ## Pull Request Template 81 | 82 | When you create a new pull request, you'll see a template with various sections and checkboxes. This template is designed to ensure that your contribution meets our project standards and provides reviewers with all necessary information. Please fill out each section of the template thoroughly. 83 | 84 | Key points in the pull request template: 85 | 86 | - Describe the type of change (bug fix, new feature, breaking change, etc.) 87 | - Explain how you've tested your changes 88 | - Confirm that you've followed project guidelines (code style, documentation, etc.) 89 | - Verify that your commit messages follow the Conventional Commits specification 90 | - Provide any additional context or screenshots that might be helpful 91 | 92 | By using this template, you help maintainers and reviewers understand your contribution more quickly and ensure that all necessary information is provided upfront. 93 | 94 | ## Questions? 95 | 96 | If you have any questions about the contribution process or these standards, please reach out to the project maintainers. 97 | 98 | Thank you for helping us maintain a consistent and efficient development process! 99 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] [Suneel Kaushik Subramanya] 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. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: upgrade-pip install-pre-commit run-pre-commit tox tox-env build install-wheel clean help test generate-key encrypt decrypt pre-commit-install 2 | 3 | # Variables 4 | TOX_ENV_NAME = py3119 # default environment name 5 | PACKAGE_NAME = 6 | 7 | # Upgrade pip 8 | upgrade-pip: 9 | @echo "Upgrading pip..." 10 | @pip install --upgrade pip 11 | 12 | # Run tests with pytest 13 | test: 14 | @echo "Running tests with pytest..." 15 | @pytest -m unit_test 16 | 17 | # Run tox with specified Python version 18 | tox: 19 | @echo "Running tox for Python version $(PYTHON_VERSION)..." 20 | @if [ -z "$(PYTHON_VERSION)" ]; then \ 21 | echo "Error: PYTHON_VERSION is not set. Supported versions: 3114, 3115, 3116, 3117, 3118, 3119."; \ 22 | exit 1; \ 23 | fi 24 | @case $(PYTHON_VERSION) in \ 25 | 3114|3115|3116|3117|3118|3119) tox -e py$(PYTHON_VERSION) ;; \ 26 | *) echo "Error: Unsupported PYTHON_VERSION. Supported versions: 3114, 3115, 3116, 3117, 3118, 3119."; exit 1 ;; \ 27 | esac 28 | 29 | # Build package 30 | build: 31 | @echo "Building package..." 32 | @python -m build --wheel --outdir dist 33 | 34 | # Install package from wheel 35 | install-wheel: 36 | @echo "Installing package..." 37 | @pip install dist/*.whl --force-reinstall 38 | 39 | # Generate new key 40 | generate-key: 41 | @echo "Generating new key..." 42 | @python cli.py --mode generate_key 43 | 44 | # Encrypt sensitive data file 45 | encrypt: 46 | @echo "Encrypting sensitive data file..." 47 | @python cli.py --mode encrypt --key "$(security_key)" --data_file "configs/test/common.yml" 48 | 49 | # Decrypt sensitive data file 50 | decrypt: 51 | @echo "Decrypting sensitive data file..." 52 | @python cli.py --mode decrypt --key "$(security_key)" --data_file "configs/test/common.yml" 53 | 54 | # Install pre-commit hooks 55 | install-pre-commit: 56 | @echo "Installing pre-commit hooks..." 57 | @pre-commit install 58 | 59 | # Run pre-commit hooks 60 | run-pre-commit: 61 | @echo "Running pre-commit hooks..." 62 | @pre-commit run --all-files || ( echo "Pre-commit checks failed"; exit 1 ) 63 | 64 | # Clean up 65 | clean: 66 | @echo "Cleaning up..." 67 | @rm -rf .pytest_cache .tox dist build .mypy_cache .ruff_cache *.egg-info 68 | @find . -type d -name "allure-reports" -exec rm -rf {} + 69 | @find . -type d -name "allure-results" -exec rm -rf {} + 70 | @find . -type d -name "__pycache__" -exec rm -rf {} + 71 | @find . -type f -name "*.pyc" -delete 72 | @find . -type f -name "*.pyo" -delete 73 | 74 | # Helper section 75 | help: 76 | @echo "Available targets:" 77 | @echo " upgrade-pip - Upgrade pip" 78 | @echo " test - Run tests with pytest" 79 | @echo " tox - Run tox with specified Python version" 80 | @echo " build - Build package" 81 | @echo " install-wheel - Install wheel" 82 | @echo " generate-key - Generate new key" 83 | @echo " encrypt - Encrypt sensitive data file" 84 | @echo " decrypt - Decrypt sensitive data file" 85 | @echo " install-pre-commit - Install pre-commit hooks" 86 | @echo " run-pre-commit - Run pre-commit hooks" 87 | @echo " clean - Clean up" 88 | @echo " help - Show this help message" 89 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | If you discover a security vulnerability in the `uaf` project, please submit it to us by [opening an issue](https://github.com/suneel944/uaf/issues/new/choose). We take all security vulnerabilities seriously and will do our best to address them promptly. 6 | 7 | ## Supported Versions 8 | 9 | | Version | Supported | 10 | | ------- | ------------------ | 11 | | 0.0.x | :white_check_mark: | 12 | 13 | We are actively developing and testing the `uaf` project and will provide security updates as needed. 14 | 15 | ## Security Updates 16 | 17 | Security updates will be provided in new releases of the `uaf` project. It is recommended that you regularly update to the latest version of the project to ensure that you have the most up-to-date security features. 18 | 19 | ## End-of-Life 20 | 21 | At this time, we do not have an end-of-life date for the `uaf` project. We will provide updates and support for the project as long as it remains active. 22 | 23 | ## Contact Us 24 | 25 | If you have any questions or concerns about the security of the `uaf` project, please contact us at [email address or other contact information]. We appreciate your contributions to the project and your help in keeping it secure. 26 | -------------------------------------------------------------------------------- /celeryconfig.py: -------------------------------------------------------------------------------- 1 | # global Celery options that apply to all configurations 2 | 3 | # Specify the serializer to use for task messages. 4 | # By default, Celery uses JSON, which is a lightweight and widely supported 5 | # format, but may not be suitable for all use cases. In this case, we use 6 | # the pickle serializer, which can handle more complex Python objects. 7 | task_serializer = "pickle" 8 | # Specify the serializer to use for task results. 9 | # By default, Celery uses JSON to serialize task results, but this may not be 10 | # suitable for all use cases, as JSON has limitations when it comes to 11 | # serializing complex data structures and types. In this case, we use the 12 | # pickle serializer for consistency with the task serializer. 13 | result_serializer = "pickle" 14 | # Specify the content types that the worker is willing to accept. 15 | # This is a security measure that helps protect against deserialization 16 | # attacks, which can occur when a malicious user sends a specially crafted 17 | # message to a worker. By default, Celery accepts a range of content types, 18 | # but we restrict it to the pickle serializer only to reduce the attack surface. 19 | accept_content = ["pickle"] 20 | # Specify a list of modules to import when starting the worker. 21 | # This is necessary to ensure that the worker has access to the task functions 22 | # that it needs to execute. It's best practice to specify the full import path 23 | # to the module to avoid potential naming conflicts or ambiguity. Note that 24 | # this option should only be used to import modules that define tasks, and not 25 | # for general-purpose imports or global variables. 26 | imports = ["uaf.device_farming.device_tasks"] 27 | # Specify the maximum number of retries for a failed task. 28 | # In this case, we set the max retries to 3, which means that Celery will attempt to execute 29 | # the task up to 3 times before marking it as failed. 30 | task_max_retries = 1 31 | # Specify the delay between retries for a failed task. 32 | # In this case, we set the retry delay to 5 seconds, which means that Celery will wait 5 seconds 33 | # between each retry attempt. 34 | task_retry_delay = 3 35 | # Specify the concurrency settings for the worker pool. 36 | # By default, Celery uses a single worker process with a concurrency of 1, which may not be sufficient 37 | # for high-load environments. In this case, we set the concurrency to 4, which means that the worker 38 | # will create 1 worker processes to handle tasks concurrently. 39 | worker_concurrency = 1 40 | # Specify the maximum number of tasks that a worker can execute before being recycled. 41 | # This setting can help prevent memory leaks and ensure that workers are periodically restarted 42 | # to free up system resources. In this case, we set the worker max tasks per child to 1000. 43 | worker_max_tasks_per_child = 1000 44 | # Control whether Celery retries connecting to the broker during the startup phase. 45 | # By default, this is set to False, meaning that Celery will not retry broker connections on startup, 46 | # and a failed connection will cause startup failure. When set to True, Celery will attempt to retry 47 | # the broker connection on startup if the first attempt fails. This is useful in environments where the broker 48 | # might not be immediately available, such as during transient network issues or delayed broker startups. 49 | broker_connection_retry_on_startup = True 50 | -------------------------------------------------------------------------------- /cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from uaf.cryptic.crypt import decrypt_file, encrypt_file, generate_key 4 | 5 | if __name__ == "__main__": 6 | parser = argparse.ArgumentParser(description="Encrypt and decrypt sensitive data") 7 | parser.add_argument( 8 | "--mode", 9 | choices=["encrypt", "decrypt", "generate_key"], 10 | help="choose either encrypt/decrypt or generate_key as mode", 11 | ) 12 | parser.add_argument( 13 | "--key", help="secret key to either encrypt/decrypt sensitive file" 14 | ) 15 | parser.add_argument("--data_file", help="Data file to be encrypted/decrypted") 16 | args = parser.parse_args() 17 | 18 | if not args.mode: 19 | parser.error( 20 | "Please provide a mode to proceed further. See help for further infomation" 21 | ) 22 | 23 | if args.mode in ["encrypt", "decrypt"]: 24 | if not args.data_file: 25 | parser.error("Please provide a file to proceed further") 26 | 27 | if args.mode == "encrypt": 28 | encrypt_file(args.data_file, args.key.encode()) 29 | elif args.mode == "decrypt": 30 | decrypt_file(args.data_file, args.key.encode()) 31 | elif args.mode == "generate_key": 32 | print(generate_key().decode()) 33 | -------------------------------------------------------------------------------- /configs/test/common.yml: -------------------------------------------------------------------------------- 1 | gAAAAABnFWYNlIjQNaAo9IHRouWO6TjzWvIHHGvT93c3dIYp1KDHd6DpDmAkwWYYI1FHJsyH2khpeDmvToTeeOC67nNnZDivaWIQzqhanCVwLIzeMaKXOVOjvjNxVlLypcGQivK9gH4YKpmO5AuzD-cTVtHNEtu_HpCQ8KbzWieY9npRJ8_lKC30QhUNnloao4sTPpcYwJxL8TtzAvisHtW9xlwYjKXzCOaKgNSO_x3yz8XS-DPLEAiiXr_8wAi8DdoNPf7YnDqzDy5Vm0kcl4CWVVJ0KQTw8iWWIAq8QCaknj6o1mWSnsPTRuWS1LmjfWH5awOteujjyQmDtAORqiL7KKK6BzsZ2WKBuy5q30MS8D-0Pc5qWK-4mZg3yD-kkRdj0Ple--wmo9lAUok2dPC_qhmgyTL87330qD7azTRF5EpVuyr4mablXmF3FbgszXow7nt4387sQaWqg5tjxroZlPR4xq4nfpcXph9BF0OWqd-G64VYd2h66pJwpMTXQScgyZcacFqeWC1pYCZBNgBlVfJiC1uE4IPXrdixNMGoI3Sr4PovhGEG4p4NZlz4k8egYeT1QvKUNISsrwTGVL6STAEtxAkXw4sGkSF4t4Y9gJPGZn-8ElNRlqHBEddaapdcsMcg-vnbavA2smuaiyTzu7j3ZQhyNpuIfaDs4wqg1NpUigpJbt_BjJNOkq9eN9roBh9QSEJnvnyj2d1JHjKeYf3_dV8erFTUDDrjZ4XDJPL00NK4zeUg7D4hY-sQSmXI2RPA-hYhy1ZC6w-JUgoHi3r5hnIKImbfHnU-qGa_gyGLQUGylaUSFT4W_oG0xWy-6-nuFWmkWyjrXSPkPYj7tMkkMGG6Kq6O6APxppuq2pQdX0O1eDsVzHs4e0_9xY5asCd4_4ox4MvkPpnMUwuEfVW4D2o00NTRSyiKJyrRmW8aXc0GMw5z5rkqdWgmqGs1l_asclnnbFzjtyMmuhh2y_m52Pi9Wg== -------------------------------------------------------------------------------- /configs/test/environments/dev.yml: -------------------------------------------------------------------------------- 1 | info: 2 | name: Development 3 | description: Holds specific information pertaining to development test environment 4 | 5 | urls: 6 | google: https://www.google.co.in 7 | amazon: https://www.amazon.in 8 | 9 | amazon: 10 | hamburger_menu_panel_heading: shop by category 11 | hamburger_menu_panel_sub_heading: TV, Appliances, Electronics 12 | hamburger_menu_panel_sub_section_extension_heading: tv, audio & cameras 13 | hamburger_menu_panel_sub_section_extension_choice: Televisions 14 | filter_section_parent_category: Home Entertainment Brands 15 | filter_section_parent_choice: Top Brands 16 | filter_section_child_category: Brands 17 | filter_section_child_choice: Sony 18 | sort_product_based_on: price-desc-rank 19 | desired_product_index: 1 20 | mobile_hamburger_menu_panel_heading: top categories for you 21 | mobile_hamburger_menu_panel_sub_heading: See All Categories 22 | mobile_category_parent: Software 23 | mobile_category_child: Office 24 | mobile_desired_product_index: 1 -------------------------------------------------------------------------------- /configs/test/environments/prod.yml: -------------------------------------------------------------------------------- 1 | info: 2 | name: Production 3 | description: Holds specific information pertaining to production test environment 4 | 5 | urls: 6 | google: https://www.google.co.in 7 | amazon: https://www.amazon.in -------------------------------------------------------------------------------- /configs/test/environments/qa.yml: -------------------------------------------------------------------------------- 1 | info: 2 | name: QA 3 | description: Holds specific information pertaining to qa test environment 4 | 5 | urls: 6 | google: https://www.google.co.in 7 | amazon: https://www.amazon.in -------------------------------------------------------------------------------- /configs/test/environments/stage.yml: -------------------------------------------------------------------------------- 1 | info: 2 | name: Stage 3 | description: Holds specific information pertaining to stage test environment 4 | 5 | urls: 6 | google: https://www.google.co.in 7 | amazon: https://www.amazon.in -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | rabbitmq: 3 | image: rabbitmq:management 4 | ports: 5 | - "5672:5672" # RabbitMQ messaging port 6 | - "15672:15672" # RabbitMQ management UI port 7 | environment: 8 | RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER} # Use environment variables for credentials 9 | RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS} 10 | volumes: 11 | - rabbitmq-data:/var/lib/rabbitmq # Persist RabbitMQ data 12 | 13 | mongo: 14 | image: mongo 15 | restart: always 16 | ports: 17 | - "27017:27017" # Expose MongoDB on default port 27017 18 | environment: 19 | MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME} # Use environment variables for security 20 | MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} 21 | volumes: 22 | - mongo-data:/data/db # Persist MongoDB data 23 | 24 | mongo-express: 25 | image: mongo-express 26 | restart: always 27 | ports: 28 | - "8081:8081" # Expose Mongo Express on port 8081 29 | environment: 30 | ME_CONFIG_MONGODB_ADMINUSERNAME: ${MONGO_INITDB_ROOT_USERNAME} # Use the same root username 31 | ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_INITDB_ROOT_PASSWORD} # Use the same root password 32 | ME_CONFIG_BASICAUTH: true # Enable basic authentication 33 | ME_CONFIG_MONGODB_URL: mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongo:27017/ 34 | 35 | allure: 36 | image: "frankescobar/allure-docker-service" 37 | environment: 38 | CHECK_RESULTS_EVERY_SECONDS: 30 # Check every 30 seconds 39 | KEEP_HISTORY: 1 # Keep historical reports 40 | ports: 41 | - "5050:5050" # Allure Service UI port 42 | user: "${UID}:${GID}" # Set user to match host user (optional) 43 | volumes: 44 | - ./allure-results:/app/allure-results:rw # Mount volumes with restricted permissions 45 | - ./allure-reports:/app/default-reports:rw 46 | 47 | allure-ui: 48 | image: "frankescobar/allure-docker-service-ui" 49 | environment: 50 | ALLURE_DOCKER_PUBLIC_API_URL: "http://localhost:5050" # Allure service API URL 51 | ALLURE_DOCKER_PUBLIC_API_URL_PREFIX: "" 52 | ports: 53 | - "5252:5252" # Allure UI port 54 | user: "${UID}:${GID}" # Set user to match host user (optional) 55 | 56 | volumes: 57 | mongo-data: # Volume for MongoDB data 58 | rabbitmq-data: # Volume for RabbitMQ data 59 | allure-results: # Volume for allure-results 60 | allure-reports: # Volume for allure-reports 61 | -------------------------------------------------------------------------------- /images/uaf.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/images/uaf.webp -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | unit_test: Run the unit tests 4 | regression: Run the regression tests 5 | web_test: Run the web tests 6 | mobile_test: Run the mobile tests 7 | 8 | addopts = -v -rs -s 9 | testpaths = tests 10 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # Exclude a variety of commonly ignored directories. 2 | exclude = [ 3 | ".bzr", 4 | ".direnv", 5 | ".eggs", 6 | ".git", 7 | ".git-rewrite", 8 | ".hg", 9 | ".ipynb_checkpoints", 10 | ".mypy_cache", 11 | ".nox", 12 | ".pants.d", 13 | ".pyenv", 14 | ".pytest_cache", 15 | ".pytype", 16 | ".ruff_cache", 17 | ".svn", 18 | ".tox", 19 | ".venv", 20 | ".vscode", 21 | "__pypackages__", 22 | "_build", 23 | "buck-out", 24 | "build", 25 | "dist", 26 | "node_modules", 27 | "site-packages", 28 | "venv", 29 | ] 30 | 31 | # Assume Python 3.11 32 | target-version = "py311" 33 | 34 | [lint] 35 | # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. 36 | # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or 37 | # McCabe complexity (`C901`) by default. 38 | select = ["E4", "E7", "E9", "F"] 39 | ignore = ["F401", "F811"] 40 | 41 | # Allow fix for all enabled rules (when `--fix`) is provided. 42 | fixable = ["ALL"] 43 | unfixable = [] 44 | 45 | # Allow unused variables when underscore-prefixed. 46 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 47 | 48 | [format] 49 | # Enable auto-formatting of code examples in docstrings. Markdown, 50 | # reStructuredText code/literal blocks and doctests are all supported. 51 | # 52 | # This is currently disabled by default, but it is planned for this 53 | # to be opt-out in the future. 54 | docstring-code-format = true 55 | 56 | # Set the line length limit used when formatting code snippets in 57 | # docstrings. 58 | # 59 | # This only has an effect when the `docstring-code-format` setting is 60 | # enabled. 61 | docstring-code-line-length = "dynamic" -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | packages = find: 3 | python_requires = >=3.11 4 | install_requires = 5 | requests>=2.32.3 6 | mypy>=1.12.0 7 | tox>=4.23.0 8 | selenium>=4.25.0 9 | appium-python-client>=4.2.0 10 | webdriver-manager>=4.0.2 11 | faker>=30.6.0 12 | ruff>=0.6.9 13 | psutil>=6.0.0 14 | pymongo>=4.10.1 15 | psycopg>=3.2.3 16 | celery>=5.4.0 17 | loguru>=0.7.2 18 | pre-commit>=4.0.1 19 | types-pyyaml>=6.0.12.20240917 20 | cryptography>=43.0.1 21 | openai>=1.51.2 22 | zip_safe = False 23 | 24 | 25 | [options.extras_require] 26 | testing = 27 | pytest>=8.3.3 28 | pytest-cov>=5.0.0 29 | pytest-xdist>=3.6.1 30 | 31 | 32 | [options.package_data] 33 | uaf = py.typed 34 | 35 | 36 | [mypy] 37 | check_untyped_defs = true 38 | disallow_any_generics = true 39 | ignore_missing_imports = true 40 | no_implicit_optional = true 41 | show_error_codes = true 42 | strict_equality = true 43 | warn_redundant_casts = true 44 | warn_return_any = true 45 | warn_unreachable = true 46 | warn_unused_configs = true 47 | no_implicit_reexport = true 48 | follow_imports = skip 49 | strict_optional = True 50 | warn_unused_ignores = False -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | 4 | from setuptools import find_packages, setup 5 | 6 | from uaf.common.helper import library_version 7 | 8 | setup( 9 | name="uaf", 10 | version=library_version(), 11 | description="Universal automation framework", 12 | long_description=open( 13 | os.path.join(os.path.dirname("__file__"), "README.md"), encoding="utf-8" 14 | ).read(), 15 | keywords=[ 16 | "uaf", 17 | "uaf python", 18 | "api automation", 19 | "web automation", 20 | "mobile automation", 21 | ], 22 | author="Suneel Kaushik Subramanya", 23 | author_email="suneel944@gmail.com", 24 | maintainer="Suneel Kaushik Subramanya", 25 | package_data={"uaf": ["py.typed"]}, 26 | packages=find_packages(include=["uaf*"]), 27 | license="MIT", 28 | # A list of classifiers to help users find your package on PyPI 29 | classifiers=[ 30 | "Development Status :: 3 - Alpha", 31 | "Programming Language :: Python", 32 | "Programming Language :: Python :: 3.11", 33 | "Environment :: Console", 34 | "Operating System :: OS Independent", 35 | "Topic :: Software Development :: Quality Assurance", 36 | "Topic :: Software Development :: Testing", 37 | ], 38 | ) 39 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/__init__.py -------------------------------------------------------------------------------- /tests/amazon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/amazon/__init__.py -------------------------------------------------------------------------------- /tests/amazon/test_mobile_amazon.py: -------------------------------------------------------------------------------- 1 | from pytest import mark 2 | 3 | from tests.fixtures.conftest import mobile_driver # type: ignore 4 | from tests.pages.mobile.amazon.home_page import HomePage 5 | from tests.pages.mobile.amazon.product_page import ProductPage 6 | from uaf.enums.appium_automation_name import AppiumAutomationName 7 | from uaf.enums.browser_make import MobileWebBrowserMake 8 | from uaf.enums.file_paths import FilePaths 9 | from uaf.enums.mobile_app_type import MobileAppType 10 | from uaf.enums.mobile_device_environment_type import MobileDeviceEnvironmentType 11 | from uaf.enums.mobile_os import MobileOs 12 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 13 | 14 | config = YamlParser(FilePaths.TEST_CONFIG_DEV) 15 | 16 | 17 | @mark.mobile_test 18 | @mark.parametrize( 19 | "mobile_driver", 20 | [ 21 | { 22 | "arg_mobile_os": MobileOs.ANDROID, 23 | "arg_mobile_app_type": MobileAppType.WEB, 24 | "arg_mobile_device_environment_type": MobileDeviceEnvironmentType.PHYSICAL, 25 | "arg_automation_name": AppiumAutomationName.UIAUTOMATOR2, 26 | "arg_mobile_web_browser": MobileWebBrowserMake.CHROME, 27 | } 28 | ], 29 | indirect=True, 30 | ) 31 | def test_mobile_web_amazon_product_search(mobile_driver): # noqa 32 | home_page = HomePage(mobile_driver) 33 | product_page = ProductPage(mobile_driver) 34 | home_page.go_to( 35 | config.get_value("urls", "amazon") 36 | ).get_header_content().click_on_hamburger_menu().select_left_menu_specifics( 37 | config.get_value("amazon", "mobile_hamburger_menu_panel_heading"), 38 | config.get_value("amazon", "mobile_hamburger_menu_panel_sub_heading"), 39 | ).get_category_content().select_category_grid( 40 | config.get_value("amazon", "mobile_category_parent") 41 | ).select_category_grid( 42 | config.get_value("amazon", "mobile_category_child") 43 | ).get_product_content().click_on_product( 44 | config.get_value("amazon", "mobile_desired_product_index") 45 | ).get_product_page() 46 | 47 | assert isinstance(product_page.get_product_text_content(), str) 48 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/fixtures/__init__.py -------------------------------------------------------------------------------- /tests/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/__init__.py -------------------------------------------------------------------------------- /tests/pages/base_page.py: -------------------------------------------------------------------------------- 1 | from uaf.enums.file_paths import FilePaths 2 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 3 | from uaf.utilities.ui.element.element_utils import ElementUtils 4 | from uaf.utilities.ui.locator.locator_utils import LocatorUtils 5 | from uaf.utilities.ui.scroller.scroll import ScrollUtils 6 | from uaf.utilities.ui.swipe.swipe_utils import SwipeUtils 7 | from uaf.utilities.ui.waiter.waits import Waits 8 | from uaf.utilities.ui.window.window_utils import WindowUtils 9 | 10 | 11 | class BasePage: 12 | def __init__(self, driver): 13 | self.driver = driver 14 | self.element = ElementUtils(driver) 15 | self.scroller = ScrollUtils(driver) 16 | self.config = YamlParser(FilePaths.COMMON) 17 | self.wait = Waits(driver) 18 | self.locator = LocatorUtils(driver) 19 | self.window = WindowUtils(driver) 20 | self.swipe = SwipeUtils(driver) 21 | -------------------------------------------------------------------------------- /tests/pages/mobile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/mobile/__init__.py -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/mobile/amazon/__init__.py -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/composition/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/mobile/amazon/composition/__init__.py -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/composition/category_content.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from tests.pages.base_page import BasePage 4 | from tests.pages.mobile.amazon.composition.product_content import ProductContent 5 | 6 | 7 | class CategoryContent(BasePage): 8 | def __init__(self, driver) -> None: 9 | super().__init__(driver) 10 | 11 | def get_product_content(self) -> ProductContent: 12 | """Retrieves product content instance 13 | 14 | Returns: 15 | ProductContent: product content instance 16 | """ 17 | return ProductContent(self.driver) 18 | 19 | def __img_category_grid(self, text: str) -> tuple[str, str]: 20 | return (By.XPATH, f".//*[@alt='{text}']") 21 | 22 | def select_category_grid(self, category: str): 23 | """Select category grid 24 | 25 | Args: 26 | category (str): name of category 27 | 28 | Returns: 29 | CategoryContent: category content instance 30 | """ 31 | self.scroller.scroll_to_element(self.__img_category_grid(category)) 32 | self.element.click_on_element(self.__img_category_grid(category)) 33 | return self 34 | -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/composition/header_content.py: -------------------------------------------------------------------------------- 1 | from appium.webdriver.common.appiumby import AppiumBy 2 | 3 | from tests.pages.base_page import BasePage 4 | from tests.pages.mobile.amazon.composition.category_content import CategoryContent 5 | from tests.pages.mobile.amazon.composition.product_content import ProductContent 6 | 7 | 8 | class HeaderContent(BasePage): 9 | def __init__(self, driver): 10 | super().__init__(driver) 11 | 12 | # Elements 13 | __BTN_HAMBURGER_MENU: tuple[str, str] = ( 14 | AppiumBy.XPATH, 15 | ".//*[@id='nav-hamburger-menu']", 16 | ) 17 | __ELE_FILTER_MENU: tuple[str, str] = ( 18 | AppiumBy.XPATH, 19 | ".//*[@data-csa-c-content-id='s-all-filters']", 20 | ) 21 | __LNK_SHOW_RESULTS: tuple[str, str] = ( 22 | AppiumBy.XPATH, 23 | "//*[contains(text(),'result')]//parent::a", 24 | ) 25 | 26 | def __tab_filter_panel_selection(self, tab_name: str) -> tuple[str, str]: 27 | return ( 28 | AppiumBy.XPATH, 29 | f".//*[@id='{tab_name}']//*[@data-action='s-vtab-action']", 30 | ) 31 | 32 | def __lnk_left_panel_menu_selection(self, text: str) -> tuple[str, str]: 33 | return ( 34 | AppiumBy.XPATH, 35 | f".//*[@id='hmenu-content']//*[contains(@class,'hmenu-visible')]//*[text()='{text}']", 36 | ) 37 | 38 | def __lnk_sort_by(self, type: str): 39 | return (AppiumBy.XPATH, f".//*[contains(@id,'sort/')]//*[text()='{type}']") 40 | 41 | def get_category_content(self) -> CategoryContent: 42 | """Retrieves filter content instance 43 | 44 | Returns: 45 | FilterContent: filter content instance 46 | """ 47 | return CategoryContent(self.driver) 48 | 49 | def get_product_content(self): 50 | """Retrieves product content instance 51 | 52 | Returns: 53 | ProductContent: product content instance 54 | """ 55 | return ProductContent(self.driver) 56 | 57 | def filter_product_based_on_specifics(self, tab_name: str, filter_specific: str): 58 | self.element.click_on_element(self.__ELE_FILTER_MENU) 59 | self.wait.wait_for_element_visibility( 60 | self.__tab_filter_panel_selection(tab_name) 61 | ) 62 | self.element.click_on_element(self.__tab_filter_panel_selection(tab_name)) 63 | self.element.click_on_element(self.__lnk_sort_by(filter_specific)) 64 | self.element.click_on_element(self.__LNK_SHOW_RESULTS) 65 | return self 66 | 67 | def click_on_hamburger_menu(self) -> "HeaderContent": 68 | self.element.click_on_element(self.__BTN_HAMBURGER_MENU) 69 | return self 70 | 71 | def select_left_menu_specifics( 72 | self, heading: str, sub_section: str 73 | ) -> "HeaderContent": 74 | self.scroller.scroll_to_element(self.__lnk_left_panel_menu_selection(heading)) 75 | self.element.click_on_element_using_js( 76 | self.__lnk_left_panel_menu_selection(sub_section) 77 | ) 78 | return self 79 | -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/composition/product_content.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from tests.pages.base_page import BasePage 4 | 5 | 6 | class ProductContent(BasePage): 7 | def __init__(self, driver): 8 | super().__init__(driver) 9 | 10 | # locators 11 | def __ele_product_tile(self, product_index: int): 12 | return ( 13 | By.XPATH, 14 | f".//*[@data-component-type='s-search-result'][{product_index}]", 15 | ) 16 | 17 | # composition 18 | def get_product_page(self): 19 | """Retrieves product page instance 20 | 21 | Returns: 22 | _type_: _description_ 23 | """ 24 | from tests.pages.mobile.amazon.product_page import ProductPage 25 | 26 | return ProductPage(self.driver) 27 | 28 | # page actions 29 | 30 | def click_on_product(self, product_index: int): 31 | """Select product 32 | 33 | Args: 34 | product_index (int): product index 35 | 36 | Returns: 37 | ProductPage: product page instance 38 | """ 39 | self.wait.wait_for_element_visibility(self.__ele_product_tile(product_index)) 40 | self.scroller.scroll_to_element(self.__ele_product_tile(product_index)) 41 | self.element.click_on_element(self.__ele_product_tile(product_index)) 42 | return self 43 | -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/home_page.py: -------------------------------------------------------------------------------- 1 | from tests.pages.base_page import BasePage 2 | from tests.pages.mobile.amazon.composition.category_content import CategoryContent 3 | from tests.pages.mobile.amazon.composition.header_content import HeaderContent 4 | from tests.pages.mobile.amazon.composition.product_content import ProductContent 5 | 6 | 7 | class HomePage(BasePage): 8 | """Page layer for amazon home page""" 9 | 10 | def __init__(self, driver): 11 | super().__init__(driver) 12 | 13 | # composition 14 | 15 | def get_category_content(self) -> CategoryContent: 16 | """Retrieves filter content instance 17 | 18 | Returns: 19 | FilterContent: FilterContent instance 20 | """ 21 | return CategoryContent(self.driver) 22 | 23 | def get_header_content(self) -> HeaderContent: 24 | """Retrieves header content instance 25 | 26 | Returns: 27 | HeaderContent: HeaderContent instance 28 | """ 29 | return HeaderContent(self.driver) 30 | 31 | def get_product_content(self) -> ProductContent: 32 | """Retrieves product content instance 33 | 34 | Returns: 35 | ProductContent: ProductContent instance 36 | """ 37 | return ProductContent(self.driver) 38 | 39 | # page actions 40 | def go_to(self, url: str) -> "HomePage": 41 | self.element.launch_url(url) 42 | return self 43 | -------------------------------------------------------------------------------- /tests/pages/mobile/amazon/product_page.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from selenium.webdriver.common.by import By 4 | 5 | from tests.pages.base_page import BasePage 6 | 7 | 8 | class ProductPage(BasePage): 9 | def __init__(self, driver): 10 | super().__init__(driver) 11 | 12 | __TXT_PRODUCT_DETAILS: tuple[str, str] = ( 13 | By.XPATH, 14 | ".//*[@id='featureSeeMore']//*[@id='feature-bullets']", 15 | ) 16 | 17 | def get_product_text_content(self) -> str | Any: 18 | self.window.switch_to_succeeding_window() 19 | self.scroller.scroll_to_element(self.__TXT_PRODUCT_DETAILS) 20 | return self.element.get_text_from_element(self.__TXT_PRODUCT_DETAILS) 21 | -------------------------------------------------------------------------------- /tests/pages/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/web/__init__.py -------------------------------------------------------------------------------- /tests/pages/web/amazon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/web/amazon/__init__.py -------------------------------------------------------------------------------- /tests/pages/web/amazon/composition/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/pages/web/amazon/composition/__init__.py -------------------------------------------------------------------------------- /tests/pages/web/amazon/composition/filter_content.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from tests.pages.base_page import BasePage 4 | from tests.pages.web.amazon.composition.product_content import ProductContent 5 | 6 | 7 | class FilterContent(BasePage): 8 | def __init__(self, driver) -> None: 9 | super().__init__(driver) 10 | 11 | def __cbox_left_filter_pane(self, text: str) -> tuple[str, str]: 12 | return (By.XPATH, f".//*[@id='s-refinements']//*[text()='{text}']/..") 13 | 14 | def select_filter_content_parent_category( 15 | self, parent_choice_category: str, parent_choice: str 16 | ) -> "FilterContent": 17 | self.wait.wait_for_element_presence( 18 | self.__cbox_left_filter_pane(parent_choice_category) 19 | ) 20 | self.scroller.scroll_to_element( 21 | self.__cbox_left_filter_pane(parent_choice_category) 22 | ) 23 | self.element.click_on_element(self.__cbox_left_filter_pane(parent_choice)) 24 | return self 25 | 26 | def select_filter_content_child_category( 27 | self, child_choice_category: str, child_choice: str 28 | ) -> ProductContent: 29 | self.wait.wait_for_element_presence( 30 | self.__cbox_left_filter_pane(child_choice_category) 31 | ) 32 | self.scroller.scroll_to_element( 33 | self.__cbox_left_filter_pane(child_choice_category) 34 | ) 35 | self.element.click_on_element(self.__cbox_left_filter_pane(child_choice)) 36 | return ProductContent(self.driver) 37 | -------------------------------------------------------------------------------- /tests/pages/web/amazon/composition/header_content.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from tests.pages.base_page import BasePage 4 | from tests.pages.web.amazon.composition.filter_content import FilterContent 5 | 6 | 7 | class HeaderContent(BasePage): 8 | def __init__(self, driver): 9 | super().__init__(driver) 10 | 11 | # Elements 12 | __BTN_HAMBURGER_MENU: tuple[str, str] = (By.XPATH, ".//*[@id='nav-hamburger-menu']") 13 | 14 | def __lnk_left_panel_menu_selection(self, text: str) -> tuple[str, str]: 15 | return ( 16 | By.XPATH, 17 | f".//*[@id='hmenu-content']//*[contains(@class,'hmenu-visible')]//*[text()='{text}']", 18 | ) 19 | 20 | def get_filter_content(self) -> FilterContent: 21 | """Retrieves filter content instance 22 | 23 | Returns: 24 | FilterContent: FilterContent instance 25 | """ 26 | return FilterContent(self.driver) 27 | 28 | def click_on_hamburger_menu(self) -> "HeaderContent": 29 | self.element.click_on_element(self.__BTN_HAMBURGER_MENU) 30 | return self 31 | 32 | def select_left_menu_specifics( 33 | self, heading: str, sub_section: str 34 | ) -> "HeaderContent": 35 | self.scroller.scroll_to_element(self.__lnk_left_panel_menu_selection(heading)) 36 | self.element.click_on_element(self.__lnk_left_panel_menu_selection(sub_section)) 37 | return self 38 | -------------------------------------------------------------------------------- /tests/pages/web/amazon/composition/product_content.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | from tests.pages.base_page import BasePage 4 | 5 | 6 | class ProductContent(BasePage): 7 | def __init__(self, driver): 8 | super().__init__(driver) 9 | 10 | def get_product_page(self): 11 | """Retrieves product page instance 12 | 13 | Returns: 14 | _type_: _description_ 15 | """ 16 | from tests.pages.web.amazon.product_page import ProductPage 17 | 18 | return ProductPage(self.driver) 19 | 20 | __TXT_PRODUCT_PRICE = (By.XPATH, "//span[@class='a-price-whole'][1]") 21 | __SLT_PRODUCT_SORT = (By.XPATH, ".//*[@id='s-result-sort-select']") 22 | 23 | def sort_product(self, value): 24 | self.wait.wait_for_page_load() 25 | self.element.select_from_drop_down(self.__SLT_PRODUCT_SORT, value) 26 | return self 27 | 28 | def click_on_the_desired_product(self, product_index): 29 | self.element.click_on_element(self.__TXT_PRODUCT_PRICE) 30 | # return product page instance 31 | return self.get_product_page() 32 | -------------------------------------------------------------------------------- /tests/pages/web/amazon/home_page.py: -------------------------------------------------------------------------------- 1 | from tests.pages.base_page import BasePage 2 | from tests.pages.web.amazon.composition.filter_content import FilterContent 3 | from tests.pages.web.amazon.composition.header_content import HeaderContent 4 | from tests.pages.web.amazon.composition.product_content import ProductContent 5 | 6 | 7 | class HomePage(BasePage): 8 | """Page layer for amazon home page""" 9 | 10 | def __init__(self, driver): 11 | super().__init__(driver) 12 | 13 | # composition 14 | 15 | def get_filter_content(self) -> FilterContent: 16 | """Retrieves filter content instance 17 | 18 | Returns: 19 | FilterContent: FilterContent instance 20 | """ 21 | return FilterContent(self.driver) 22 | 23 | def get_header_content(self) -> HeaderContent: 24 | """Retrieves header content instance 25 | 26 | Returns: 27 | HeaderContent: HeaderContent instance 28 | """ 29 | return HeaderContent(self.driver) 30 | 31 | def get_product_content(self) -> ProductContent: 32 | """Retrieves product content instance 33 | 34 | Returns: 35 | ProductContent: ProductContent instance 36 | """ 37 | return ProductContent(self.driver) 38 | 39 | # page actions 40 | def go_to(self, url: str) -> "HomePage": 41 | self.element.launch_url(url) 42 | return self 43 | -------------------------------------------------------------------------------- /tests/pages/web/amazon/product_page.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from selenium.webdriver.common.by import By 4 | 5 | from tests.pages.base_page import BasePage 6 | 7 | 8 | class ProductPage(BasePage): 9 | def __init__(self, driver): 10 | super().__init__(driver) 11 | 12 | __TXT_PRODUCT_CONTENT: tuple[str, str] = (By.ID, "feature-bullets") 13 | 14 | def get_product_text_content(self) -> str | Any: 15 | self.window.switch_to_succeeding_window() 16 | self.scroller.scroll_to_element(self.__TXT_PRODUCT_CONTENT) 17 | return self.element.get_text_from_element(self.__TXT_PRODUCT_CONTENT) 18 | -------------------------------------------------------------------------------- /tests/parabank/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/parabank/__init__.py -------------------------------------------------------------------------------- /tests/parabank/test_fund_transfer.py: -------------------------------------------------------------------------------- 1 | from tests.fixtures.conftest import web_driver 2 | from pytest import mark 3 | from uaf.enums.browser_make import WebBrowserMake 4 | from selenium.webdriver.common.by import By 5 | from uaf.utilities.ui.waiter.waits import Waits 6 | 7 | 8 | @mark.parametrize( 9 | "web_driver", [{"arg_browser_make": WebBrowserMake.CHROME}], indirect=True 10 | ) 11 | def test_fund_transfer(web_driver): 12 | waiter = Waits(web_driver) 13 | web_driver.get("https://parabank.parasoft.com/parabank/index.htm") 14 | web_driver.find_element(By.XPATH, ".//*[@type='text'][@name='username']").send_keys( 15 | "test_1" 16 | ) 17 | web_driver.find_element( 18 | By.XPATH, ".//*[@type='password'][@name='password']" 19 | ).send_keys("demo") 20 | web_driver.find_element(By.XPATH, ".//*[@type='submit']").click() 21 | waiter.wait_for_element_visibility( 22 | (By.XPATH, ".//a[text()='Transfer Funds']") 23 | ).click() 24 | -------------------------------------------------------------------------------- /tests/test_data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/test_data/__init__.py -------------------------------------------------------------------------------- /tests/test_data/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/test_data/api/__init__.py -------------------------------------------------------------------------------- /tests/test_data/appium/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from uaf.enums.browser_make import MobileWebBrowserMake 4 | 5 | __all__ = ["MobileWebBrowserMake", "Optional"] 6 | -------------------------------------------------------------------------------- /tests/test_data/web/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/test_data/web/__init__.py -------------------------------------------------------------------------------- /tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/tests/unit/__init__.py -------------------------------------------------------------------------------- /tests/unit/test_crypt.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | from pytest import mark, raises 4 | 5 | from uaf.cryptic.crypt import generate_key, encrypt_file, decrypt_file 6 | 7 | 8 | @mark.unit_test 9 | def test_generate_key(): 10 | key = generate_key() 11 | assert isinstance(key, bytes) 12 | assert len(key) == 44 # Base64 encoded 32-byte key 13 | 14 | 15 | @mark.unit_test 16 | @mark.parametrize( 17 | "test_message", [b"This is a test message.", b"Another test message."] 18 | ) 19 | def test_encrypt_decrypt_file(test_message): 20 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 21 | temp_file.write(test_message) 22 | temp_file_path = temp_file.name 23 | 24 | try: 25 | key = generate_key() 26 | 27 | encrypt_file(temp_file_path, key) 28 | with open(temp_file_path, "rb") as f: 29 | encrypted_content = f.read() 30 | assert encrypted_content != test_message 31 | 32 | decrypt_file(temp_file_path, key) 33 | with open(temp_file_path, "rb") as f: 34 | decrypted_content = f.read() 35 | assert decrypted_content == test_message 36 | 37 | finally: 38 | os.unlink(temp_file_path) 39 | 40 | 41 | @mark.unit_test 42 | def test_encrypt_decrypt_with_wrong_key(): 43 | test_message = b"This is another test message." 44 | with tempfile.NamedTemporaryFile(delete=False) as temp_file: 45 | temp_file.write(test_message) 46 | temp_file_path = temp_file.name 47 | 48 | try: 49 | key1 = generate_key() 50 | key2 = generate_key() 51 | 52 | encrypt_file(temp_file_path, key1) 53 | 54 | # Use pytest.raises to expect an exception 55 | with raises(Exception): 56 | decrypt_file(temp_file_path, key2) 57 | 58 | finally: 59 | os.unlink(temp_file_path) 60 | -------------------------------------------------------------------------------- /tests/unit/test_device_tasks.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock, patch 3 | from uuid import UUID 4 | 5 | from uaf.device_farming.device_tasks import ( 6 | add_new_devices_to_list, 7 | reserve_device, 8 | release_device, 9 | check_device, 10 | ) 11 | from uaf.enums.device_status import DeviceStatus 12 | from uaf.enums.mobile_device_environment_type import MobileDeviceEnvironmentType 13 | from uaf.enums.mobile_os import MobileOs 14 | 15 | 16 | @fixture 17 | def mock_mongo_client(): 18 | return MagicMock() 19 | 20 | 21 | @fixture 22 | def mock_core_utils(): 23 | return MagicMock() 24 | 25 | 26 | @fixture 27 | def mock_config(): 28 | config = MagicMock() 29 | config.get_value.return_value = "test_collection" 30 | return config 31 | 32 | 33 | @mark.unit_test 34 | @patch("uaf.device_farming.device_tasks.mongo_client", new_callable=MagicMock) 35 | @patch("uaf.device_farming.device_tasks.CoreUtils", new_callable=MagicMock) 36 | @patch("uaf.device_farming.device_tasks.config", new_callable=MagicMock) 37 | @patch("platform.system") 38 | def test_add_new_devices_to_list( 39 | mock_system, mock_config, mock_core_utils, mock_mongo_client 40 | ): 41 | mock_system.return_value = "Linux" 42 | mock_mongo_client.find_many.return_value = [{"device_id": "existing_device"}] 43 | mock_core_utils.fetch_connected_android_devices_ids.return_value = [ 44 | "new_device", 45 | "existing_device", 46 | ] 47 | 48 | add_new_devices_to_list() 49 | 50 | # Adjust the expectation to use the config mock instead of hardcoded "test_collection" 51 | mock_mongo_client.insert_many.assert_called_once_with( 52 | mock_config.get_value(), # Use the mock config's get_value() 53 | [ 54 | { 55 | "device_id": "new_device", 56 | "device_type": MobileDeviceEnvironmentType.PHYSICAL.value, 57 | "device_os": MobileOs.ANDROID.value, 58 | "status": DeviceStatus.AVAILABLE.value, 59 | } 60 | ], 61 | ) 62 | 63 | 64 | @mark.unit_test 65 | @patch("uaf.device_farming.device_tasks.mongo_client", new_callable=MagicMock) 66 | @patch("uaf.device_farming.device_tasks.config", new_callable=MagicMock) 67 | @patch("uaf.device_farming.device_tasks.choice") 68 | @patch("uaf.device_farming.device_tasks.__get_unique_id") 69 | def test_reserve_device( 70 | mock_get_unique_id, mock_choice, mock_config, mock_mongo_client 71 | ): 72 | mock_mongo_client.find_many.return_value = [ 73 | {"device_id": "device1", "device_os": "android"}, 74 | {"device_id": "device2", "device_os": "ios"}, 75 | ] 76 | mock_choice.return_value = "device1" 77 | mock_get_unique_id.return_value = UUID("12345678-1234-5678-1234-567812345678") 78 | 79 | device_id, uuid = reserve_device("android") 80 | 81 | assert device_id == "device1" 82 | assert uuid == UUID("12345678-1234-5678-1234-567812345678") 83 | mock_mongo_client.update_one.assert_called_once() 84 | mock_mongo_client.insert_one.assert_called_once() 85 | 86 | 87 | @mark.unit_test 88 | @patch("uaf.device_farming.device_tasks.mongo_client", new_callable=MagicMock) 89 | @patch("uaf.device_farming.device_tasks.config", new_callable=MagicMock) 90 | def test_release_device(mock_config, mock_mongo_client): 91 | device_id = "test_device" 92 | session_id = UUID("12345678-1234-5678-1234-567812345678") 93 | 94 | release_device(device_id, session_id) 95 | 96 | assert mock_mongo_client.update_one.call_count == 2 97 | 98 | 99 | @mark.unit_test 100 | @patch("uaf.device_farming.device_tasks.mongo_client", new_callable=MagicMock) 101 | @patch("uaf.device_farming.device_tasks.config", new_callable=MagicMock) 102 | def test_check_device(mock_config, mock_mongo_client): 103 | mock_mongo_client.find_many.return_value = [ 104 | {"device_id": "device1"}, 105 | {"device_id": "device2"}, 106 | ] 107 | 108 | check_device() 109 | 110 | assert mock_mongo_client.update_one.call_count == 2 111 | # Adjust the expectation to use the config mock instead of hardcoded "test_collection" 112 | mock_mongo_client.update_one.assert_called_with( 113 | mock_config.get_value(), # Use the mock config's get_value() 114 | {"device_id": "device2"}, 115 | {"$set": {"status": DeviceStatus.AVAILABLE.value}}, 116 | ) 117 | -------------------------------------------------------------------------------- /tests/unit/test_element_utils.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock, patch 3 | from selenium.webdriver.common.by import By 4 | 5 | from uaf.utilities.ui.element.element_utils import ElementUtils 6 | 7 | 8 | @fixture 9 | def mock_driver(): 10 | return MagicMock() 11 | 12 | 13 | @fixture 14 | def element_utils(mock_driver): 15 | return ElementUtils(mock_driver) 16 | 17 | 18 | @mark.unit_test 19 | def test_element_utils_init(element_utils): 20 | assert element_utils.driver is not None 21 | assert element_utils.wait is not None 22 | assert element_utils.locator is not None 23 | 24 | 25 | @mark.unit_test 26 | @patch( 27 | "uaf.utilities.ui.locator.locator_utils.LocatorUtils.by_locator_to_mobile_element" 28 | ) 29 | def test_click_on_element_using_js(mock_by_locator, element_utils): 30 | by_locator = (By.ID, "test_id") 31 | mock_element = MagicMock() 32 | mock_by_locator.return_value = mock_element 33 | 34 | element_utils.click_on_element_using_js(by_locator) 35 | 36 | element_utils.driver.execute_script.assert_called_once_with( 37 | "arguments[0].click();", mock_element 38 | ) 39 | 40 | 41 | @mark.unit_test 42 | @patch("uaf.utilities.ui.waiter.waits.Waits.wait_for_element_to_be_clickable") 43 | def test_click_on_element(mock_wait_for_clickable, element_utils): 44 | by_locator = (By.ID, "test_id") 45 | mock_element = MagicMock() 46 | mock_wait_for_clickable.return_value = mock_element 47 | 48 | element_utils.click_on_element(by_locator) 49 | 50 | mock_wait_for_clickable.assert_called_once_with(by_locator) 51 | mock_element.click.assert_called_once() 52 | 53 | 54 | @mark.unit_test 55 | @patch("uaf.utilities.ui.waiter.waits.Waits.wait_for_page_load") 56 | def test_launch_url(mock_wait_for_page_load, element_utils): 57 | url = "https://example.com" 58 | element_utils.launch_url(url) 59 | element_utils.driver.get.assert_called_once_with(url) 60 | mock_wait_for_page_load.assert_called_once() 61 | 62 | 63 | @mark.unit_test 64 | @patch("uaf.utilities.ui.waiter.waits.Waits.wait_for_title") 65 | def test_fetch_title(mock_wait_for_title, element_utils): 66 | title = "Test Title" 67 | element_utils.driver.title = title 68 | 69 | result = element_utils.fetch_title(title) 70 | 71 | mock_wait_for_title.assert_called_once_with(title) 72 | assert result == title 73 | 74 | 75 | @mark.unit_test 76 | @patch("uaf.utilities.ui.locator.locator_utils.LocatorUtils.by_locator_to_web_element") 77 | @patch("time.sleep") 78 | def test_send_keys(mock_sleep, mock_by_locator, element_utils): 79 | by_locator = (By.ID, "test_id") 80 | text = "test text" 81 | mock_element = MagicMock() 82 | mock_by_locator.return_value = mock_element 83 | 84 | element_utils.send_keys(by_locator, text) 85 | mock_element.send_keys.assert_called_once_with(text) 86 | 87 | element_utils.send_keys(by_locator, text, enter_char_by_char=True) 88 | assert mock_element.send_keys.call_count == len(text) + 1 89 | assert mock_sleep.call_count == len(text) 90 | 91 | 92 | @mark.unit_test 93 | @patch("uaf.utilities.ui.locator.locator_utils.LocatorUtils.by_locator_to_web_element") 94 | def test_get_text_from_element(mock_by_locator, element_utils): 95 | by_locator = (By.ID, "test_id") 96 | expected_text = "Test Text" 97 | mock_element = MagicMock() 98 | mock_element.text = expected_text 99 | mock_by_locator.return_value = mock_element 100 | 101 | result = element_utils.get_text_from_element(by_locator) 102 | 103 | assert result == expected_text 104 | 105 | 106 | @mark.unit_test 107 | @patch("uaf.utilities.ui.locator.locator_utils.LocatorUtils.by_locator_to_web_element") 108 | @patch("uaf.utilities.ui.element.element_utils.Select") 109 | def test_select_from_drop_down(mock_select, mock_by_locator, element_utils): 110 | by_locator = (By.ID, "test_id") 111 | value = "option1" 112 | mock_element = MagicMock() 113 | mock_by_locator.return_value = mock_element 114 | 115 | element_utils.select_from_drop_down(by_locator, value) 116 | 117 | mock_select.assert_called_once_with(mock_element) 118 | mock_select.return_value.select_by_value.assert_called_once_with(value) 119 | -------------------------------------------------------------------------------- /tests/unit/test_enums.py: -------------------------------------------------------------------------------- 1 | from pytest import mark 2 | 3 | from uaf.enums.browser_make import WebBrowserMake 4 | from uaf.enums.file_paths import FilePaths 5 | from uaf.enums.mobile_os import MobileOs 6 | from uaf.enums.environments import Environments 7 | from uaf.enums.execution_mode import ExecutionMode 8 | 9 | 10 | @mark.unit_test 11 | @mark.parametrize( 12 | "arg_browser_enum", 13 | [ 14 | WebBrowserMake.BRAVE, 15 | WebBrowserMake.CHROME, 16 | WebBrowserMake.CHROMIUM, 17 | WebBrowserMake.IE, 18 | WebBrowserMake.MSEDGE, 19 | WebBrowserMake.FIREFOX, 20 | ], 21 | ) 22 | def test_browser_enum(arg_browser_enum: WebBrowserMake): 23 | assert isinstance(arg_browser_enum.value, str) 24 | 25 | 26 | @mark.unit_test 27 | @mark.parametrize( 28 | "arg_filepath_enum", 29 | [ 30 | FilePaths.COMMON, 31 | FilePaths.TEST_CONFIG_DEV, 32 | FilePaths.TEST_CONFIG_PROD, 33 | FilePaths.TEST_CONFIG_QA, 34 | FilePaths.TEST_CONFIG_STAGE, 35 | ], 36 | ) 37 | def test_filepath_enum(arg_filepath_enum: FilePaths): 38 | assert isinstance(arg_filepath_enum.value, str) 39 | 40 | 41 | @mark.unit_test 42 | @mark.parametrize("arg_mobileOs_enum", [MobileOs.ANDROID, MobileOs.IOS]) 43 | def test_mobileOs_enum(arg_mobileOs_enum: MobileOs): 44 | assert isinstance(arg_mobileOs_enum.value, str) 45 | 46 | 47 | @mark.unit_test 48 | @mark.parametrize( 49 | "arg_env_enum", 50 | [ 51 | Environments.DEVELOPMENT, 52 | Environments.PRODUCTION, 53 | Environments.QA, 54 | Environments.STAGE, 55 | ], 56 | ) 57 | def test_environment_enum(arg_env_enum: Environments): 58 | assert isinstance(arg_env_enum.value, str) 59 | 60 | 61 | @mark.unit_test 62 | @mark.parametrize("arg_exec_mode", [ExecutionMode.LOCAL, ExecutionMode.REMOTE]) 63 | def test_execution_mode_enum(arg_exec_mode: ExecutionMode): 64 | assert isinstance(arg_exec_mode.value, str) 65 | -------------------------------------------------------------------------------- /tests/unit/test_helper.py: -------------------------------------------------------------------------------- 1 | from pytest import mark 2 | 3 | from uaf.common.helper import library_version 4 | from uaf import version as uaf_version 5 | 6 | 7 | @mark.unit_test 8 | def test_library_version(): 9 | assert library_version() == uaf_version.version 10 | assert isinstance(library_version(), str) 11 | -------------------------------------------------------------------------------- /tests/unit/test_locator_utils.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock 3 | from selenium.webdriver.common.by import By 4 | from appium.webdriver.common.appiumby import AppiumBy 5 | from uaf.utilities.ui.locator.locator_utils import LocatorUtils 6 | 7 | 8 | @fixture 9 | def mock_driver(): 10 | driver = MagicMock() 11 | driver.find_element.return_value = MagicMock() 12 | driver.find_elements.return_value = [MagicMock(), MagicMock()] 13 | return driver 14 | 15 | 16 | @fixture 17 | def locator_utils(mock_driver): 18 | return LocatorUtils(mock_driver) 19 | 20 | 21 | @mark.unit_test 22 | def test_locator_utils_init(locator_utils, mock_driver): 23 | assert locator_utils.driver == mock_driver 24 | 25 | 26 | @mark.unit_test 27 | def test_by_locator_to_web_element(locator_utils, mock_driver): 28 | by_locator = (By.ID, "test_id") 29 | element = locator_utils.by_locator_to_web_element(by_locator) 30 | mock_driver.find_element.assert_called_once_with(By.ID, "test_id") 31 | assert element == mock_driver.find_element.return_value 32 | 33 | 34 | @mark.unit_test 35 | def test_by_locator_to_mobile_element(locator_utils, mock_driver): 36 | by_locator = (AppiumBy.ACCESSIBILITY_ID, "test_accessibility_id") 37 | element = locator_utils.by_locator_to_mobile_element(by_locator) 38 | mock_driver.find_element.assert_called_once_with( 39 | AppiumBy.ACCESSIBILITY_ID, "test_accessibility_id" 40 | ) 41 | assert element == mock_driver.find_element.return_value 42 | 43 | 44 | @mark.unit_test 45 | def test_by_locator_to_web_elements(locator_utils, mock_driver): 46 | by_locator = (By.CLASS_NAME, "test_class") 47 | elements = locator_utils.by_locator_to_web_elements(by_locator) 48 | mock_driver.find_elements.assert_called_once_with(By.CLASS_NAME, "test_class") 49 | assert elements == mock_driver.find_elements.return_value 50 | assert len(elements) == 2 51 | 52 | 53 | @mark.unit_test 54 | def test_perform_by_to_element_conversion(locator_utils, mock_driver): 55 | by_locator = (By.NAME, "test_name") 56 | element = locator_utils._LocatorUtils__perform_by_to_element_conversion(by_locator) 57 | mock_driver.find_element.assert_called_once_with(By.NAME, "test_name") 58 | assert element == mock_driver.find_element.return_value 59 | -------------------------------------------------------------------------------- /tests/unit/test_mobile_native_swipe_utils.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture, raises 2 | from unittest.mock import MagicMock, patch 3 | from selenium.common.exceptions import NoSuchElementException 4 | from uaf.utilities.ui.swipe.swipe_utils import SwipeUtils 5 | from uaf.enums.direction import Direction 6 | 7 | 8 | @fixture 9 | def mock_driver(): 10 | return MagicMock() 11 | 12 | 13 | @fixture 14 | def swipe_utils(mock_driver): 15 | return SwipeUtils(mock_driver) 16 | 17 | 18 | @mark.unit_test 19 | def test_swipe_utils_init(swipe_utils): 20 | assert swipe_utils.driver is not None 21 | assert swipe_utils.finger is not None 22 | assert swipe_utils.locator is not None 23 | assert swipe_utils.actions is not None 24 | 25 | 26 | @mark.unit_test 27 | @patch("selenium.webdriver.common.action_chains.ActionChains.perform") 28 | def test_swipe_till_text_visibility(mock_perform, swipe_utils, mock_driver): 29 | mock_driver.get_window_size.return_value = {"width": 1080, "height": 1920} 30 | mock_driver.page_source = "
Some other text
" 31 | 32 | def side_effect(*args, **kwargs): 33 | if mock_perform.call_count == 2: 34 | mock_driver.page_source = "
WhatsApp
" 35 | 36 | mock_perform.side_effect = side_effect 37 | swipe_utils.swipe_till_text_visibility("WhatsApp", Direction.DOWN, max_swipe=3) 38 | assert "WhatsApp" in mock_driver.page_source 39 | assert mock_perform.call_count == 2 40 | 41 | 42 | @mark.unit_test 43 | def test_swipe_till_text_visibility_invalid_max_swipe(swipe_utils): 44 | with raises(ValueError, match="max_swipe must be greater than 0"): 45 | swipe_utils.swipe_till_text_visibility("WhatsApp", Direction.DOWN, max_swipe=0) 46 | 47 | 48 | @mark.unit_test 49 | @patch("selenium.webdriver.common.action_chains.ActionChains.perform") 50 | def test_long_swipe_down(mock_perform, swipe_utils, mock_driver): 51 | mock_driver.get_window_size.return_value = {"width": 1080, "height": 1920} 52 | swipe_utils.long_swipe(Direction.DOWN) 53 | mock_perform.assert_called_once() 54 | 55 | 56 | @mark.unit_test 57 | @patch("selenium.webdriver.common.action_chains.ActionChains.perform") 58 | def test_long_swipe_up(mock_perform, swipe_utils, mock_driver): 59 | mock_driver.get_window_size.return_value = {"width": 1080, "height": 1920} 60 | swipe_utils.long_swipe(Direction.UP) 61 | mock_perform.assert_called_once() 62 | 63 | 64 | @mark.unit_test 65 | @patch("selenium.webdriver.common.action_chains.ActionChains.perform") 66 | def test_short_swipe(mock_perform, swipe_utils, mock_driver): 67 | mock_driver.get_window_size.return_value = {"width": 1080, "height": 1920} 68 | swipe_utils.short_swipe(Direction.UP) 69 | mock_perform.assert_called_once() 70 | 71 | 72 | @mark.unit_test 73 | @patch("selenium.webdriver.common.action_chains.ActionChains.perform") 74 | def test_swipe(mock_perform, swipe_utils, mock_driver): 75 | mock_driver.get_window_size.return_value = {"width": 1080, "height": 1920} 76 | swipe_utils.swipe(100, 200, 100, 800, iterate_times=3) 77 | assert mock_perform.call_count == 3 78 | -------------------------------------------------------------------------------- /tests/unit/test_mongo_utils.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock, patch 3 | from pymongo.errors import ConnectionFailure, OperationFailure 4 | 5 | from uaf.utilities.database.mongo_utils import MongoUtility 6 | 7 | 8 | @fixture 9 | def mock_mongo_client(): 10 | with patch("uaf.utilities.database.mongo_utils.MongoClient") as mock_client: 11 | yield mock_client 12 | 13 | 14 | @mark.unit_test 15 | def test_mongo_utility_init(): 16 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 17 | assert mongo_util.connection_string == "mongodb://localhost:27017/testdb" 18 | assert mongo_util.pool_size == 10 19 | assert mongo_util.uuid_representation == "standard" 20 | 21 | 22 | @mark.unit_test 23 | def test_mongo_utility_connect(mock_mongo_client): 24 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 25 | mongo_util.connect() 26 | mock_mongo_client.assert_called_once_with( 27 | "mongodb://localhost:27017/testdb", 28 | maxPoolSize=10, 29 | uuidRepresentation="standard", 30 | ) 31 | 32 | 33 | @mark.unit_test 34 | def test_mongo_utility_connect_failure(mock_mongo_client): 35 | mock_mongo_client.side_effect = ConnectionFailure("Connection failed") 36 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 37 | try: 38 | mongo_util.connect() 39 | except ConnectionFailure as e: 40 | assert str(e) == "Failed to connect to MongoDB: Connection failed" 41 | 42 | 43 | @mark.unit_test 44 | def test_mongo_utility_disconnect(mock_mongo_client): 45 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 46 | mongo_util.connect() 47 | mongo_util.disconnect() 48 | mock_mongo_client.return_value.close.assert_called_once() 49 | 50 | 51 | @mark.unit_test 52 | def test_mongo_utility_insert_one(mock_mongo_client): 53 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 54 | mongo_util.connect() 55 | 56 | mock_collection = MagicMock() 57 | mock_mongo_client.return_value.get_default_database.return_value.__getitem__.return_value = ( 58 | mock_collection 59 | ) 60 | 61 | document = {"name": "John Doe", "age": 30} 62 | mongo_util.insert_one("test_collection", document) 63 | 64 | mock_collection.insert_one.assert_called_once_with(document) 65 | 66 | 67 | @mark.unit_test 68 | def test_mongo_utility_find_one(mock_mongo_client): 69 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 70 | mongo_util.connect() 71 | 72 | mock_collection = MagicMock() 73 | mock_mongo_client.return_value.get_default_database.return_value.__getitem__.return_value = ( 74 | mock_collection 75 | ) 76 | 77 | filter_query = {"name": "John Doe"} 78 | mongo_util.find_one("test_collection", filter_query) 79 | 80 | mock_collection.find_one.assert_called_once_with(filter_query) 81 | 82 | 83 | @mark.unit_test 84 | def test_mongo_utility_update_one(mock_mongo_client): 85 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 86 | mongo_util.connect() 87 | 88 | mock_collection = MagicMock() 89 | mock_mongo_client.return_value.get_default_database.return_value.__getitem__.return_value = ( 90 | mock_collection 91 | ) 92 | 93 | filter_query = {"name": "John Doe"} 94 | update_data = {"$set": {"age": 31}} 95 | mongo_util.update_one("test_collection", filter_query, update_data) 96 | 97 | mock_collection.update_one.assert_called_once_with(filter_query, update_data) 98 | 99 | 100 | @mark.unit_test 101 | def test_mongo_utility_delete_one(mock_mongo_client): 102 | mongo_util = MongoUtility("mongodb://localhost:27017/testdb") 103 | mongo_util.connect() 104 | 105 | mock_collection = MagicMock() 106 | mock_mongo_client.return_value.get_default_database.return_value.__getitem__.return_value = ( 107 | mock_collection 108 | ) 109 | 110 | filter_query = {"name": "John Doe"} 111 | mongo_util.delete_one("test_collection", filter_query) 112 | 113 | mock_collection.delete_one.assert_called_once_with(filter_query) 114 | -------------------------------------------------------------------------------- /tests/unit/test_postgres_utility.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock, patch 3 | from uaf.utilities.database.postgres_utility import PostgresUtility 4 | 5 | 6 | @fixture 7 | def mock_psycopg_connect(): 8 | with patch( 9 | "uaf.utilities.database.postgres_utility.psycopg.connect" 10 | ) as mock_connect: 11 | mock_conn = MagicMock() 12 | mock_connect.return_value = mock_conn 13 | yield mock_connect 14 | 15 | 16 | @fixture 17 | def postgres_utility(mock_psycopg_connect): 18 | return PostgresUtility("mock_connection_string") 19 | 20 | 21 | @mark.unit_test 22 | def test_postgres_utility_init(postgres_utility, mock_psycopg_connect): 23 | assert postgres_utility.connection_string == "mock_connection_string" 24 | mock_psycopg_connect.assert_called_once_with("mock_connection_string") 25 | assert postgres_utility.conn.autocommit is True 26 | 27 | 28 | @mark.unit_test 29 | def test_postgres_utility_context_manager(mock_psycopg_connect): 30 | with PostgresUtility("mock_connection_string") as pu: 31 | assert isinstance(pu, PostgresUtility) 32 | mock_psycopg_connect.return_value.close.assert_called_once() 33 | 34 | 35 | @mark.unit_test 36 | def test_postgres_utility_connect(postgres_utility, mock_psycopg_connect): 37 | postgres_utility.conn.closed = True 38 | postgres_utility.connect() 39 | assert mock_psycopg_connect.call_count == 2 # Once in __init__, once in connect 40 | 41 | 42 | @mark.unit_test 43 | def test_postgres_utility_disconnect(postgres_utility): 44 | postgres_utility.disconnect() 45 | postgres_utility.conn.close.assert_called_once() 46 | 47 | 48 | @mark.unit_test 49 | def test_postgres_utility_ping(postgres_utility): 50 | mock_cursor = MagicMock() 51 | postgres_utility.conn.cursor.return_value.__enter__.return_value = mock_cursor 52 | mock_cursor.fetchone.return_value = (1,) 53 | 54 | result = postgres_utility.ping() 55 | assert result == (1,) 56 | mock_cursor.execute.assert_called_once_with("SELECT 1") 57 | 58 | 59 | @mark.unit_test 60 | def test_postgres_utility_execute(postgres_utility): 61 | postgres_utility.execute("INSERT INTO test_table VALUES (%s, %s)", (1, "test")) 62 | postgres_utility.conn.cursor.return_value.__enter__.return_value.execute.assert_called_once_with( 63 | "INSERT INTO test_table VALUES (%s, %s)", (1, "test") 64 | ) 65 | 66 | 67 | @mark.unit_test 68 | def test_postgres_utility_fetch_one(postgres_utility): 69 | mock_cursor = MagicMock() 70 | postgres_utility.conn.cursor.return_value.__enter__.return_value = mock_cursor 71 | mock_cursor.fetchone.return_value = {"id": 1, "name": "test"} 72 | 73 | result = postgres_utility.fetch_one("SELECT * FROM test_table WHERE id = %s", (1,)) 74 | assert result == {"id": 1, "name": "test"} 75 | mock_cursor.execute.assert_called_once_with( 76 | "SELECT * FROM test_table WHERE id = %s", (1,) 77 | ) 78 | 79 | 80 | @mark.unit_test 81 | def test_postgres_utility_fetch_many(postgres_utility): 82 | mock_cursor = MagicMock() 83 | postgres_utility.conn.cursor.return_value.__enter__.return_value = mock_cursor 84 | mock_cursor.fetchall.return_value = [ 85 | {"id": 1, "name": "test1"}, 86 | {"id": 2, "name": "test2"}, 87 | ] 88 | 89 | result = postgres_utility.fetch_many("SELECT * FROM test_table") 90 | assert result == [{"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}] 91 | mock_cursor.execute.assert_called_once_with("SELECT * FROM test_table", None) 92 | 93 | 94 | @mark.unit_test 95 | def test_postgres_utility_modify(postgres_utility): 96 | postgres_utility.modify( 97 | "UPDATE test_table SET name = %s WHERE id = %s", ("new_name", 1) 98 | ) 99 | postgres_utility.conn.cursor.return_value.__enter__.return_value.execute.assert_called_once_with( 100 | "UPDATE test_table SET name = %s WHERE id = %s", ("new_name", 1) 101 | ) 102 | 103 | 104 | @mark.unit_test 105 | def test_postgres_utility_modify_many(postgres_utility): 106 | params_list = [(1, "test1"), (2, "test2")] 107 | postgres_utility.modify_many("INSERT INTO test_table VALUES (%s, %s)", params_list) 108 | postgres_utility.conn.cursor.return_value.__enter__.return_value.executemany.assert_called_once_with( 109 | "INSERT INTO test_table VALUES (%s, %s)", params_list 110 | ) 111 | -------------------------------------------------------------------------------- /tests/unit/test_waits.py: -------------------------------------------------------------------------------- 1 | from pytest import mark, fixture 2 | from unittest.mock import MagicMock, patch, call 3 | from selenium.webdriver.common.by import By 4 | from uaf.utilities.ui.waiter.waits import Waits 5 | 6 | 7 | @fixture 8 | def mock_driver(): 9 | return MagicMock() 10 | 11 | 12 | @fixture 13 | def mock_web_driver_wait(): 14 | with patch("uaf.utilities.ui.waiter.waits.WebDriverWait") as mock: 15 | yield mock 16 | 17 | 18 | @fixture 19 | def mock_expected_conditions(): 20 | with patch("uaf.utilities.ui.waiter.waits.EC") as mock: 21 | yield mock 22 | 23 | 24 | @fixture 25 | def mock_yaml_parser(): 26 | with patch("uaf.utilities.ui.waiter.waits.YamlParser") as mock: 27 | mock_instance = mock.return_value 28 | mock_instance.get_value.return_value = 30 29 | yield mock 30 | 31 | 32 | @fixture 33 | def waits(mock_driver, mock_web_driver_wait, mock_yaml_parser): 34 | return Waits(mock_driver) 35 | 36 | 37 | @mark.unit_test 38 | def test_waits_init(waits, mock_driver, mock_web_driver_wait): 39 | assert waits.driver == mock_driver 40 | mock_web_driver_wait.assert_called_once_with(mock_driver, 30) 41 | 42 | 43 | @mark.unit_test 44 | def test_wait_for_element_to_be_clickable(waits, mock_expected_conditions): 45 | by_locator = (By.ID, "test_id") 46 | waits.wait_for_element_to_be_clickable(by_locator) 47 | waits.wait.until.assert_called_once_with( 48 | mock_expected_conditions.element_to_be_clickable(by_locator) 49 | ) 50 | 51 | 52 | @mark.unit_test 53 | def test_wait_for_title(waits, mock_expected_conditions): 54 | title = "Test Title" 55 | waits.wait_for_title(title) 56 | waits.wait.until.assert_called_once_with(mock_expected_conditions.title_is(title)) 57 | 58 | 59 | @mark.unit_test 60 | def test_wait_for_element_presence(waits, mock_expected_conditions): 61 | by_locator = (By.ID, "test_id") 62 | waits.wait_for_element_presence(by_locator) 63 | waits.wait.until.assert_called_once_with( 64 | mock_expected_conditions.presence_of_element_located(by_locator) 65 | ) 66 | 67 | 68 | @mark.unit_test 69 | def test_wait_for_element_visibility(waits, mock_expected_conditions): 70 | by_locator = (By.ID, "test_id") 71 | waits.wait_for_element_visibility(by_locator) 72 | waits.wait.until.assert_called_once_with( 73 | mock_expected_conditions.visibility_of_element_located(by_locator) 74 | ) 75 | 76 | 77 | @mark.unit_test 78 | def test_wait_for_until(waits): 79 | def custom_condition(x): 80 | return True 81 | 82 | waits.wait_for_until(custom_condition) 83 | waits.wait.until.assert_called_once_with(custom_condition) 84 | 85 | 86 | @mark.unit_test 87 | def test_wait_for_page_load(waits, mock_driver): 88 | waits.wait_for_page_load() 89 | assert waits.wait.until.call_count == 1 90 | lambda_func = waits.wait.until.call_args[0][0] 91 | lambda_func(mock_driver) 92 | mock_driver.execute_script.assert_called_once_with( 93 | 'return document.readyState === "complete"' 94 | ) 95 | -------------------------------------------------------------------------------- /tests/unit/test_yaml_parser_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import tempfile 3 | import yaml 4 | from pytest import mark, fixture 5 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 6 | 7 | 8 | @fixture 9 | def temp_yaml_file(): 10 | content = { 11 | "section1": {"key1": "value1", "key2": "value2"}, 12 | "section2": {"key3": "value3"}, 13 | } 14 | with tempfile.NamedTemporaryFile( 15 | mode="w", suffix=".yml", delete=False 16 | ) as temp_file: 17 | yaml.dump(content, temp_file) 18 | yield temp_file.name 19 | os.unlink(temp_file.name) 20 | 21 | 22 | @mark.unit_test 23 | def test_yaml_parser_init(temp_yaml_file): 24 | parser = YamlParser(temp_yaml_file) 25 | assert parser.config == { 26 | "section1": {"key1": "value1", "key2": "value2"}, 27 | "section2": {"key3": "value3"}, 28 | } 29 | 30 | 31 | @mark.unit_test 32 | def test_get_section(temp_yaml_file): 33 | parser = YamlParser(temp_yaml_file) 34 | assert parser.get_section("section1") == {"key1": "value1", "key2": "value2"} 35 | 36 | 37 | @mark.unit_test 38 | def test_get_value(temp_yaml_file): 39 | parser = YamlParser(temp_yaml_file) 40 | assert parser.get_value("section1", "key1") == "value1" 41 | 42 | 43 | @mark.unit_test 44 | def test_set_value(temp_yaml_file): 45 | parser = YamlParser(temp_yaml_file) 46 | parser.set_value("section1", "key4", "value4") 47 | assert parser.get_value("section1", "key4") == "value4" 48 | 49 | 50 | @mark.unit_test 51 | def test_save(temp_yaml_file): 52 | parser = YamlParser(temp_yaml_file) 53 | parser.set_value("section3", "key5", "value5") 54 | parser.save() 55 | 56 | with open(temp_yaml_file) as f: 57 | content = yaml.safe_load(f) 58 | 59 | assert "section3" in content 60 | assert content["section3"]["key5"] == "value5" 61 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox:tox] 2 | requires = tox>=4.23.0 3 | skipsdist = False 4 | envlist = 3114, 3115, 3116, 3117, 3118, 3119, mypy 5 | 6 | [gh-actions] 7 | python = 8 | 3.11.4: 3114, mypy 9 | 3.11.5: 3115, mypy 10 | 3.11.6: 3116, mypy 11 | 3.11.7: 3117, mypy 12 | 3.11.8: 3118, mypy 13 | 3.11.9: 3119, mypy 14 | 15 | [testenv] 16 | deps = 17 | build 18 | wheel 19 | pytest 20 | commands = 21 | python -m build --wheel --outdir dist 22 | ls -l dist 23 | sh -c 'pip install dist/*.whl' 24 | make install-pre-commit 25 | make run-pre-commit 26 | allowlist_externals = 27 | make 28 | ls 29 | sh 30 | 31 | [testenv:3114] 32 | basepython = python3.11.4 33 | 34 | [testenv:3115] 35 | basepython = python3.11.5 36 | 37 | [testenv:3116] 38 | basepython = python3.11.6 39 | 40 | [testenv:3117] 41 | basepython = python3.11.7 42 | 43 | [testenv:3118] 44 | basepython = python3.11.8 45 | 46 | [testenv:3119] 47 | basepython = python3.11.9 48 | 49 | [testenv:mypy] 50 | deps = mypy 51 | commands = mypy uaf -------------------------------------------------------------------------------- /uaf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/__init__.py -------------------------------------------------------------------------------- /uaf/ai/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/ai/__init__.py -------------------------------------------------------------------------------- /uaf/ai/bot.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional 2 | 3 | import openai 4 | 5 | from uaf.enums.file_paths import FilePaths 6 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 7 | 8 | config = YamlParser(FilePaths.COMMON) 9 | openai.api_key = config.get_value("chatgpt", "api_key") 10 | completion = openai.ChatCompletion() 11 | 12 | 13 | class bot: 14 | def get_chat_response( 15 | self, 16 | question: str, 17 | chat_log: list[dict[str, Any]] | None = None, 18 | require_chat_log_output: bool = False, 19 | ): 20 | """Provisions users to asks question on trivial or complicated scenarios using chat gpt 21 | 22 | Args: 23 | message (str): query to be answered 24 | """ 25 | if chat_log is None: 26 | chat_log = [ 27 | { 28 | "role": "system", 29 | "content": "You are a helpful, upbeat and funny assistant.", 30 | } 31 | ] 32 | chat_log.append({"role": "user", "content": question}) 33 | response = completion.create( 34 | model=config.get_value("chatgpt", "engine"), 35 | messages=chat_log, 36 | max_tokens=config.get_value("chatgpt", "max_tokens"), 37 | temperature=config.get_value("chatgpt", "temperature"), 38 | n=1, 39 | stop=None, 40 | ) 41 | answer = response.choices[0]["message"]["content"] 42 | chat_log.append({"role": "assistant", "content": answer}) 43 | if require_chat_log_output: 44 | return answer, chat_log 45 | return bot.__print_response(answer) 46 | 47 | @staticmethod 48 | def __print_response(response): 49 | """Prints response in a formatted way just like web page 50 | 51 | Args: 52 | response (str): unformatted text 53 | """ 54 | # Add indentation to each line of the response 55 | formatted_response = "\n".join([" " + line for line in response.split("\n")]) 56 | print(formatted_response) 57 | -------------------------------------------------------------------------------- /uaf/common/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | uaf: Common classes 3 | """ 4 | -------------------------------------------------------------------------------- /uaf/common/helper.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Dict 2 | 3 | from uaf import version as uaf_version 4 | 5 | 6 | def library_version() -> str: 7 | """Return a version of this python library 8 | 9 | Returns: 10 | str: library version 11 | """ 12 | return uaf_version.version 13 | -------------------------------------------------------------------------------- /uaf/cryptic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/cryptic/__init__.py -------------------------------------------------------------------------------- /uaf/cryptic/crypt.py: -------------------------------------------------------------------------------- 1 | from cryptography.fernet import Fernet 2 | 3 | 4 | def generate_key(): 5 | """Generate a new AES-256 key""" 6 | key = Fernet.generate_key() 7 | return key 8 | 9 | 10 | def encrypt_file(file: str, key: bytes): 11 | """Encrypt a given input file using AES-256 key 12 | 13 | Args: 14 | file (str): relative file path with extension 15 | key (_type_): AES-256 key 16 | """ 17 | f = Fernet(key) 18 | plain_text = __read_file(file) 19 | cipher_text = f.encrypt(plain_text) 20 | __write_file(file, cipher_text) 21 | 22 | 23 | def decrypt_file(file: str, key: bytes): 24 | """Decrypt a given input file using AES-256 key 25 | 26 | Args: 27 | file (str): relative file path with extension 28 | key (_type_): AES-256 key 29 | """ 30 | f = Fernet(key) 31 | cipher_text = __read_file(file) 32 | plain_text = f.decrypt(cipher_text) 33 | __write_file(file, plain_text) 34 | 35 | 36 | def __read_file(file): 37 | with open(file, "rb") as _file: 38 | return _file.read() 39 | 40 | 41 | def __write_file(file, content): 42 | with open(file, "wb") as _file: 43 | _file.write(content) 44 | -------------------------------------------------------------------------------- /uaf/decorators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/decorators/__init__.py -------------------------------------------------------------------------------- /uaf/decorators/loggers/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from loguru import logger as _logger 5 | 6 | __all__ = ["_logger"] 7 | 8 | # Remove the default logger 9 | _logger.remove(0) 10 | 11 | # Define the different logging levels and their corresponding colors 12 | logging_levels = { 13 | "TRACE": "", 14 | "DEBUG": "", 15 | "INFO": "", 16 | "SUCCESS": "", 17 | "WARNING": "", 18 | "ERROR": "", 19 | "CRITICAL": "", 20 | } 21 | 22 | # Configure console logger for INFO and higher level logs 23 | _logger.add( 24 | sys.stdout, 25 | level="INFO", 26 | colorize=True, 27 | format="{time:YYYY-MM-DD HH:mm:ss.SSSSSS} | {level} | {message}", 28 | ) 29 | 30 | # Configure file logger for ERROR and higher level logs 31 | _logger.add( 32 | os.path.join("logs", "error.log"), 33 | rotation="1 day", 34 | retention="7 days", 35 | level="ERROR", 36 | encoding="utf-8", 37 | enqueue=True, 38 | format="{time:YYYY-MM-DD HH:mm:ss.SSSSSS} | {level} | {message}", 39 | ) 40 | 41 | # Configure file logger for all level logs 42 | _logger.add( 43 | os.path.join("logs", "all.log"), 44 | rotation="1 day", 45 | retention="7 days", 46 | encoding="utf-8", 47 | enqueue=True, 48 | format="{time:YYYY-MM-DD HH:mm:ss.SSSSSS} | {level} | {message}", 49 | ) 50 | 51 | # Add custom logging levels 52 | for level, color in logging_levels.items(): 53 | _logger.level(level, color=color) 54 | 55 | 56 | def exception_hook(exception_type, value, traceback): 57 | _logger.opt(depth=1).critical( 58 | "Unhandled exception occurred", exception=(exception_type, value, traceback) 59 | ) 60 | 61 | 62 | # Add the custom exception hook 63 | sys.excepthook = exception_hook 64 | -------------------------------------------------------------------------------- /uaf/decorators/loggers/logger.py: -------------------------------------------------------------------------------- 1 | import time 2 | from typing import Any, TypeVar, cast 3 | from collections.abc import Callable 4 | from functools import wraps 5 | from collections.abc import Callable 6 | import psutil 7 | from . import _logger as logger 8 | 9 | 10 | F = TypeVar("F", bound=Callable[..., Any]) 11 | 12 | 13 | def stringify_argument(arg: Any) -> str: 14 | """ 15 | Converts a function argument into a string for logging. If the argument is an object, 16 | it converts the object's dictionary (__dict__) into a string. Otherwise, it uses the 17 | repr() function to get a string representation of the argument. 18 | 19 | Args: 20 | arg (Any): The argument to convert to a string. 21 | 22 | Returns: 23 | str: The string representation of the argument. 24 | """ 25 | if hasattr(arg, "__dict__"): 26 | return str(arg.__dict__) 27 | else: 28 | return repr(arg) 29 | 30 | 31 | def log(func: F) -> F: 32 | """ 33 | Decorator that logs the parameters passed to the decorated function. Each parameter 34 | is converted to a string using stringify_argument and logged before the function 35 | execution. 36 | 37 | Args: 38 | func (Callable): The function to decorate. 39 | 40 | Returns: 41 | Callable: The decorated function with parameter logging. 42 | """ 43 | 44 | @wraps(func) 45 | def wrapper(*args: Any, **kwargs: Any) -> Any: 46 | args_str = ", ".join(map(stringify_argument, args)) 47 | kwargs_str = ", ".join( 48 | [f"{key}={stringify_argument(value)}" for key, value in kwargs.items()] 49 | ) 50 | all_args_str = ", ".join(filter(None, [args_str, kwargs_str])) 51 | logger.info(f"Arguments provided for function {func.__name__}: {all_args_str}") 52 | return func(*args, **kwargs) 53 | 54 | return cast(F, wrapper) 55 | 56 | 57 | def log_performance(condition=None): 58 | """ 59 | Logs performance metrics based on the requirement 60 | 61 | Ex: @log_performance(condition=lambda f, r: (time.time() - f.start_time) > 5) 62 | def my_slow_function(arg1, arg2): 63 | pass 64 | 65 | @log_performance() 66 | def my_slow_function2(arg1): 67 | pass 68 | """ 69 | 70 | def _decorated(func: Callable[[Any], Any]) -> Callable[[Any], Any]: 71 | @wraps(func) 72 | def wrapper(*args: Any, **kwargs: Any) -> Any: 73 | start_time = time.time() 74 | """ 75 | process.memory_info().rss / 1024 / 1024 captures the current resident set size 76 | (RSS) of the Python process, which is the portion of the memory occupied by the process that 77 | is held in RAM (i.e., not swapped out to disk). 78 | 79 | The psutil library's memory_info method returns a named tuple containing several memory-related 80 | metrics for the process, including its RSS. The RSS is expressed in bytes, so dividing it by 1024 81 | twice converts it to megabytes, which is a more human-readable unit of measurement. 82 | """ 83 | mem_before = psutil.Process().memory_info().rss / 1024 / 1024 84 | cpu_before = psutil.Process().cpu_percent() 85 | try: 86 | result = func(*args, **kwargs) 87 | except Exception as e: 88 | logger.error(f"Error in function {func.__name__}: {e}") 89 | raise e 90 | finally: 91 | end_time = time.time() 92 | execution_time = end_time - start_time 93 | mem_after = psutil.Process().memory_info().rss / 1024 / 1024 94 | cpu_after = psutil.Process().cpu_percent() 95 | if condition and condition(func, result): 96 | cpu_percent = cpu_after - cpu_before 97 | mem_usage = mem_after - mem_before 98 | logger.info( 99 | f"Function {func.__name__} took {execution_time:.2f} seconds to execute" 100 | ) 101 | logger.info(f"CPU usage: {cpu_percent:.2f}%") 102 | logger.info(f"Memory usage: {mem_usage:.2f} MB") 103 | else: 104 | logger.info( 105 | f"Function {func.__name__} took {execution_time:.2f} seconds to execute" 106 | ) 107 | return result 108 | 109 | return wrapper 110 | 111 | return _decorated 112 | -------------------------------------------------------------------------------- /uaf/decorators/pytest/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/decorators/pytest/__init__.py -------------------------------------------------------------------------------- /uaf/decorators/pytest/pytest_ordering.py: -------------------------------------------------------------------------------- 1 | def order(n): 2 | """ 3 | This decorator marks a test function with a specified order 4 | """ 5 | 6 | def decorator(func): 7 | setattr(func, "_order", n) 8 | return func 9 | 10 | return decorator 11 | 12 | 13 | def pytest_collection_modifyitems(items): 14 | """ 15 | This hook is called after collecting all the test items and allows modifying the test item list 16 | """ 17 | items.sort( 18 | key=lambda item: ( 19 | item.obj._order if hasattr(item.obj, "_order") else float("inf") 20 | ) 21 | ) 22 | -------------------------------------------------------------------------------- /uaf/device_farming/__init__.py: -------------------------------------------------------------------------------- 1 | from celery import Celery 2 | 3 | from uaf.enums.file_paths import FilePaths 4 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 5 | 6 | __all__ = ["FilePaths", "YamlParser"] 7 | 8 | 9 | def get_celery_app(): 10 | config = YamlParser(FilePaths.COMMON) 11 | celery_app = Celery( 12 | "device_farm", 13 | broker=config.get_value("celery", "broker_url"), 14 | backend=config.get_value("celery", "result_backend"), 15 | ) 16 | return celery_app 17 | -------------------------------------------------------------------------------- /uaf/enums/__init__.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | 3 | from webdriver_manager.chrome import ChromeDriverManager 4 | from webdriver_manager.firefox import GeckoDriverManager 5 | from webdriver_manager.microsoft import EdgeChromiumDriverManager, IEDriverManager 6 | from webdriver_manager.opera import OperaDriverManager 7 | 8 | __all__ = [ 9 | "Enum", 10 | "unique", 11 | "ChromeDriverManager", 12 | "GeckoDriverManager", 13 | "IEDriverManager", 14 | "EdgeChromiumDriverManager", 15 | "OperaDriverManager", 16 | ] 17 | -------------------------------------------------------------------------------- /uaf/enums/appium_automation_name.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class AppiumAutomationName(Enum): 6 | """Appium automation name constants""" 7 | 8 | UIAUTOMATOR2 = "UiAutomator2" 9 | ESPRESSO = "Espresso" 10 | UiAutomator1 = "UiAutomator1" 11 | XCUITEST = "XCUITest" 12 | INSTRUMENTS = "Instruments" 13 | YOU_I_ENGINE = "YouiEngine" 14 | -------------------------------------------------------------------------------- /uaf/enums/browser_make.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class WebBrowserMake(Enum): 6 | """Web browser names as constants""" 7 | 8 | CHROME = "chrome" 9 | FIREFOX = "firefox" 10 | BRAVE = "brave" 11 | MSEDGE = "msedge" 12 | IE = "ie" 13 | CHROMIUM = "chromium" 14 | SAFARI = "safari" 15 | 16 | 17 | @unique 18 | class MobileWebBrowserMake(Enum): 19 | """Mobile web browser name as constants""" 20 | 21 | CHROME = "Chrome" 22 | SAFARI = "Safari" 23 | CHROMIUM = "Chromium" 24 | BROWSER = "Browser" 25 | -------------------------------------------------------------------------------- /uaf/enums/device_status.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class DeviceStatus(Enum): 6 | """Device status as constant 7 | 8 | Args: 9 | Enum (_type_): _description_ 10 | """ 11 | 12 | AVAILABLE = "available" 13 | TERMINATED = "terminated" 14 | IN_USE = "in_use" 15 | FAULTY = "faulty" 16 | -------------------------------------------------------------------------------- /uaf/enums/direction.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class Direction(Enum): 6 | DOWN = ("down",) 7 | UP = ("up",) 8 | RIGHT = ("right",) 9 | LEFT = "left" 10 | -------------------------------------------------------------------------------- /uaf/enums/driver_executable_paths.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | from webdriver_manager.core.manager import DriverManager 4 | 5 | from . import ( 6 | ChromeDriverManager, 7 | EdgeChromiumDriverManager, 8 | Enum, 9 | GeckoDriverManager, 10 | IEDriverManager, 11 | OperaDriverManager, 12 | unique, 13 | ) 14 | 15 | 16 | @unique 17 | class DriverExecutablePaths(Enum): 18 | """Driver exectable paths as constants 19 | 20 | Args: 21 | Enum (DriverExecutablePaths): enum 22 | 23 | Raises: 24 | ValueError: if unknown driver is specified 25 | 26 | Returns: 27 | str: driver executable path 28 | """ 29 | 30 | CHROME: tuple[str, str] = ("chromedriver", ChromeDriverManager()) 31 | FIREFOX: tuple[str, str] = ("geckodriver", GeckoDriverManager()) 32 | IE: tuple[str, str] = ("IEDriverServer", IEDriverManager()) 33 | MSEDGE: tuple[str, str] = ("msedgedriver", EdgeChromiumDriverManager()) 34 | OPERA: tuple[str, str] = ("operadriver", OperaDriverManager()) 35 | 36 | # defined to resolve mypy error 37 | driver_manager: DriverManager 38 | 39 | def __new__(cls, value, driver_manager): 40 | """Create custom instance of DriverExecutablePaths enum 41 | 42 | Args: 43 | value (str): value of the enum constant being created 44 | driver_manager (WebDriver): value of the enum constant being created 45 | 46 | Returns: 47 | DriverExecutablePaths: instance 48 | """ 49 | obj = object.__new__(cls) 50 | obj._value_ = value 51 | obj.driver_manager = driver_manager 52 | return obj 53 | 54 | def __get_path(self): 55 | """Fetches path of the driver executable 56 | 57 | Returns: 58 | str: driver executable path 59 | """ 60 | return self.driver_manager.install() 61 | 62 | @classmethod 63 | def get_driver_path(cls, driver: "DriverExecutablePaths"): 64 | """Fetches driver executable path 65 | 66 | Args: 67 | driver (DriverExecutablePaths): enum 68 | 69 | Raises: 70 | ValueError: if unknown driver is specified 71 | 72 | Returns: 73 | str: driver executable path 74 | """ 75 | for enum_val in cls: 76 | if enum_val == driver: 77 | return enum_val.__get_path() 78 | raise ValueError(f"Unknown driver: {driver}") 79 | -------------------------------------------------------------------------------- /uaf/enums/environments.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class Environments(Enum): 6 | """Test environments as constant 7 | 8 | Args: 9 | Enum (Environments): enum 10 | """ 11 | 12 | DEVELOPMENT = "dev" 13 | STAGE = "stage" 14 | QA = "qa" 15 | PRODUCTION = "prod" 16 | -------------------------------------------------------------------------------- /uaf/enums/execution_mode.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class ExecutionMode(Enum): 6 | """Test execution modes as constant 7 | 8 | Args: 9 | Enum (ExecutionMode): enum 10 | """ 11 | 12 | LOCAL = "local" 13 | REMOTE = "REMOTE" 14 | -------------------------------------------------------------------------------- /uaf/enums/file_paths.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class FilePaths(Enum): 6 | """File Paths as constants 7 | 8 | Args: 9 | Enum (FilePaths): enum 10 | """ 11 | 12 | COMMON = "configs/test/common.yml" 13 | TEST_CONFIG_DEV = "configs/test/environments/dev.yml" 14 | TEST_CONFIG_QA = "configs/test/environments/qa.yml" 15 | TEST_CONFIG_STAGE = "configs/test/environments/stage.yml" 16 | TEST_CONFIG_PROD = "configs/test/environments/prod.yml" 17 | -------------------------------------------------------------------------------- /uaf/enums/mobile_app_status.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class MobileAppStatus(Enum): 6 | """Mobile application status to indicate whether the app is installed or requires installation. 7 | 8 | Args: 9 | Enum (MobileAppStatus): enum 10 | """ 11 | 12 | EXISTING = "existing" 13 | REQUIRES_INSTALLATION = "requires_installation" 14 | -------------------------------------------------------------------------------- /uaf/enums/mobile_app_type.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class MobileAppType(Enum): 6 | """Mobile app type as constant 7 | 8 | Args: 9 | Enum (MobileAppType): enum 10 | """ 11 | 12 | HYBRID = "hybrid" 13 | NATIVE = "native" 14 | WEB = "web" 15 | -------------------------------------------------------------------------------- /uaf/enums/mobile_device_environment_type.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class MobileDeviceEnvironmentType(Enum): 6 | """Mobile device environment type as constant 7 | 8 | Args: 9 | Enum (MobileDeviceEnvironmentType): enum 10 | """ 11 | 12 | PHYSICAL = "physical" 13 | EMULATOR = "emulator" 14 | CLOUD = "cloud" 15 | SIMULATOR = "simulator" 16 | -------------------------------------------------------------------------------- /uaf/enums/mobile_os.py: -------------------------------------------------------------------------------- 1 | from . import Enum, unique 2 | 3 | 4 | @unique 5 | class MobileOs(Enum): 6 | """Mobile Os variants as constant 7 | 8 | Args: 9 | Enum (MobileOs): enum 10 | """ 11 | 12 | ANDROID = "android" 13 | IOS = "ios" 14 | FIREFOX_OS = "FirefoxOS" 15 | -------------------------------------------------------------------------------- /uaf/factories/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Factories Package 3 | 4 | This package implements the Factory Design Pattern for creating web and mobile drivers. It provides 5 | a modular and extensible structure for instantiating drivers based on various platforms (e.g., Android, iOS, 6 | Chrome, Firefox). The goal of the `factories` package is to encapsulate the instantiation logic for different 7 | platforms and browsers, making the system flexible and scalable. 8 | 9 | ## Structure 10 | 11 | The `factories` package is organized into the following submodules: 12 | 13 | 1. **driver/**: 14 | - This submodule contains the core logic for creating both mobile and web drivers using the Factory Design Pattern. 15 | - Divided into `abstract_factory/` (for abstract base classes) and `concrete_factory/` (for concrete implementations), 16 | the `driver` module handles the logic for driver creation across platforms and browsers. 17 | 18 | 2. **abstract_factory/driver/**: 19 | - Contains abstract base classes that define the blueprints for creating mobile and web drivers. 20 | - **Abstract Mobile Factory**: 21 | - `abstract_mobile_driver_factory.py`: Defines the structure for creating mobile drivers. 22 | - `abstract_products/abstract_mobile/`: Contains abstract classes for platform-specific mobile drivers like Android and iOS. 23 | - **Abstract Web Factory**: 24 | - `abstract_web_driver_factory.py`: Defines the structure for creating web drivers. 25 | - `abstract_products/abstract_web/`: Contains abstract classes for platform-specific web drivers like Chrome, Firefox, Edge, etc. 26 | 27 | 3. **concrete_factory/driver/**: 28 | - Contains concrete implementations of the abstract factories, providing platform-specific logic to create drivers. 29 | - **Concrete Mobile Factory**: 30 | - `concrete_mobile_driver_factory.py`: Implements the mobile driver creation logic for platforms like Android and iOS. 31 | - `concrete_products/mobile/`: Contains concrete classes for Android and iOS driver implementations. 32 | - **Concrete Web Factory**: 33 | - `concrete_web_driver_factory.py`: Implements the web driver creation logic for browsers like Chrome, Firefox, and Edge. 34 | - `concrete_products/web/`: Contains concrete classes for browser-specific implementations such as Chrome, Firefox, and Edge. 35 | 36 | ## Purpose 37 | 38 | The purpose of the `factories` package is to provide an extensible and modular system for driver creation using 39 | the Factory Design Pattern. This pattern allows the system to grow by adding support for new platforms, browsers, 40 | or environments without modifying the core logic. 41 | 42 | The package is designed to abstract the complexity of driver creation from the client code, 43 | ensuring that all drivers are created consistently and conform to predefined interfaces. 44 | 45 | ## Usage 46 | 47 | The `factories` package is designed to be used by clients who need to create mobile or web drivers 48 | without worrying about the underlying details of instantiation. The factories take platform-specific 49 | or browser-specific parameters and return a configured driver instance. 50 | 51 | Example for mobile driver creation: 52 | 53 | ```python 54 | from factories.driver.concrete_factory.concrete_mobile_driver_factory import ConcreteMobileDriverFactory 55 | 56 | mobile_driver_factory = ConcreteMobileDriverFactory() 57 | driver = mobile_driver_factory.get_mobile_driver( 58 | os=MobileOs.ANDROID, 59 | app_type=MobileAppType.NATIVE, 60 | execution_mode=ExecutionMode.LOCAL, 61 | environment=Environments.STAGING, 62 | capabilities={"platformName": "Android", "deviceName": "Pixel_4"} 63 | ) 64 | 65 | from factories.driver.concrete_factory.concrete_web_driver_factory import ConcreteWebDriverFactory 66 | 67 | web_driver_factory = ConcreteWebDriverFactory() 68 | driver = web_driver_factory.get_web_driver( 69 | browser_make=WebBrowserMake.CHROME, 70 | options={"headless": True} 71 | ) 72 | """ 73 | -------------------------------------------------------------------------------- /uaf/factories/driver/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Driver Module 3 | 4 | This module provides the core structure for managing driver creation through the Factory Design Pattern. 5 | It encapsulates the logic for creating both mobile and web drivers, supporting various platforms (e.g., Android, iOS) 6 | and browsers (e.g., Chrome, Firefox, Edge, etc.). The `driver` module serves as a central location for defining 7 | the abstract and concrete factories required for driver instantiation. 8 | 9 | This module is part of the `factories.driver` hierarchy, which is divided into abstract factories, concrete 10 | factories, and platform-specific product definitions. 11 | 12 | ## Structure 13 | 14 | The `driver` module is organized as follows: 15 | 16 | - `abstract_factory/`: Contains abstract base classes for creating web and mobile drivers. 17 | - `abstract_factory.py`: Core abstract factory definitions for web and mobile drivers. 18 | - `abstract_products/`: Contains product-specific abstract base classes. 19 | - `abstract_mobile/`: Contains abstract classes for mobile driver platforms. 20 | - `abstract_android.py`: Defines the abstract Android driver class. 21 | - `abstract_ios.py`: Defines the abstract iOS driver class. 22 | - `abstract_mobile.py`: Common base class for mobile drivers. 23 | - `abstract_web/`: Contains abstract classes for web driver platforms. 24 | - `abstract_chrome.py`: Defines the abstract Chrome web driver class. 25 | - `abstract_firefox.py`: Defines the abstract Firefox web driver class. 26 | - `abstract_msedge.py`: Defines the abstract Microsoft Edge web driver class. 27 | - `abstract_ie.py`: Defines the abstract Internet Explorer driver class. 28 | - `abstract_chromium.py`: Defines the abstract Chromium driver class. 29 | - `abstract_brave.py`: Defines the abstract Brave browser driver class. 30 | - `abstract_web_driver.py`: Common base class for web drivers, defining shared behavior. 31 | 32 | - `concrete_factory/`: Contains concrete implementations of the abstract factories. 33 | - `concrete_mobile_driver_factory.py`: Concrete factory for creating mobile drivers (e.g., Android, iOS). 34 | - `concrete_web_driver_factory.py`: Concrete factory for creating web drivers (e.g., Chrome, Firefox, etc.). 35 | - `concrete_products/`: Contains platform-specific concrete driver classes. 36 | - `mobile/`: Concrete implementations for mobile drivers. 37 | - `concrete_android_driver.py`: Concrete implementation for the Android mobile driver. 38 | - `concrete_ios_driver.py`: Concrete implementation for the iOS mobile driver. 39 | - `web/`: Concrete implementations for web drivers. 40 | - `concrete_chrome_driver.py`: Concrete implementation for the Chrome web driver. 41 | - `concrete_firefox_driver.py`: Concrete implementation for the Firefox web driver. 42 | - `concrete_msedge_driver.py`: Concrete implementation for the Microsoft Edge web driver. 43 | - `concrete_ie_driver.py`: Concrete implementation for the Internet Explorer web driver. 44 | - `concrete_brave_driver.py`: Concrete implementation for the Brave browser driver. 45 | - `concrete_web_driver.py`: Common concrete implementation for general web drivers. 46 | 47 | ## Purpose 48 | 49 | The `driver` module is responsible for managing driver creation using the Factory Design Pattern. By separating 50 | abstract and concrete implementations, this module allows for flexibility in driver creation while supporting 51 | multiple platforms and browsers. The factories enable consistent and scalable driver instantiation for both 52 | mobile and web applications. 53 | 54 | This module makes it easy to extend support for new platforms or browsers without modifying existing code, 55 | ensuring the system remains maintainable and scalable. 56 | 57 | ## Usage 58 | 59 | The `driver` module follows the Factory Design Pattern, where concrete factories extend abstract factory base classes 60 | and provide specific logic for driver creation. Client code interacts with these factories to create mobile or 61 | web drivers based on the required platform or browser. 62 | 63 | Example of creating a mobile driver: 64 | 65 | ```python 66 | from factories.driver.concrete_factory.concrete_mobile_driver_factory import ConcreteMobileDriverFactory 67 | 68 | mobile_driver_factory = ConcreteMobileDriverFactory() 69 | driver = mobile_driver_factory.get_mobile_driver( 70 | os=MobileOs.ANDROID, 71 | app_type=MobileAppType.NATIVE, 72 | execution_mode=ExecutionMode.LOCAL, 73 | environment=Environments.STAGING, 74 | capabilities={"platformName": "Android", "deviceName": "Pixel_4"} 75 | ) 76 | 77 | from factories.driver.concrete_factory.concrete_web_driver_factory import ConcreteWebDriverFactory 78 | 79 | web_driver_factory = ConcreteWebDriverFactory() 80 | driver = web_driver_factory.get_web_driver( 81 | browser_make=WebBrowserMake.CHROME, 82 | options={"headless": True} 83 | ) 84 | """ 85 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractFactory Module 3 | 4 | This module contains the abstract base classes that define the blueprint for creating web and mobile drivers using the Factory Design Pattern. 5 | The abstract factory classes in this module provide the framework for platform-agnostic driver creation, allowing concrete implementations 6 | to define the specifics for each platform or browser type. 7 | 8 | This module is part of the `factories.driver.abstract_factory` hierarchy and contains the essential base classes for creating both mobile 9 | and web drivers in a flexible and extendable manner. 10 | 11 | ## Structure 12 | 13 | The `abstract_factory` module defines several abstract factories for creating drivers: 14 | 15 | - `abstract_factory.py`: The central file that contains high-level factory base classes. 16 | - `abstract_products/`: A subdirectory that contains abstract products, which are specific for both web and mobile drivers. These define 17 | platform or browser-specific base classes for concrete implementations. 18 | - `abstract_mobile/`: Contains abstract base classes for mobile platforms such as Android and iOS. 19 | - `abstract_web/`: Contains abstract base classes for web browsers such as Chrome, Firefox, Edge, and others. 20 | 21 | ## Purpose 22 | 23 | The purpose of this module is to define common interfaces for creating drivers in a structured and extendable way. These abstract factories 24 | ensure that all concrete driver factories follow a consistent pattern for creating web or mobile drivers. 25 | 26 | By using these abstract base classes, the system can easily expand to support new platforms or browsers without modifying the core driver 27 | logic. 28 | 29 | ## Usage 30 | 31 | Concrete factories (e.g., `ConcreteMobileDriverFactory`, `ConcreteWebDriverFactory`) inherit from the abstract factories defined in this 32 | module and implement platform-specific driver creation logic. This design pattern ensures that driver creation is encapsulated, 33 | and the client code only interacts with a simplified interface for driver retrieval. 34 | 35 | Example of extending an abstract factory for mobile drivers: 36 | 37 | ```python 38 | class ConcreteMobileDriverFactory(AbstractMobileDriverFactory): 39 | def get_mobile_driver(self, os: MobileOs, app_type: MobileAppType, execution_mode: ExecutionMode, environment: Environments, capabilities: dict[str, Any]): 40 | # Implementation for creating mobile driver instance (Android or iOS) 41 | pass 42 | 43 | class ConcreteWebDriverFactory(AbstractWebDriverFactory): 44 | def get_web_driver(self, browser_make: WebBrowserMake, options: dict[str, Any] | None = None): 45 | # Implementation for creating web driver instance (Chrome, Firefox, etc.) 46 | pass 47 | """ 48 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_factory.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | from typing import Any 3 | 4 | from uaf.enums.browser_make import WebBrowserMake 5 | from uaf.enums.mobile_os import MobileOs 6 | from uaf.enums.mobile_app_type import MobileAppType 7 | from uaf.enums.environments import Environments 8 | from uaf.enums.execution_mode import ExecutionMode 9 | 10 | 11 | class AbstractWebDriverFactory(metaclass=ABCMeta): 12 | """Abstract base class for web driver factories. 13 | 14 | This class defines the skeleton for creating web driver factory instances. 15 | All derived classes must implement the methods for creating and retrieving web drivers. 16 | """ 17 | 18 | @abstractmethod 19 | def __init__(self) -> None: 20 | """Initialize the abstract web driver factory. 21 | 22 | This method serves as the constructor for any subclass implementing the web driver factory. 23 | """ 24 | pass 25 | 26 | @abstractmethod 27 | def get_web_driver( 28 | self, 29 | *, 30 | browser_make: WebBrowserMake, 31 | options: dict[str, Any] | None = None, 32 | ): 33 | """Retrieve the web driver for a specified browser. 34 | 35 | Args: 36 | browser_make (WebBrowserMake): The web browser make enum specifying which browser to use. 37 | options (dict[str, Any], optional): A dictionary of browser options or capabilities. Defaults to None. 38 | """ 39 | pass 40 | 41 | 42 | class AbstractMobileDriverFactory(metaclass=ABCMeta): 43 | """Abstract base class for mobile driver factories. 44 | 45 | This class defines the skeleton for creating mobile driver factory instances. 46 | All derived classes must implement the methods for creating and retrieving mobile drivers. 47 | """ 48 | 49 | @abstractmethod 50 | def get_mobile_driver( 51 | self, 52 | *, 53 | os: MobileOs, 54 | app_type: MobileAppType, 55 | execution_mode: ExecutionMode, 56 | environment: Environments, 57 | capabilities: dict[str, Any], 58 | ): 59 | """Retrieve the mobile driver based on specified parameters. 60 | 61 | Args: 62 | os (MobileOs): The operating system for the mobile device (e.g., Android, iOS). 63 | app_type (MobileAppType): The type of mobile application (e.g., native, web, hybrid). 64 | execution_mode (ExecutionMode): The mode in which the tests will be executed (e.g., local, remote). 65 | environment (Environments): The environment where the application will run (e.g., staging, production). 66 | capabilities (dict[str, Any]): A dictionary of desired capabilities for configuring the mobile driver. 67 | """ 68 | pass 69 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractProducts Module 3 | 4 | This module contains abstract base classes representing platform-specific products (web and mobile drivers). 5 | It serves as the core for defining platform-agnostic driver creation logic within the Factory Design Pattern. 6 | The classes in this module act as the blueprint for different browser drivers and mobile platform drivers, 7 | ensuring consistency across concrete implementations. 8 | 9 | This module is part of the `factories.driver.abstract_factory.abstract_products` hierarchy, 10 | and it is further divided into submodules for mobile and web driver products. 11 | 12 | ## Structure 13 | 14 | The `abstract_products` module is organized into the following submodules: 15 | 16 | - `abstract_mobile/`: Contains abstract base classes for creating mobile drivers. 17 | - `abstract_android.py`: Abstract class for Android mobile drivers. 18 | - `abstract_ios.py`: Abstract class for iOS mobile drivers. 19 | - `abstract_mobile.py`: Common base class for mobile drivers (Android and iOS). 20 | 21 | - `abstract_web/`: Contains abstract base classes for creating web drivers. 22 | - `abstract_chrome.py`: Abstract class for Chrome web drivers. 23 | - `abstract_firefox.py`: Abstract class for Firefox web drivers. 24 | - `abstract_msedge.py`: Abstract class for Microsoft Edge web drivers. 25 | - `abstract_chromium.py`: Abstract class for Chromium-based web drivers. 26 | - `abstract_ie.py`: Abstract class for Internet Explorer web drivers. 27 | - `abstract_brave.py`: Abstract class for Brave web drivers. 28 | - `abstract_web_driver.py`: Common base class for all web drivers, defining shared behaviors. 29 | 30 | ## Purpose 31 | 32 | The purpose of this module is to provide a structure that ensures platform-specific (web and mobile) drivers follow a 33 | consistent creation pattern. Each abstract product represents a browser or mobile platform driver, and concrete classes 34 | implement these abstract products to provide platform-specific functionality. 35 | 36 | These abstract classes allow the factories to define driver creation in a modular way, ensuring easy expansion to new 37 | platforms or browsers while adhering to the predefined structure. 38 | 39 | ## Usage 40 | 41 | Concrete products (e.g., `ConcreteAndroidDriver`, `ConcreteChromeDriver`) inherit from the abstract products defined in this module 42 | and implement platform-specific logic for driver creation. By adhering to the abstract products, driver creation remains 43 | consistent and extensible across platforms. 44 | 45 | Example of extending an abstract product for a mobile driver: 46 | 47 | ```python 48 | class ConcreteAndroidDriver(AbstractAndroid): 49 | def get_driver(self, capabilities: dict[str, Any]): 50 | # Implementation for creating Android mobile driver 51 | pass 52 | 53 | class ConcreteChromeDriver(AbstractChrome): 54 | def get_web_driver(self, options: dict[str, Any] | None = None): 55 | # Implementation for creating Chrome web driver 56 | pass 57 | """ 58 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_mobile/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractMobile Module 3 | 4 | This module contains the abstract base classes used for creating mobile drivers within the factory design pattern. 5 | These classes provide a blueprint for different mobile platform drivers (e.g., Android, iOS), ensuring that any 6 | concrete implementations will adhere to a consistent interface for creating mobile drivers. 7 | 8 | This module is part of the `factories.driver.abstract_factory.abstract_products.abstract_mobile` hierarchy and is 9 | responsible for defining platform-agnostic methods for creating mobile drivers. Each abstract class in this module 10 | corresponds to a specific mobile driver type (e.g., Android or iOS). 11 | 12 | ## Structure 13 | 14 | The `abstract_mobile` module defines abstract classes for different mobile platforms: 15 | 16 | - `abstract_android.py`: Abstract class for the Android mobile driver. 17 | - `abstract_ios.py`: Abstract class for the iOS mobile driver. 18 | - `abstract_mobile.py`: Common base class that both Android and iOS mobile drivers extend, defining shared behavior 19 | and interfaces. 20 | 21 | ## Purpose 22 | 23 | Each abstract class defines the blueprint for creating specific mobile drivers, which concrete classes will implement. 24 | This provides flexibility and a consistent interface for creating mobile drivers in the factory design pattern, ensuring 25 | easy extension and maintenance of different mobile platforms. 26 | 27 | ## Usage 28 | 29 | Concrete mobile driver factories (e.g., `ConcreteAndroidDriver`, `ConcreteIOSDriver`) inherit from these abstract 30 | classes and implement the methods for creating the actual mobile drivers. The factories ensure that the instantiation 31 | logic is hidden from the client code, providing a clean interface for mobile driver creation. 32 | 33 | Example of how the abstract mobile classes are extended: 34 | 35 | ```python 36 | class ConcreteAndroidDriver(AbstractAndroid): 37 | def get_mobile_driver(self, capabilities: dict[str, Any]): 38 | # Implementation for creating Android mobile driver instance 39 | pass 40 | 41 | class ConcreteIOSDriver(AbstractIOS): 42 | def get_mobile_driver(self, capabilities: dict[str, Any]): 43 | # Implementation for creating iOS mobile driver instance 44 | pass 45 | """ 46 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_mobile/abstract_android.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | class AbstractAndroid(ABC): 6 | """Abstract base class for Android driver interface. 7 | 8 | This class provides the skeleton for creating Android driver instances. 9 | Any concrete class inheriting from this must implement the `get_driver` method. 10 | """ 11 | 12 | @abstractmethod 13 | def get_driver(self, *, capabilities: dict[str, Any]): 14 | """Fetch or create an Android driver instance. 15 | 16 | Args: 17 | capabilities (dict[str, Any]): A dictionary of capabilities used to configure the Android driver. 18 | """ 19 | pass 20 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_mobile/abstract_ios.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | 5 | class AbstractIOS(ABC): 6 | """Abstract base class for iOS driver interface. 7 | 8 | This class provides the skeleton for creating iOS driver instances. 9 | Any concrete class inheriting from this must implement the `get_driver` method. 10 | """ 11 | 12 | @abstractmethod 13 | def get_driver(self, *, capabilities: dict[str, Any]): 14 | """Fetch or create an iOS driver instance. 15 | 16 | Args: 17 | capabilities (dict[str, Any]): A dictionary of capabilities used to configure the iOS driver. 18 | """ 19 | pass 20 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_mobile/abstract_mobile.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | from uaf.enums.mobile_os import MobileOs 5 | from uaf.enums.environments import Environments 6 | from uaf.enums.execution_mode import ExecutionMode 7 | from uaf.enums.mobile_app_type import MobileAppType 8 | 9 | 10 | class AbstractMobile(ABC): 11 | """Abstract base class for mobile interface. 12 | 13 | This class defines the skeleton for creating mobile instances and retrieving mobile drivers. 14 | All derived classes must implement the methods for initializing and retrieving mobile drivers. 15 | """ 16 | 17 | @abstractmethod 18 | def __init__( 19 | self, 20 | *, 21 | os: MobileOs, 22 | app_type: MobileAppType, 23 | execution_mode: ExecutionMode, 24 | environment: Environments, 25 | ) -> None: 26 | """Initialize the mobile interface. 27 | 28 | This method serves as the constructor for any subclass implementing the mobile interface. 29 | 30 | Args: 31 | os (MobileOs): The operating system for the mobile device (e.g., Android, iOS). 32 | app_type (MobileAppType): The type of mobile application (e.g., native, web, hybrid). 33 | execution_mode (ExecutionMode): The mode of test execution (e.g., local, remote). 34 | environment (Environments): The environment where the mobile application will run (e.g., staging, production). 35 | """ 36 | pass 37 | 38 | @abstractmethod 39 | def get_mobile_driver(self, *, capabilities: dict[str, Any]): 40 | """Retrieve or create a new mobile driver instance based on the provided capabilities. 41 | 42 | Args: 43 | capabilities (dict[str, Any]): A dictionary of desired capabilities for configuring the mobile driver. 44 | """ 45 | pass 46 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | AbstractWeb Module 3 | 4 | This module contains the abstract base classes used for creating web drivers within the factory design pattern. 5 | These classes provide a blueprint for different web browser drivers (e.g., Chrome, Firefox, Edge), ensuring that 6 | any concrete implementations will adhere to a consistent interface for creating web drivers. 7 | 8 | This module is part of the `factories.driver.abstract_factory.abstract_products.abstract_web` hierarchy and 9 | is responsible for defining platform-agnostic methods for creating web drivers. Each abstract class in this 10 | module corresponds to a specific browser or web driver type. 11 | 12 | ## Structure 13 | 14 | The `abstract_web` module defines abstract classes for different web browsers: 15 | 16 | - `abstract_chrome.py`: Abstract class for Chrome web driver. 17 | - `abstract_firefox.py`: Abstract class for Firefox web driver. 18 | - `abstract_msedge.py`: Abstract class for Microsoft Edge web driver. 19 | - `abstract_chromium.py`: Abstract class for Chromium-based web drivers. 20 | - `abstract_ie.py`: Abstract class for Internet Explorer web driver. 21 | - `abstract_brave.py`: Abstract class for Brave browser driver. 22 | - `abstract_web_driver.py`: Common base class that all other web drivers extend, defining shared behavior and interfaces. 23 | 24 | ## Purpose 25 | 26 | Each abstract class defines the blueprint for creating specific web drivers, which concrete classes 27 | will implement. This provides flexibility and a consistent interface for creating web drivers 28 | in the factory design pattern, ensuring easy extension and maintenance of different browser drivers. 29 | 30 | ## Usage 31 | 32 | Concrete web driver factories (e.g., `ConcreteChromeDriver`, `ConcreteFirefoxDriver`) inherit from these 33 | abstract classes and implement the methods for creating the actual browser drivers. The factories ensure 34 | that the instantiation logic is hidden from the client code, providing a clean interface for web driver 35 | creation. 36 | 37 | Example of how the abstract web classes are extended: 38 | 39 | ```python 40 | class ConcreteChromeDriver(AbstractChrome): 41 | def get_driver(self, capabilities: dict[str, Any]): 42 | # Implementation for creating Chrome web driver instance 43 | pass 44 | """ 45 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_brave.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.chrome.options import Options as ChromeOptions 4 | 5 | 6 | class AbstractBrave(ABC): 7 | """Abstract base class for Brave web driver interface. 8 | 9 | This class provides a blueprint for creating Brave browser driver instances. 10 | Any class that inherits from `AbstractBrave` must implement the `get_web_driver` method, 11 | which will handle the instantiation of the Brave web driver with the appropriate options. 12 | 13 | The Brave browser shares much of its functionality with Chrome, so `ChromeOptions` is used for 14 | configuring the driver options. 15 | 16 | ## Methods 17 | 18 | - `get_web_driver`: Abstract method that subclasses must implement to create and return a Brave 19 | web driver instance. 20 | 21 | """ 22 | 23 | @abstractmethod 24 | def get_web_driver(self, *, options: ChromeOptions | None = None): 25 | """Fetch or create a Brave browser driver instance. 26 | 27 | Args: 28 | options (ChromeOptions | None, optional): A dictionary of options or capabilities for configuring 29 | the Brave web driver. Defaults to None. 30 | """ 31 | pass 32 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_chrome.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.chrome.options import Options as ChromeOptions 4 | 5 | 6 | class AbstractChrome(ABC): 7 | """Abstract base class for Chrome web driver interface. 8 | 9 | This class provides the blueprint for creating Chrome browser driver instances. 10 | Any class inheriting from `AbstractChrome` must implement the `get_web_driver` method, 11 | which will handle the instantiation of the Chrome web driver with the appropriate options. 12 | 13 | ## Methods 14 | 15 | - `get_web_driver`: Abstract method that subclasses must implement to create and return 16 | a Chrome web driver instance. 17 | 18 | """ 19 | 20 | @abstractmethod 21 | def get_web_driver(self, *, options: ChromeOptions | None = None): 22 | """Fetch or create a Chrome web driver instance. 23 | 24 | Args: 25 | options (ChromeOptions | None, optional): A dictionary of options or capabilities for configuring 26 | the Chrome web driver. Defaults to None. 27 | """ 28 | pass 29 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_chromium.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.chrome.options import Options as ChromeOptions 4 | 5 | 6 | class AbstractChromium(ABC): 7 | """Abstract base class for Chromium web driver interface. 8 | 9 | This class provides a blueprint for creating Chromium-based browser driver instances. 10 | Any class inheriting from `AbstractChromium` must implement the `get_web_driver` method, 11 | which handles the instantiation of the Chromium web driver using the appropriate options. 12 | 13 | Chromium is the open-source foundation for browsers like Chrome, so `ChromeOptions` is used 14 | for configuring the driver options. 15 | 16 | ## Methods 17 | 18 | - `get_web_driver`: Abstract method that subclasses must implement to create and return 19 | a Chromium web driver instance. 20 | """ 21 | 22 | @abstractmethod 23 | def get_web_driver(self, *, options: ChromeOptions | None = None): 24 | """Fetch or create a Chromium web driver instance. 25 | 26 | Args: 27 | options (ChromeOptions | None, optional): A dictionary of options or capabilities for configuring 28 | the Chromium web driver. Defaults to None. 29 | """ 30 | pass 31 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_firefox.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.firefox.options import Options as FirefoxOptions 4 | 5 | 6 | class AbstractFirefox(ABC): 7 | """Abstract base class for Firefox web driver interface. 8 | 9 | This class provides a blueprint for creating Firefox browser driver instances. 10 | Any class inheriting from `AbstractFirefox` must implement the `get_web_driver` method, 11 | which handles the instantiation of the Firefox web driver with the appropriate options. 12 | 13 | ## Methods 14 | 15 | - `get_web_driver`: Abstract method that subclasses must implement to create and return 16 | a Firefox web driver instance. 17 | """ 18 | 19 | @abstractmethod 20 | def get_web_driver(self, *, options: FirefoxOptions | None = None): 21 | """Fetch or create a Firefox web driver instance. 22 | 23 | Args: 24 | options (FirefoxOptions | None, optional): A dictionary of options or capabilities for configuring 25 | the Firefox web driver. Defaults to None. 26 | """ 27 | pass 28 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_ie.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.ie.options import Options as IeOptions 4 | 5 | 6 | class AbstractIE(ABC): 7 | """Abstract base class for Internet Explorer (IE) web driver interface. 8 | 9 | This class provides a blueprint for creating Internet Explorer (IE) browser driver instances. 10 | Any class inheriting from `AbstractIE` must implement the `get_web_driver` method, 11 | which handles the instantiation of the IE web driver with the appropriate options. 12 | 13 | ## Methods 14 | 15 | - `get_web_driver`: Abstract method that subclasses must implement to create and return 16 | an IE web driver instance. 17 | """ 18 | 19 | @abstractmethod 20 | def get_web_driver(self, *, options: IeOptions | None = None): 21 | """Fetch or create an Internet Explorer (IE) web driver instance. 22 | 23 | Args: 24 | options (IeOptions | None, optional): A dictionary of options or capabilities for configuring 25 | the IE web driver. Defaults to None. 26 | """ 27 | pass 28 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_msedge.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | from selenium.webdriver.edge.options import Options as MsEdgeOptions 4 | 5 | 6 | class AbstractMsedge(ABC): 7 | """Abstract base class for Microsoft Edge (MsEdge) web driver interface. 8 | 9 | This class provides a blueprint for creating Microsoft Edge browser driver instances. 10 | Any class inheriting from `AbstractMsedge` must implement the `get_web_driver` method, 11 | which handles the instantiation of the Microsoft Edge web driver with the appropriate options. 12 | 13 | ## Methods 14 | 15 | - `get_web_driver`: Abstract method that subclasses must implement to create and return 16 | a Microsoft Edge web driver instance. 17 | """ 18 | 19 | @abstractmethod 20 | def get_web_driver(self, *, options: MsEdgeOptions | None = None): 21 | """Fetch or create a Microsoft Edge (MsEdge) web driver instance. 22 | 23 | Args: 24 | options (MsEdgeOptions | None, optional): A dictionary of options or capabilities for configuring 25 | the Microsoft Edge web driver. Defaults to None. 26 | """ 27 | pass 28 | -------------------------------------------------------------------------------- /uaf/factories/driver/abstract_factory/abstract_products/abstract_web/abstract_web_driver.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from typing import Any 3 | 4 | from uaf.enums.browser_make import WebBrowserMake 5 | 6 | 7 | class AbstractWebDriver(ABC): 8 | """Abstract base class for web driver interface. 9 | 10 | This class provides a blueprint for creating browser-specific web driver instances. 11 | Any class inheriting from `AbstractWebDriver` must implement the `__init__` method 12 | to initialize the browser type and the `get_web_driver` method to fetch the configured web driver. 13 | 14 | ## Methods 15 | 16 | - `__init__`: Abstract method for initializing the web driver with the specific browser make (e.g., Chrome, Firefox). 17 | - `get_web_driver`: Abstract method to create and return a web driver instance based on the provided options. 18 | 19 | """ 20 | 21 | @abstractmethod 22 | def __init__(self, *, browser_make: WebBrowserMake) -> None: 23 | """Initialize the web driver with the specified browser make. 24 | 25 | Args: 26 | browser_make (WebBrowserMake): The browser make enum (e.g., Chrome, Firefox, Edge). 27 | """ 28 | pass 29 | 30 | @abstractmethod 31 | def get_web_driver(self, *, options: dict[str, Any] | None = None): 32 | """Fetch or create a web driver instance based on the specified browser make. 33 | 34 | Args: 35 | options (dict[str, Any] | None, optional): A dictionary of browser capabilities and options. Defaults to None. 36 | """ 37 | pass 38 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ConcreteFactory Module 3 | 4 | This module contains the concrete implementations of the Factory Design Pattern for creating web and mobile drivers. 5 | The concrete factories in this module provide platform-specific logic to instantiate drivers for various environments 6 | and platforms (e.g., Android, iOS, Chrome, Firefox). These concrete classes inherit from the abstract factory base classes 7 | and define the actual creation logic for each driver type. 8 | 9 | This module is part of the `factories.driver.concrete_factory` hierarchy and complements the abstract factory module 10 | by providing real-world driver instantiation implementations. 11 | 12 | ## Structure 13 | 14 | The `concrete_factory` module is organized as follows: 15 | 16 | - `concrete_mobile_driver_factory.py`: Contains the logic for creating mobile drivers, supporting Android and iOS platforms. 17 | - `concrete_web_driver_factory.py`: Contains the logic for creating web drivers, supporting various browsers like Chrome, Firefox, and Edge. 18 | - `concrete_products/`: A subdirectory that contains the concrete driver implementations for each platform. 19 | - `mobile/`: Contains concrete classes for mobile drivers. 20 | - `concrete_android_driver.py`: Defines the Android mobile driver instantiation logic. 21 | - `concrete_ios_driver.py`: Defines the iOS mobile driver instantiation logic. 22 | - `web/`: Contains concrete classes for web drivers. 23 | - `concrete_chrome_driver.py`: Defines the Chrome browser driver instantiation logic. 24 | - `concrete_firefox_driver.py`: Defines the Firefox browser driver instantiation logic. 25 | - `concrete_msedge_driver.py`: Defines the Microsoft Edge browser driver instantiation logic. 26 | - `concrete_ie_driver.py`: Defines the Internet Explorer browser driver instantiation logic. 27 | - `concrete_brave_driver.py`: Defines the Brave browser driver instantiation logic. 28 | - `concrete_web_driver.py`: Common concrete implementation for general web drivers. 29 | 30 | ## Purpose 31 | 32 | The purpose of the `concrete_factory` module is to provide platform-specific implementations for the abstract factories defined in the 33 | `abstract_factory` module. These concrete classes are responsible for creating mobile and web drivers with platform-specific options 34 | and capabilities. This module makes it possible to easily extend support for new platforms or browsers without modifying the core logic. 35 | 36 | ## Usage 37 | 38 | The `concrete_factory` module is used to instantiate drivers based on the provided platform or browser type. 39 | The concrete factories hide the complexity of driver creation from the client, ensuring that all drivers 40 | are created according to platform-specific requirements. 41 | 42 | Example for creating a mobile driver: 43 | 44 | ```python 45 | from factories.driver.concrete_factory.concrete_mobile_driver_factory import ConcreteMobileDriverFactory 46 | 47 | mobile_driver_factory = ConcreteMobileDriverFactory() 48 | driver = mobile_driver_factory.get_mobile_driver( 49 | os=MobileOs.ANDROID, 50 | app_type=MobileAppType.NATIVE, 51 | execution_mode=ExecutionMode.LOCAL, 52 | environment=Environments.STAGING, 53 | capabilities={"platformName": "Android", "deviceName": "Pixel_4"} 54 | ) 55 | 56 | from factories.driver.concrete_factory.concrete_web_driver_factory import ConcreteWebDriverFactory 57 | 58 | web_driver_factory = ConcreteWebDriverFactory() 59 | driver = web_driver_factory.get_web_driver( 60 | browser_make=WebBrowserMake.CHROME, 61 | options={"headless": True} 62 | ) 63 | """ 64 | 65 | from typing import Any, Optional 66 | 67 | from appium.webdriver.webdriver import WebDriver 68 | from uaf.enums.browser_make import WebBrowserMake 69 | from uaf.enums.mobile_os import MobileOs 70 | from uaf.enums.environments import Environments 71 | from uaf.enums.execution_mode import ExecutionMode 72 | from uaf.enums.mobile_app_type import MobileAppType 73 | from uaf.factories.driver.abstract_factory import abstract_factory 74 | from uaf.factories.driver.concrete_factory.concrete_products.mobile.concrete_mobile_driver import ( 75 | ConcreteMobileDriver, 76 | ) 77 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_web_driver import ( 78 | ConcreteWebDriver, 79 | ) 80 | 81 | __all__ = [ 82 | "abstract_factory", 83 | "Optional", 84 | "Any", 85 | "WebDriver", 86 | "WebBrowserMake", 87 | "MobileOs", 88 | "ExecutionMode", 89 | "MobileAppType", 90 | "Environments", 91 | "ConcreteMobileDriver", 92 | "ConcreteWebDriver", 93 | ] 94 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_factory.py: -------------------------------------------------------------------------------- 1 | from . import ( 2 | Any, 3 | ConcreteMobileDriver, 4 | ConcreteWebDriver, 5 | MobileOs, 6 | Optional, 7 | WebDriver, 8 | Environments, 9 | ExecutionMode, 10 | MobileAppType, 11 | WebBrowserMake, 12 | abstract_factory, 13 | ) 14 | 15 | 16 | class ConcreteMobileDriverFactory(abstract_factory.AbstractMobileDriverFactory): 17 | """Concrete implementation of a mobile driver factory. 18 | 19 | This class implements the method to fetch or create mobile driver instances based on 20 | the mobile OS, app type, execution mode, environment, and capabilities provided. 21 | """ 22 | 23 | def get_mobile_driver( 24 | self, 25 | *, 26 | os: MobileOs, 27 | app_type: MobileAppType, 28 | execution_mode: ExecutionMode, 29 | environment: Environments, 30 | capabilities: dict[str, Any], 31 | ) -> tuple[WebDriver, int]: 32 | """Fetch or create a mobile driver instance. 33 | 34 | Args: 35 | os (MobileOs): The mobile operating system (e.g., Android, iOS). 36 | app_type (MobileAppType): The type of mobile application (e.g., native, web, hybrid). 37 | execution_mode (ExecutionMode): The test execution mode (e.g., local, remote). 38 | environment (Environments): The environment where the mobile application will run (e.g., staging, production). 39 | capabilities (dict[str, Any]): A dictionary of desired capabilities for configuring the mobile driver. 40 | 41 | Returns: 42 | tuple[WebDriver, int]: A tuple containing the mobile driver instance and a session identifier. 43 | """ 44 | return ConcreteMobileDriver( 45 | os=os, 46 | app_type=app_type, 47 | execution_mode=execution_mode, 48 | environment=environment, 49 | ).get_mobile_driver( 50 | capabilities=capabilities 51 | ) # type: ignore[misc] 52 | 53 | 54 | class ConcreteWebDriverFactory(abstract_factory.AbstractWebDriverFactory): 55 | """Concrete implementation of a web driver factory. 56 | 57 | This class implements the method to fetch or create web driver instances based on 58 | the browser make and provided options. 59 | """ 60 | 61 | def __init__(self) -> None: 62 | """Initialize the web driver factory.""" 63 | pass 64 | 65 | def get_web_driver( 66 | self, 67 | *, 68 | browser_make: WebBrowserMake, 69 | options: Optional[dict[str, Any]] = None, 70 | ) -> WebDriver: 71 | """Fetch or create a web driver instance. 72 | 73 | Args: 74 | browser_make (WebBrowserMake): The web browser make enum specifying which browser to use. 75 | options (Optional[dict[str, Any]], optional): A dictionary of browser options or capabilities. Defaults to None. 76 | 77 | Raises: 78 | ValueError: If an invalid browser type is specified. 79 | 80 | Returns: 81 | WebDriver: The browser driver instance based on the selected browser make. 82 | """ 83 | try: 84 | WebBrowserMake(browser_make) 85 | except ValueError: 86 | raise ValueError(f"Invalid browser type specified: {browser_make}") 87 | return ConcreteWebDriver(browser_make=browser_make).get_web_driver( 88 | options=options 89 | ) 90 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ConcreteProducts Module 3 | 4 | This module contains platform-specific and browser-specific concrete implementations of the driver classes. 5 | The `concrete_products` module is part of the Factory Design Pattern, where the abstract products defined in 6 | the `abstract_products` module are implemented with real-world logic for creating drivers for various mobile 7 | and web platforms. 8 | 9 | This module is part of the `factories.driver.concrete_factory.concrete_products` hierarchy and serves to instantiate 10 | drivers for specific platforms, including mobile platforms like Android and iOS, as well as web browsers like Chrome, 11 | Firefox, Microsoft Edge, Internet Explorer, and Brave. 12 | 13 | ## Structure 14 | 15 | The `concrete_products` module is divided into two subdirectories: 16 | 17 | 1. **mobile/**: 18 | - Contains concrete implementations for mobile drivers. 19 | - `concrete_android_driver.py`: Implements the logic to create an Android driver instance. 20 | - `concrete_ios_driver.py`: Implements the logic to create an iOS driver instance. 21 | - `concrete_mobile_driver.py`: General mobile driver implementation that combines Android and iOS logic. 22 | 23 | 2. **web/**: 24 | - Contains concrete implementations for web drivers across different browsers. 25 | - `concrete_chrome_driver.py`: Implements the logic to create a Chrome web driver instance. 26 | - `concrete_firefox_driver.py`: Implements the logic to create a Firefox web driver instance. 27 | - `concrete_msedge_driver.py`: Implements the logic to create a Microsoft Edge web driver instance. 28 | - `concrete_ie_driver.py`: Implements the logic to create an Internet Explorer web driver instance. 29 | - `concrete_brave_driver.py`: Implements the logic to create a Brave browser driver instance. 30 | - `concrete_web_driver.py`: General web driver implementation for browser-agnostic driver creation. 31 | 32 | ## Purpose 33 | 34 | The purpose of the `concrete_products` module is to provide platform-specific implementations for the driver classes. 35 | These concrete classes implement the driver creation logic for specific mobile platforms and web browsers, making it 36 | possible to generate fully configured driver instances based on the required platform or browser. 37 | 38 | This module enables the separation of driver logic for each platform, allowing for scalable and maintainable code. 39 | It ensures that new platforms or browsers can be easily added by implementing new concrete classes that extend 40 | the abstract base classes. 41 | 42 | ## Usage 43 | 44 | The `concrete_products` module works in conjunction with the `concrete_factory` module to instantiate drivers. 45 | Clients interact with the concrete factory classes, which then use the platform-specific logic from `concrete_products` 46 | to create the appropriate driver instance. 47 | 48 | Example for creating a concrete mobile driver: 49 | 50 | ```python 51 | from factories.driver.concrete_factory.concrete_products.mobile.concrete_android_driver import ConcreteAndroidDriver 52 | 53 | android_driver = ConcreteAndroidDriver(remote_url="http://localhost:4723/wd/hub") 54 | driver = android_driver.get_driver(capabilities={"platformName": "Android", "deviceName": "Pixel_4"}) 55 | 56 | from factories.driver.concrete_factory.concrete_web_driver_factory import ConcreteWebDriverFactory 57 | 58 | web_driver_factory = ConcreteWebDriverFactory() 59 | driver = web_driver_factory.get_web_driver( 60 | browser_make=WebBrowserMake.CHROME, 61 | options={"headless": True} 62 | ) 63 | """ 64 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/mobile/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Mobile ConcreteProducts Module 3 | 4 | This module provides the concrete implementations for mobile platform drivers, specifically targeting Android and iOS platforms. 5 | These concrete classes extend the abstract mobile driver products defined in the `abstract_mobile` module and implement the 6 | logic required to instantiate mobile drivers for these platforms. 7 | 8 | This module is part of the `factories.driver.concrete_factory.concrete_products.mobile` hierarchy and works in conjunction 9 | with the concrete mobile factories to provide fully configured mobile drivers based on the provided capabilities and platform configurations. 10 | 11 | ## Structure 12 | 13 | The `mobile` module contains the following concrete classes: 14 | 15 | - `concrete_android_driver.py`: Implements the logic to create an Android mobile driver instance. 16 | - `concrete_ios_driver.py`: Implements the logic to create an iOS mobile driver instance. 17 | - `concrete_mobile_driver.py`: General implementation for mobile driver creation, combining the logic for Android and iOS platforms. 18 | 19 | ## Purpose 20 | 21 | The purpose of this module is to provide platform-specific driver creation logic for Android and iOS devices. These classes handle the 22 | complexity of setting up mobile drivers and abstract the details from the client, ensuring that mobile drivers can be created 23 | consistently and easily across different environments. 24 | 25 | This module is part of the broader Factory Design Pattern used in the project, where the concrete mobile driver factories utilize 26 | these platform-specific implementations to create the correct driver instance. 27 | 28 | ## Usage 29 | 30 | This module is used by the `concrete_mobile_driver_factory.py` to instantiate mobile drivers for Android and iOS platforms. The 31 | concrete classes within the `mobile` module encapsulate the platform-specific logic required to create and configure these drivers. 32 | 33 | Example for creating an Android mobile driver: 34 | 35 | ```python 36 | from factories.driver.concrete_factory.concrete_products.mobile.concrete_android_driver import ConcreteAndroidDriver 37 | 38 | android_driver = ConcreteAndroidDriver(remote_url="http://localhost:4723/wd/hub") 39 | driver = android_driver.get_driver(capabilities={"platformName": "Android", "deviceName": "Pixel_4"}) 40 | 41 | from factories.driver.concrete_factory.concrete_products.mobile.concrete_ios_driver import ConcreteIOSDriver 42 | 43 | ios_driver = ConcreteIOSDriver(remote_url="http://localhost:4723/wd/hub") 44 | driver = ios_driver.get_driver(capabilities={"platformName": "iOS", "deviceName": "iPhone_12"}) 45 | """ 46 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/mobile/concrete_android_driver.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_mobile.abstract_android import ( 4 | AbstractAndroid, 5 | ) 6 | 7 | 8 | class ConcreteAndroidDriver(AbstractAndroid): 9 | """Concrete implementation class for creating an Android mobile driver.""" 10 | 11 | def __init__(self, remote_url: str): 12 | """Initializes the ConcreteAndroidDriver with the provided remote URL for remote execution. 13 | 14 | Args: 15 | remote_url (str): The remote URL for executing the Android driver. 16 | """ 17 | self.remote_url = remote_url 18 | 19 | def get_driver(self, *, capabilities: dict[str, Any]): 20 | """Fetches and returns the Android mobile driver instance. 21 | 22 | Args: 23 | capabilities (dict[str, Any]): The capabilities required to configure the Android driver. 24 | 25 | Returns: 26 | WebDriver: The Android driver instance configured with the provided capabilities. 27 | """ 28 | from appium.options.android import UiAutomator2Options 29 | from appium.webdriver import Remote 30 | 31 | return Remote( 32 | self.remote_url, 33 | options=UiAutomator2Options().load_capabilities(capabilities), 34 | direct_connection=True, 35 | ) 36 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/mobile/concrete_ios_driver.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_mobile.abstract_ios import ( 4 | AbstractIOS, 5 | ) 6 | 7 | 8 | class ConcreteIOSDriver(AbstractIOS): 9 | """Concrete implementation class for creating an iOS mobile driver.""" 10 | 11 | def __init__(self, remote_url: str): 12 | """Initializes the ConcreteIOSDriver with the provided remote URL for remote execution. 13 | 14 | Args: 15 | remote_url (str): The remote URL for executing the iOS driver. 16 | """ 17 | self.remote_url = remote_url 18 | 19 | def get_driver(self, *, capabilities: dict[str, Any]): 20 | """Fetches and returns the iOS mobile driver instance. 21 | 22 | Args: 23 | capabilities (dict[str, Any]): The capabilities required to configure the iOS driver. 24 | 25 | Returns: 26 | WebDriver: The iOS driver instance configured with the provided capabilities. 27 | """ 28 | from appium.options.ios import XCUITestOptions 29 | from appium.webdriver import Remote 30 | 31 | options = XCUITestOptions().load_capabilities(capabilities) 32 | return Remote( 33 | self.remote_url, 34 | options=options, 35 | direct_connection=True, 36 | ) 37 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/mobile/concrete_mobile_driver.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from appium.webdriver.webdriver import WebDriver 4 | from uaf.enums.file_paths import FilePaths 5 | from uaf.enums.mobile_os import MobileOs 6 | from uaf.enums.environments import Environments 7 | from uaf.enums.execution_mode import ExecutionMode 8 | from uaf.enums.mobile_app_type import MobileAppType 9 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_mobile.abstract_mobile import ( 10 | AbstractMobile, 11 | ) 12 | from uaf.factories.driver.concrete_factory.concrete_products.mobile.concrete_android_driver import ( 13 | ConcreteAndroidDriver, 14 | ) 15 | from uaf.factories.driver.concrete_factory.concrete_products.mobile.concrete_ios_driver import ( 16 | ConcreteIOSDriver, 17 | ) 18 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 19 | from uaf.utilities.ui.appium_core.appium_core_utils import CoreUtils 20 | 21 | 22 | class ConcreteMobileDriver(AbstractMobile): 23 | """ 24 | Concrete mobile driver factory that produces mobile drivers for both Android and iOS. 25 | This factory ensures that the drivers returned are compatible with the mobile OS, 26 | application type, execution mode, and environment. 27 | """ 28 | 29 | def __init__( 30 | self, 31 | *, 32 | os: MobileOs, 33 | app_type: MobileAppType, 34 | execution_mode: ExecutionMode, 35 | environment: Environments, 36 | ) -> None: 37 | """Initialize the mobile driver instance. 38 | 39 | Args: 40 | os (MobileOs): The mobile operating system (e.g., Android, iOS). 41 | app_type (MobileAppType): The type of mobile application (e.g., native, web, hybrid). 42 | execution_mode (ExecutionMode): The execution mode for the tests (e.g., local, remote). 43 | environment (Environments): The environment for the mobile driver (e.g., staging, production). 44 | """ 45 | self.env = environment 46 | self.os = os 47 | self.app_type = app_type 48 | self.exec_mode = execution_mode 49 | 50 | def get_mobile_driver( 51 | self, *, capabilities: dict[str, Any] 52 | ) -> tuple[WebDriver, int]: 53 | """Fetch or create a mobile driver instance. 54 | 55 | This method launches the Appium service and returns the appropriate mobile driver 56 | based on the mobile OS (Android or iOS) and the given capabilities. It also handles 57 | setting up the Appium base URL and port. 58 | 59 | Args: 60 | capabilities (dict[str, Any]): A dictionary of mobile driver capabilities. 61 | 62 | Returns: 63 | tuple[WebDriver, int]: A tuple containing the mobile driver instance and the Appium port. 64 | """ 65 | common_config = YamlParser(FilePaths.COMMON) 66 | port = CoreUtils.launch_appium_service(self.os, self.app_type) 67 | remote_url = ( 68 | common_config.get_value( 69 | "appium", 70 | ( 71 | "appium_base_url_local" 72 | if self.exec_mode.value == ExecutionMode.LOCAL.value 73 | else "appium_base_url_remote" 74 | ), 75 | ), 76 | )[0].replace("${port}", str(port)) 77 | from urllib.parse import urlparse 78 | 79 | host = urlparse(remote_url).hostname 80 | CoreUtils.wait_for_appium_service_to_load(30, host, port) 81 | return ( 82 | ConcreteAndroidDriver(remote_url).get_driver(capabilities=capabilities) 83 | if self.os.value == MobileOs.ANDROID.value 84 | else ConcreteIOSDriver(remote_url).get_driver(capabilities=capabilities) 85 | ), port 86 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Web ConcreteProducts Module 3 | 4 | This module contains the concrete implementations for web browser drivers within the Factory Design Pattern. 5 | It supports various web browsers such as Chrome, Firefox, Microsoft Edge, Internet Explorer, and Brave. 6 | Each concrete class in this module implements the logic for creating and configuring web drivers specific to the browser 7 | requested, based on the abstract classes defined in the `abstract_web` module. 8 | 9 | This module is part of the `factories.driver.concrete_factory.concrete_products.web` hierarchy, 10 | and it works alongside the concrete web driver factories to instantiate fully configured web drivers. 11 | 12 | ## Structure 13 | 14 | The `web` module contains the following concrete classes for web browsers: 15 | 16 | - `concrete_chrome_driver.py`: Implements the logic to create a Chrome web driver instance. 17 | - `concrete_firefox_driver.py`: Implements the logic to create a Firefox web driver instance. 18 | - `concrete_msedge_driver.py`: Implements the logic to create a Microsoft Edge web driver instance. 19 | - `concrete_ie_driver.py`: Implements the logic to create an Internet Explorer web driver instance. 20 | - `concrete_brave_driver.py`: Implements the logic to create a Brave browser driver instance. 21 | - `concrete_chromium_driver.py`: Implements the logic to create a Chromium-based web driver instance. 22 | - `concrete_web_driver.py`: Provides a common implementation for browser-agnostic web driver creation. 23 | 24 | ## Purpose 25 | 26 | The purpose of this module is to provide concrete, browser-specific implementations for web drivers, ensuring that 27 | each browser’s unique requirements and configurations are handled properly. These concrete classes extend the abstract 28 | web driver products, encapsulating the logic for creating and launching web drivers for Chrome, Firefox, Edge, and others. 29 | 30 | This module works within the Factory Design Pattern, where concrete web driver factories use these classes to create 31 | browser driver instances based on the client's requirements. 32 | 33 | ## Usage 34 | 35 | The `web` module is used by the `concrete_web_driver_factory.py` to instantiate drivers for specific web browsers. 36 | The concrete classes within this module handle browser-specific details and ensure that drivers are created with 37 | the correct options and configurations. 38 | 39 | Example for creating a Chrome web driver: 40 | 41 | ```python 42 | from factories.driver.concrete_factory.concrete_products.web.concrete_chrome_driver import ConcreteChromeDriver 43 | 44 | chrome_driver = ConcreteChromeDriver(remote_url="http://localhost:4444/wd/hub") 45 | driver = chrome_driver.get_driver(options={"headless": True}) 46 | 47 | from factories.driver.concrete_factory.concrete_products.web.concrete_firefox_driver import ConcreteFirefoxDriver 48 | 49 | firefox_driver = ConcreteFirefoxDriver(remote_url="http://localhost:4444/wd/hub") 50 | driver = firefox_driver.get_driver(options={"headless": True}) 51 | """ 52 | 53 | from selenium import webdriver 54 | 55 | __all__ = ["webdriver"] 56 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_brave_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.chrome.options import Options as ChromeOptions 2 | from selenium.webdriver.chrome.service import Service 3 | from webdriver_manager.chrome import ChromeDriverManager 4 | from webdriver_manager.core.os_manager import ChromeType 5 | 6 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_brave import ( 7 | AbstractBrave, 8 | ) 9 | 10 | from . import webdriver 11 | 12 | 13 | class ConcreteBraveDriver(AbstractBrave): 14 | """Concrete implementation class for creating a Brave browser web driver.""" 15 | 16 | def get_web_driver(self, *, options: ChromeOptions | None = None): 17 | """Fetches and returns a Brave browser web driver instance. 18 | 19 | Args: 20 | options (ChromeOptions | None, optional): The Chrome options to configure the Brave web driver. If not provided, 21 | it will attempt to load from the environment variable `BRAVE_EXECUTABLE`. 22 | 23 | Raises: 24 | RuntimeError: If the environment variable 'BRAVE_EXECUTABLE' is not set when options are None. 25 | 26 | Returns: 27 | WebDriver: An instance of the Brave web driver configured with the provided or default options. 28 | """ 29 | if options is None: 30 | import os 31 | 32 | env_data_fetch_response = os.getenv("BRAVE_EXECUTABLE", default=None) 33 | if env_data_fetch_response is None: 34 | raise RuntimeError( 35 | "Environment variable 'BRAVE_EXECUTABLE' is not set!!" 36 | ) 37 | options = ChromeOptions() 38 | options.add_argument("start-maximized") 39 | options.binary_location = env_data_fetch_response 40 | 41 | return webdriver.Chrome( 42 | options=options, 43 | service=Service( 44 | executable_path=ChromeDriverManager( 45 | chrome_type=ChromeType.BRAVE 46 | ).install() 47 | ), 48 | ) 49 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_chrome_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.chrome.options import Options as ChromeOptions 2 | from selenium.webdriver.chrome.service import Service 3 | from webdriver_manager.chrome import ChromeDriverManager 4 | 5 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_chrome import ( 6 | AbstractChrome, 7 | ) 8 | 9 | from . import webdriver 10 | 11 | 12 | class ConcreteChromeDriver(AbstractChrome): 13 | """Concrete implementation class for creating a Chrome web driver.""" 14 | 15 | def get_web_driver(self, *, options: ChromeOptions | None = None): 16 | """Fetches and returns a Chrome browser web driver instance. 17 | 18 | Args: 19 | options (ChromeOptions | None, optional): The Chrome options to configure the web driver. Defaults to None. 20 | If not provided, default options (maximized window) will be applied. 21 | 22 | Returns: 23 | WebDriver: A Chrome WebDriver instance configured with the provided or default options. 24 | """ 25 | if options is None: 26 | options = ChromeOptions() 27 | options.add_argument("start-maximized") 28 | 29 | return webdriver.Chrome( 30 | options=options, 31 | service=Service(executable_path=ChromeDriverManager().install()), 32 | ) 33 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_chromium_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.chrome.options import Options as ChromeOptions 2 | from selenium.webdriver.chrome.service import Service 3 | from webdriver_manager.chrome import ChromeDriverManager 4 | from webdriver_manager.core.os_manager import ChromeType 5 | 6 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_chromium import ( 7 | AbstractChromium, 8 | ) 9 | 10 | from . import webdriver 11 | 12 | 13 | class ConcreteChromiumDriver(AbstractChromium): 14 | """Concrete implementation class for creating a Chromium web driver.""" 15 | 16 | def get_web_driver(self, *, options: ChromeOptions | None = None): 17 | """Fetches and returns a Chromium browser web driver instance. 18 | 19 | Args: 20 | options (ChromeOptions | None, optional): The Chrome options to configure the Chromium web driver. 21 | Defaults to None. If not provided, default options 22 | (maximized window) will be applied. 23 | 24 | Returns: 25 | WebDriver: A Chromium WebDriver instance configured with the provided or default options. 26 | """ 27 | if options is None: 28 | options = ChromeOptions() 29 | options.add_argument("start-maximized") 30 | 31 | return webdriver.Chrome( 32 | options=options, 33 | service=Service( 34 | executable_path=ChromeDriverManager( 35 | chrome_type=ChromeType.CHROMIUM 36 | ).install() 37 | ), 38 | ) 39 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_firefox_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.firefox.options import Options as FirefoxOptions 2 | from selenium.webdriver.firefox.service import Service 3 | from webdriver_manager.firefox import GeckoDriverManager 4 | 5 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_firefox import ( 6 | AbstractFirefox, 7 | ) 8 | 9 | from . import webdriver 10 | 11 | 12 | class ConcreteFirefoxDriver(AbstractFirefox): 13 | """Concrete implementation class for creating a Firefox web driver.""" 14 | 15 | def get_web_driver(self, *, options: FirefoxOptions | None = None): 16 | """Fetches and returns a Firefox browser web driver instance. 17 | 18 | Args: 19 | options (FirefoxOptions | None, optional): The Firefox options to configure the web driver. 20 | Defaults to None. If not provided, default options 21 | (maximized window) will be applied. 22 | 23 | Returns: 24 | WebDriver: A Firefox WebDriver instance configured with the provided or default options. 25 | """ 26 | if options is None: 27 | options = FirefoxOptions() 28 | options.add_argument("--start-maximized") 29 | 30 | return webdriver.Firefox( 31 | options=options, 32 | service=Service(executable_path=GeckoDriverManager().install()), 33 | ) 34 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_ie_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.ie.options import Options as IeOptions 2 | from selenium.webdriver.ie.service import Service 3 | from webdriver_manager.microsoft import IEDriverManager 4 | 5 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_ie import ( 6 | AbstractIE, 7 | ) 8 | 9 | from . import webdriver 10 | 11 | 12 | class ConcreteIEDriver(AbstractIE): 13 | """Concrete implementation class for creating an Internet Explorer (IE) web driver.""" 14 | 15 | def get_web_driver(self, *, options: IeOptions | None = None): 16 | """Fetches and returns an Internet Explorer (IE) web driver instance. 17 | 18 | Args: 19 | options (IeOptions | None, optional): The Internet Explorer options to configure the web driver. 20 | Defaults to None. If not provided, default options 21 | (maximized window) will be applied. 22 | 23 | Returns: 24 | WebDriver: An IE WebDriver instance configured with the provided or default options. 25 | """ 26 | if options is None: 27 | options = IeOptions() 28 | options.add_argument("start-maximized") 29 | 30 | return webdriver.Ie( 31 | options=options, 32 | service=Service(executable_path=IEDriverManager().install()), 33 | ) 34 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_msedge_driver.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.edge.options import Options as MsEdgeOptions 2 | from selenium.webdriver.edge.service import Service 3 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 4 | 5 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_msedge import ( 6 | AbstractMsedge, 7 | ) 8 | 9 | from . import webdriver 10 | 11 | 12 | class ConcreteMsedgeDriver(AbstractMsedge): 13 | """Concrete implementation class for creating a Microsoft Edge (MsEdge) web driver.""" 14 | 15 | def get_web_driver(self, *, options: MsEdgeOptions | None = None): 16 | """Fetches and returns a Microsoft Edge (MsEdge) web driver instance. 17 | 18 | Args: 19 | options (MsEdgeOptions | None, optional): The Microsoft Edge options to configure the web driver. 20 | Defaults to None. If not provided, default options 21 | (maximized window) will be applied. 22 | 23 | Returns: 24 | WebDriver: A Microsoft Edge WebDriver instance configured with the provided or default options. 25 | """ 26 | if options is None: 27 | options = MsEdgeOptions() 28 | options.add_argument("start-maximized") 29 | 30 | return webdriver.Edge( 31 | options=options, 32 | service=Service(executable_path=EdgeChromiumDriverManager().install()), 33 | ) 34 | -------------------------------------------------------------------------------- /uaf/factories/driver/concrete_factory/concrete_products/web/concrete_web_driver.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from uaf.enums.browser_make import WebBrowserMake 4 | from uaf.factories.driver.abstract_factory.abstract_products.abstract_web.abstract_web_driver import ( 5 | AbstractWebDriver, 6 | ) 7 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_brave_driver import ( 8 | ConcreteBraveDriver, 9 | ) 10 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_chrome_driver import ( 11 | ConcreteChromeDriver, 12 | ) 13 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_chromium_driver import ( 14 | ConcreteChromiumDriver, 15 | ) 16 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_firefox_driver import ( 17 | ConcreteFirefoxDriver, 18 | ) 19 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_ie_driver import ( 20 | ConcreteIEDriver, 21 | ) 22 | from uaf.factories.driver.concrete_factory.concrete_products.web.concrete_msedge_driver import ( 23 | ConcreteMsedgeDriver, 24 | ) 25 | 26 | 27 | class ConcreteWebDriver(AbstractWebDriver): 28 | """ 29 | Concrete implementation class for creating web browser drivers. 30 | 31 | This factory class produces web drivers for various web browsers, ensuring that the correct driver is instantiated 32 | based on the specified browser type. It supports different browser variants (e.g., Chrome, Brave, Firefox, etc.) 33 | and guarantees that the resulting web driver is compatible with the user's requested browser. 34 | 35 | The `get_web_driver` method returns an abstract web browser interface, while internally, it instantiates a 36 | concrete product based on the user's choice. 37 | """ 38 | 39 | def __init__(self, *, browser_make: WebBrowserMake): 40 | """Initializes the ConcreteWebDriver with the specified browser type. 41 | 42 | Args: 43 | browser_make (WebBrowserMake): Enum specifying the web browser type (e.g., Chrome, Firefox, Brave). 44 | """ 45 | self.browser_make = browser_make 46 | 47 | def get_web_driver(self, *, options: dict[str, Any] | None = None): 48 | """Fetches and returns the web driver instance for the specified browser type. 49 | 50 | Args: 51 | options (dict[str, Any] | None, optional): Browser capabilities or options. Defaults to None. 52 | 53 | Raises: 54 | ValueError: If an invalid browser type is specified. 55 | 56 | Returns: 57 | WebDriver: A web driver instance for the specified browser. 58 | """ 59 | match self.browser_make.value: 60 | case WebBrowserMake.BRAVE.value: 61 | return ConcreteBraveDriver().get_web_driver(options=options) 62 | case WebBrowserMake.CHROME.value: 63 | return ConcreteChromeDriver().get_web_driver(options=options) 64 | case WebBrowserMake.CHROMIUM.value: 65 | return ConcreteChromiumDriver().get_web_driver(options=options) 66 | case WebBrowserMake.IE.value: 67 | return ConcreteIEDriver().get_web_driver(options=options) 68 | case WebBrowserMake.MSEDGE.value: 69 | return ConcreteMsedgeDriver().get_web_driver(options=options) 70 | case WebBrowserMake.FIREFOX.value: 71 | return ConcreteFirefoxDriver().get_web_driver(options=options) 72 | case _: 73 | raise ValueError("Invalid browser type specified.") 74 | -------------------------------------------------------------------------------- /uaf/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/py.typed -------------------------------------------------------------------------------- /uaf/utilities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/database/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/database/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/database/postgres_utility.py: -------------------------------------------------------------------------------- 1 | import psycopg 2 | from typing import Any 3 | 4 | 5 | class PostgresUtility: 6 | def __init__(self, connection_string: str): 7 | """ 8 | Constructor for PostgresUtility class. 9 | 10 | Args: 11 | connection_string (str): PostgreSQL connection string 12 | """ 13 | self.connection_string = connection_string 14 | self.conn = psycopg.connect(connection_string) 15 | self.conn.autocommit = True 16 | 17 | def __enter__(self) -> "PostgresUtility": 18 | """ 19 | Support for the with statement to automatically manage resources. 20 | """ 21 | self.connect() 22 | return self 23 | 24 | def __exit__(self, exc_type, exc_val, exc_tb): 25 | """ 26 | Exit method for context manager to close connections. 27 | """ 28 | self.disconnect() 29 | 30 | def connect(self): 31 | """ 32 | Reconnect to PostgreSQL if the connection is closed. 33 | """ 34 | if self.conn.closed: 35 | self.conn = psycopg.connect(self.connection_string) 36 | self.conn.autocommit = True 37 | 38 | def disconnect(self): 39 | """ 40 | Close the connection to PostgreSQL. 41 | """ 42 | if self.conn: 43 | self.conn.close() 44 | 45 | def ping(self) -> tuple[Any, ...] | None: 46 | """ 47 | Ping the database to ensure connection is live. 48 | 49 | Returns: 50 | Optional[tuple[Any, ...]]: The result of the ping query or None if no rows are returned. 51 | """ 52 | with self.conn.cursor() as cursor: 53 | cursor.execute("SELECT 1") 54 | result = cursor.fetchone() 55 | if result is not None: 56 | return tuple(result) 57 | return None 58 | 59 | def execute(self, query: str, params: tuple[Any, ...] | None = None) -> None: 60 | """ 61 | General method to execute a SQL query (INSERT, UPDATE, DELETE) without expecting any result. 62 | 63 | Args: 64 | query (str): The SQL query to execute. 65 | params (Optional[tuple[Any, ...]]): Parameters for the query. 66 | """ 67 | with self.conn.cursor() as cursor: 68 | cursor.execute(query, params) 69 | 70 | def fetch_one( 71 | self, query: str, params: tuple[Any, ...] | None = None 72 | ) -> dict[str, Any] | None: 73 | """ 74 | Fetch a single row from the database. 75 | 76 | Args: 77 | query (str): The SQL SELECT query. 78 | params (Optional[tuple[Any, ...]]): Parameters for the query. 79 | 80 | Returns: 81 | Optional[dict[str, Any]]: A single row as a dictionary or None if no rows are found. 82 | """ 83 | with self.conn.cursor(row_factory=psycopg.rows.dict_row) as cursor: 84 | cursor.execute(query, params) 85 | result = cursor.fetchone() 86 | return result if result is not None else None 87 | 88 | def fetch_many( 89 | self, query: str, params: tuple[Any, ...] | None = None 90 | ) -> list[dict[str, Any]]: 91 | """ 92 | Fetch multiple rows from the database. 93 | 94 | Args: 95 | query (str): The SQL SELECT query. 96 | params (Optional[tuple[Any, ...]]): Parameters for the query. 97 | 98 | Returns: 99 | list[dict[str, Any]]: A list of rows as dictionaries. 100 | """ 101 | with self.conn.cursor(row_factory=psycopg.rows.dict_row) as cursor: 102 | cursor.execute(query, params) 103 | results = cursor.fetchall() 104 | return results if results is not None else [] 105 | 106 | def modify(self, query: str, params: tuple[Any, ...] | None = None) -> None: 107 | """ 108 | Generalized method to perform INSERT, UPDATE, or DELETE operations. 109 | 110 | Args: 111 | query (str): The SQL query to execute (INSERT, UPDATE, DELETE). 112 | params (Optional[tuple[Any, ...]]): Parameters for the query. 113 | """ 114 | self.execute(query, params) 115 | 116 | def modify_many(self, query: str, params_list: list[tuple[Any, ...]]) -> None: 117 | """ 118 | Generalized method to perform batch INSERT, UPDATE, or DELETE operations. 119 | 120 | Args: 121 | query (str): The SQL query to execute. 122 | params_list (list[tuple[Any, ...]]): List of parameter tuples for the query. 123 | """ 124 | with self.conn.cursor() as cursor: 125 | cursor.executemany(query, params_list) 126 | -------------------------------------------------------------------------------- /uaf/utilities/faker/__init__.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Any, Optional 3 | 4 | from faker import Faker 5 | 6 | __all__ = ["Faker", "random", "Optional", "Any"] 7 | -------------------------------------------------------------------------------- /uaf/utilities/faker/faker_utils.py: -------------------------------------------------------------------------------- 1 | from . import Any, Faker, Optional, random 2 | 3 | 4 | class FakerUtils: 5 | """ 6 | Utility class to generate fake data using Faker library 7 | """ 8 | 9 | @staticmethod 10 | def get_instance(): 11 | """ 12 | Provides default instance with locale set en_US 13 | """ 14 | return FakerUtils() 15 | 16 | def __init__(self, locale: str = "en_US"): 17 | """Constructor 18 | 19 | Args: 20 | locale (str, optional): geographical style of names to utilise in generating fake data. Defaults to "en_US". 21 | """ 22 | self.fake = Faker(locale) 23 | 24 | def random_int(self, min_val: int = 0, max_val: int = 100): 25 | """Generate random integer 26 | 27 | Args: 28 | min_val (int, optional): minimum value. Defaults to 0. 29 | max_val (int, optional): maximum value. Defaults to 100. 30 | 31 | Returns: 32 | int: random integer in specified range 33 | """ 34 | return self.fake.random_int(min=min_val, max=max_val) 35 | 36 | def random_float(self, min_val: float = 0, max_val: float = 100): 37 | """Generate random floating point number 38 | 39 | Args: 40 | min_val (float, optional): minimum value. Defaults to 0. 41 | max_val (float, optional): maximum value. Defaults to 100. 42 | 43 | Returns: 44 | float: floating point number in specified range 45 | """ 46 | return self.fake.pyfloat(min_value=min_val, max_value=max_val) 47 | 48 | def random_bool(self): 49 | """Returns random boolean condition, either True/False 50 | 51 | Returns: 52 | bool: True/False 53 | """ 54 | return self.fake.pybool() 55 | 56 | def random_str(self, length: int, allowed_chars: Optional[str] = None): 57 | """Generate random string using in the allowed chars and specified length 58 | 59 | Args: 60 | length (int): length of the string 61 | allowed_chars (Optional[str], optional): string data. Defaults to None. 62 | """ 63 | if allowed_chars: 64 | return "".join(random.choices(allowed_chars, k=length)) 65 | return self.fake.pystr(min_chars=length, max_chars=length) 66 | 67 | def random_list(self, length: int = 10, item_type: Any = int, **kwargs): 68 | """Generates random list of data of same data type 69 | 70 | Args: 71 | length (int, optional): length of data. Defaults to 10. 72 | item_type (Any, optional): type of data. Defaults to int. 73 | 74 | Raises: 75 | ValueError: if provided unsupported data type 76 | 77 | Returns: 78 | list[Any]: list of data of same/specified data type 79 | """ 80 | if isinstance(item_type, int): 81 | return [self.random_int(**kwargs) for _ in range(length)] 82 | elif isinstance(item_type, float): 83 | return [self.random_float(**kwargs) for _ in range(length)] 84 | elif isinstance(item_type, bool): 85 | return [self.random_bool(**kwargs) for _ in range(length)] 86 | elif isinstance(item_type, str): 87 | return [self.random_str(**kwargs) for _ in range(length)] 88 | else: 89 | raise ValueError(f"Unsupported item_type: {item_type}") 90 | -------------------------------------------------------------------------------- /uaf/utilities/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/parser/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/parser/yaml_parser_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml # type: ignore 3 | from uaf.enums.file_paths import FilePaths 4 | 5 | 6 | class YamlParser: 7 | """Utility class for parsing yaml documents""" 8 | 9 | def __init__(self, relativeFilepathWithExtension: FilePaths | str): 10 | """Constructor 11 | 12 | Args: 13 | relativeFilepathWithExtension (FilePaths | str): relative filepath of the yaml document 14 | """ 15 | if isinstance(relativeFilepathWithExtension, FilePaths): 16 | self.filePath = os.path.join( 17 | os.getcwd(), relativeFilepathWithExtension.value 18 | ) 19 | else: 20 | self.filePath = relativeFilepathWithExtension 21 | with open(self.filePath) as f: 22 | self.config = yaml.safe_load(f) 23 | 24 | def get_section(self, section): 25 | """Fetches specified section from a yaml document 26 | 27 | Args: 28 | section (str): section name 29 | 30 | Raises: 31 | ValueError: if section is invalid/doesn't exist 32 | 33 | Returns: 34 | dict[str, Any]: data in key value pair 35 | """ 36 | if section in self.config: 37 | return self.config[section] 38 | else: 39 | raise ValueError(f"Selected {section} section is invalid/doesn't exist!") 40 | 41 | def get_value(self, section, key): 42 | """Fetches specified key value from a specified section 43 | 44 | Args: 45 | section (str): name of the section 46 | key (str): name of the key 47 | 48 | Raises: 49 | ValueError: if section-key pair is invalid/doesn't exist 50 | 51 | Returns: 52 | Any: value 53 | """ 54 | if section in self.config and key in self.config[section]: 55 | return self.config[section][key] 56 | else: 57 | raise ValueError( 58 | f"Selected {section}-{key} section-key pair is invalid/doesn't exist!" 59 | ) 60 | 61 | def set_value(self, section, key, value): 62 | """Set specified value in specified section's key 63 | 64 | Args: 65 | section (str): name of the section 66 | key (str): name of the key 67 | value (Any): data to be set 68 | """ 69 | if section not in self.config: 70 | self.config[section] = {} 71 | self.config[section][key] = value 72 | 73 | def save(self): 74 | """Saves the data to the yaml document in focus""" 75 | with open(self.filePath, "w") as f: 76 | yaml.dump(self.config, f, default_flow_style=False) 77 | -------------------------------------------------------------------------------- /uaf/utilities/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/appium_core/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from uaf.enums.file_paths import FilePaths 4 | from uaf.enums.mobile_os import MobileOs 5 | from uaf.enums.mobile_app_type import MobileAppType 6 | from uaf.enums.mobile_device_environment_type import MobileDeviceEnvironmentType 7 | from uaf.utilities.faker.faker_utils import FakerUtils 8 | from uaf.utilities.ui.appium_core.appium_service import AppiumService 9 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 10 | 11 | __all__ = [ 12 | "Optional", 13 | "YamlParser", 14 | "FilePaths", 15 | "MobileOs", 16 | "AppiumService", 17 | "MobileAppType", 18 | "MobileDeviceEnvironmentType", 19 | "FakerUtils", 20 | ] 21 | -------------------------------------------------------------------------------- /uaf/utilities/ui/element/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/element/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/element/element_utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.support.select import Select 5 | 6 | from uaf.decorators.loggers.logger import log 7 | from uaf.utilities.ui.locator.locator_utils import LocatorUtils 8 | from uaf.utilities.ui.waiter.waits import Waits 9 | 10 | 11 | class ElementUtils: 12 | """Selenium wrapper utiity for element interactions""" 13 | 14 | def __init__(self, driver): 15 | """Constructor 16 | 17 | Args:__wait_for_title 18 | driver (webdriver): webdriver instance 19 | """ 20 | self.driver = driver 21 | self.wait = Waits(self.driver) 22 | self.locator = LocatorUtils(driver) 23 | 24 | @log 25 | def tap_on_element(self, positions: list[tuple[int, int]], duration: int): 26 | self.driver.tap(positions, duration) 27 | return self 28 | 29 | @log 30 | def click_on_element_using_js(self, by_locator: tuple[str, str]): 31 | """Click on an element using javascript""" 32 | element = self.locator.by_locator_to_mobile_element(by_locator) # type: ignore 33 | self.driver.execute_script("arguments[0].click();", element) 34 | return self 35 | 36 | @log 37 | def click_on_element(self, by_locator: tuple[By, str]): 38 | """Clicks on an element which is interactable""" 39 | self.wait.wait_for_element_to_be_clickable(by_locator).click() # type: ignore 40 | return self 41 | 42 | @log 43 | def launch_url(self, url: str): 44 | """Launch a url 45 | 46 | Args: 47 | url (str): website page address 48 | """ 49 | self.driver.get(url) 50 | self.wait.wait_for_page_load() 51 | return self 52 | 53 | @log 54 | def fetch_title(self, title: str): 55 | """Fetches title of the current active/in-foucs webpage 56 | 57 | Args: 58 | title (str): title of website 59 | 60 | Returns: 61 | str: actual title of website 62 | """ 63 | self.wait.wait_for_title(title) # type: ignore 64 | return self.driver.title 65 | 66 | @log 67 | def send_keys( 68 | self, by_locator: tuple[By, str], text: str, enter_char_by_char: bool = False 69 | ): 70 | """Send text to edit field 71 | 72 | Args: 73 | by_locator (_type_): by locator 74 | text (str): text to be entered 75 | enter_char_by_char (bool, optional): if True, enters texts character by character. Defaults to False. 76 | """ 77 | if not enter_char_by_char: 78 | self.locator.by_locator_to_web_element(by_locator).send_keys(text) # type: ignore 79 | else: 80 | for i in text: 81 | self.locator.by_locator_to_web_element(by_locator).send_keys(i) # type: ignore 82 | time.sleep(0.5) # introduced forced delay to mimic user typing 83 | return self 84 | 85 | @log 86 | def get_text_from_element(self, by_locator: tuple[By, str]): 87 | return self.locator.by_locator_to_web_element(by_locator).text # type: ignore 88 | 89 | @log 90 | def select_from_drop_down(self, by_locator: tuple[By, str], value: str): 91 | element = self.locator.by_locator_to_web_element(by_locator) # type: ignore 92 | select = Select(element) 93 | select.select_by_value(value) 94 | return self 95 | -------------------------------------------------------------------------------- /uaf/utilities/ui/locator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/locator/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/locator/locator_utils.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from appium.webdriver.common.appiumby import AppiumBy 4 | from appium.webdriver.webelement import WebElement as MobileElement 5 | from selenium.webdriver.common.by import By 6 | from selenium.webdriver.remote.webelement import WebElement 7 | 8 | from uaf.decorators.loggers.logger import log 9 | 10 | 11 | class LocatorUtils: 12 | def __init__(self, driver): 13 | self.driver = driver 14 | 15 | def __perform_by_to_element_conversion(self, by_locator) -> Any: 16 | locator_type, locator_value = by_locator # type: ignore 17 | return self.driver.find_element(locator_type, locator_value) # type: ignore 18 | 19 | @log 20 | def by_locator_to_web_element(self, by_locator: tuple[By, str]) -> WebElement: 21 | """Convert by_locator to web_element 22 | 23 | Args: 24 | by_locator (tuple[By,str]): (locatory type, locator value) 25 | 26 | Returns: 27 | webelement: webelement instance 28 | """ 29 | return self.__perform_by_to_element_conversion(by_locator) 30 | 31 | @log 32 | def by_locator_to_mobile_element( 33 | self, by_locator: tuple[AppiumBy, str] 34 | ) -> MobileElement: 35 | """Convert by_locator to mobile version of webelement 36 | 37 | Args: 38 | by_locator (tuple[AppiumBy, str]): (locator type, locator value) 39 | 40 | Returns: 41 | MobileElement: appium webelement instance 42 | """ 43 | return self.__perform_by_to_element_conversion(by_locator) 44 | 45 | @log 46 | def by_locator_to_web_elements(self, by_locator: tuple[str]) -> list[WebElement]: 47 | """Convert by_locator to list of web_elements 48 | 49 | Args: 50 | by_locator (tuple[str]): (locator type, locator value) 51 | 52 | Returns: 53 | list[webelement]: list of webelement instance 54 | """ 55 | locator_type, locator_value = by_locator # type: ignore 56 | return self.driver.find_elements(locator_type, locator_value) # type: ignore 57 | -------------------------------------------------------------------------------- /uaf/utilities/ui/scroller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/scroller/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/scroller/scroll.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from selenium.webdriver.common.action_chains import ActionChains 4 | from selenium.webdriver.common.by import By 5 | from selenium.webdriver.common.keys import Keys 6 | 7 | from uaf.decorators.loggers.logger import log 8 | from uaf.utilities.ui.locator.locator_utils import LocatorUtils 9 | from uaf.utilities.ui.waiter.waits import Waits 10 | 11 | 12 | class ScrollUtils: 13 | def __init__(self, driver) -> None: 14 | self.driver = driver 15 | self.wait = Waits(driver) 16 | self.locator = LocatorUtils(driver) 17 | 18 | @log 19 | def scroll_to_element(self, by_locator): 20 | """Scroll to element 21 | 22 | Args: 23 | by_locator (tuple): (locator_type, locator_value) 24 | """ 25 | element = self.locator.by_locator_to_web_element(by_locator) # type: ignore 26 | actions = ActionChains(self.driver) 27 | actions.move_to_element(element) 28 | actions.perform() 29 | 30 | @log 31 | def scroll_to_bottom(self): 32 | """Scrolls to bottom of the page""" 33 | body = self.driver.find_element(By.TAG_NAME, "body") 34 | body.send_keys(Keys.END) 35 | 36 | @log 37 | def scroll_to_top(self): 38 | """Scrolls to page top""" 39 | body = self.driver.find_element(By.TAG_NAME, "body") 40 | body.send_keys(Keys.HOME) 41 | 42 | @log 43 | def is_bottom_reached(self): 44 | """Check if bottom reached during scrolling 45 | 46 | Returns: 47 | bool: True/False 48 | """ 49 | return self.driver.execute_script( 50 | "return (window.innerHeight + window.pageYOffset) >= document.body.scrollHeight;" 51 | ) 52 | 53 | @log 54 | def is_scroll_paused(self): 55 | """Checks if scrolling is paused 56 | 57 | Returns: 58 | bool: True/False 59 | """ 60 | initial_position = self.driver.execute_script("return window.pageYOffset;") 61 | time.sleep(0.5) # Adjust the delay as needed 62 | final_position = self.driver.execute_script("return window.pageYOffset;") 63 | return initial_position == final_position 64 | 65 | @log 66 | def scroll_to_bottom_with_pause(self, pause_duraton=1): 67 | """Scrolls to bottom of the page with pause 68 | 69 | Args: 70 | pause_duraton (int, optional): Duration for which scroll action is halted. Defaults to 1. 71 | """ 72 | while not self.is_bottom_reached(): 73 | self.scroll_to_bottom() 74 | time.sleep(pause_duraton) 75 | -------------------------------------------------------------------------------- /uaf/utilities/ui/swipe/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/swipe/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/swipe/swipe_utils.py: -------------------------------------------------------------------------------- 1 | from selenium.common.exceptions import NoSuchElementException 2 | from selenium.webdriver.common.action_chains import ActionChains 3 | from selenium.webdriver.common.actions.interaction import POINTER_TOUCH 4 | from selenium.webdriver.common.actions.pointer_input import PointerInput 5 | 6 | from uaf.enums.direction import Direction 7 | from uaf.utilities.ui.locator.locator_utils import LocatorUtils 8 | 9 | 10 | class SwipeUtils: 11 | """Swipe utils which mimics swiping action using w3c actions \n 12 | **Note:** Works only on native context and webview context is out of focus, 13 | consider using javascript \n 14 | Reference: issue : https://github.com/appium/python-client/issues/867 15 | """ 16 | 17 | def __init__(self, driver) -> None: 18 | self.driver = driver 19 | self.finger = PointerInput(POINTER_TOUCH, "finger") 20 | self.locator = LocatorUtils(driver) 21 | self.actions = ActionChains(driver) 22 | 23 | def __build_w3c_actions(self, start_x, start_y, end_x, end_y): 24 | self.actions.w3c_actions.pointer_action.move_to_location(start_x, start_y) 25 | self.actions.w3c_actions.pointer_action.pointer_down() 26 | self.actions.w3c_actions.pointer_action.move_to_location(end_x, end_y) 27 | self.actions.w3c_actions.pointer_action.release() 28 | 29 | def swipe(self, start_x, start_y, end_x, end_y, iterate_times=1): 30 | while iterate_times > 0: 31 | self.__build_w3c_actions(start_x, start_y, end_x, end_y) 32 | self.actions.perform() 33 | iterate_times -= 1 34 | 35 | def long_swipe(self, direction: Direction): 36 | start_x, start_y, end_x, end_y = 0, 0, 0, 0 37 | view_port = self.driver.get_window_size() 38 | match direction.value: 39 | case Direction.UP.value: 40 | start_x = view_port["width"] // 2 41 | start_y = int(view_port["height"] * 0.10) 42 | end_x = view_port["width"] // 2 43 | end_y = int(view_port["height"] * 0.8) 44 | case Direction.DOWN.value: 45 | start_x = view_port["width"] // 2 46 | start_y = int(view_port["height"] * 0.8) 47 | end_x = view_port["width"] // 2 48 | end_y = int(view_port["height"] * 0.10) 49 | case _: 50 | raise ValueError("Invalid direction!") 51 | self.__build_w3c_actions(start_x, start_y, end_x, end_y) 52 | self.actions.perform() 53 | 54 | def short_swipe(self, direction: Direction): 55 | start_x, start_y, end_x, end_y = 0, 0, 0, 0 56 | view_port = self.driver.get_window_size() 57 | match direction.value: 58 | case Direction.UP.value: 59 | start_x = view_port["width"] // 2 60 | start_y = int(view_port["height"] * 0.10) 61 | end_x = view_port["width"] // 2 62 | end_y = int(view_port["height"] * 0.45) 63 | case Direction.DOWN.value: 64 | start_x = view_port["width"] // 2 65 | start_y = int(view_port["height"] * 0.45) 66 | end_x = view_port["width"] // 2 67 | end_y = int(view_port["height"] * 0.10) 68 | case _: 69 | raise ValueError("Invalid direction!") 70 | self.__build_w3c_actions(start_x, start_y, end_x, end_y) 71 | self.actions.perform() 72 | 73 | def swipe_till_text_visibility(self, text: str, direction: Direction, max_swipe=10): 74 | if max_swipe <= 0: 75 | raise ValueError("max_swipe must be greater than 0") 76 | 77 | start_x, start_y, end_x, end_y = 0, 0, 0, 0 78 | view_port = self.driver.get_window_size() 79 | match direction.value: 80 | case Direction.LEFT.value: 81 | start_x = view_port["width"] // 2 82 | start_y = view_port["height"] // 2 83 | end_x = int(view_port["width"] * 0.90) 84 | end_y = view_port["height"] // 2 85 | case Direction.UP.value: 86 | start_x = view_port["width"] // 2 87 | start_y = int(view_port["height"] * 0.15) 88 | end_x = view_port["width"] // 2 89 | end_y = int(view_port["height"] * 0.45) 90 | case Direction.RIGHT.value: 91 | start_x = view_port["width"] // 2 92 | start_y = view_port["height"] // 2 93 | end_x = int(view_port["width"] * 0.10) 94 | end_y = view_port["height"] // 2 95 | case Direction.DOWN.value: 96 | start_x = view_port["width"] // 2 97 | start_y = int(view_port["height"] * 0.45) 98 | end_x = view_port["width"] // 2 99 | end_y = int(view_port["height"] * 0.10) 100 | case _: 101 | raise ValueError("Invalid direction!") 102 | 103 | while max_swipe > 0: 104 | if text in self.driver.page_source: 105 | return 106 | self.__build_w3c_actions(start_x, start_y, end_x, end_y) 107 | self.actions.perform() 108 | max_swipe -= 1 109 | 110 | raise NoSuchElementException( 111 | f"Text '{text}' not found after {max_swipe} swipes" 112 | ) 113 | -------------------------------------------------------------------------------- /uaf/utilities/ui/waiter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/waiter/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/waiter/waits.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support import expected_conditions as EC 2 | from selenium.webdriver.support.wait import WebDriverWait 3 | 4 | from uaf.decorators.loggers.logger import log 5 | from uaf.enums.file_paths import FilePaths 6 | from uaf.utilities.parser.yaml_parser_utils import YamlParser 7 | 8 | 9 | class Waits: 10 | """WebDriverWait wrapper utility""" 11 | 12 | __config = YamlParser(FilePaths.COMMON) 13 | 14 | def __init__(self, driver): 15 | """Constructor 16 | 17 | Args: 18 | driver (webdriver): webdriver instance 19 | """ 20 | self.driver = driver 21 | self.wait = WebDriverWait( 22 | driver, Waits.__config.get_value("waits", "max_time_out") 23 | ) 24 | 25 | @log 26 | def wait_for_element_to_be_clickable(self, by_locator): 27 | """Wait for an element till it becomes clickable 28 | 29 | Args: 30 | by_locator: by locator 31 | """ 32 | return self.wait.until(EC.element_to_be_clickable(by_locator)) 33 | 34 | @log 35 | def wait_for_title(self, title: str): 36 | """Wait for title to load in active page in focus 37 | 38 | Args: 39 | title (str): title of current active page 40 | """ 41 | self.wait.until(EC.title_is(title)) 42 | 43 | @log 44 | def wait_for_element_presence(self, by_locator): 45 | """Wait for an element presence in the dom 46 | 47 | Args: 48 | by_locator: by locator 49 | """ 50 | return self.wait.until(EC.presence_of_element_located(by_locator)) 51 | 52 | @log 53 | def wait_for_element_visibility(self, by_locator): 54 | """Wait for an element visibility in UI 55 | 56 | Args: 57 | by_locator: by locator 58 | """ 59 | return self.wait.until(EC.visibility_of_element_located(by_locator)) 60 | 61 | @log 62 | def __page_load_js_script(self): 63 | """Page load JavaScript script to detect page load completion 64 | 65 | Returns: 66 | bool: True/False 67 | """ 68 | return self.driver.execute_script('return document.readyState === "complete"') 69 | 70 | @log 71 | def wait_for_until(self, func): 72 | """Waits for custom condition 73 | 74 | Args: 75 | func (lambda): lambda func 76 | """ 77 | self.wait.until(func) 78 | 79 | @log 80 | def wait_for_page_load(self): 81 | """Wait for page load using JavaScript""" 82 | self.wait_for_until(lambda driver: self.__page_load_js_script()) 83 | -------------------------------------------------------------------------------- /uaf/utilities/ui/window/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suneel944/UAF/0790697734611d6d6f31eadf636eb6aaef29597a/uaf/utilities/ui/window/__init__.py -------------------------------------------------------------------------------- /uaf/utilities/ui/window/window_utils.py: -------------------------------------------------------------------------------- 1 | from uaf.decorators.loggers.logger import log 2 | from uaf.utilities.ui.waiter.waits import Waits 3 | 4 | 5 | class WindowUtils: 6 | def __init__(self, driver) -> None: 7 | self.driver = driver 8 | self.wait = Waits(driver) 9 | 10 | @log 11 | def switch_to_succeeding_window(self): 12 | handle_pointer = "" 13 | handle = self.driver.current_window_handle 14 | window_handles: list[str] = self.driver.window_handles 15 | window_handles_size = len(window_handles) 16 | for i in range(window_handles_size): 17 | handle_pointer = window_handles[i] 18 | if handle_pointer.__eq__(handle): 19 | if i == (window_handles_size - 1): 20 | handle_pointer = window_handles[i] 21 | break 22 | handle_pointer = window_handles[i + 1] 23 | break 24 | # switch to window 25 | self.driver.switch_to.window(handle_pointer) 26 | 27 | @log 28 | def switch_to_preceeding_window(self): 29 | handle_pointer = "" 30 | handle = self.driver.current_window_handle 31 | window_handles: list[str] = self.driver.window_handles 32 | window_handles_size = len(window_handles) 33 | for i in range(window_handles_size): 34 | handle_pointer = window_handles[i] 35 | if handle_pointer.__eq__(handle): 36 | if i == 0: 37 | handle_pointer = window_handles[window_handles_size - 1] 38 | break 39 | handle_pointer = window_handles[i - 1] 40 | break 41 | # switch to window 42 | self.driver.switch_to.window(handle) 43 | 44 | @log 45 | def switch_to_tab(self, tab_index: int): 46 | window_handles: list[str] = self.driver.window_handles 47 | self.driver.switch_to_window(window_handles[tab_index]) 48 | -------------------------------------------------------------------------------- /uaf/version.py: -------------------------------------------------------------------------------- 1 | """Contains version number of the package""" 2 | 3 | version = "0.0.1" 4 | --------------------------------------------------------------------------------