Package pom_elements

The Python package pom-elements for a Page Object Model that extends to Page Elements.

pom-elements allows for creating Page Object Models for your testing needs and extending the model to Elements on the Page. The project is currently only supporting Selenium webdriver instance, but is intended to help test writers abstract testing organization around web page development choices.

GITHUB

Examples - Elements

Write tests using any Selenium Locator (i.e. css, id, xpath, name, etc.) of your choosing.

>>> element = MultiElement(
...     css="#recordlist li:nth-child(4)",
...     webdriver=selenium,
...     timeout=5)
...
>>> element.is_visible()
True
>>> element.click()

Or simplify complex xpath arguments with XPathElement.

>>> xpath_element = XPathElement(
...                     webdriver=selenium,
...                     xpath='//a',
...                     foo='bar',
...                     baz='spam',
...                     timeout=3
...                 )
>>> xpath_element.xpath
'//a[@foo="bar"][@baz="spam"]'
>>> xpath_element.is_invisible()
False
>>> xpath_element.can_be_clicked()
True

Examples - Page and PageObject

Organize your tests around a Page.

>>> class GoogleHomePage(Page):
...     search_bar = XPathElemnent(xpath='//input', name="search")
...
>>> google = GoogleHomePage(
...              webdriver=selenium,
...              url='http://google.com'
...           )
>>> google.go()
>>> google.current_url
'https://www.google.com'
>>> google.search_bar.send_keys('pom-elements is cool!')

Or a section of a page as an individual PageObject.

>>> class Header(PageObject):
...     dropdown = XPathElement(xpath'//button', html_class="btn btn-secondary dropdown-toggle")
...
>>> header = Header(webriver=webdriver)
>>> header.dropdown.click()

Putting it all togher allows organizing entire pages with PageObjects and PageElements…

>>> class PageHeader(PageObject):
...     dropdown = MultiElement(css="div > a")
...
>>> class PageFooter(PageObject):
...     logo = XPath(xpath='//img[dat-attrib="best_image"]')
...
>>> class MyPage(PageObject):
...     header = PageHeader()
...     footer = PageFooter()

…Set the webdriver on the Parent PageObject instance…

>>> mypage = MyPage(webdriver=selenium)
...
>>> mypage.header.dropdown.is_visible()
True

…and access the webdriver on all instances of the PageObject or Element set on them.

>>> mypage.footer.click()

Example - HTML Elements

The pom_elements.html module is both a way to interact with all html dom elements, as well a proof of concept for how one could organize classes for their own projects. Not every html element has a direct html Element class, but common selectors do have classes available in this library.

A common use case is to select attributes that live in a specific html div.

>>> from pom_elements.html import Div
>>> elem = Div(html_id="bar", text="Text that resides in a <div> element.")
>>> elem.xath
'//div[id="bar"][contains(text(), "Text that resides in a <div> element.")]'
>>> elem.is_visible()
True

Features

  • Organize your tests to reflect how your web page is desinged (Page Objects).
  • Augment your current tests by using through the organization of Pages, PageObjects, or individual Elements.
  • Allows test writers to bring their favorite locator of choice (MultiElement).
  • Use any number of nested PageObjects without needing to manually hand off a webdriver to each instance.
  • Improve the experience of debugging through geting the Element on every call (Descriptor Protocol).
Expand source code
"""The Python package `pom-elements` for a Page Object Model that extends to Page Elements.

`pom-elements` allows for creating Page Object Models for your testing needs and
extending the model to Elements on the Page. The project is currently only supporting
Selenium webdriver instance, but is intended to help test writers abstract testing
organization around web page development choices.

.. include:: ../documentation.md
"""

from .multi_element import MultiElement
from .page_object import Page, PageObject
from .xpath_element import XPathElement

__all__ = ["MultiElement", "XPathElement", "PageObject", "Page"]
__author__ = "Nick Beaird"
__author_email__ = "nicklasbeaird@gmail.com"
__license__ = "MIT"
__url__ = "https://github.com/nickbeaird/pom-elements"
__version__ = "0.1.1"

Sub-modules

pom_elements.base_element
pom_elements.html

The Python package pom-elements.html module is for interacting with html Elements …

pom_elements.multi_element
pom_elements.page_object
pom_elements.xpath_element

Classes

class MultiElement (webdriver:  = None, timeout: Union[float, NoneType] = 0.5, **kwargs)

Set any Page Element using a Selenium Locators.

Args

