├── .gitignore ├── .idea └── vcs.xml ├── LICENSE ├── README.md ├── __init__.py ├── pages ├── __init__.py ├── base_page.py ├── home_page.py ├── login_page.py ├── main_page.py └── signup_page.py ├── requirements.txt ├── tests ├── __init__.py ├── base_test.py └── test_sign_in_page.py └── utils ├── __init__.py ├── locators.py ├── test_cases.py └── users.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | *__pycache__* 3 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Mesut Güneş 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 | 2 | # Selenium Page Object Model with Python 3 | 4 | Page-object-model (POM) is a pattern that you can apply it to develop efficient automation framework. With page-model, it is possible to minimise maintenance cost. Basically page-object means that your every page is inherited from a base class which includes basic functionalities for every pages. If you have some new functionality that every pages have, you can simple add it to the base class. 5 | 6 | `BasePage` class include basic functionality and driver initialization 7 | ```python 8 | # base_page.py 9 | class BasePage(object): 10 | def __init__(self, driver, base_url='http://www.amazon.com/'): 11 | self.base_url = base_url 12 | self.driver = driver 13 | self.timeout = 30 14 | 15 | def find_element(self, *locator): 16 | return self.driver.find_element(*locator) 17 | ``` 18 | 19 | `MainPage` is derived from the `BasePage class, it contains methods related to this page, which will be used to create test steps. 20 | ```python 21 | # main_page.py 22 | class MainPage(BasePage): 23 | def __init__(self, driver): 24 | self.locator = MainPageLocators 25 | super().__init__(driver) # Python3 version 26 | 27 | def check_page_loaded(self): 28 | return True if self.find_element(*self.locator.LOGO) else False 29 | ``` 30 | 31 | When you want to write tests, you should derive your test class from `BaseTest` which holds basic functionality for your tests. Then you can call page and related methods in accordance with the steps in the test cases 32 | ```python 33 | # test_sign_in_page.py 34 | class TestSignInPage(BaseTest): 35 | def test_sign_in_with_valid_user(self): 36 | print("\n" + str(test_cases(4))) 37 | main_page = MainPage(self.driver) 38 | login_page = main_page.click_sign_in_button() 39 | result = login_page.login_with_valid_user("valid_user") 40 | self.assertIn("yourstore/home", result.get_url()) 41 | ``` 42 | 43 | #### If you want to run all tests, you should type: 44 | ```sh 45 | python -m unittest 46 | ``` 47 | 48 | 49 | #### If you want to run just a class, you should type: 50 | ```sh 51 | python -m unittest tests.test_sign_in_page.TestSignInPage 52 | ``` 53 | 54 | #### If you want to run just a test method, you should type: 55 | ```sh 56 | python -m unittest tests.test_sign_in_page.TestSignInPage.test_page_load 57 | ``` 58 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunesmes/page-object-python-selenium/a6336018fff8cab1af8b76a6a60948dbb4e57b96/__init__.py -------------------------------------------------------------------------------- /pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunesmes/page-object-python-selenium/a6336018fff8cab1af8b76a6a60948dbb4e57b96/pages/__init__.py -------------------------------------------------------------------------------- /pages/base_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.action_chains import ActionChains 2 | from selenium.webdriver.support.ui import WebDriverWait 3 | from selenium.webdriver.support import expected_conditions as EC 4 | from selenium.common.exceptions import TimeoutException 5 | 6 | 7 | # this Base class is serving basic attributes for every single page inherited from Page class 8 | class BasePage(object): 9 | def __init__(self, driver, base_url='http://www.amazon.com/'): 10 | self.base_url = base_url 11 | self.driver = driver 12 | self.timeout = 30 13 | 14 | def find_element(self, *locator): 15 | return self.driver.find_element(*locator) 16 | 17 | def open(self, url): 18 | url = self.base_url + url 19 | self.driver.get(url) 20 | 21 | def get_title(self): 22 | return self.driver.title 23 | 24 | def get_url(self): 25 | return self.driver.current_url 26 | 27 | def hover(self, *locator): 28 | element = self.find_element(*locator) 29 | hover = ActionChains(self.driver).move_to_element(element) 30 | hover.perform() 31 | 32 | def wait_element(self, *locator): 33 | try: 34 | WebDriverWait(self.driver, 10).until(EC.presence_of_element_located(locator)) 35 | except TimeoutException: 36 | print("\n * ELEMENT NOT FOUND WITHIN GIVEN TIME! --> %s" %(locator[1])) 37 | self.driver.quit() 38 | -------------------------------------------------------------------------------- /pages/home_page.py: -------------------------------------------------------------------------------- 1 | from pages.base_page import BasePage 2 | 3 | 4 | class HomePage(BasePage): 5 | pass 6 | -------------------------------------------------------------------------------- /pages/login_page.py: -------------------------------------------------------------------------------- 1 | from utils.locators import * 2 | from pages.base_page import BasePage 3 | from utils import users 4 | 5 | 6 | class LoginPage(BasePage): 7 | def __init__(self, driver): 8 | self.locator = LoginPageLocators 9 | super(LoginPage, self).__init__(driver) # Python2 version 10 | 11 | def enter_email(self, email): 12 | self.find_element(*self.locator.EMAIL).send_keys(email) 13 | 14 | def enter_password(self, password): 15 | self.find_element(*self.locator.PASSWORD).send_keys(password) 16 | 17 | def click_login_button(self): 18 | self.find_element(*self.locator.SUBMIT).click() 19 | 20 | def login(self, user): 21 | user = users.get_user(user) 22 | print(user) 23 | self.enter_email(user["email"]) 24 | self.enter_password(user["password"]) 25 | self.click_login_button() 26 | 27 | def login_with_valid_user(self, user): 28 | self.login(user) 29 | return HomePage(self.driver) 30 | 31 | def login_with_in_valid_user(self, user): 32 | self.login(user) 33 | return self.find_element(*self.locator.ERROR_MESSAGE).text 34 | -------------------------------------------------------------------------------- /pages/main_page.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.keys import Keys 2 | from pages.base_page import BasePage 3 | from pages.login_page import LoginPage 4 | from pages.signup_page import SignUpBasePage 5 | from utils.locators import * 6 | 7 | 8 | # Page objects are written in this module. 9 | # Depends on the page functionality we can have more functions for new classes 10 | 11 | class MainPage(BasePage): 12 | def __init__(self, driver): 13 | self.locator = MainPageLocators 14 | super().__init__(driver) # Python3 version 15 | 16 | def check_page_loaded(self): 17 | return True if self.find_element(*self.locator.LOGO) else False 18 | 19 | def search_item(self, item): 20 | self.find_element(*self.locator.SEARCH).send_keys(item) 21 | self.find_element(*self.locator.SEARCH).send_keys(Keys.ENTER) 22 | return self.find_element(*self.locator.SEARCH_LIST).text 23 | 24 | def click_sign_up_button(self): 25 | self.find_element(*self.locator.SIGNUP).click() 26 | return SignUpBasePage(self.driver) 27 | 28 | def click_sign_in_button(self): 29 | self.find_element(*self.locator.LOGIN).click() 30 | return LoginPage(self.driver) 31 | -------------------------------------------------------------------------------- /pages/signup_page.py: -------------------------------------------------------------------------------- 1 | from pages.base_page import BasePage 2 | 3 | 4 | class SignUpBasePage(BasePage): 5 | pass 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | async-generator==1.10 2 | attrs==22.2.0 3 | certifi==2022.12.7 4 | charset-normalizer==3.1.0 5 | exceptiongroup==1.1.1 6 | h11==0.14.0 7 | idna==3.4 8 | outcome==1.2.0 9 | pip==22.2.2 10 | PySocks==1.7.1 11 | requests==2.31.0 12 | selenium==4.8.2 13 | setuptools==65.5.1 14 | sniffio==1.3.0 15 | sortedcontainers==2.4.0 16 | trio==0.22.0 17 | trio-websocket==0.10.0 18 | urllib3==1.26.15 19 | wsproto==1.2.0 -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunesmes/page-object-python-selenium/a6336018fff8cab1af8b76a6a60948dbb4e57b96/tests/__init__.py -------------------------------------------------------------------------------- /tests/base_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from selenium import webdriver 3 | from selenium.webdriver.chrome.options import Options 4 | 5 | 6 | # I am using python unittest for asserting cases. 7 | # In this module, there should be test cases. 8 | # If you want to run it, you should type: python 9 | 10 | class BaseTest(unittest.TestCase): 11 | 12 | def setUp(self): 13 | options = Options() 14 | # options.add_argument("--headless") # Runs Chrome in headless mode. 15 | options.add_argument('--no-sandbox') # # Bypass OS security model 16 | options.add_argument('disable-infobars') 17 | options.add_argument("--disable-extensions") 18 | options.add_argument("--start-fullscreen") 19 | options.add_argument('--disable-gpu') 20 | 21 | self.driver = webdriver.Chrome(options=options) 22 | # self.driver = webdriver.Firefox() 23 | self.driver.get("http://www.amazon.com") 24 | 25 | def tearDown(self): 26 | self.driver.close() 27 | 28 | 29 | if __name__ == "__main__": 30 | suite = unittest.TestLoader().loadTestsFromTestCase(TestPages) 31 | unittest.TextTestRunner(verbosity=1).run(suite) 32 | -------------------------------------------------------------------------------- /tests/test_sign_in_page.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tests.base_test import BaseTest 3 | from pages.main_page import * 4 | from utils.test_cases import test_cases 5 | 6 | 7 | # I am using python unittest for asserting cases. 8 | # In this module, there should be test cases. 9 | # If you want to run it, you should type: python 10 | 11 | class TestSignInPage(BaseTest): 12 | 13 | def test_page_load(self): 14 | print("\n" + str(test_cases(0))) 15 | page = MainPage(self.driver) 16 | self.assertTrue(page.check_page_loaded()) 17 | 18 | def test_search_item(self): 19 | print("\n" + str(test_cases(1))) 20 | page = MainPage(self.driver) 21 | search_result = page.search_item("iPhone") 22 | self.assertIn("iPhone", search_result) 23 | 24 | def test_sign_up_button(self): 25 | print("\n" + str(test_cases(2))) 26 | page = MainPage(self.driver) 27 | sign_up_page = page.click_sign_up_button() 28 | self.assertIn("ap/register", sign_up_page.get_url()) 29 | 30 | def test_sign_in_button(self): 31 | print("\n" + str(test_cases(3))) 32 | page = MainPage(self.driver) 33 | login_page = page.click_sign_in_button() 34 | self.assertIn("ap/signin", login_page.get_url()) 35 | 36 | def test_sign_in_with_valid_user(self): 37 | print("\n" + str(test_cases(4))) 38 | main_page = MainPage(self.driver) 39 | login_page = main_page.click_sign_in_button() 40 | result = login_page.login_with_valid_user("valid_user") 41 | self.assertIn("yourstore/home", result.get_url()) 42 | 43 | def test_sign_in_with_in_valid_user(self): 44 | print("\n" + str(test_cases(5))) 45 | main_page = MainPage(self.driver) 46 | login_page = main_page.click_sign_in_button() 47 | result = login_page.login_with_in_valid_user("invalid_user") 48 | self.assertIn("There was a problem with your request", result) 49 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunesmes/page-object-python-selenium/a6336018fff8cab1af8b76a6a60948dbb4e57b96/utils/__init__.py -------------------------------------------------------------------------------- /utils/locators.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | 3 | 4 | # for maintainability we can seperate web objects by page name 5 | 6 | class MainPageLocators(object): 7 | LOGO = (By.ID, 'nav-logo') 8 | ACCOUNT = (By.ID, 'nav-link-accountList') 9 | SIGNUP = (By.CSS_SELECTOR, '#nav-signin-tooltip > div > a') 10 | LOGIN = (By.CSS_SELECTOR, '#nav-signin-tooltip > a') 11 | SEARCH = (By.ID, 'twotabsearchtextbox') 12 | SEARCH_LIST = (By.CSS_SELECTOR, 'div[data-component-type="s-search-result"]') 13 | 14 | 15 | class LoginPageLocators(object): 16 | EMAIL = (By.ID, 'ap_email') 17 | PASSWORD = (By.ID, 'ap_password') 18 | SUBMIT = (By.ID, 'signInSubmit-input') 19 | ERROR_MESSAGE = (By.ID, 'message_error') 20 | -------------------------------------------------------------------------------- /utils/test_cases.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- we should add test cases here because we can miss some cases while writing automation code or 2 | # some manuel testers (test analystes) can handle this more efficiently we can obtain test cases from test management 3 | # tools, I used this for my previous project: 4 | # http://www.inflectra.com/SpiraTest/Integrations/Unit-Test-Frameworks.aspx We can also record the result of test 5 | # cases to test management tool 6 | 7 | # for maintainability, we can seperate web test cases by page name but I just listed every case in same array 8 | 9 | 10 | def test_cases(number): 11 | return testCases[number] 12 | 13 | 14 | testCases = [ 15 | # [severity, description] 16 | ['Blocker', 'when user goes to main page, page should be loaded'], 17 | ['Blocker', 'In Main page, when user search "Nexus 5" button, he should see result for Nexus 5'], 18 | ['Blocker', 'In Main page, when user click "Sing up" button, he should see Sign up Page'], 19 | ['Blocker', 'In Main page, when user click "Sing in" button, he should see Sign in Page'], 20 | ['Blocker', 'In Login Page, when user login with a valid user, he should see Home Page'], 21 | ['Blocker', 'In Login Page, when user login with a in-valid user, he should see Error Message'], 22 | ] 23 | -------------------------------------------------------------------------------- /utils/users.py: -------------------------------------------------------------------------------- 1 | from operator import itemgetter 2 | 3 | # we can store test data in this module like users 4 | users = [ 5 | {"name": "invalid_user", "email": "invalidUser@test.com", "password": "qwert1235"}, 6 | {"name": "valid_user", "email": "validUser@yahoo.com", "password": "ValidPassword"}, 7 | {"name": "Staff2", "email": "staff@test.com", "password": "qwert1235"}, 8 | {"name": "Admin0", "email": "admin@test.com", "password": "qwert1234"}, 9 | {"name": "Admin1", "email": "admin@test.com", "password": "qwert1234"}, 10 | {"name": "Admin2", "email": "admin@test.com", "password": "qwert1234"}, 11 | ] 12 | 13 | 14 | def get_user(name): 15 | try: 16 | return next(user for user in users if user["name"] == name) 17 | except: 18 | print("\n User %s is not defined, enter a valid user.\n" % name) 19 | --------------------------------------------------------------------------------