├── .github
└── workflows
│ └── codeql-analysis.yml
├── .readthedocs.yaml
├── LICENSE
├── README.md
├── docs
├── _static
│ └── css
│ │ └── custom.css
├── conf.py
├── index.rst
├── new_logo.JPG
├── requirements.txt
└── selenium-page-factory_logo.png
├── selenium-page-factory_logo.png
├── seleniumpagefactory
├── Pagefactory.py
└── __init__.py
├── setup.py
└── tests
├── Factory_approch.py
├── __init__.py
├── selenium_unitest.py
└── test_selenium.py
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '43 22 * * 3'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'python' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37 | # Learn more:
38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39 |
40 | steps:
41 | - name: Checkout repository
42 | uses: actions/checkout@v2
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.readthedocs.yaml:
--------------------------------------------------------------------------------
1 | # Set the OS, Python version and other tools you might need
2 | build:
3 | os: ubuntu-22.04
4 | tools:
5 | python: "3.12"
6 |
7 | # Build documentation in the "docs/" directory with Sphinx
8 | # sphinx:
9 | # configuration: docs/conf.py
10 |
11 | # Optional but recommended, declare the Python requirements required
12 | # to build your documentation
13 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
14 | python:
15 | install:
16 | - requirements: docs/requirements.txt
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2023 Sujit Nayakwadi
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 | selenium-page-factory
2 | =====================
3 |
4 |
5 |
6 | Python library provides page factory approach to implement page object model in selenium
7 |
8 | 
9 | [](https://pypi.org/project/selenium-page-factory/)
10 | 
11 | 
12 | [](https://selenium-page-factory.readthedocs.io/en/latest/)
13 | 
14 |
15 | Introduction
16 | ============
17 |
18 | * A Page Factory is one way of implementing a Page Object Model. In order to support the Page Object pattern.
19 | * As in Java we are using @findBy, here we are declaring all web element in dictionary.
20 | Dictionary keys become WebElement / class member variable with having all extended WebElement methods.
21 |
22 |
23 | Main Features
24 | =============
25 |
26 | * Initialise all the webElements declared in Point at a time.
27 | * All WebElements methods are re-define to add extra features eg- click method extended to have explicit wait for element to be clickable.
28 | * Cent percent unittest coverage.
29 | * Supports Selenium 4 ActionChains methods
30 | * Now Support Appium for mobile testing
31 | * Raised custom Page factory exceptions
32 |
33 | Installation
34 | =============
35 |
36 | ```shell
37 | pip install selenium-page-factory
38 | ```
39 |
40 | Pre-Requisite
41 | =============
42 | Every Page in Page Object Model should have WebDriver object as class member
43 | as shown below
44 |
45 | ```python
46 | class PageClass(PageFactory):
47 |
48 | def __init__(self,driver):
49 | self.driver = driver # Required
50 | self.timeout = 15 #(Optional - Customise your explicit wait for every webElement)
51 | self.highlight = True #(Optional - To highlight every webElement in PageClass)
52 | self.mobile_test = False #(Optional - Added for Appium support)
53 | ```
54 |
55 | Extended WebElements Methods
56 | ===================
57 |
58 |
59 |
60 | set_text |
61 | get_text |
62 |
63 |
64 | clear_text |
65 | click_button |
66 |
67 |
68 | double_click |
69 | get_list_item_count |
70 |
71 |
72 | select_element_by_text |
73 | select_element_by_index |
74 |
75 |
76 | select_element_by_value |
77 | get_all_list_item |
78 |
79 |
80 | get_list_selected_item |
81 | highlight |
82 |
83 |
84 | is_Enabled |
85 | is_Checked |
86 |
87 |
88 | getAttribute |
89 | hover |
90 |
91 |
92 | visibility_of_element_located |
93 | invisibility_of_element_located |
94 |
95 |
96 | element_to_be_clickable |
97 | text_to_be_present_in_element |
98 |
99 |
100 |
101 | context_click |
102 | execute_script |
103 |
104 |
105 |
106 |
107 | click_and_hold |
108 | release |
109 |
110 |
111 | hover_with_offset |
112 | Coming soon... |
113 |
114 |
115 |
116 |
117 | Note:
118 | Every WebElement will be created after verifying it's Presence and visibility on Page at Run-Time.
119 |
120 |
121 | *[selenium-page-factory Documentation](https://selenium-page-factory.readthedocs.io)*
122 |
123 | Selenium Python Framework Example *[here](https://github.com/NayakwadiS/Selenium_Python_UnitTest_HTML)*
124 |
125 |
126 |
--------------------------------------------------------------------------------
/docs/_static/css/custom.css:
--------------------------------------------------------------------------------
1 | .wy-side-nav-search {
2 | /* Rainforest alliance light gray */
3 | background-color: #325270;
4 | }
5 |
6 | .wy-nav-side {
7 | /* menu */
8 | background: #456e96;
9 | /* GCP green on brown */
10 | color: #799d44;
11 | }
12 | .rst-versions .rst-current-version{
13 | /*footer*/
14 | background-color: #325270;
15 | }
16 | .wy-alert-title, .rst-content .admonition-title {
17 | /* Note */
18 | background: #325270;
19 | }
20 |
21 | .wy-nav-content {
22 | max-width: inherit;
23 | }
24 |
25 | .current{
26 | background: #456e96;
27 | }
28 |
29 | .wy-side-scroll {
30 | width: auto;
31 | overflow-y: auto;
32 | }
33 |
34 | p {
35 | margin-bottom: 0.5rem;
36 | }
37 |
38 | h1, h2, h3, h4, h5 {
39 | margin-bottom: 12px;
40 | }
41 |
42 | #global-coffee-data-standard h2 {
43 | /* Rainforest alliance green */
44 | border-top: 3px dashed #4e917a;
45 | padding-top: 0.5rem;
46 | margin-top: 1rem;
47 | }
48 |
49 | .caption-text {
50 | font-weight: bold;
51 | }
52 |
53 |
54 | div.wy-table-responsive > table.docutils td {
55 | word-wrap: break-word;
56 | white-space: inherit;
57 | max-width: 20%;
58 | }
59 |
60 | div.wy-table-responsive > table.docutils td:nth-child(3) {
61 | max-width: 40%;
62 | }
63 |
64 | div:not(#contents).topic {
65 | /* Rainforest alliance green */
66 | border: 1px solid #4e917a;
67 | padding: 3px;
68 | width: 100%;
69 | border-radius: 3px;
70 | }
71 |
72 | div.topic p,
73 | div.topic p.topic-title {
74 | margin-bottom: 0;
75 | }
76 |
77 | div:not(#contents).topic p.topic-title {
78 | display: none;
79 | }
80 |
81 | button.btn-example-data {
82 | background-color: #4e917a !important;
83 | margin-top: 5px;
84 | margin-bottom: 5px;
85 | }
86 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | from recommonmark.transform import AutoStructify
2 | from recommonmark.parser import CommonMarkParser
3 |
4 | # -- Project information -------------------------
5 |
6 | project = 'selenium-page-factory'
7 |
8 | # The short X.Y version
9 | version = ''
10 | # The full version, including alpha/beta/rc tags
11 | release = '2.7'
12 |
13 | # The master toctree document.
14 | master_doc = 'index'
15 |
16 | html_favicon = 'new_logo.JPG'
17 |
18 | html_static_path = ['_static']
19 |
20 | def setup(app):
21 | app.add_stylesheet('css/custom.css?v20240414')
22 |
23 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 | * Python library provides page factory approach to implement page object model in selenium
4 | * A Page Factory is one way of implementing a Page Object Model. In order to support the Page Object pattern.
5 | * As in Java we are using @findBy, here we are declaring all web element in dictionary.
6 | Dictionary keys become WebElement / class member variable with having all extended WebElement methods.
7 |
8 | Github Project Page
9 | ===================
10 |
11 | https://github.com/NayakwadiS/selenium-page-factory
12 |
13 | Main Features
14 | =============
15 |
16 | * Initialise all the webElements declared in Point at a time.
17 | * All WebElements methods are re-define to add extra features eg- click method extended to have explicit wait for element to be clickable.
18 | * Cent percent unittest coverage.
19 | * Supports Selenium 4 ActionChains methods
20 | * Now Support Appium for mobile testing
21 | * Raised custom Page factory exceptions
22 |
23 |
24 | Installation
25 | =============
26 | pip install::
27 |
28 | pip install selenium-page-factory
29 |
30 | Update
31 | ===============
32 | To updated to the lasted version::
33 |
34 | pip install selenium-page-factory --upgrade
35 |
36 | Pre-Requisite
37 | =============
38 | Every Page in Page Object Model should have WebDriver object as class member
39 | as shown below::
40 |
41 | class PageClass(PageFactory):
42 |
43 | def __init__(self,driver):
44 | self.driver = driver
45 | self.timeout = 15 #(Optional - Customise your explicit wait for every webElement)
46 | self.highlight = True #(Optional - To highlight every webElement in PageClass)
47 | self.mobile_test = False #(Optional - Added for Appium support)
48 |
49 | Extended WebElements Methods
50 | ============================
51 | .. raw:: html
52 |
53 |
54 |
55 |
56 | set_text |
57 | get_text |
58 |
59 |
60 | clear_text |
61 | click_button |
62 |
63 |
64 | double_click |
65 | get_list_item_count |
66 |
67 |
68 | select_element_by_text |
69 | select_element_by_index |
70 |
71 |
72 | select_element_by_value |
73 | get_all_list_item |
74 |
75 |
76 | get_list_selected_item |
77 | highlight |
78 |
79 |
80 | is_Enabled |
81 | is_Checked |
82 |
83 |
84 | getAttribute |
85 | hover |
86 |
87 |
88 | visibility_of_element_located |
89 | invisibility_of_element_located |
90 |
91 |
92 | element_to_be_clickable |
93 | execute_script |
94 |
95 |
96 | context_click |
97 | text_to_be_present_in_element |
98 |
99 |
100 | click_and_hold |
101 | release |
102 |
103 |
104 | hover_with_offset |
105 | Coming soon... |
106 |
107 |
108 |
109 |
110 | ============================
111 |
112 | .. note::
113 |
114 | Every WebElement will be created after verifying it's Presence and visibility on Page at Run-Time.
115 |
116 | Examples
117 | =============
118 |
119 | Python - Unittest
120 | --------------
121 |
122 | Inside test_Login.py::
123 |
124 | import unittest
125 | from selenium import webdriver
126 | from seleniumpagefactory.Pagefactory import PageFactory
127 |
128 | class LoginPage(PageFactory):
129 |
130 | def __init__(self,driver):
131 | # It is necessary to to initialise driver as page class member to implement Page Factory
132 | self.driver = driver
133 |
134 | # define locators dictionary where key name will became WebElement using PageFactory
135 | locators = {
136 | "edtUserName": ('ID', 'user_login'),
137 | "edtPassword": ('NAME', 'pwd'),
138 | "btnSignIn": ('XPATH', '//input[@value="Log In"]')
139 | }
140 |
141 | def login(self):
142 | # set_text(), click_button() methods are extended methods in PageFactory
143 | self.edtUserName.set_text("") # edtUserName become class variable using PageFactory
144 | self.edtPassword.set_text("")
145 | self.btnSignIn.click_button()
146 |
147 |
148 | class LoginTest(unittest.TestCase):
149 |
150 | def test_Login(self):
151 | driver = webdriver.Chrome()
152 | driver.get("https://s1.demo.opensourcecms.com/wordpress/wp-login.php")
153 |
154 | pglogin = LoginPage(driver)
155 | pglogin.login()
156 |
157 | if __name__ == "__main__":
158 | unittest.main()
159 |
160 |
161 | Python - Pytest
162 | ---------------
163 |
164 | Inside test_Login.py::
165 |
166 | import pytest
167 | from selenium import webdriver
168 | from seleniumpagefactory.Pagefactory import PageFactory
169 |
170 | def test_Login():
171 | driver = webdriver.Chrome("")
172 | driver.get("https://s1.demo.opensourcecms.com/wordpress/wp-login.php")
173 |
174 | pglogin = LoginPage(driver)
175 | pglogin.login()
176 |
177 | class LoginPage(PageFactory):
178 |
179 | def __init__(self,driver):
180 | # It is necessary to to initialise driver as page class member to implement Page Factory
181 | self.driver = driver
182 |
183 | # define locators dictionary where key name will became WebElement using PageFactory
184 | locators = {
185 | "edtUserName": ('ID', 'user_login'),
186 | "edtPassword": ('NAME', 'pwd'),
187 | "btnSignIn": ('XPATH', '//input[@value="Log In"]')
188 | "lnkPost": ('XPATH', '//div[contains(text(),"Posts")]'),
189 | "lstAction": ('ID', 'bulk-action-selector-top')
190 | }
191 |
192 | def login(self):
193 | # set_text(), click_button() methods are extended methods in PageFactory
194 | self.edtUserName.set_text("") # edtUserName become class variable using PageFactory
195 | self.edtPassword.set_text("")
196 | self.btnSignIn.click_button()
197 |
198 | WebElement Methods Usage
199 | ==========================
200 | set_text
201 | ---------
202 | To perform set text operation::
203 |
204 | class LoginPage(PageFactory):
205 |
206 | def login(self):
207 | self.edtUserName.set_text("opensourcecms")
208 |
209 | get_text
210 | ---------
211 | To get text from edit box::
212 |
213 | class LoginPage(PageFactory):
214 |
215 | def login(self):
216 | text_from_element = self.edtUserName.get_text()
217 |
218 | clear_text
219 | ---------
220 | To clear text from edit box::
221 |
222 | class LoginPage(PageFactory):
223 |
224 | def login(self):
225 | self.edtUserName.clear_text()
226 |
227 | click_button
228 | -------------
229 | To Click on any WebElement::
230 |
231 | class LoginPage(PageFactory):
232 |
233 | def login(self):
234 | self.btnSignIn.click_button()
235 |
236 | click_and_hold
237 | -------------
238 | To click_and_hold on Element::
239 |
240 | class LoginPage(PageFactory):
241 |
242 | def login(self):
243 | self.btnSignIn.click_and_hold()
244 |
245 | release
246 | -------------
247 | Releasing a held mouse button on an element::
248 |
249 | class LoginPage(PageFactory):
250 |
251 | def login(self):
252 | self.btnSignIn.release()
253 |
254 |
255 | get_list_item_count
256 | ------------------
257 | Get list item count::
258 |
259 | class customPage(PageFactory):
260 |
261 | def perform_list_operation(self):
262 | list_item_count = self.lstAction.get_list_item_count()
263 |
264 | select_element_by_text
265 | ----------------------
266 | To Select list item by using visible text::
267 |
268 | class customPage(PageFactory):
269 |
270 | def perform_list_operation(self):
271 | self.lstAction.select_element_by_text("India")
272 |
273 | select_element_by_index
274 | ----------------------
275 | To Select list item by using index::
276 |
277 | class customPage(PageFactory):
278 |
279 | def perform_list_operation(self):
280 | self.lstAction.select_element_by_index(0)
281 |
282 | select_element_by_value
283 | ----------------------
284 | To Select list item by using webElement value property::
285 |
286 | class customPage(PageFactory):
287 |
288 | def perform_list_operation(self):
289 | self.lstAction.select_element_by_value("country India")
290 |
291 | get_all_list_item
292 | ------------------
293 | Get all list items::
294 |
295 | class customPage(PageFactory):
296 |
297 | def perform_list_operation(self):
298 | list_items = self.lstAction.get_all_list_item()
299 |
300 | get_list_selected_item
301 | ------------------
302 | Get selected list item::
303 |
304 | class customPage(PageFactory):
305 |
306 | def perform_list_operation(self):
307 | selected_list_item = self.lstAction.get_list_selected_item()
308 |
309 | hover
310 | -------------
311 | To hover on any WebElement::
312 |
313 | class customPage(PageFactory):
314 |
315 | def login(self):
316 | self.btnSignIn.hover()
317 |
318 | is_Checked
319 | ------------------
320 | Verify RadioButton and CheckBox::
321 |
322 | class customPage(PageFactory):
323 |
324 | def checkbox_radiobutton_operation(self):
325 | checkBox_is_selected = self.chkGender.is_Checked()
326 |
327 | is_Enabled
328 | ------------------
329 | Verify Enable state of WebElemnt::
330 |
331 | class customPage(PageFactory):
332 |
333 | def checkbox_radiobutton_operation(self):
334 | checkBox_is_enabled = self.chkGender.is_Enabled()
335 |
336 | getAttribute
337 | ------------------
338 | Get HTML attribute value of WebElemnt::
339 |
340 | class customPage(PageFactory):
341 |
342 | def link_operation(self):
343 | title_attribute = self.nextLink.getAttribute("title")
344 |
--------------------------------------------------------------------------------
/docs/new_logo.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayakwadiS/selenium-page-factory/050cd296587084dfd4a6b6c0ccb0de3813c2277e/docs/new_logo.JPG
--------------------------------------------------------------------------------
/docs/requirements.txt:
--------------------------------------------------------------------------------
1 | alabaster==0.7.12
2 | # via sphinx
3 | babel==2.11.0
4 | # via sphinx
5 | certifi==2024.7.4
6 | # via requests
7 | charset-normalizer==2.1.1
8 | # via requests
9 | docutils==0.19
10 | # via sphinx
11 | idna==3.7
12 | # via requests
13 | imagesize==1.4.1
14 | # via sphinx
15 | jinja2==3.1.6
16 | # via sphinx
17 | markupsafe==2.1.1
18 | # via jinja2
19 | packaging==22.0
20 | # via sphinx
21 | pygments==2.15.0
22 | # via sphinx
23 | pytz==2022.7
24 | # via babel
25 | requests==2.32.0
26 | # via sphinx
27 | snowballstemmer==2.2.0
28 | # via sphinx
29 | sphinx==5.3.0
30 | # via -r docs.in
31 | sphinxcontrib-applehelp==1.0.2
32 | # via sphinx
33 | sphinxcontrib-devhelp==1.0.2
34 | # via sphinx
35 | sphinxcontrib-htmlhelp==2.0.0
36 | # via sphinx
37 | sphinxcontrib-jsmath==1.0.1
38 | # via sphinx
39 | sphinxcontrib-qthelp==1.0.3
40 | # via sphinx
41 | sphinxcontrib-serializinghtml==1.1.5
42 | # via sphinx
43 | urllib3==1.26.19
44 | # via requests
45 | recommonmark
46 |
--------------------------------------------------------------------------------
/docs/selenium-page-factory_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayakwadiS/selenium-page-factory/050cd296587084dfd4a6b6c0ccb0de3813c2277e/docs/selenium-page-factory_logo.png
--------------------------------------------------------------------------------
/selenium-page-factory_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NayakwadiS/selenium-page-factory/050cd296587084dfd4a6b6c0ccb0de3813c2277e/selenium-page-factory_logo.png
--------------------------------------------------------------------------------
/seleniumpagefactory/Pagefactory.py:
--------------------------------------------------------------------------------
1 | from selenium.webdriver.support.ui import WebDriverWait
2 | from selenium.webdriver.common.by import By
3 | from selenium.webdriver.support import expected_conditions as EC
4 | from selenium.common.exceptions import *
5 | from selenium.webdriver import ActionChains
6 | from selenium.webdriver.remote.webelement import WebElement
7 | from selenium.webdriver.support.ui import Select
8 |
9 |
10 | class PageFactoryException(Exception):
11 | """Base class for exceptions in this module."""
12 | pass
13 |
14 |
15 | class ElementNotFoundException(PageFactoryException):
16 | """Raised when the element is not found in the page. (Using ` EC.presence_of_element_located` function from within __getattr__ method)."""
17 |
18 |
19 | class ElementNotVisibleException(PageFactoryException):
20 | """Raised when the element is not visible in the page. (Using `EC.visibility_of_element_located` function from within __getattr__ method)."""
21 |
22 |
23 | class PageFactory(object):
24 | timeout = 10
25 | highlight = False
26 | mobile_test = False # Added for Mobile support
27 |
28 | TYPE_OF_LOCATORS = {
29 | 'css': By.CSS_SELECTOR,
30 | 'id': By.ID,
31 | 'name': By.NAME,
32 | 'xpath': By.XPATH,
33 | 'link_text': By.LINK_TEXT,
34 | 'partial_link_text': By.PARTIAL_LINK_TEXT,
35 | 'tag': By.TAG_NAME,
36 | 'class_name': By.CLASS_NAME
37 | }
38 |
39 | def __init__(self):
40 | pass
41 |
42 | def __get__(self, instance, owner):
43 | if not instance:
44 | return None
45 | else:
46 | self.driver = instance.driver
47 |
48 | def __getattr__(self, loc):
49 |
50 | if self.mobile_test == True:
51 | if loc in self.locators.keys():
52 | element = self.find_element_by_accessibility_id(self.locators[loc][1])
53 | return element
54 | else:
55 | if loc in self.locators.keys():
56 | locator = (self.TYPE_OF_LOCATORS[self.locators[loc][0].lower()], self.locators[loc][1])
57 | try:
58 | element = WebDriverWait(self.driver, self.timeout).until(
59 | EC.presence_of_element_located(locator)
60 | )
61 | except (StaleElementReferenceException, NoSuchElementException, TimeoutException) as e:
62 | raise ElementNotFoundException(
63 | "An exception of type " + type(e).__name__ +
64 | " occurred. With Element -: " + loc +
65 | " - locator: (" + locator[0] + ", " + locator[1] + ")"
66 | )
67 |
68 | try:
69 | element = WebDriverWait(self.driver, self.timeout).until(
70 | EC.visibility_of_element_located(locator)
71 | )
72 | except (StaleElementReferenceException, NoSuchElementException, TimeoutException) as e:
73 | raise ElementNotVisibleException(
74 | "An exception of type " + type(e).__name__ +
75 | " occurred. With Element -: " + loc +
76 | " - locator: (" + locator[0] + ", " + locator[1] + ")"
77 | )
78 |
79 | element = self.get_web_element(*locator)
80 | element._locator = locator
81 | return element
82 | return super().__getattr__(loc)
83 |
84 | def get_web_element(self, *loc):
85 | element = self.driver.find_element(*loc)
86 | self.highlight_web_element(element)
87 | return element
88 |
89 | def highlight_web_element(self, element):
90 | """
91 | To highlight webElement
92 | :param: WebElement
93 | :return: None
94 | """
95 | if self.highlight:
96 | self.driver.execute_script("arguments[0].style.border='2px ridge #33ffff'", element)
97 |
98 | def select_element_by_text(self, text):
99 | """
100 | Select webElement from dropdown list
101 | :param: Text of Item in dropdown
102 | :return: None
103 | """
104 | select = Select(self)
105 | select.select_by_visible_text(text)
106 |
107 | def select_element_by_index(self, index):
108 | """
109 | Select webElement from dropdown list
110 | :param: Index of Item in dropdown
111 | :return: None
112 | """
113 | select = Select(self)
114 | select.select_by_index(index)
115 |
116 | def select_element_by_value(self, value):
117 | """
118 | Select webElement from dropdown list
119 | :param: value of Item in dropdown
120 | :return: None
121 | """
122 | select = Select(self)
123 | select.select_by_value(value)
124 |
125 | def get_list_item_count(self):
126 | """
127 | Count of Item from Dropdown
128 | :param: None
129 | :return: count
130 | """
131 | select = Select(self)
132 | return len(select.options)
133 |
134 | def get_all_list_item(self):
135 | """
136 | Get list of Item from Dropdown
137 | :param: None
138 | :return: list of items present in dropdown
139 | """
140 | select = Select(self)
141 | list_item = []
142 | for item in select.options:
143 | list_item.append(item.text)
144 | return list_item
145 |
146 | def get_list_selected_item(self):
147 | """
148 | Get list of Selected item in Dropdown
149 | :param: None
150 | :return: list of items selected in dropdown
151 | """
152 | select = Select(self)
153 | list_item = []
154 | for item in select.all_selected_options:
155 | list_item.append(item.text)
156 | return list_item
157 |
158 | def verify_list_item(self, text):
159 | """
160 | Verify text to be present in Dropdown
161 | :param: item to be verify
162 | :return: True / False
163 | """
164 | select = Select(self)
165 | list_item = []
166 | for item in select.options:
167 | if text == item.text:
168 | return True
169 | return False
170 |
171 | def click_button(self):
172 | """
173 | Perform click on webElement
174 | :param: None
175 | :return: webElement
176 | """
177 | self.element_to_be_clickable()
178 | self.click()
179 | return self
180 |
181 | def double_click(self):
182 | """
183 | perform Double click on webElement
184 | :param: None
185 | :return: webElement
186 | """
187 | self.element_to_be_clickable()
188 | ActionChains(self.parent).double_click(self).perform()
189 | return self
190 |
191 | def context_click(self):
192 | """
193 | perform Right click on webElement
194 | Added support for Selenium 4
195 | :param: None
196 | :return: webElement
197 | """
198 | self.element_to_be_clickable()
199 | ActionChains(self.parent).context_click(on_element=self)
200 | return self
201 |
202 | def click_and_hold(self):
203 | """
204 | This method will replace the moveToElement(onElement).clickAndHold()
205 | Added support for Selenium 4
206 | :param: None
207 | :return: webElement
208 | """
209 | self.element_to_be_clickable()
210 | ActionChains(self.parent).click_and_hold(on_element=self)
211 | return self
212 |
213 | def release(self):
214 | """
215 | Releasing a held mouse button on an element.
216 | Added support for Selenium 4
217 | :param: None
218 | :return: webElement
219 | """
220 | ActionChains(self.parent).release(on_element=self)
221 | return self
222 |
223 | def hover_with_offset(self, x, y):
224 | """
225 | Move the mouse by an offset of the specified element. Offsets are relative to the top-left corner of the element.
226 | Added support for Selenium 4
227 | :param: x and y offset
228 | :return: webElement
229 | """
230 | ActionChains(self.parent).move_to_element_with_offset(to_element=self, xoffset=x, yoffset=y)
231 | return self
232 |
233 | def set_text(self, value):
234 | """
235 | type text in input box
236 | :param: Text to be Enter
237 | :return: webElement
238 | """
239 | self.element_to_be_clickable()
240 | self.send_keys(value)
241 | return self
242 |
243 | def get_text(self):
244 | """
245 | get text from input box
246 | :param: None
247 | :return: text from webElement
248 | """
249 | return self.text
250 |
251 | def clear_text(self):
252 | """
253 | Clear text from EditBox
254 | :param: None
255 | :return: None
256 | """
257 | self.clear()
258 |
259 | def hover(self):
260 | """
261 | perform hover operation on webElement
262 | :param: None
263 | :return: None
264 | """
265 | ActionChains(self.parent).move_to_element(self).perform()
266 |
267 | def is_Checked(self):
268 | """
269 | Check Radio button / CheckBox is selected
270 | :param: None
271 | :return: Boolean
272 | """
273 | return self.isSelected()
274 |
275 | def is_Enabled(self):
276 | """
277 | get Enable state of webElement
278 | :param: None
279 | :return: Boolean
280 | """
281 | return self.isEnabled()
282 |
283 | def getAttribute(self, attributeName):
284 | """
285 | get webElement attribute
286 | :param: name of Attribute
287 | :return: webElement attribute value
288 | """
289 | return self.get_attribute(attributeName)
290 |
291 | def setAttribute(self, attributeName, value):
292 | """
293 | set webElement attribute
294 | :param: name of Attribute
295 | :return: webElement attribute value
296 | """
297 | return self.parent.execute_script("arguments[0].setAttribute(" + attributeName + "," + value + ")", self)
298 |
299 | def w3c(self):
300 | return self.w3c
301 |
302 | def element_to_be_clickable(self, timeout=None):
303 | """
304 | Wait till the element to be clickable
305 | """
306 | if timeout is None:
307 | timeout = PageFactory().timeout
308 | return WebDriverWait(self.parent, timeout).until(
309 | EC.element_to_be_clickable(self._locator)
310 | )
311 |
312 | def invisibility_of_element_located(self, timeout=None):
313 | """
314 | Wait till the element to be invisible
315 | """
316 | if timeout is None:
317 | timeout = PageFactory().timeout
318 | return WebDriverWait(self.parent, timeout).until(
319 | EC.invisibility_of_element_located(self._locator)
320 | )
321 |
322 | def visibility_of_element_located(self, timeout=None):
323 | """
324 | Wait till the element to be visible
325 | """
326 | if timeout is None:
327 | timeout = PageFactory().timeout
328 | return WebDriverWait(self.parent, timeout).until(
329 | EC.visibility_of(self)
330 | )
331 |
332 | def execute_script(self, script):
333 | """
334 | Execute JavaScript using web driver on selected web element
335 | :param: Javascript to be execute
336 | :return: None / depends on Script
337 | """
338 | return self.parent.execute_script(script, self)
339 |
340 |
341 | WebElement.click_button = PageFactory.click_button
342 | WebElement.double_click = PageFactory.double_click
343 | WebElement.context_click = PageFactory.context_click
344 | WebElement.click_and_hold = PageFactory.click_and_hold
345 | WebElement.release = PageFactory.release
346 | WebElement.hover_with_offset = PageFactory.hover_with_offset
347 | WebElement.element_to_be_clickable = PageFactory.element_to_be_clickable
348 | WebElement.invisibility_of_element_located = PageFactory.invisibility_of_element_located
349 | WebElement.visibility_of_element_located = PageFactory.visibility_of_element_located
350 | WebElement.set_text = PageFactory.set_text
351 | WebElement.get_text = PageFactory.get_text
352 | WebElement.hover = PageFactory.hover
353 | WebElement.clear_text = PageFactory.clear_text
354 | WebElement.w3c = PageFactory.w3c
355 | WebElement.is_Checked = PageFactory.is_Checked
356 | WebElement.is_Enabled = PageFactory.is_Enabled
357 | WebElement.getAttribute = PageFactory.getAttribute
358 | WebElement.setAttribute = PageFactory.setAttribute
359 | WebElement.select_element_by_text = PageFactory.select_element_by_text
360 | WebElement.select_element_by_index = PageFactory.select_element_by_index
361 | WebElement.select_element_by_value = PageFactory.select_element_by_value
362 | WebElement.get_list_item_count = PageFactory.get_list_item_count
363 | WebElement.get_all_list_item = PageFactory.get_all_list_item
364 | WebElement.get_list_selected_item = PageFactory.get_list_selected_item
365 | WebElement.execute_script = PageFactory.execute_script
366 | WebElement.verify_list_item = PageFactory.verify_list_item
367 |
--------------------------------------------------------------------------------
/seleniumpagefactory/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The MIT License (MIT)
3 | Copyright (c) 2019 Sujit Nayakwadi
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 | SOFTWARE.
19 | """
20 | __VERSION__='0.1'
21 | from seleniumpagefactory.Pagefactory import PageFactory
22 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, Extension ,find_packages
2 | from os import path
3 |
4 | this_directory = path.abspath(path.dirname(__file__))
5 | with open(path.join(this_directory, 'README.md'), encoding='utf-8') as f:
6 | long_description = f.read()
7 |
8 | setup(
9 | name="selenium-page-factory",
10 | version="2.6",
11 | author = "Sujit Nayakwadi",
12 | author_email="nayakwadi_sujit@rediffmail.com",
13 | description="Python library provides page factory approach to implement page object model in selenium",
14 | license="MIT",
15 | keywords="selenium, page object model, pom, pages, page factory",
16 | install_requires=['selenium'],
17 | url="https://github.com/NayakwadiS/selenium-page-factory",
18 | packages=find_packages(),
19 | long_description = long_description,
20 | long_description_content_type='text/markdown',
21 | tests_require=["pytest"],
22 | )
23 |
--------------------------------------------------------------------------------
/tests/Factory_approch.py:
--------------------------------------------------------------------------------
1 | from seleniumpagefactory.Pagefactory import PageFactory
2 | import unittest
3 | from os import environ
4 |
5 | from selenium import webdriver
6 |
7 | class LoginPage(PageFactory):
8 |
9 | def __init__(self,driver):
10 | # It is necessary to to initialise driver as page class member to implement Page Factory
11 | self.driver = driver
12 |
13 | # define locators dictionary where key name will became WebElement using PageFactory
14 | locators = {
15 | "edtUserName": ('ID', 'user_login'),
16 | "edtPassword": ('NAME', 'pwd'),
17 | "btnSignIn": ('XPATH', '//input[@value="Log In"]'),
18 | "lnkPost": ('XPATH','//div[contains(text(),"Posts")]'),
19 | "lstAction": ('ID','bulk-action-selector-top')
20 | }
21 |
22 | def login(self):
23 | # set_text(), click_button() methods are extended methods in PageFactory
24 | self.edtUserName.set_text("opensourcecms") # edtUserName become class variable using PageFactory
25 | self.edtPassword.set_text("opensourcecms")
26 | self.btnSignIn.click_button()
27 |
28 |
29 | class LoginTest(unittest.TestCase):
30 |
31 | def test_Login(self):
32 | driver = webdriver.Chrome("D:\AutomationTool\Resources\drivers\chromedriver.exe")
33 | driver.get("https://s1.demo.opensourcecms.com/wordpress/wp-login.php")
34 | pglogin = LoginPage(driver)
35 | pglogin.edtUserName.set_text("opensourcecms") # edtUserName become class variable using PageFactory
36 | pglogin.edtPassword.set_text("opensourcecms")
37 | pglogin.btnSignIn.click_button()
38 | pglogin.lnkPost.click_button()
39 | print(pglogin.lstAction.get_all_list_item())
40 |
41 |
42 | if __name__ == "__main__":
43 | unittest.main()
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | from seleniumpagefactory.Pagefactory import PageFactory
--------------------------------------------------------------------------------
/tests/selenium_unitest.py:
--------------------------------------------------------------------------------
1 | from seleniumpagefactory.Pagefactory import PageFactory
2 | import pytest
3 | from selenium import webdriver
4 |
5 | class LoginPage(PageFactory):
6 |
7 | def __init__(self,driver):
8 | # It is necessary to to initialise driver as page class member to implement Page Factory
9 | self.driver =driver
10 |
11 | # define locators dictionary where key name will became WebElement using PageFactory
12 | locators = {
13 | "edtUserName": ('ID', 'user_login'),
14 | "edtPassword": ('NAME', 'pwd'),
15 | "btnSignIn": ('XPATH', '//input[@value="Log In"]')
16 | }
17 |
18 | def login(self):
19 | # set_text(), click_button() methods are extended methods in PageFactory
20 | self.edtUserName.set_text("opensourcecms") # edtUserName become class variable using PageFactory
21 | self.edtPassword.set_text("opensourcecms")
22 | self.btnSignIn.click_button()
23 |
24 |
25 | def test_Login(self):
26 | driver = webdriver.Chrome("path for driver exe")
27 | driver.maximize_window()
28 | driver.implicitly_wait(5)
29 | driver.get("https://s1.demo.opensourcecms.com/wordpress/wp-login.php")
30 |
31 | pglogin = LoginPage(driver)
32 | pglogin.login()
33 |
--------------------------------------------------------------------------------
/tests/test_selenium.py:
--------------------------------------------------------------------------------
1 | mport pytest
2 | from selenium import webdriver
3 | from seleniumpagefactory.Pagefactory import PageFactory
4 |
5 | @pytest.fixture
6 | def wp():
7 | driver = webdriver.Chrome()
8 | driver.maximize_window()
9 | driver.get("https://s1.demo.opensourcecms.com/wordpress/wp-login.php")
10 | try:
11 | yield driver
12 | finally:
13 | driver.close()
14 |
15 |
16 | class LoginPage(PageFactory):
17 |
18 | def __init__(self,driver):
19 | # It is necessary to to initialise driver as page class member to implement Page Factory
20 | self.driver =driver
21 |
22 | # define locators dictionary where key name will became WebElement using PageFactory
23 | locators = {
24 | "edtUserName": ('ID', 'user_login'),
25 | "edtPassword": ('NAME', 'pwd'),
26 | "btnSignIn": ('XPATH', '//input[@value="Log In"]'),
27 | "edtUserLoginCssSelector": ('CSS', "#user_login"),
28 | "unknownElement": ("CSS", "somethingWrong"),
29 | }
30 |
31 | def login(self):
32 | # set_text(), click_button() methods are extended methods in PageFactory
33 | self.edtUserName.set_text("opensourcecms") # edtUserName become class variable using PageFactory
34 | self.edtPassword.set_text("opensourcecms")
35 | self.btnSignIn.click_button()
36 |
37 |
38 | def test_Login(wp):
39 | pglogin = LoginPage(wp)
40 | pglogin.login()
41 |
42 |
43 | def test_css_selector_call_twice(wp):
44 | pglogin = LoginPage(wp)
45 | pglogin.edtUserLoginCssSelector.set_text("test")
46 | # CSS selector were not working as expected while calling a CSS SELECTOR twice
47 | # because By.CSS_SELECTOR return a string 'css selector' which is
48 | # not present in TYPE_OF_LOCATORS, also getattr were altering locators
49 | # attribue
50 | pglogin.edtUserLoginCssSelector.set_text("test2")
51 |
52 |
53 | def test_not_found_element(wp):
54 | with pytest.raises(Exception) as ex:
55 | pglogin = LoginPage(wp)
56 | pglogin.timeout = 2
57 | pglogin.unknownElement
58 | assert 'unknownElement - locator: (css selector, somethingWrong)' in str(ex.value)
59 |
--------------------------------------------------------------------------------