├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── base ├── __init__.py └── page_base.py ├── config.json ├── extensions ├── __init__.py ├── screenshot_extensions.py └── webdriver_extended.py ├── helpers ├── __init__.py └── webdriver_listener.py ├── locators ├── __init__.py └── locators.py ├── pages ├── demoblaze │ ├── __init__.py │ └── home_page.py └── phptravels │ ├── __init__.py │ ├── login_page.py │ ├── search_flights_form.py │ ├── search_hotels_form.py │ ├── search_tours_form.py │ └── search_transfers_form.py ├── pytest.ini ├── requirements.txt ├── tests ├── __init__.py ├── demoblaze_tests │ ├── __init__.py │ ├── conftest.py │ └── test_homepage.py └── phptravel_tests_deprecated │ ├── test_log_in.py │ ├── test_search_flight.py │ ├── test_search_hotel.py │ ├── test_search_tour.py │ └── test_search_transfer.py └── utils ├── __init__.py ├── driver_factory.py ├── functions.py ├── read_xlsx.py ├── search_flights_data.py ├── search_flights_input_data.xlsx ├── search_hotels_data.py ├── search_hotels_input_data.xlsx ├── search_tours_data.py ├── search_tours_input_data.xlsx ├── search_transfers_data.py └── search_transfers_input_data.xlsx /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | 140 | # PyCharm 141 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 142 | # be found at https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore 143 | # and can be added to the global gitignore or merged into this file. For a more nuclear 144 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 145 | .idea/ 146 | 147 | # VSCode 148 | .vscode -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at artstrug@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Artur Strug 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Last commit](https://img.shields.io/github/last-commit/startrug/selenium-python-framework?color=9cf&logo=git) 2 | ![GitHub top language](https://img.shields.io/github/languages/top/startrug/selenium-python-framework?color=blue) 3 | 4 | # :warning: Important notice 5 | I would like to inform everyone who is interested in this project that I decided to go back to working on it. 6 | I am planning to implement some major changes like separating framework from tests, adding sample tests for another website than phptravels.com and removing tests implemented before. 7 | Thank you guys for forking and giving stars for this project. I appreciate it a lot! 8 | 9 | # Test Automation Project 10 | 11 | This is my first test automation project based on Selenium-Webdriver with Python. It's still developing package of automated tests of [phptravels.net](https://phptravels.net) demo website. 12 | The collection of tests contains: 13 | - user login tests (correct / incorrect login and password) 14 | - hotels search tests 15 | - flights search tests 16 | - tours search tests 17 | - transfers search tests 18 | 19 | ## Project Structure 20 | Here you can find a short description of main directories and it's content 21 | - [locators](locators) - there are locators of web elements in locators.py grouped in classes 22 | - [pages](pages) - there are sets of method for each test step (notice: some repeated methods were moved to [functions.py](utils/functions.py)) 23 | - [tests](tests) - there are sets of tests for main functionalities of website 24 | - [reports](reports) - if you run tests with Allure, tests reports will be saved in this directory 25 | - [utils](utils) - this directory contains files responsible for configuration, e.g. driver_factory.py for webdriver management or [read_xlsx.py](utils/read_xlsx.py) for reading input data from xlsx files included in project 26 | 27 | ## Project Features 28 | - framework follows page object pattern 29 | - data-driven tests - in most tests the option of loading data from an xlsx file has been implemented 30 | - logger has been implemented in each step of test cases, e.g. 31 | ``` 32 | @allure.step("Setting destination to '{1}'") 33 | def set_destination(self, destination): 34 | self.logger.info(f"Setting destination: {destination}") 35 | self.driver.find_element(*SearchHotelsFormLocators.destination_inactive).click() 36 | ``` 37 | ![Logs screenshot](https://raw.githubusercontent.com/startrug/phptravels-selenium-py/screenshots/logger.png "Logs screenshot") 38 | - the ability to easily generate legible and attractive test reports using Allure (for more look [Generate Test Report](README.md#generate-test-report) section below) 39 | - tests can be run on popular browsers - Chrome and Firefox are preconfigured in DriverFactory class and both can be select in [conftest.py](tests/conftest.py), e.g. 40 | ``` 41 | @pytest.fixture() 42 | def setup(request): 43 | driver = DriverFactory.get_driver("chrome") 44 | ``` 45 | 46 | 47 | ## Getting Started 48 | 49 | To enjoy the automated tests, develop the framework or adapt it to your own purposes, just download the project or clone repository. You need to install packages using pip according to requirements.txt file. 50 | Run the command below in terminal: 51 | 52 | ``` 53 | $ pip install -r requirements.txt 54 | ``` 55 | 56 | ## Run Automated Tests 57 | 58 | To run selected test without Allure report you need to set pytest as default test runner in Pycharm first 59 | ``` 60 | File > Settings > Tools > Python Integrated Tools > Testing 61 | ``` 62 | After that you just need to choose one of the tests from "tests" directory and click "Run test" green arrow. There are 2 versions of test in each test file. In general test cases you can easily modify test inputs. Data-driven tests base on xlsx files from [utils](utils) directory. 63 | 64 | ## Generate Test Report 65 | 66 | To generate all tests report using Allure you need to run tests by command first: 67 | ``` 68 | $ pytest --alluredir= 69 | ``` 70 | After that you need to use command: 71 | ``` 72 | $ allure serve 73 | ``` 74 | ![Allure report screenshot](https://raw.githubusercontent.com/startrug/phptravels-selenium-py/screenshots/allure_report.png "Allure report screenshot") 75 | 76 | Report is generated in Chrome browser. 77 | 78 | -------------------------------------------------------------------------------- /base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/base/__init__.py -------------------------------------------------------------------------------- /base/page_base.py: -------------------------------------------------------------------------------- 1 | import allure 2 | 3 | 4 | class PageBase: 5 | def __init__(self, driver): 6 | self.driver = driver 7 | 8 | @allure.step("Opening main page") 9 | def open(self): 10 | self.driver.open() 11 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "browser": "chrome", 3 | "headless_mode": false, 4 | "base_url": "http://www.demoblaze.com/", 5 | "timeout": 10 6 | } -------------------------------------------------------------------------------- /extensions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/extensions/__init__.py -------------------------------------------------------------------------------- /extensions/screenshot_extensions.py: -------------------------------------------------------------------------------- 1 | class ScreenShotExtensions(): 2 | @staticmethod 3 | def take_standard_screenshot(driver, file_name): 4 | driver.save_screenshot(file_name, False) 5 | -------------------------------------------------------------------------------- /extensions/webdriver_extended.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.support.event_firing_webdriver import EventFiringWebDriver 2 | 3 | 4 | class WebDriverExtended(EventFiringWebDriver): 5 | def __init__(self, driver, event_listener, config): 6 | super().__init__(driver, event_listener) 7 | self.base_url = config["base_url"] 8 | 9 | def open(self): 10 | self.get(self.base_url) -------------------------------------------------------------------------------- /helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/helpers/__init__.py -------------------------------------------------------------------------------- /helpers/webdriver_listener.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import datetime 3 | from selenium.webdriver.support.events import AbstractEventListener 4 | 5 | 6 | class WebDriverListener(AbstractEventListener): 7 | log_filename = datetime.datetime.now().strftime("%Y%m%d") 8 | logging.basicConfig( 9 | # log file will be created in "tests" directory. Feel free to change the path or filename 10 | filename=f"{log_filename}.log", 11 | format="%(asctime)s: %(levelname)s: %(message)s", 12 | level=logging.INFO 13 | ) 14 | 15 | def __init__(self): 16 | self.logger = logging.getLogger("selenium") 17 | 18 | def before_navigate_to(self, url, driver): 19 | self.logger.info(f"Navigating to {url}") 20 | 21 | def after_navigate_to(self, url, driver): 22 | self.logger.info(f"{url} opened") 23 | 24 | def before_find(self, by, value, driver): 25 | self.logger.info(f"Searching for element by {by} {value}") 26 | 27 | def after_find(self, by, value, driver): 28 | self.logger.info(f"Element by {by} {value} found") 29 | 30 | def before_click(self, element, driver): 31 | if element.get_attribute("text") is None: 32 | self.logger.info(f"Clicking on {element.get_attribute('class')}") 33 | else: 34 | self.logger.info(f"Clicking on {element.get_attribute('text')}") 35 | 36 | def after_click(self, element, driver): 37 | if element.get_attribute("text") is None: 38 | self.logger.info(f"{element.get_attribute('class')} clicked") 39 | else: 40 | self.logger.info(f"{element.get_attribute('text')} clicked") 41 | 42 | def before_change_value_of(self, element, driver): 43 | self.logger.info(f"{element.get_attribute('text')} value changed") 44 | 45 | def before_quit(self, driver): 46 | self.logger.info("Driver quitting") 47 | 48 | def after_quit(self, driver): 49 | self.logger.info("Driver quitted") 50 | 51 | def on_exception(self, exception, driver): 52 | self.logger.info(exception) 53 | -------------------------------------------------------------------------------- /locators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/locators/__init__.py -------------------------------------------------------------------------------- /locators/locators.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | 4 | class SetLanguageLocators: 5 | lang_menu = (By.XPATH, "//a[@id='dropdownLangauge']") 6 | russian_lang = (By.XPATH, "//a[@id='ru']") 7 | farsi_lang = (By.XPATH, "//a[@id='fa']") 8 | german_lang = (By.XPATH, "//a[@id='de']") 9 | vietnamese_lang = (By.XPATH, "//a[@id='vi']") 10 | french_lang = (By.XPATH, "//a[@id='fr']") 11 | turkish_lang = (By.XPATH, "//a[@id='tr']") 12 | arabic_lang = (By.XPATH, "//a[@id='ar']") 13 | spanish_lang = (By.XPATH, "//a[@id='es']") 14 | english_lang = (By.XPATH, "//a[@id='en']") 15 | 16 | 17 | class SetCurrencyLocators: 18 | currency_menu = (By.CSS_SELECTOR, "#dropdownCurrency") 19 | usd = (By.XPATH, "//a[text()='USD']") 20 | gbp = (By.XPATH, "//a[text()='GBP']") 21 | sar = (By.XPATH, "//a[text()='SAR']") 22 | eur = (By.XPATH, "//a[text()='EUR']") 23 | pkr = (By.XPATH, "//a[text()='PKR']") 24 | kwd = (By.XPATH, "//a[text()='KWD']") 25 | egp = (By.XPATH, "//a[text()='EGP']") 26 | jpy = (By.XPATH, "//a[text()='JPY']") 27 | inr = (By.XPATH, "//a[text()='INR']") 28 | cny = (By.XPATH, "//a[text()='CNY']") 29 | rub = (By.XPATH, "//a[text()='RUB']") 30 | vietnam_dong = (By.XPATH, "//a[@class='dropdown-item text-center'][contains(text(),'Vietnam')]") 31 | 32 | 33 | class LogInLocators: 34 | user_account_menu = (By.XPATH, "//div[@class='dropdown dropdown-login dropdown-tab']") 35 | login_link = (By.LINK_TEXT, "Login") 36 | logout_link = (By.LINK_TEXT, "Logout") 37 | account_link = (By.LINK_TEXT, "Account") 38 | sign_up_link = (By.XPATH, "Sign Up") 39 | email_input = (By.XPATH, "//input[@placeholder='Email']") 40 | password_input = (By.XPATH, "//input[@placeholder='Password']") 41 | invalid_data_msg = (By.XPATH, "//div[@class='alert alert-danger']") 42 | 43 | 44 | class HeaderNavLocators: 45 | home_page_link = (By.LINK_TEXT, "Home") 46 | blog_link = (By.LINK_TEXT, "Blog") 47 | offers_link = (By.LINK_TEXT, "Offers") 48 | company_menu = (By.LINK_TEXT, "Company") 49 | about_us_link = (By.LINK_TEXT, "About us") 50 | contact_us_link = (By.LINK_TEXT, "Contact Us") 51 | terms_and_conditions_link = (By.LINK_TEXT, "Company") 52 | privacy_policy_link = (By.LINK_TEXT, "Privacy Policy") 53 | 54 | 55 | class SearchHotelsFormLocators: 56 | destination_inactive = (By.XPATH, "//span[text()='Search by Hotel or City Name']") 57 | destination_input = (By.XPATH, "//input[@class='select2-input select2-focused']") 58 | search_match = (By.XPATH, "//span[@class='select2-match']") 59 | checkin_input = (By.XPATH, "//input[@id='checkin']") 60 | checkout_input = (By.XPATH, "//input[@id='checkout']") 61 | adults_input_value = (By.XPATH, "//div[@class='col o2']//input[@name='adults']") 62 | kids_input_value = (By.XPATH, "//div[@class='col 01']//input[@name='children']") 63 | adults_add = (By.XPATH, "//div[@class='col o2']//button[contains(@class,'btn btn-white bootstrap-touchspin-up')]") 64 | adults_sub = (By.XPATH, "//div[@class='col o2']//button[contains(@class,'btn btn-white bootstrap-touchspin-down')]") 65 | kids_add = (By.XPATH, "//div[@class='col 01']//button[contains(@class,'btn btn-white bootstrap-touchspin-up')]") 66 | kids_sub = (By.XPATH, "//div[@class='col 01']//button[contains(@class,'btn btn-white bootstrap-touchspin-down')]") 67 | search_btn = (By.XPATH, "//div[@class='col-md-2 col-xs-12 o1']//button[@class='btn btn-primary btn-block']") 68 | 69 | 70 | class SearchFlightsFormLocators: 71 | one_way_radio = (By.XPATH, "//label[text()='One Way']") 72 | round_trip_radio = (By.XPATH, "//label[text()='Round Trip']") 73 | cabinclass_dropdown = (By.XPATH, "//div[@class='col-xs-4 col-md-2']") 74 | loc_from_inactive = (By.XPATH, "//div[@id='s2id_location_from']") 75 | loc_to_inactive = (By.XPATH, "//div[@id='s2id_location_to']") 76 | loc_input_active = (By.XPATH, "//input[@class='select2-input select2-focused']") 77 | flight_date_start = (By.CSS_SELECTOR, "#FlightsDateStart") 78 | flight_date_end = (By.CSS_SELECTOR, "#FlightsDateEnd") 79 | datepicker_nav_title_start = (By.XPATH, "//div[7]//nav[1]//div[2]") 80 | datepicker_nav_title_end = (By.XPATH, "//div[8]//nav[1]//div[2]") 81 | datepicker_nav_title_years = (By.XPATH, "//div[@class='datepicker--nav-title']//i") 82 | datepicker_nav_title_months = (By.XPATH, "//div[@class='datepicker--nav-title']") 83 | adults_input_value = (By.XPATH, "//input[@name='fadults']") 84 | adults_add = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-up '])[3]") 85 | adults_sub = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-down '])[3]") 86 | kids_input_value = (By.XPATH, "//input[@name='fchildren']") 87 | kids_add = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-up '])[4]") 88 | kids_sub = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-down '])[4]") 89 | infants_input_value = (By.XPATH, "//input[@name='finfant']") 90 | infants_add = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-up '])[5]") 91 | infants_sub = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-down '])[5]") 92 | search_btn = (By.XPATH, "//div[@class='col-xs-12 col-md-1']//button[@class='btn-primary btn btn-block']") 93 | 94 | 95 | class SearchToursFormLocators: 96 | tour_destination_inactive = (By.XPATH, "//div[@id='s2id_autogen8']") 97 | tour_destination_active = (By.XPATH, "//input[@class='select2-input select2-focused']") 98 | tour_type_dropdown = (By.XPATH, "//span[contains(text(),'Select')]") 99 | tour_type_input = (By.XPATH, "//div[@id='tourtype_chosen']//input[@class='chosen-search-input']") 100 | tour_date = (By.CSS_SELECTOR, "#DateTours") 101 | datepicker_nav_title_start = (By.XPATH, "//div[6]//nav[1]//div[2]") 102 | datepicker_nav_title_years = (By.XPATH, "//div[@class='datepicker--nav-title']//i") 103 | datepicker_nav_title_months = (By.XPATH, "//div[@class='datepicker--nav-title']") 104 | adults_input_value = (By.XPATH, "//div[@class='col-md-12']//input[@name='adults']") 105 | adults_add = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-up '])[6]") 106 | adults_sub = (By.XPATH, "(//button[@class='btn btn-white bootstrap-touchspin-down '])[6]") 107 | search_btn = (By.XPATH, "//div[@class='col-md-2 col-xs-12']//button[@type='submit']") 108 | 109 | 110 | class SearchTransferLocators: 111 | pick_up_loc = (By.XPATH, "//span[contains(text(),'Location')]") 112 | drop_off_loc = (By.CSS_SELECTOR, "#carlocations2") 113 | depart_date = (By.CSS_SELECTOR, "#dropdate") 114 | return_date = (By.CSS_SELECTOR, "#returndate") 115 | datepicker_nav_title_start = (By.XPATH, "//div[@id='datepickers-container']//div[3]//nav[1]//div[2]") 116 | datepicker_nav_title_end = (By.XPATH, "//div[4]//nav[1]//div[2]") 117 | datepicker_nav_title_years = (By.XPATH, "//div[@class='datepicker--nav-title']//i") 118 | datepicker_nav_title_months = (By.XPATH, "//div[@class='datepicker--nav-title']") 119 | depart_time_selector = (By.XPATH, "(//span[contains(.,'Time')])[1]") 120 | depart_time_imput = (By.XPATH, "(//input[@class='chosen-search-input'])[4]") 121 | return_time_selector = (By.XPATH, "//div[@class='chosen-container chosen-container-single']" 122 | "//span[contains(text(),'Time')]") 123 | return_time_input = (By.XPATH, "(//input[@class='chosen-search-input'])[5]") 124 | search_btn = (By.XPATH, "//div[@class='col-md-2 col-xs-12']//button[@class='btn-primary btn btn-block']") 125 | 126 | 127 | class SearchTabsLocators: 128 | hotels_tab = (By.XPATH, "//a[@data-name='hotels']") 129 | flights_tab = (By.XPATH, "//a[@data-name='flights']") 130 | tours_tab = (By.XPATH, "//a[@data-name='tours']") 131 | transfer_tab = (By.XPATH, "//a[@data-name='cars']") 132 | visa_tab = (By.XPATH, "//a[@data-name='visa']") 133 | 134 | 135 | class SearchResultsLocators: 136 | search_title = (By.XPATH, "//span[@class='text-primary']") 137 | change_search_btn = (By.XPATH, "//button[@data-target='#change-search']") 138 | 139 | 140 | class UserAccountLocators: 141 | welcome_msg = (By.XPATH, "//h3[@class='text-align-left']") 142 | bookings_tab = (By.LINK_TEXT, "Bookings") 143 | my_profile_tab = (By.LINK_TEXT, "My profile") 144 | wishlist_tab = (By.LINK_TEXT, "Wishlist") 145 | newsletter_tab = (By.LINK_TEXT, "Newsletter") 146 | -------------------------------------------------------------------------------- /pages/demoblaze/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/pages/demoblaze/__init__.py -------------------------------------------------------------------------------- /pages/demoblaze/home_page.py: -------------------------------------------------------------------------------- 1 | from base.page_base import PageBase 2 | import allure 3 | 4 | 5 | class HomePage(PageBase): 6 | def __init__(self, driver): 7 | super().__init__(driver) 8 | 9 | @allure.step("Getting title of the page") 10 | def get_page_title(self): 11 | return self.driver.title 12 | -------------------------------------------------------------------------------- /pages/phptravels/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/pages/phptravels/__init__.py -------------------------------------------------------------------------------- /pages/phptravels/login_page.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from selenium.webdriver.common.keys import Keys 3 | from locators.locators import LogInLocators 4 | from base.page_base import PageBase 5 | 6 | 7 | class LogInPage(PageBase): 8 | def __init__(self, driver): 9 | super().__init__(driver) 10 | 11 | @allure.step("Expanding account menu") 12 | def expand_account_menu(self): 13 | self.driver.find_element(*LogInLocators.user_account_menu).click() 14 | 15 | @allure.step("Opening login page") 16 | def open_login_page(self): 17 | self.driver.find_element(*LogInLocators.login_link).click() 18 | 19 | @allure.step("Login with email: '1'") 20 | def set_user_inputs(self, email, password): 21 | self.driver.find_element(*LogInLocators.email_input).click() 22 | self.driver.find_element(*LogInLocators.email_input).send_keys(email) 23 | self.driver.find_element(*LogInLocators.password_input).click() 24 | self.driver.find_element( 25 | *LogInLocators.password_input).send_keys(password, Keys.ENTER) 26 | 27 | @allure.step("Logout") 28 | def logout(self): 29 | self.driver.find_element(*LogInLocators.logout_link).click() 30 | -------------------------------------------------------------------------------- /pages/phptravels/search_flights_form.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from allure_commons.types import AttachmentType 3 | from selenium.webdriver.common.by import By 4 | from locators.locators import SearchFlightsFormLocators, SearchTabsLocators 5 | from utils.functions import set_travellers_number, get_datestamp, click_displayed_datestamp 6 | 7 | 8 | class SearchFlightsForm: 9 | 10 | def __init__(self, driver): 11 | self.driver = driver 12 | 13 | @allure.step("Opening phptravels.net website") 14 | def open_page(self): 15 | self.driver.get("http://www.phptravels.net/") 16 | 17 | @allure.step("Opening Flights tab") 18 | def open_flights_tab(self): 19 | self.driver.find_element(*SearchTabsLocators.flights_tab).click() 20 | 21 | @allure.step("Selecting trip type to: '{1}'") 22 | def set_trip_type(self, trip_type): 23 | self.driver.find_element(By.XPATH, f"//label[text()='{trip_type}']").click() 24 | 25 | @allure.step("Selecting one way trip") 26 | def set_one_way(self): 27 | self.driver.find_element(*SearchFlightsFormLocators.one_way_radio).click() 28 | 29 | @allure.step("Selecting round trip") 30 | def set_round_trip(self): 31 | self.driver.find_element(*SearchFlightsFormLocators.round_trip_radio).click() 32 | 33 | @allure.step("Setting cabin class") 34 | def set_cabin_class(self, cabin_class): 35 | self.driver.find_element(*SearchFlightsFormLocators.cabinclass_dropdown).click() 36 | self.driver.find_element(By.XPATH, f"//li[text()='{cabin_class}']").click() 37 | 38 | @allure.step("Setting location from: '{1}'") 39 | def set_loc_from(self, loc_from): 40 | self.driver.find_element(*SearchFlightsFormLocators.loc_from_inactive).click() 41 | self.driver.find_element(*SearchFlightsFormLocators.loc_input_active).send_keys(loc_from) 42 | self.driver.find_element(By.XPATH, f"//div[@class='select2-result-label'][contains(.,'({loc_from})')]").click() 43 | 44 | @allure.step("Setting location to: '{1}'") 45 | def set_loc_to(self, loc_to): 46 | self.driver.find_element(*SearchFlightsFormLocators.loc_to_inactive).click() 47 | self.driver.find_element(*SearchFlightsFormLocators.loc_input_active).send_keys(loc_to) 48 | self.driver.find_element(By.XPATH, f"//div[@class='select2-result-label'][contains(.,'({loc_to})')]").click() 49 | 50 | @allure.step("Setting start date to '{1}'/'{2}'/'{3}'") 51 | def set_start_date(self, start_year, start_month, start_day): 52 | self.driver.find_element(*SearchFlightsFormLocators.flight_date_start).click() 53 | current_year = get_datestamp(self.driver, SearchFlightsFormLocators, ["datepicker_nav_title_years"]) 54 | if current_year != start_year: 55 | self.driver.find_element(*SearchFlightsFormLocators.datepicker_nav_title_start).click() 56 | self.driver.find_element(By.XPATH, f"//div[text()='{current_year}']").click() 57 | self.driver.find_element(By.XPATH, f"//div[contains(text(),'{start_year}')]").click() 58 | current_month = get_datestamp(self.driver, SearchFlightsFormLocators, ["datepicker_nav_title_months"]) 59 | if current_month[0:3] != start_month: 60 | self.driver.find_element(By.XPATH, f"//div[contains(@class,'cell-month')]" 61 | f"[contains(.,'{start_month}')]").click() 62 | days = self.driver.find_elements(By.XPATH, f"//div[contains(@class,'cell-day')]" 63 | f"[text()='{start_day}']") 64 | click_displayed_datestamp(days) 65 | 66 | @allure.step("Setting end date to '{1}'/'{2}'/'{3}'") 67 | def set_end_date(self, end_year, end_month, end_day): 68 | current_year = get_datestamp(self.driver, SearchFlightsFormLocators, ["datepicker_nav_title_years"]) 69 | if current_year != end_year: 70 | self.driver.find_element(*SearchFlightsFormLocators.datepicker_nav_title_end).click() 71 | self.driver.find_element(By.XPATH, f"//div[text()='{current_year}']").click() 72 | self.driver.find_element(By.XPATH, f"//div[contains(text(),'{end_year}')]").click() 73 | current_month = get_datestamp(self.driver, SearchFlightsFormLocators, ["datepicker_nav_title_months"]) 74 | if current_month[0:3] != end_month and current_year == end_year: 75 | self.driver.find_element(*SearchFlightsFormLocators.datepicker_nav_title_end).click() 76 | months = self.driver.find_elements(By.XPATH, f"//div[contains(text(),'{end_month}')]") 77 | click_displayed_datestamp(months) 78 | if current_month[0:3] != end_month and current_year != end_year: 79 | months = self.driver.find_elements(By.XPATH, f"//div[contains(text(),'{end_month}')]") 80 | click_displayed_datestamp(months) 81 | days = self.driver.find_elements(By.XPATH, f"//div[contains(@class,'cell-day')]" 82 | f"[text()='{end_day}']") 83 | click_displayed_datestamp(days) 84 | 85 | @allure.step("Setting number of adults to '{1}'") 86 | def set_adults_number(self, adults_num): 87 | set_travellers_number(self.driver, adults_num, SearchFlightsFormLocators, 88 | ["adults_input_value", "adults_add", "adults_sub"]) 89 | 90 | @allure.step("Setting number of adults to '{1}'") 91 | def set_kids_number(self, kids_num): 92 | set_travellers_number(self.driver, kids_num, SearchFlightsFormLocators, 93 | ["kids_input_value", "kids_add", "kids_sub"]) 94 | 95 | @allure.step("Setting number of infants to '{1}'") 96 | def set_infants_number(self, infants_num): 97 | set_travellers_number(self.driver, infants_num, SearchFlightsFormLocators, 98 | ["infants_input_value", "infants_add", "infants_sub"]) 99 | 100 | @allure.step("Performing search") 101 | def search_perform(self): 102 | self.driver.find_element(*SearchFlightsFormLocators.search_btn).click() 103 | allure.attach(self.driver.get_screenshot_as_png(), name="search_results", attachment_type=AttachmentType.PNG) 104 | 105 | @allure.step("Getting input start date") 106 | def get_start_date(self): 107 | start_date = self.driver.find_element(*SearchFlightsFormLocators.flight_date_start) 108 | start_date_val = start_date.get_attribute("value") 109 | 110 | @allure.step("Getting input end date") 111 | def get_end_date(self): 112 | end_date = self.driver.find_element(*SearchFlightsFormLocators.flight_date_end) 113 | end_date_val = end_date.get_attribute("value") 114 | -------------------------------------------------------------------------------- /pages/phptravels/search_hotels_form.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from allure_commons.types import AttachmentType 3 | from locators.locators import SearchHotelsFormLocators 4 | from utils.functions import set_travellers_number 5 | 6 | 7 | class SearchHotelsForm: 8 | 9 | def __init__(self, driver): 10 | self.driver = driver 11 | 12 | @allure.step("Opening phptravels.net website") 13 | def open_page(self): 14 | self.driver.get("http://www.phptravels.net/") 15 | 16 | @allure.step("Setting destination to '{1}'") 17 | def set_destination(self, destination): 18 | self.driver.find_element(*SearchHotelsFormLocators.destination_inactive).click() 19 | self.driver.find_element(*SearchHotelsFormLocators.destination_input).send_keys(destination) 20 | self.driver.find_element(*SearchHotelsFormLocators.search_match).click() 21 | 22 | @allure.step("Setting date range from '{1}' to '{2}'") 23 | def set_date_range(self, check_in, check_out): 24 | self.driver.find_element(*SearchHotelsFormLocators.checkin_input).click() 25 | self.driver.find_element(*SearchHotelsFormLocators.checkin_input).send_keys(check_in) 26 | self.driver.find_element(*SearchHotelsFormLocators.checkout_input).click() 27 | self.driver.find_element(*SearchHotelsFormLocators.checkout_input).send_keys(check_out) 28 | 29 | @allure.step("Setting number of adults to '{1}'") 30 | def set_adults_number(self, adults_num): 31 | set_travellers_number(self.driver, adults_num, SearchHotelsFormLocators, 32 | ["adults_input_value", "adults_add", "adults_sub"]) 33 | 34 | @allure.step("Setting number of adults to '{1}'") 35 | def set_kids_number(self, kids_num): 36 | set_travellers_number(self.driver, kids_num, SearchHotelsFormLocators, 37 | ["kids_input_value", "kids_add", "kids_sub"]) 38 | 39 | @allure.step("Performing search") 40 | def search_perform(self): 41 | self.driver.find_element(*SearchHotelsFormLocators.search_btn).click() 42 | allure.attach(self.driver.get_screenshot_as_png(), name="search_results", attachment_type=AttachmentType.PNG) 43 | -------------------------------------------------------------------------------- /pages/phptravels/search_tours_form.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from allure_commons.types import AttachmentType 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.common.keys import Keys 5 | from locators.locators import SearchTabsLocators, SearchToursFormLocators 6 | from utils.functions import set_travellers_number, get_datestamp, click_displayed_datestamp 7 | 8 | 9 | class SearchToursForm: 10 | 11 | def __init__(self, driver): 12 | self.driver = driver 13 | 14 | @allure.step("Opening phptravels.net website") 15 | def open_page(self): 16 | self.driver.get("http://www.phptravels.net/") 17 | 18 | @allure.step("Opening Tours tab") 19 | def open_tours_tab(self): 20 | self.driver.find_element(*SearchTabsLocators.tours_tab).click() 21 | 22 | @allure.step("Setting tour destination: '{1}'") 23 | def set_tour_destination(self, tour_destination): 24 | self.driver.find_element(*SearchToursFormLocators.tour_destination_inactive).click() 25 | self.driver.find_element(*SearchToursFormLocators.tour_destination_active).send_keys(tour_destination) 26 | self.driver.find_element(By.XPATH, f"//div[@class='select2-result-label']" 27 | f"[contains(.,'{tour_destination}')]").click() 28 | 29 | @allure.step("Setting tour type: '{1}'") 30 | def set_tour_type(self, tour_type): 31 | self.driver.find_element(*SearchToursFormLocators.tour_type_dropdown).click() 32 | self.driver.find_element(*SearchToursFormLocators.tour_type_input).send_keys(tour_type, Keys.ENTER) 33 | 34 | @allure.step("Setting tour date to '{1}'/'{2}'/'{3}'") 35 | def set_date(self, start_year, start_month, start_day): 36 | self.driver.find_element(*SearchToursFormLocators.tour_date).click() 37 | current_year = get_datestamp(self.driver, SearchToursFormLocators, ["datepicker_nav_title_years"]) 38 | if current_year != start_year: 39 | self.driver.find_element(*SearchToursFormLocators.datepicker_nav_title_start).click() 40 | self.driver.find_element(By.XPATH, f"//div[text()='{current_year}']").click() 41 | self.driver.find_element(By.XPATH, f"//div[contains(text(),'{start_year}')]").click() 42 | current_month = get_datestamp(self.driver, SearchToursFormLocators, ["datepicker_nav_title_months"]) 43 | if current_month[0:3] != start_month: 44 | self.driver.find_element(By.XPATH, f"//div[contains(@class,'cell-month')]" 45 | f"[contains(.,'{start_month}')]").click() 46 | days = self.driver.find_elements(By.XPATH, f"//div[contains(@class,'cell-day')]" 47 | f"[text()='{start_day}']") 48 | click_displayed_datestamp(days) 49 | 50 | @allure.step("Setting number of adults to '{1}'") 51 | def set_adults_number(self, adults_num): 52 | set_travellers_number(self.driver, adults_num, SearchToursFormLocators, 53 | ["adults_input_value", "adults_add", "adults_sub"]) 54 | 55 | @allure.step("Performing search") 56 | def search_perform(self): 57 | self.driver.find_element(*SearchToursFormLocators.search_btn).click() 58 | allure.attach(self.driver.get_screenshot_as_png(), name="search_results", attachment_type=AttachmentType.PNG) 59 | -------------------------------------------------------------------------------- /pages/phptravels/search_transfers_form.py: -------------------------------------------------------------------------------- 1 | import allure 2 | from allure_commons.types import AttachmentType 3 | from selenium.webdriver.common.by import By 4 | from selenium.webdriver.common.keys import Keys 5 | from selenium.webdriver.support.select import Select 6 | 7 | from locators.locators import SearchTabsLocators, SearchTransferLocators 8 | from utils.functions import get_datestamp, click_displayed_datestamp 9 | 10 | 11 | class SearchTransfersForm: 12 | 13 | def __init__(self, driver): 14 | self.driver = driver 15 | 16 | @allure.step("Opening phptravels.net website") 17 | def open_page(self): 18 | self.driver.get("http://www.phptravels.net/") 19 | 20 | @allure.step("Opening Transfer tab") 21 | def open_transfer_tab(self): 22 | self.driver.find_element(*SearchTabsLocators.transfer_tab).click() 23 | 24 | @allure.step("Setting pick up location: '{1}'") 25 | def set_pick_up_loc(self, pick_up_loc): 26 | self.driver.find_element(*SearchTransferLocators.pick_up_loc).click() 27 | self.driver.find_element(By.XPATH, f"//li[contains(text(),'{pick_up_loc}')]").click() 28 | 29 | @allure.step("Setting drop off location: '{1}'") 30 | def set_drop_off_loc(self, drop_off_loc): 31 | self.driver.find_element(*SearchTransferLocators.drop_off_loc).click() 32 | loc_select = Select(self.driver.find_element(*SearchTransferLocators.drop_off_loc)) 33 | loc_select.select_by_visible_text(drop_off_loc) 34 | 35 | @allure.step("Setting depart date to '{1}'/'{2}'/'{3}'") 36 | def set_depart_date(self, start_year, start_month, start_day): 37 | self.driver.find_element(*SearchTransferLocators.depart_date).click() 38 | current_year = get_datestamp(self.driver, SearchTransferLocators, ["datepicker_nav_title_years"]) 39 | if current_year != start_year: 40 | self.driver.find_element(*SearchTransferLocators.datepicker_nav_title_start).click() 41 | self.driver.find_element(By.XPATH, f"//div[text()='{current_year}']").click() 42 | self.driver.find_element(By.XPATH, f"//div[contains(text(),'{start_year}')]").click() 43 | current_month = get_datestamp(self.driver, SearchTransferLocators, ["datepicker_nav_title_months"]) 44 | if current_month[0:3] != start_month: 45 | self.driver.find_element(By.XPATH, f"//div[contains(@class,'cell-month')]" 46 | f"[contains(.,'{start_month}')]").click() 47 | days = self.driver.find_elements(By.XPATH, f"//div[contains(@class,'cell-day')]" 48 | f"[text()='{start_day}']") 49 | click_displayed_datestamp(days) 50 | 51 | @allure.step("Setting depart time to '{depart_time}'") 52 | def set_depart_time(self, depart_time): 53 | self.driver.find_element(*SearchTransferLocators.depart_time_selector).click() 54 | self.driver.find_element(*SearchTransferLocators.depart_time_imput).send_keys(depart_time, Keys.ENTER) 55 | 56 | @allure.step("Setting return date to '{1}'/'{2}'/'{3}'") 57 | def set_return_date(self, end_year, end_month, end_day): 58 | self.driver.find_element(*SearchTransferLocators.return_date).click() 59 | current_year = get_datestamp(self.driver, SearchTransferLocators, ["datepicker_nav_title_years"]) 60 | if current_year != end_year: 61 | self.driver.find_element(*SearchTransferLocators.datepicker_nav_title_end).click() 62 | self.driver.find_element(By.XPATH, f"//div[text()='{current_year}']").click() 63 | self.driver.find_element(By.XPATH, f"//div[contains(text(),'{end_year}')]").click() 64 | current_month = get_datestamp(self.driver, SearchTransferLocators, ["datepicker_nav_title_months"]) 65 | if current_month[0:3] != end_month and current_year == end_year: 66 | self.driver.find_element(*SearchTransferLocators.datepicker_nav_title_end).click() 67 | months = self.driver.find_elements(By.XPATH, f"//div[contains(text(),'{end_month}')]") 68 | click_displayed_datestamp(months) 69 | if current_month[0:3] != end_month and current_year != end_year: 70 | months = self.driver.find_elements(By.XPATH, f"//div[contains(text(),'{end_month}')]") 71 | click_displayed_datestamp(months) 72 | days = self.driver.find_elements(By.XPATH, f"//div[contains(@class,'cell-day')]" 73 | f"[text()='{end_day}']") 74 | click_displayed_datestamp(days) 75 | 76 | @allure.step("Setting return time to '{return_time}'") 77 | def set_return_time(self, return_time): 78 | self.driver.find_element(*SearchTransferLocators.return_time_selector).click() 79 | self.driver.find_element(*SearchTransferLocators.return_time_input).send_keys(return_time, Keys.ENTER) 80 | 81 | @allure.step("Performing search") 82 | def search_perform(self): 83 | self.driver.find_element(*SearchTransferLocators.search_btn).click() 84 | allure.attach(self.driver.get_screenshot_as_png(), name="search_results", attachment_type=AttachmentType.PNG) 85 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli=true 3 | log_level=INFO -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | allure-pytest==2.8.6 2 | allure-python-commons==2.8.6 3 | atomicwrites==1.3.0 4 | attrs==19.3.0 5 | certifi==2020.12.5 6 | chardet==3.0.4 7 | colorama==0.4.1 8 | configparser==4.0.2 9 | crayons==0.3.0 10 | idna==2.8 11 | importlib-metadata==0.23 12 | iniconfig==1.1.1 13 | more-itertools==7.2.0 14 | msedge-selenium-tools==3.141.3 15 | packaging==19.2 16 | pluggy==0.13.0 17 | py==1.10.0 18 | pyparsing==2.4.4 19 | pytest==5.2.2 20 | requests==2.22.0 21 | selenium==3.141.0 22 | six==1.13.0 23 | toml==0.10.2 24 | urllib3==1.26.5 25 | wcwidth==0.1.7 26 | webdriver-manager==3.4.0 27 | xlrd==1.2.0 28 | zipp==0.6.0 29 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/tests/__init__.py -------------------------------------------------------------------------------- /tests/demoblaze_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/tests/demoblaze_tests/__init__.py -------------------------------------------------------------------------------- /tests/demoblaze_tests/conftest.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import allure 4 | import pytest 5 | from allure_commons.types import AttachmentType 6 | 7 | from utils.driver_factory import DriverFactory 8 | 9 | CONFIG_PATH = "config.json" 10 | DEFAULT_WAIT_TIME = 10 11 | SUPPORTED_BROWSERS = ["chrome", "firefox", "edge"] 12 | DEFAULT_URL = "http://www.demoblaze.com/" 13 | 14 | 15 | @pytest.fixture(scope='session') 16 | def config(): 17 | config_file = open(CONFIG_PATH) 18 | return json.load(config_file) 19 | 20 | 21 | @pytest.fixture(scope="session") 22 | def browser_setup(config): 23 | if "browser" not in config: 24 | raise Exception('The config file does not contain "browser"') 25 | elif config["browser"] not in SUPPORTED_BROWSERS: 26 | raise Exception(f'"{config["browser"]}" is not a supported browser') 27 | return config["browser"] 28 | 29 | 30 | @pytest.fixture(scope='session') 31 | def wait_time_setup(config): 32 | return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME 33 | 34 | 35 | @pytest.fixture(scope='session') 36 | def url_setup(config): 37 | return config["base_url"] if "base_url" in config else DEFAULT_URL 38 | 39 | 40 | @pytest.fixture() 41 | def setup(request, config): 42 | driver = DriverFactory.get_driver(config) 43 | driver.implicitly_wait(config["timeout"]) 44 | request.cls.driver = driver 45 | before_failed = request.session.testsfailed 46 | if config["browser"] == "firefox": 47 | driver.maximize_window() 48 | yield 49 | if request.session.testsfailed != before_failed: 50 | allure.attach(driver.get_screenshot_as_png(), 51 | name="Test failed", attachment_type=AttachmentType.PNG) 52 | driver.quit() 53 | -------------------------------------------------------------------------------- /tests/demoblaze_tests/test_homepage.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | 4 | from pages.demoblaze.home_page import HomePage 5 | 6 | 7 | @pytest.mark.usefixtures("setup") 8 | class TestHomePage: 9 | @allure.title("Home page - smoke test") 10 | @allure.description("Check if home page of Demoblaze has correct title") 11 | def test_homepage_title(self): 12 | homepage = HomePage(self.driver) 13 | homepage.open() 14 | assert("STORE" in homepage.get_page_title()) 15 | -------------------------------------------------------------------------------- /tests/phptravel_tests_deprecated/test_log_in.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | from locators.locators import UserAccountLocators, LogInLocators 4 | from pages.phptravels.login_page import LogInPage 5 | 6 | 7 | @pytest.mark.usefixtures("setup") 8 | class TestLogIn: 9 | 10 | @allure.title("Login with valid data test") 11 | @allure.description("This is test of login with valid data") 12 | def test_login_passed(self): 13 | log_in_page = LogInPage(self.driver) 14 | log_in_page.open() 15 | log_in_page.expand_account_menu() 16 | log_in_page.open_login_page() 17 | log_in_page.set_user_inputs("user@phptravels.com", "demouser") 18 | welcome_msg = "Hi, Demo User" 19 | assert welcome_msg in self.driver.find_element( 20 | *UserAccountLocators.welcome_msg).text 21 | log_in_page.expand_account_menu() 22 | log_in_page.logout() 23 | 24 | @allure.title("Login with invalid email test") 25 | @allure.description("This is test of login with invalid email") 26 | def test_login_failed(self): 27 | log_in_page = LogInPage(self.driver) 28 | log_in_page.open() 29 | log_in_page.expand_account_menu() 30 | log_in_page.open_login_page() 31 | log_in_page.set_user_inputs("admin@phptravels.com", "demouser") 32 | error_msg = "Invalid Email or Password" 33 | assert error_msg in self.driver.find_element( 34 | *LogInLocators.invalid_data_msg).text 35 | -------------------------------------------------------------------------------- /tests/phptravel_tests_deprecated/test_search_flight.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | 4 | from pages.phptravels.search_flights_form import SearchFlightsForm 5 | from utils.read_xlsx import XlsxReader 6 | 7 | 8 | @pytest.mark.usefixtures("setup") 9 | class TestFlightSearch: 10 | 11 | @allure.title("Search flight test") 12 | @allure.description("This is test of searching flight") 13 | def test_search_flight_general(self): 14 | search_flight = SearchFlightsForm(self.driver) 15 | search_flight.open_page() 16 | search_flight.open_flights_tab() 17 | # Trip type: One Way, Round Trip 18 | search_flight.set_trip_type("Round Trip") 19 | # Cabin class: Economy, First, Business 20 | search_flight.set_cabin_class("First") 21 | search_flight.set_loc_from("LUZ") 22 | search_flight.set_loc_to("OSL") 23 | search_flight.set_start_date("2019", "Dec", "25") 24 | search_flight.set_end_date("2020", "Jun", "2") 25 | search_flight.set_adults_number(2) 26 | search_flight.set_kids_number(4) 27 | search_flight.set_infants_number(1) 28 | search_flight.search_perform() 29 | 30 | @allure.title("Search flight test: one way") 31 | @allure.description("This is test of searching one way flight") 32 | @pytest.mark.parametrize("data", XlsxReader.get_xlsx_flights_data()) 33 | def test_search_flight_one_way(self, data): 34 | search_flight = SearchFlightsForm(self.driver) 35 | search_flight.open_page() 36 | search_flight.open_flights_tab() 37 | search_flight.set_one_way() 38 | search_flight.set_cabin_class(data.cabin_class) 39 | search_flight.set_loc_from(data.location_from) 40 | search_flight.set_loc_to(data.location_to) 41 | search_flight.set_start_date( 42 | data.start_year, data.start_month, data.start_day) 43 | search_flight.set_adults_number(data.adults_num) 44 | search_flight.set_kids_number(data.kids_num) 45 | search_flight.set_infants_number(data.infants_num) 46 | search_flight.search_perform() 47 | 48 | @allure.title("Search flight test: round trip") 49 | @allure.description("This is test of searching round trip flight") 50 | @pytest.mark.parametrize("data", XlsxReader.get_xlsx_flights_data()) 51 | def test_search_flight_round_trip(self, data): 52 | search_flight = SearchFlightsForm(self.driver) 53 | search_flight.open_page() 54 | search_flight.open_flights_tab() 55 | search_flight.set_round_trip() 56 | search_flight.set_cabin_class(data.cabin_class) 57 | search_flight.set_loc_from(data.location_from) 58 | search_flight.set_loc_to(data.location_to) 59 | search_flight.set_start_date( 60 | data.start_year, data.start_month, data.start_day) 61 | search_flight.set_end_date(data.end_year, data.end_month, data.end_day) 62 | search_flight.set_adults_number(data.adults_num) 63 | search_flight.set_kids_number(data.kids_num) 64 | search_flight.set_infants_number(data.infants_num) 65 | search_flight.get_start_date() 66 | search_flight.get_end_date() 67 | search_flight.search_perform() 68 | -------------------------------------------------------------------------------- /tests/phptravel_tests_deprecated/test_search_hotel.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | 4 | from locators.locators import SearchResultsLocators 5 | from pages.phptravels.search_hotels_form import SearchHotelsForm 6 | from utils.read_xlsx import XlsxReader 7 | 8 | 9 | @pytest.mark.usefixtures("setup") 10 | class TestHotelSearch: 11 | @allure.title("Search hotel test") 12 | @allure.description("This is test of searching hotel in Warsaw") 13 | def test_search_hotel_1(self): 14 | search_hotel = SearchHotelsForm(self.driver) 15 | search_hotel.open_page() 16 | search_hotel.set_destination("Warsaw") 17 | search_hotel.set_date_range("29/12/2019", "03/01/2020") 18 | search_hotel.set_adults_number(3) 19 | search_hotel.set_kids_number(0) 20 | search_hotel.search_perform() 21 | 22 | results_title = "Warsaw" 23 | assert results_title in self.driver.find_element( 24 | *SearchResultsLocators.search_title).text 25 | 26 | @allure.title("Search hotel test 2") 27 | @allure.description("This is data driven test of searching hotels") 28 | @pytest.mark.parametrize("data", XlsxReader.get_xlsx_hotels_data()) 29 | def test_search_hotel_2(self, data): 30 | search_hotel = SearchHotelsForm(self.driver) 31 | search_hotel.open_page() 32 | search_hotel.set_destination(data.destination) 33 | search_hotel.set_date_range(data.check_in, data.check_out) 34 | search_hotel.set_adults_number(data.adults_num) 35 | search_hotel.set_kids_number(data.kids_num) 36 | search_hotel.search_perform() 37 | 38 | results_title = data.destination 39 | assert results_title in self.driver.find_element( 40 | *SearchResultsLocators.search_title).text 41 | -------------------------------------------------------------------------------- /tests/phptravel_tests_deprecated/test_search_tour.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | 4 | from pages.phptravels.search_tours_form import SearchToursForm 5 | from utils.read_xlsx import XlsxReader 6 | 7 | 8 | @pytest.mark.usefixtures("setup") 9 | class TestTourSearch: 10 | 11 | @allure.title("Search tours test") 12 | @allure.description("This is test of searching tour") 13 | def test_search_tour_general(self): 14 | search_tour = SearchToursForm(self.driver) 15 | search_tour.open_page() 16 | search_tour.open_tours_tab() 17 | search_tour.set_tour_destination("Sheraton Trip") 18 | search_tour.set_tour_type("Private") 19 | search_tour.set_date("2020", "Jan", "3") 20 | search_tour.set_adults_number(10) 21 | search_tour.search_perform() 22 | 23 | @allure.title("Search flight test: one way") 24 | @allure.description("This is test of searching one way flight") 25 | @pytest.mark.parametrize("data", XlsxReader.get_xlsx_tours_data()) 26 | def test_search_tour_data_driven(self, data): 27 | search_tour = SearchToursForm(self.driver) 28 | search_tour.open_page() 29 | search_tour.open_tours_tab() 30 | search_tour.set_tour_destination(data.destination) 31 | search_tour.set_tour_type(data.tour_type) 32 | search_tour.set_date(data.start_year, data.start_month, data.start_day) 33 | search_tour.set_adults_number(data.adults_num) 34 | search_tour.search_perform() 35 | -------------------------------------------------------------------------------- /tests/phptravel_tests_deprecated/test_search_transfer.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import allure 3 | 4 | from pages.phptravels.search_transfers_form import SearchTransfersForm 5 | from utils.read_xlsx import XlsxReader 6 | 7 | 8 | @pytest.mark.usefixtures("setup") 9 | class TestTransferSearch: 10 | 11 | @allure.title("Search transfers test") 12 | @allure.description("This is test of searching transfers") 13 | def test_search_transfer_general(self): 14 | search_transfer = SearchTransfersForm(self.driver) 15 | search_transfer.open_page() 16 | search_transfer.open_transfer_tab() 17 | search_transfer.set_pick_up_loc("Manchester") 18 | search_transfer.set_drop_off_loc("Petra") 19 | search_transfer.set_depart_date("2019", "Dec", "29") 20 | search_transfer.set_depart_time("12:30") 21 | search_transfer.set_return_date("2020", "Jan", "8") 22 | search_transfer.set_return_time("15:00") 23 | search_transfer.search_perform() 24 | 25 | @allure.title("Search transfer test") 26 | @allure.description("This is test of searching transfer") 27 | @pytest.mark.parametrize("data", XlsxReader.get_xlsx_transfers_data()) 28 | def test_search_transfer_data_driven(self, data): 29 | search_transfer = SearchTransfersForm(self.driver) 30 | search_transfer.open_page() 31 | search_transfer.open_transfer_tab() 32 | search_transfer.set_pick_up_loc(data.pick_up_loc) 33 | search_transfer.set_drop_off_loc(data.drop_off_loc) 34 | search_transfer.set_depart_date( 35 | data.depart_year, data.depart_month, data.depart_day) 36 | search_transfer.set_depart_time(data.depart_time) 37 | search_transfer.set_return_date( 38 | data.return_year, data.return_month, data.return_day) 39 | search_transfer.set_return_time(data.return_time) 40 | search_transfer.search_perform() 41 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/utils/__init__.py -------------------------------------------------------------------------------- /utils/driver_factory.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from webdriver_manager.chrome import ChromeDriverManager 3 | from webdriver_manager.firefox import GeckoDriverManager 4 | from webdriver_manager.microsoft import EdgeChromiumDriverManager 5 | from helpers.webdriver_listener import WebDriverListener 6 | from msedge.selenium_tools import EdgeOptions, Edge 7 | from extensions.webdriver_extended import WebDriverExtended 8 | 9 | 10 | class DriverFactory: 11 | @staticmethod 12 | def get_driver(config) -> WebDriverExtended: 13 | if config["browser"] == "chrome": 14 | options = webdriver.ChromeOptions() 15 | options.add_argument("start-maximized") 16 | if config["headless_mode"] is True: 17 | options.add_argument("--headless") 18 | driver = WebDriverExtended( 19 | webdriver.Chrome(ChromeDriverManager().install(), options=options), 20 | WebDriverListener(), config 21 | ) 22 | return driver 23 | elif config["browser"] == "firefox": 24 | options = webdriver.FirefoxOptions() 25 | if config["headless_mode"] is True: 26 | options.headless = True 27 | driver = WebDriverExtended( 28 | webdriver.Firefox(executable_path=GeckoDriverManager().install(), options=options), 29 | WebDriverListener(), config 30 | ) 31 | return driver 32 | elif config["browser"] == "edge": 33 | options = EdgeOptions() 34 | options.use_chromium = True 35 | if config["headless_mode"] is True: 36 | options.headless = True 37 | driver_path = EdgeChromiumDriverManager().install() 38 | driver = WebDriverExtended( 39 | Edge(executable_path=driver_path, options=options), 40 | WebDriverListener(), config 41 | ) 42 | return driver 43 | raise Exception("Provide valid driver name") 44 | -------------------------------------------------------------------------------- /utils/functions.py: -------------------------------------------------------------------------------- 1 | def adjust_travellers_number(travellers_input_val, num, subtract_btn, add_btn): 2 | while travellers_input_val > num: 3 | subtract_btn.click() 4 | travellers_input_val -= 1 5 | while travellers_input_val < num: 6 | add_btn.click() 7 | travellers_input_val += 1 8 | 9 | 10 | def set_travellers_number(driver, num, form_loc, params: list): 11 | travellers_number_input = driver.find_element(*getattr(form_loc, params[0])) 12 | travellers_input_val = int(travellers_number_input.get_attribute("value")) 13 | add_btn = driver.find_element(*getattr(form_loc, params[1])) 14 | subtract_btn = driver.find_element(*getattr(form_loc, params[2])) 15 | adjust_travellers_number(travellers_input_val, num, subtract_btn, add_btn) 16 | 17 | 18 | def get_datestamp(driver, form_loc, params: list): 19 | datestamps = driver.find_elements(*getattr(form_loc, params[0])) 20 | for datestamp in datestamps: 21 | if datestamp.is_displayed(): 22 | current_datestamp = datestamp.text 23 | return current_datestamp 24 | 25 | 26 | def click_displayed_datestamp(datestamps): 27 | for datestamp in datestamps: 28 | if datestamp.is_displayed(): 29 | datestamp.click() 30 | break 31 | -------------------------------------------------------------------------------- /utils/read_xlsx.py: -------------------------------------------------------------------------------- 1 | import xlrd 2 | 3 | from utils.search_flights_data import SearchFlightsData 4 | from utils.search_hotels_data import SearchHotelsData 5 | from utils.search_tours_data import SearchToursData 6 | from utils.search_transfers_data import SearchTransfersData 7 | 8 | 9 | class XlsxReader: 10 | @staticmethod 11 | def get_xlsx_hotels_data(): 12 | wb = xlrd.open_workbook(f"./utils/search_hotels_input_data.xlsx") 13 | sheet = wb.sheet_by_index(0) 14 | data = [] 15 | 16 | for i in range(1, sheet.nrows): 17 | search_hotels_data = SearchHotelsData(sheet.cell(i, 0).value, # destination 18 | sheet.cell(i, 1).value, # check in date 19 | sheet.cell(i, 2).value, # check out date 20 | int(sheet.cell(i, 3).value), # adults number 21 | int(sheet.cell(i, 4).value)) # kids number 22 | data.append(search_hotels_data) 23 | return data 24 | 25 | @staticmethod 26 | def get_xlsx_flights_data(): 27 | wb = xlrd.open_workbook(f"./utils/search_flights_input_data.xlsx") 28 | sheet = wb.sheet_by_index(0) 29 | data = [] 30 | 31 | for i in range(1, sheet.nrows): 32 | search_flights_data = SearchFlightsData(sheet.cell(i, 0).value, # cabin class 33 | sheet.cell(i, 1).value, # location from 34 | sheet.cell(i, 2).value, # location to 35 | sheet.cell(i, 3).value, # start year 36 | sheet.cell(i, 4).value, # start month 37 | sheet.cell(i, 5).value, # start day 38 | sheet.cell(i, 6).value, # end year 39 | sheet.cell(i, 7).value, # end month 40 | sheet.cell(i, 8).value, # end day 41 | int(sheet.cell(i, 9).value), # adults number 42 | int(sheet.cell(i, 10).value), # kids number 43 | int(sheet.cell(i, 11).value)) # infants number 44 | data.append(search_flights_data) 45 | return data 46 | 47 | @staticmethod 48 | def get_xlsx_tours_data(): 49 | wb = xlrd.open_workbook(f"./utils/search_tours_input_data.xlsx") 50 | sheet = wb.sheet_by_index(0) 51 | data = [] 52 | 53 | for i in range(1, sheet.nrows): 54 | search_tours_data = SearchToursData(sheet.cell(i, 0).value, # destination 55 | sheet.cell(i, 1).value, # tour type 56 | sheet.cell(i, 2).value, # start year 57 | sheet.cell(i, 3).value, # start month 58 | sheet.cell(i, 4).value, # start day 59 | int(sheet.cell(i, 5).value)) # adults number 60 | data.append(search_tours_data) 61 | return data 62 | 63 | @staticmethod 64 | def get_xlsx_transfers_data(): 65 | wb = xlrd.open_workbook(f"./utils/search_transfers_input_data.xlsx") 66 | sheet = wb.sheet_by_index(0) 67 | data = [] 68 | 69 | for i in range(1, sheet.nrows): 70 | search_transfers_data = SearchTransfersData(sheet.cell(i, 0).value, # pick up location 71 | sheet.cell(i, 1).value, # drop off location 72 | sheet.cell(i, 2).value, # depart year 73 | sheet.cell(i, 3).value, # depart month 74 | sheet.cell(i, 4).value, # depart day 75 | sheet.cell(i, 5).value, # depart time 76 | sheet.cell(i, 6).value, # return year 77 | sheet.cell(i, 7).value, # return month 78 | sheet.cell(i, 8).value, # return day 79 | sheet.cell(i, 9).value) # return time 80 | data.append(search_transfers_data) 81 | return data 82 | -------------------------------------------------------------------------------- /utils/search_flights_data.py: -------------------------------------------------------------------------------- 1 | class SearchFlightsData: 2 | 3 | def __init__(self, cabin_class, location_from, location_to, start_year, start_month, start_day, 4 | end_year, end_month, end_day, adults_num, kids_num, infants_num): 5 | self.cabin_class = cabin_class 6 | self.location_from = location_from 7 | self.location_to = location_to 8 | self.start_year = start_year 9 | self.start_month = start_month 10 | self.start_day = start_day 11 | self.end_year = end_year 12 | self.end_month = end_month 13 | self.end_day = end_day 14 | self.adults_num = adults_num 15 | self.kids_num = kids_num 16 | self.infants_num = infants_num 17 | -------------------------------------------------------------------------------- /utils/search_flights_input_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/utils/search_flights_input_data.xlsx -------------------------------------------------------------------------------- /utils/search_hotels_data.py: -------------------------------------------------------------------------------- 1 | class SearchHotelsData: 2 | 3 | def __init__(self, destination, check_in, check_out, adults_num, kids_num): 4 | self.destination = destination 5 | self.check_in = check_in 6 | self.check_out = check_out 7 | self.adults_num = adults_num 8 | self.kids_num = kids_num 9 | -------------------------------------------------------------------------------- /utils/search_hotels_input_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/utils/search_hotels_input_data.xlsx -------------------------------------------------------------------------------- /utils/search_tours_data.py: -------------------------------------------------------------------------------- 1 | class SearchToursData: 2 | 3 | def __init__(self, destination, tour_type, start_year, start_month, start_day, adults_num): 4 | self.destination = destination 5 | self.tour_type = tour_type 6 | self.start_year = start_year 7 | self.start_month = start_month 8 | self.start_day = start_day 9 | self.adults_num = adults_num 10 | -------------------------------------------------------------------------------- /utils/search_tours_input_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/utils/search_tours_input_data.xlsx -------------------------------------------------------------------------------- /utils/search_transfers_data.py: -------------------------------------------------------------------------------- 1 | class SearchTransfersData: 2 | 3 | def __init__(self, pick_up_loc, drop_off_loc, depart_year, depart_month, depart_day, depart_time, 4 | return_year, return_month, return_day, return_time): 5 | self.pick_up_loc = pick_up_loc 6 | self.drop_off_loc = drop_off_loc 7 | self.depart_year = depart_year 8 | self.depart_month = depart_month 9 | self.depart_day = depart_day 10 | self.depart_time = depart_time 11 | self.return_year = return_year 12 | self.return_month = return_month 13 | self.return_day = return_day 14 | self.return_time = return_time 15 | -------------------------------------------------------------------------------- /utils/search_transfers_input_data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startrug/selenium-python-framework/f9e355b2efa42da9e8900dd3bd897ea5fa7926f7/utils/search_transfers_input_data.xlsx --------------------------------------------------------------------------------