webdriver : webdriver
A selenium webdriver instance.
timeout : float
An integer or float. Defaults to default timeout if not set.
css : str
the string represenation of a css selector. Uses By.CSS_SELECTOR.
id_ : str
the html id of the page element. Uses By.ID.
class_name : str
the html class of the page element. Uses By.NAME.
link_text : str
the texts of provided link. Uses By.LINK_TEXT.
partial_link_text : str
the partial text of a link. Uses. By.PARTIAL_LINK_TEXT.
tag : str
the name of the name. Uses By.TAG_NAME.
xpath : str
the xpath query of the page element. Uses. By.XPATH.

Raises

AttributeError
The MultiClass selector must have one locator type and query.
AttributeError
The MultiClass cannot have more then one locator type and query.
Expand source code
class MultiElement(BaseElement):
    """Set any Page Element using a Selenium Locators.

    Args:
        webdriver (webdriver): A selenium webdriver instance.
        timeout (float): An integer or float. Defaults to default timeout if not set.
        css (str): the string represenation of a css selector. Uses By.CSS_SELECTOR.
        id_ (str): the html id of the page element. Uses By.ID.
        class_name (str): the html class of the page element. Uses By.NAME.
        link_text (str): the texts of provided link. Uses By.LINK_TEXT.
        partial_link_text (str): the partial text of a link. Uses. By.PARTIAL_LINK_TEXT.
        tag (str): the name of the name. Uses By.TAG_NAME.
        xpath (str): the xpath query of the page element. Uses. By.XPATH.

    Raises:
        AttributeError: The MultiClass selector must have one locator type and query.
        AttributeError: The MultiClass cannot have more then one locator type and query.
    """

    LOCATOR = {
        "css": By.CSS_SELECTOR,
        "id_": By.ID,
        "name": By.NAME,
        "class_name": By.CLASS_NAME,
        "link_text": By.LINK_TEXT,
        "partial_link_text": By.PARTIAL_LINK_TEXT,
        "tag": By.TAG_NAME,
        "xpath": By.XPATH,
    }
    """The dictionary containing the Selenium LOCATOR reference."""

    def __init__(
        self, webdriver: webdriver = None, timeout: Optional[float] = 0.5, **kwargs
    ) -> None:
        locators = {k: v for k, v in kwargs.items() if k in self.LOCATOR}
        if len(locators) == 0 or kwargs is None:
            raise AttributeError("No attribute was set. Please set a Locator.")
        if len(locators) > 1:
            raise AttributeError(
                "Two attributes set and this can only set a single Locator."
            )
        k, v = next(iter(locators.items()))
        self._locator = (self.LOCATOR[k], v)
        super().__init__(webdriver, timeout)

    @property
    def locator(self) -> Tuple[By, str]:
        """Return the locator set on the class."""
        return self._locator

    @locator.setter
    def locator(self, locator: Union[Tuple[str, str], str]) -> None:
        """Set the locator for the class."""
        raise NotImplementedError(
            "Changing an Element's locator is not avilable at this time."
        )

Ancestors

Class variables

var LOCATOR

The dictionary containing the Selenium LOCATOR reference.

Instance variables

var default_timeout

Inherited from: BaseElement.default_timeout

Get the default timeout.

var locator : Tuple[selenium.webdriver.common.by.By, str]

Return the locator set on the class.

Expand source code
@property
def locator(self) -> Tuple[By, str]:
    """Return the locator set on the class."""
    return self._locator
var webdriver

Inherited from: BaseElement.webdriver

Get the Element's Selenium webdriver instance …

Methods

