├── .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 |
--------------------------------------------------------------------------------