PART 2: creating wrapper for the ui elements
BUILDING TEST AUTOMATION FRAMEWORK WITH TEST JUNKIE & SELENIUM WEBDRIVER
THE GOAL
Create a wrapper to abstract WebDriver API and generalize operations on UI elements.
THE HOW
I'm going to provide the code snippet bellow for the wrapper object. Feel free to copy-paste the
code into the UiObject.py
file. I wont be explaining what each
method does as it is documented in the code and the code itself is very straight forward. Besides,
you made it through part 1, you wont have any issues understanding this.
# coding=utf-8
from selenium.webdriver import ActionChains
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
from src.pages.Browser import Browser
class UiObject:
def __init__(self, by, locator, **kwargs):
self.__by = by
self.__locator = locator
self.__web_element = kwargs.get("web_element", None)
@staticmethod
def from_web_element(web_element):
"""
If you want to initiate UiObject from a WebElement:
aka return of driver.find_element(By.SOMETHING, "//something")
Use this method.
:param web_element: WebElement object
:return: UiObject object instance
"""
return UiObject(by=None, locator=None, web_element=web_element)
def get_element(self, wait=10):
"""
Will use WebDriver to locate element in the DOM
:param wait: INT, If the element is not present on the page at
the time of this call, how long you want to wait for it
:return: WebElement object
"""
if self.__web_element:
return self.__web_element
self.wait_to_appear(wait)
return Browser.get_driver().find_element(self.by, self.locator)
def get_elements(self, wait=10):
"""
Will use WebDriver to locate elements in the DOM
:param wait: INT, If the element is not present on the page at
the time of this call, how long you want to wait for it
:return: LIST of WebElement objects
"""
if self.__web_element:
return [self.__web_element]
self.wait_to_appear(wait)
return Browser.get_driver().find_elements(self.by, self.locator)
@property
def locator(self):
"""
:return: STRING, locator this object was initialize with
"""
return self.__locator
@property
def by(self):
"""
:return: STRING, By ID this object was initialize with aka By.XPATH or By.ID
"""
return self.__by
def exists(self, wait=1):
"""
:param wait: INT, seconds to wait before returning verdict
:return: BOOLEAN, if object currently exists in the DOM
"""
try:
WebDriverWait(Browser.get_driver(), wait).until(
EC.presence_of_element_located((self.by, self.locator)))
return True
except:
return False
def visible(self, wait=1):
"""
:param wait: INT, seconds to wait before returning verdict
:return: BOOLEAN, if object is currently visible on the page
"""
try:
WebDriverWait(Browser.get_driver(), wait).until(
EC.invisibility_of_element_located((self.by, self.locator)))
return False
except:
return True
def clickable(self, wait=1):
"""
:param wait: INT, seconds to wait before returning verdict
:return: BOOLEAN, if object exists in the DOM
"""
try:
WebDriverWait(Browser.get_driver(), wait).until(
EC.element_to_be_clickable((self.by, self.locator)))
return True
except:
return False
def wait_to_appear(self, wait=10):
"""
:param wait: INT, how long you want to wait for the element to appear
:return: self (UiObject)
"""
if self.exists(wait):
return self
raise AssertionError("Locator did not appear: {} in {} seconds!"
.format(self.locator, wait))
def wait_to_disappear(self, wait=10):
"""
:param wait: INT, how long you want to wait for the element to disappear
:return: self (UiObject)
"""
if not self.visible(wait):
return self
raise AssertionError("Locator did not disappear: {} in {} seconds!"
.format(self.locator, wait))
def wait_to_be_clickable(self, wait=10):
"""
:param wait: INT, how long you want to wait for the element to be click-able
:return: self (UiObject)
"""
if self.clickable(wait):
return self
if self.exists():
raise AssertionError("Locator did not become click-able: {} in {} seconds"
.format(self.locator, wait))
raise AssertionError("Locator does not exist: {}".format(self.locator))
def get_text(self, encoding=None, wait=10):
"""
:param encoding: STRING, aka "utf-8", if encoding is provided, the text will be
automatically encoded and returned to the caller
:param wait: INT, how long you want to wait for the element to appear
:return: STRING, text value
"""
text = self.get_element(wait).text
return text.encode(encoding) if encoding else text
def set_text(self, value, wait=10):
"""
:param value: STRING, text value to type on the element
:param wait: INT, how long you want to wait for the element to appear
:return: STRING, text value
"""
self.get_element(wait).clear()
self.get_element(wait).send_keys(value)
return self
def press_key(self, key, use_ac=False, wait=10):
"""
:param key: STRING, special key code aka Keys.ENTER
:param use_ac: BOOLEAN, if you want to use ActionChains for this operation
:param wait: INT, how long you want to wait for the element to appear
:return: self (UiObject)
"""
if use_ac:
ActionChains(Browser.get_driver()).send_keys(key).perform()
else:
self.get_element(wait).send_keys(key)
return self
def get_attribute(self, value, wait=10):
"""
:param value: STRING, aka "class" or "name" etc
:param wait: INT, how long you want to wait for the element to appear
:return: STRING, attribute value as text
"""
return self.get_element(wait).get_attribute(value)
def click(self, use_ac=False, wait=10):
"""
:param use_ac: BOOLEAN, if you want to use ActionChains for this operation
:param wait: INT, how long you want to wait for the element to appear
:return:
"""
if use_ac:
ActionChains(Browser.get_driver()).move_to_element(
self.get_element(wait)).click().perform()
else:
self.get_element(wait).click()
return self
Just looking at the method names you already figured out that this class allows you to interact
with DOM elements. I tried to minimize the number of methods to what is needed
for this class to allow me to test my website and to get the scope across as well.
However, it can be expanded to have more functionality.
Just remember, when you decide to add more functionality to the UiObject class, that it only deals
with the DOM elements and the scope of functionality should not be expanded beyond that.
TEST IT
Again, I will use Google to test this out but this time I'm going to run queries on the UI
and submit them using ENTER or a good ol mouse click on the search icon.
This way you can see UiObject
in action and how much cleaner the
code looks when we use this wrapper.
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from random import randint
import time
search_field = UiObject(By.NAME, "q")
search_icon = UiObject(By.XPATH, "//button[@jsname = 'Tg7LZd']")
driver = Browser.get_driver()
driver.get("https://google.com")
for query in ["test automation with Test Junkie",
"Test Junkie with Selenium WebDriver",
"Test Junkie tutorials"]:
search_field.set_text(query)
if not search_icon.exists():
search_field.press_key(Keys.ENTER)
else:
search_icon.click()
time.sleep(randint(1, 5))
Now we are ready to start creating Page Objects!
Next >>