Tutorials / Selenium WebDriver series
Part 2: Wrapping Selenium UI elements
Raw WebDriver code repeats the same three steps for every element: wait for it, find it, act on it. A small UiObject class does that once, so page objects and tests only say what to do.
The UiObject class
A UiObject is a locator plus the operations you need on it. It doesn't look the element up until you use it, so page objects can define all their elements up front, before the page is even open:
src/pages/UiObject.pypython
from selenium.common.exceptions import TimeoutException 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, web_element=None): self.__by = by self.__locator = locator self.__web_element = web_element @staticmethod def from_web_element(web_element): # wrap an element you already found, e.g. one of get_elements() return UiObject(by=None, locator=None, web_element=web_element) @property def by(self): return self.__by @property def locator(self): return self.__locator def __wait(self, condition, wait): try: WebDriverWait(Browser.get_driver(), wait).until(condition((self.by, self.locator))) return True except TimeoutException: return False def get_element(self, wait=10): if self.__web_element is not None: 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): if self.__web_element is not None: return [self.__web_element] self.wait_to_appear(wait) return Browser.get_driver().find_elements(self.by, self.locator) def exists(self, wait=1): return self.__wait(EC.presence_of_element_located, wait) def visible(self, wait=1): return self.__wait(EC.visibility_of_element_located, wait) def clickable(self, wait=1): return self.__wait(EC.element_to_be_clickable, wait) def wait_to_appear(self, wait=10): 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): if self.__wait(EC.invisibility_of_element_located, wait): return self raise AssertionError("Locator did not disappear: {} in {} seconds".format(self.locator, wait)) def wait_to_be_clickable(self, wait=10): if self.clickable(wait): return self if self.exists(): raise AssertionError("Locator did not become clickable: {} in {} seconds".format(self.locator, wait)) raise AssertionError("Locator does not exist: {}".format(self.locator)) def get_text(self, wait=10): return self.get_element(wait).text def set_text(self, value, wait=10): element = self.get_element(wait) element.clear() element.send_keys(value) return self def press_key(self, key, use_ac=False, wait=10): 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, name, wait=10): return self.get_element(wait).get_attribute(name) def click(self, use_ac=False, wait=10): if use_ac: ActionChains(Browser.get_driver()).move_to_element(self.get_element(wait)).click().perform() elif self.__web_element is not None: self.__web_element.click() else: self.wait_to_be_clickable(wait) self.get_element(wait).click() return self
- Every lookup waits first:
get_element()waits up to 10 seconds for the element to appear,click()for it to be clickable. Tests don't needtime.sleep(). exists(),visible()andclickable()answer a question and never raise; thewait_to_...methods raise a readableAssertionErrorinstead, which Test Junkie reports as a failure.from_web_element()wraps an element you already found, e.g. one of several matches fromget_elements().
Keep it about elements
UiObject only deals with elements on the page. Navigation, cookies and tabs belong in Browser; what a page can do belongs in its page object (part 3).Try it
demo_part2.pypython
from selenium.webdriver.common.by import By from src.pages.Browser import Browser from src.pages.UiObject import UiObject from src.utils.Settings import Settings docs_link = UiObject(By.XPATH, "//nav//a[normalize-space() = 'DOCUMENTATION']") first_heading = UiObject(By.TAG_NAME, "h1") Browser.get_driver().get(Settings.DOMAIN) print("home page h1:", first_heading.get_text()) docs_link.click() print("after the click:", Browser.get_driver().current_url) print("left menu visible:", UiObject(By.ID, "leftCol").visible()) Browser.shutdown()
python demo_part2.pytext
home page h1: The Python test runner built for precision. after the click: https://www.test-junkie.com/documentation/ left menu visible: True