def can_be_clicked(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.can_be_clicked

Return true if the webelement can be clicked in the time (seconds) provided …

def click(self, timeout: Union[float, NoneType] = None)

Inherited from: BaseElement.click

Click the web element if it is available to be clicked.

def find(self, timeout: Union[float, NoneType] = None) -> selenium.webdriver.remote.webelement.WebElement

Inherited from: BaseElement.find

Returns the defined Selenium WebElement in the provided timeout or raise an error …

def get_all_attributes(self) -> dict

Inherited from: BaseElement.get_all_attributes

Return a dictionary containing all of the attributes of an element.

def get_attribute(self, name)

Inherited from: BaseElement.get_attribute

Returns the attribute of the name provided.

def get_property(self, value: str) -> Any

Inherited from: BaseElement.get_property

Get the property of the element …

def is_displayed(self)

Inherited from: BaseElement.is_displayed

Returns true if the Element is displayed else false.

def is_invisible(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.is_invisible

Return true if the webelement is invisible on the page within the time (seconds) provided …

def is_visible(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.is_visible

Return true if the webelement is visible on the page in the time (seconds) provided …

class Page (webdriver: , url: str)

The class representing all of the methods and interactions of a web page.

A Page represents all of the functionality of the webdriver that is not encapsulated in the PageElement objects. The attached methods are all interactions with the web browser instance, and not elements on a single web page.

Args

webdriver : webdriver
A selenium webdriver instance for the PageObject.
url : str
The url of the the PageObject.
Expand source code
class Page(PageObject):
    """The class representing all of the methods and interactions of a web page.

    A Page represents all of the functionality of the webdriver that is not encapsulated
    in the PageElement objects. The attached methods are all interactions with the web
    browser instance, and not elements on a single web page.

    Args:
        webdriver (webdriver): A selenium webdriver instance for the PageObject.
        url (str): The url of the the PageObject.
    """

    def __init__(self, webdriver: webdriver, url: str) -> None:
        super().__init__(webdriver=webdriver)
        self._url = url

    @property
    def url(self) -> str:
        """Get the url defined for this Page Object."""
        return self._url

    @url.setter
    def url(self, url: str) -> None:
        """Set the url for this instance.

        Args:
            url: The url that this Page should be defined.
        """
        self._url = url

    @property
    def current_url(self) -> str:
        """Return the url of the page currently being displayed by the webdriver."""
        return self.webdriver.current_url

    def go(self) -> None:
        """Navigate to the url set as the url for this Page object."""
        self.webdriver.get(self.url)

    def get(self, url: str) -> None:
        """Navigate to the provided url.

        Args:
            url: A url as a string.
        """
        self.webdriver.get(url)

    def back(self) -> None:
        """Navigate back one page on the web browser."""
        self.webdriver.back()

    def forward(self) -> None:
        """Navigate forward one page on the web browser."""
        self.webdriver.forward()

    def refresh(self) -> None:
        """Refresh the web browser."""
        self.webdriver.refresh()

    @property
    def title(self) -> str:
        """The title of the current web page."""
        return self.webdriver.title

    def quit(self) -> None:
        """Quit the webdriver instance."""
        self.webdriver.quit()

    def maximize_window(self) -> None:
        """Maximize the web browser's window."""
        self.webdriver.maximize_window()

    def minimize_window(self) -> None:
        """Minimize the web browser's window."""
        self.webdriver.minimize_window()

Ancestors

Instance variables

var current_url : str

Return the url of the page currently being displayed by the webdriver.

Expand source code
@property
def current_url(self) -> str:
    """Return the url of the page currently being displayed by the webdriver."""
    return self.webdriver.current_url
var title : str

The title of the current web page.

Expand source code
@property
def title(self) -> str:
    """The title of the current web page."""
    return self.webdriver.title
var url : str

Get the url defined for this Page Object.

Expand source code
@property
def url(self) -> str:
    """Get the url defined for this Page Object."""
    return self._url

Methods

def back(self) -> NoneType

Navigate back one page on the web browser.

Expand source code
def back(self) -> None:
    """Navigate back one page on the web browser."""
    self.webdriver.back()
def forward(self) -> NoneType

Navigate forward one page on the web browser.

Expand source code
def forward(self) -> None:
    """Navigate forward one page on the web browser."""
    self.webdriver.forward()
def get(self, url: str) -> NoneType

Navigate to the provided url.

Args

url
A url as a string.
Expand source code
def get(self, url: str) -> None:
    """Navigate to the provided url.

    Args:
        url: A url as a string.
    """
    self.webdriver.get(url)
def go(self) -> NoneType

Navigate to the url set as the url for this Page object.

Expand source code
def go(self) -> None:
    """Navigate to the url set as the url for this Page object."""
    self.webdriver.get(self.url)
def maximize_window(self) -> NoneType

Maximize the web browser's window.

Expand source code
def maximize_window(self) -> None:
    """Maximize the web browser's window."""
    self.webdriver.maximize_window()
def minimize_window(self) -> NoneType

Minimize the web browser's window.

Expand source code
def minimize_window(self) -> None:
    """Minimize the web browser's window."""
    self.webdriver.minimize_window()
def quit(self) -> NoneType

Quit the webdriver instance.

Expand source code
def quit(self) -> None:
    """Quit the webdriver instance."""
    self.webdriver.quit()
def refresh(self) -> NoneType

Refresh the web browser.

Expand source code
def refresh(self) -> None:
    """Refresh the web browser."""
    self.webdriver.refresh()
class PageObject (webdriver:  = None)

Class that allows for easily outlining your web pages as classes.

Todo

Add type hints for the Selenium library to add Optional[webdriver].

Args

webdriver : webdriver
A selenium webdriver instance for the PageObject.
Expand source code
class PageObject:
    """Class that allows for easily outlining your web pages as classes.

    Todo:
        Add type hints for the Selenium library to add Optional[webdriver].

    Args:
        webdriver (webdriver): A selenium webdriver instance for the PageObject.
    """

    def __init__(self, webdriver: webdriver = None) -> None:
        if webdriver is not None:
            self.webdriver = webdriver

    def __get__(self, instance, owner):
        """Return a PageObject and set the webdriver to the webdriver of the parent PageObject instance.

        The PageObject class allows users to create any number of PageObject instances
        as class variables on a PageObject class and pass the webdriver from parent
        PageObject class to itself.

        Additionally, we are propogating the webdriver as instance variables rather than
        class variables, as this allows us to create any number of PageObject instances
        under a single PageObject representing the Page and use separate webdrivers. For
        example, one test can verify a PageObject with a WebDriver.session_id for Chrome,
        while a separate instantiated PageObject Page can have a WebDriver.session_id
        for Firefox, Safari, or any other browser as available with Selenium.
        """
        instance_webdriver = getattr(instance, "webdriver", None)
        if instance_webdriver is not None:
            self.webdriver = instance_webdriver
        return self

Subclasses

class XPathElement (webdriver:  = None, timeout: int = 5, xpath: str = '', **kwargs)

Generate xpath query and return Selenium WebElements as Page Elements.

Args

webdriver : webdriver
A Selenium webdriver instance.
timeout : float
An integer or float setting the default timeout for all XPath methods.
xpath : str
The xpath can be directly set, or is auto-generated by the tag class variable, and attrbutes provided.
text : str
Searches the provded Element for text that contains this value.
html_INPUT : str
Adds the arg with the html_ removed from this. Useful for class and id.
INPUT_contains : str
Add an xpath query to search for attributes that contain the value listed.

Examples

>>> print(XPath(html_id="foo").xpath)
'//*[@id="foo"]'
>>> print(XPath(xpath='//div/span', data-attrbite='bar', name='baz').xpath)
'//div/span[@data-attribute="bar"][@name="baz"]'
>>> print(XPath(class_contains="foo").xpath)
'//*[contains(@class, "foo")]'
Expand source code
class XPathElement(BaseElement):
    """Generate xpath query and return Selenium WebElements as Page Elements.

    Args:
        webdriver (webdriver): A Selenium webdriver instance.
        timeout (float): An integer or float setting the default timeout for all XPath methods.
        xpath (str): The xpath can be directly set, or is auto-generated by the tag class variable, and attrbutes provided.
        text (str): Searches the provded Element for text that contains this value.
        html_INPUT (str): Adds the arg with the html_ removed from this. Useful for class and id.
        INPUT_contains (str): Add an xpath query to search for attributes that contain the value listed.


    Examples:
        >>> print(XPath(html_id="foo").xpath)
        '//*[@id="foo"]'

        >>> print(XPath(xpath='//div/span', data-attrbite='bar', name='baz').xpath)
        '//div/span[@data-attribute="bar"][@name="baz"]'

        >>> print(XPath(class_contains="foo").xpath)
        '//*[contains(@class, "foo")]'
    """

    tag = "*"
    """tag class attribute is the xpath html tag (i.e. div, span, ul)."""

    def __init__(
        self, webdriver: webdriver = None, timeout: int = 5, xpath: str = "", **kwargs,
    ) -> None:
        self.user_input = kwargs
        self.xpath = xpath
        super().__init__(webdriver, timeout)

    @property
    def locator(self) -> Tuple[By, str]:
        """Return the Xpath Locator element."""
        return (By.XPATH, self.xpath)

    def find(self, timeout: Optional[float] = None) -> WebElement:
        """Return the Selenium WebElement in the provided timeout or raise an error.

        Args:
            timeout: The length of time that we expect a WebElement to be returned within.
            Defaults to the _default_timeout if not set.
        """
        if timeout is None:
            timeout = self.default_timeout

        try:
            wait = WebDriverWait(self.webdriver, timeout)
            elem = wait.until(EC.presence_of_element_located(self.locator))

        except TimeoutException as exc:
            raise AssertionError(
                f"Unable to find {str(self.__repr__())}, xpath: {self.xpath} in the {timeout} seconds."
            ) from exc

        self.web_element = elem
        return elem

    @property
    def xpath(self) -> str:
        """Get the xpath value as a string."""
        return self._xpath

    @xpath.setter
    def xpath(self, xpath: str) -> None:  # noqa: C901
        """Set the xpath value of the string as value.

        Args:
            xpath: The xpath of the page element.
        """
        if not isinstance(xpath, str):
            raise ValueError("xpath must be of type str")

        generated_xpath = ""
        if xpath != "":
            generated_xpath = xpath
        else:
            generated_xpath = f"//{self.tag}"

        # Parse self.user_input and calculate xpath based on values provided.
        for key in self.user_input.keys():
            if key.startswith("html_"):
                arg_strip_html = key[5:]
                generated_xpath += f'[@{arg_strip_html}="{self.user_input[key]}"]'
            elif key == "text":
                generated_xpath += f'[contains(text(), "{self.user_input[key]}")]'
            elif key.endswith("_contains"):
                arg_in_xpath_args = key[:-9]
                generated_xpath += (
                    f'[contains(@{arg_in_xpath_args}, "{self.user_input[key]}")]'
                )
            else:
                generated_xpath += f'[@{key}="{self.user_input[key]}"]'
        self._xpath = generated_xpath

    def __repr__(self) -> str:
        """Return __repr__ of XPathElement."""
        return f'{self.__class__.__name__}(xpath="{str(self.xpath)}")'

Ancestors

Subclasses

Class variables

var tag

tag class attribute is the xpath html tag (i.e. div, span, ul).

Instance variables

var default_timeout

Inherited from: BaseElement.default_timeout

Get the default timeout.

var locator : Tuple[selenium.webdriver.common.by.By, str]

Return the Xpath Locator element.

Expand source code
@property
def locator(self) -> Tuple[By, str]:
    """Return the Xpath Locator element."""
    return (By.XPATH, self.xpath)
var webdriver

Inherited from: BaseElement.webdriver

Get the Element's Selenium webdriver instance …

var xpath : str

Get the xpath value as a string.

Expand source code
@property
def xpath(self) -> str:
    """Get the xpath value as a string."""
    return self._xpath

Methods

def can_be_clicked(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.can_be_clicked

Return true if the webelement can be clicked in the time (seconds) provided …

def click(self, timeout: Union[float, NoneType] = None)

Inherited from: BaseElement.click

Click the web element if it is available to be clicked.

def find(self, timeout: Union[float, NoneType] = None) -> selenium.webdriver.remote.webelement.WebElement

Return the Selenium WebElement in the provided timeout or raise an error.

Args

timeout
The length of time that we expect a WebElement to be returned within.

Defaults to the _default_timeout if not set.

Expand source code
def find(self, timeout: Optional[float] = None) -> WebElement:
    """Return the Selenium WebElement in the provided timeout or raise an error.

    Args:
        timeout: The length of time that we expect a WebElement to be returned within.
        Defaults to the _default_timeout if not set.
    """
    if timeout is None:
        timeout = self.default_timeout

    try:
        wait = WebDriverWait(self.webdriver, timeout)
        elem = wait.until(EC.presence_of_element_located(self.locator))

    except TimeoutException as exc:
        raise AssertionError(
            f"Unable to find {str(self.__repr__())}, xpath: {self.xpath} in the {timeout} seconds."
        ) from exc

    self.web_element = elem
    return elem
def get_all_attributes(self) -> dict

Inherited from: BaseElement.get_all_attributes

Return a dictionary containing all of the attributes of an element.

def get_attribute(self, name)

Inherited from: BaseElement.get_attribute

Returns the attribute of the name provided.

def get_property(self, value: str) -> Any

Inherited from: BaseElement.get_property

Get the property of the element …

def is_displayed(self)

Inherited from: BaseElement.is_displayed

Returns true if the Element is displayed else false.

def is_invisible(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.is_invisible

Return true if the webelement is invisible on the page within the time (seconds) provided …

def is_visible(self, timeout: Union[int, NoneType] = None) -> bool

Inherited from: BaseElement.is_visible

Return true if the webelement is visible on the page in the time (seconds) provided …