jlane9/selenium_data_attributes

View on GitHub
sda/structures.py

Summary

Maintainability
A
0 mins
Test Coverage
# -*- coding: utf-8 -*-
"""sda.structures

.. codeauthor:: John Lane <jlane@fanthreesixty.com>

"""

from __future__ import unicode_literals
import inspect
from six import string_types
import sys
import warnings
from selenium.webdriver.common.by import By
from sda.element import Element, join
from sda.mixins import ClickMixin, InputMixin, SelectMixin, SelectiveMixin, TextMixin, to_int

__all__ = ['Button', 'Div', 'Dropdown', 'Form', 'Image', 'InputCheckbox', 'InputRadio', 'InputText', 'Link',
           'MultiSelect', 'Select', 'Text']


class Button(Element, ClickMixin, TextMixin):
    """The Button implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <button id="someClassId" class="someClass" on-click="javascript.function" >Click Me</button>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <button data-qa-id="some.identifier" id="someClassId" class="someClass" on-click="javascript.function">
                Click Me
            </button>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//button[@data-qa-id="some.identifier"]")
            b = structures.Button(driver, *locator)

            # Example usage
            b.click()
    """

    pass


class Div(Element):
    """The Div implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <div id="someClassId" class="someClass">
                ...
            </div>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <div data-qa-id="some.identifier" id="someClassId" class="someClass">
                ...
            </div>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//button[@data-qa-id="some.identifier"]")
            d = structures.Button(driver, *locator)

    """

    pass


class Dropdown(Element, ClickMixin, TextMixin):
    """The Dropdown implementation

    .. note:: This structure is specifically for a Bootstrap dropdown

    **Example Use:**

    .. code-block:: html

            <div class="dropdown">
                <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Dropdown Example
                <span class="caret"></span></button>
                <ul class="dropdown-menu">
                    <li><a href="#">HTML</a></li>
                    ...
                </ul>
            </div>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value as well as "data-qa-model" with a type.

        .. code-block:: html

            <div class="dropdown" data-qa-id="some.identifier" data-qa-model="dropdown">
                <button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown">Dropdown Example
                <span class="caret"></span></button>
                <ul class="dropdown-menu">
                    <li><a href="#">HTML</a></li>
                    ...
                </ul>
            </div>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//input[@data-qa-id="some.identifier"]")
            d = structures.Dropdown(driver, *locator)

            # Example usage
            d.expand()

    """

    _toggle_xpath = (By.XPATH, '/descendant-or-self::*[(contains(@class, "dropdown-toggle") or '
                               '@ng-mouseover or @ng-click or @on-click)]')

    @property
    def container(self):
        """Dropdown container

        :return:
        """

        xpath = '/following-sibling::*[(contains(@class, "dropdown-menu") or contains(@class, "tree") or @ng-show) ' \
                'and (self::div or self::ul)]'
        child = '/descendant-or-self::*[(contains(@class, "dropdown-menu") or contains(@class, "tree") or @ng-show) ' \
                'and (self::div or self::ul)]'

        xpath_term = join(self.search_term, self.toggle, (By.XPATH, xpath))
        child_term = join(self.search_term, self.toggle, (By.XPATH, child))

        return Div(self.driver, By.XPATH, '|'.join([xpath_term[1], child_term[1]]))

    @property
    def toggle(self):
        """Show/hide toggle button

        :return:
        """

        return Button(self.driver, *join(self.search_term, self._toggle_xpath))

    def _hover_or_click(self, hover):
        """Toggle by hovering or clicking

        :param bool hover: True, hover instead of clicking
        :return:
        """

        if hover:
            self.toggle.hover()

        else:
            self.toggle.click()

    def expand(self, hover=False):
        """Show dropdown

        :return:
        :rtype: bool
        """

        if not self.container.is_displayed():
            self._toggle_or_hover(hover)

            return self.container.wait_until_appears()

        return False

    def collapse(self, hover=False):
        """Hide dropdown

        :return:
        :rtype: bool
        """

        if self.container.is_displayed():
            self._toggle_or_hover(hover)

            return self.container.wait_until_disappears()

        return False


class Field(Element):
    """Field implementation
    """

    def label(self):
        """Returns the label for the input item

        :return: Field label
        :rtype: str
        """

        if self.exists():
            return Text(self.driver, By.XPATH, '//label[@for="{0}"]'.format(str(self.id))).visible_text() \
                if self.id else ''

        return ''


class Form(Element):
    """The Form implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <form id="someForm">
                <input id="someClassId" type="checkbox" class="someClass">
                ...
            </form>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <form id="someForm" data->
                <input id="someClassId" type="checkbox" class="someClass">
                ...
            </form>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//input[@data-qa-id="some-identifier"]")
            form = structures.Form(driver, *locator)

            # Example usage
            field = form.get_field('someClassId')
    """

    def _get_field(self, field_name):
        """Returns field with id `field_name`

        :param basestring field_name: Form field to get
        :return:
        """

        if not isinstance(field_name, string_types):
            raise TypeError

        xpath = '/descendant-or-self::*[((self::input and @type="text") or ' \
                'self::textarea or self::select) and @name="{}"]'
        elements = self.driver.find_elements(*join(self.search_term, (By.XPATH, xpath.format(field_name))))

        if elements:
            return elements[0]

    def get_field(self, field_name):
        """Returns field with id `field_name`

        :param basestring field_name: Form field to get
        :return:
        """

        types = {
            'input': InputText,
            'textarea': InputText,
            'select': Select
        }

        paths = {
            'input': '/descendant-or-self::*[((self::input and @type="text") or self::textarea) and @name="{}"]',
            'textarea': '/descendant-or-self::*[((self::input and @type="text") or self::textarea) and @name="{}"]',
            'select': '/descendant-or-self::*[self::select and @name="{}"]'

        }

        field = self._get_field(field_name)

        if not field:
            return

        field_type = types.get(field.tag_name, None)
        field_path = paths.get(field.tag_name, None)

        if field_type:
            field_type(self.driver, *join(self.search_term, (By.XPATH, field_path.format(field_name))))

        else:
            warnings.warn('{} type not currently supported within form'.format(str(field.tag_name)))


class Image(Element):
    """The Image implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <img id="someClassId" class="someClass" />


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <img data-qa-id="some.identifier" id="someClassId" class="someClass" />


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//img[@data-qa-id="some.identifier"]")
            i = structures.Image(driver, *locator)

            # Returns tag attribute 'src'
            i.source()
    """

    def source(self):
        """Returns image source URL

        :return: Image source URL
        :rtype: str
        """

        return self.src


class InputCheckbox(Field, SelectiveMixin):
    """The InputCheckbox implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <input id="someClassId" type="checkbox" class="someClass">


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <input data-qa-id="some.identifier" id="someClassId" type="checkbox" class="someClass">


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//input[@data-qa-id="some.identifier"]")
            c = structures.InputCheckbox(driver, *locator)

            # Example usage
            c.select()
    """

    pass


class InputRadio(InputCheckbox, SelectiveMixin):
    """The InputRadio implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <input id="someClassId" type="radio" class="someClass">


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <input data-qa-id="some.identifier" id="someClassId" type="radio" class="someClass">


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            r = structures.InputRadio(driver, "//input[@data-qa-id="some.identifier"]")

            # Input Radio inherits from InputCheckbox
            r.select()
    """

    pass


class InputText(Field, InputMixin, ClickMixin):
    """The InputText implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <input id="someClassId" type="text" class="someClass">


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <input data-qa-id="some.identifier" id="someClassId" type="text" class="someClass">


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//input[@data-qa-id="some.identifier"]")
            t = structures.InputText(driver, *locator)

            # Example usage
            t.input('Hello World')
    """

    pass


class Link(Button, ClickMixin, TextMixin):
    """The Link implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <a id="someClassId" class="someClass" href="/some/link/path">Click Me</a>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <a data-qa-id="some.identifier" id="someClassId" class="someClass" href="/some/link/path">Click Me</a>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//a[@data-qa-id="some.identifier"]")
            l = structures.Link(driver, *locator)

            # Inherits from Button
            l.click()
    """

    pass


class MultiSelect(Element):
    """The MultiSelect implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <div id="someClassId" class="someClass" isteven-multi-select input-model="some.model"
            output-model="format.model" helper-elements="filter all none">
                ...
            </div>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value as well as "data-qa-model" with a type.

        .. code-block:: html

            <div data-qa-id="some.identifier" data-qa-model="multiselect" id="someClassId" class="someClass"
            isteven-multi-select input-model="some.model" output-model="format.model" helper-elements="filter all none">
                ...
            </div>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//a[@data-qa-id="some.identifier"]")
            m = structures.MultiSelect(driver, *locator)

            # Example usage
            l.expand()

    """

    @property
    def _container(self):
        """iSteven dropdown container

        :return:
        """

        xpath = '/descendant-or-self::div[contains(@class, "checkboxLayer")]'

        return Div(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _toggle(self):
        """Show/hide button

        :return:
        """

        xpath = '/descendant-or-self::button[contains(@ng-click, "toggle")]'

        return Button(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _select_all(self):
        """Select all button

        :return:
        """

        xpath = '/descendant-or-self::button[contains(@ng-click, "all")]'

        return Button(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _select_none(self):
        """Select none button

        :return:
        """

        xpath = '/descendant-or-self::button[contains(@ng-click, "none")]'

        return Button(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _reset(self):
        """Reset button

        :return:
        """

        xpath = '/descendant-or-self::button[contains(@ng-click, "reset")]'

        return Button(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _filter(self):
        """Search field

        :return:
        """

        xpath = '/descendant-or-self::input[contains(@ng-click, "filter")]'

        return InputText(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    @property
    def _clear(self):
        """Clear search button

        :return:
        """

        xpath = '/descendant-or-self::button[contains(@ng-click, "clear")]'

        return Button(self.driver, *join(self.search_term, (By.XPATH, xpath)))

    def _get_index(self, idx):
        """Return item at index 'i'

        :param str idx: Index
        :return:
        """

        idx = to_int(idx)
        xpath = '/descendant-or-self::div[contains(@ng-repeat, "filteredModel")][{}]'

        if isinstance(idx, int):

            if idx in range(0, len(self.options())):
                return Button(self.driver, *join(self.search_term, (By.XPATH, xpath.format(idx))))

    def _get_text(self, text):
        """Return selection that contains text criteria

        :param str text: Text criteria
        :return:
        """

        xpath = '/descendant-or-self::label[contains(., "{}")]/ancestor::div[contains(@ng-repeat, "filteredModel")]'

        if isinstance(text, string_types):
            return Button(self.driver, *join(self.search_term, (By.XPATH, xpath.format(text))))

    def expand(self):
        """Show iSteven dropdown

        :return:
        :rtype: bool
        """

        if not self._container.is_displayed():

            self._toggle.click()
            return self._container.wait_until_appears()

        return False

    def collapse(self):
        """Hide iSteven dropdown

        :return:
        :rtype: bool
        """

        if self._container.is_displayed():

            self._toggle.click()
            return self._container.wait_until_disappears()

        return False

    def select_all(self):
        """Select all possible selections

        :return:
        :rtype: bool
        """

        self.expand()
        return self._select_all.click()

    def select_none(self):
        """Deselect all selections

        :return:
        :rtype: bool
        """

        self.expand()
        return self._select_none.click()

    def reset(self):
        """Reset selection to default state

        :return:
        :rtype: bool
        """

        self.expand()
        return self._reset.click()

    def search(self, value, clear=True):
        """Filter selections to those matching search criteria

        :param str value: Search criteria
        :param bool clear: Clear previous search criteria
        :return:
        :rtype: bool
        """

        self.expand()
        return self._filter.input(value, clear)

    def clear_search(self):
        """Click clear search button

        :return:
        :rtype: bool
        """

        self.expand()
        return self._clear.click()

    def select_by_index(self, index):
        """Select option at index 'i'

        :param str index: Index
        :return:
        :rtype: bool
        """

        self.expand()

        option = self._get_index(index)

        if option.exists() and 'selected' not in option.class_:
            return option.click()

        return False

    def select_by_text(self, text):
        """Select option that matches text criteria

        :param str text: Text criteria
        :return:
        :rtype: bool
        """

        self.expand()

        option = self._get_text(text)

        if option.exists() and 'selected' not in option.class_:
            option.click()

        return False

    def deselect_by_index(self, index):
        """Deselect option at index 'i'

        :param str index: Index
        :return:
        :rtype: bool
        """

        self.expand()

        option = self._get_index(index)

        if option.exists() and 'selected' in option.class_:
            option.click()

        return False

    def deselect_by_text(self, text):
        """Deselect option that matches text criteria

        :param str text: Text criteria
        :return:
        :rtype: bool
        """

        self.expand()

        option = self._get_text(text)

        if option.exists() and 'selected' in option.class_:
            option.click()

        return False

    def options(self, include_group=True):
        """Return all available options

        :param bool include_group: True, to include groupings
        :return: List of options
        :rtype: list
        """

        if include_group:
            xpath = '/descendant-or-self::div[contains(@ng-repeat, "filteredModel")]//label'

        else:
            xpath = '/descendant-or-self::div[contains(@ng-repeat, "filteredModel") and ' \
                    'not(contains(@class, "multiSelectGroup"))]//label'

        search_term = join(self.search_term, (By.XPATH, xpath))

        return [element.get_attribute('textContent').encode('ascii', 'ignore')
                for element in self.driver.find_elements(*search_term)]

    def selected_options(self):
        """Return all selected options

        :return: List of selected options
        :rtype: list
        """

        search_term = join(self.search_term, (By.XPATH, '/descendant-or-self::div[contains(@ng-repeat, '
                                                        '"filteredModel") and contains(@class, "selected")]//label'))

        return [element.get_attribute('textContent').encode('ascii', 'ignore')
                for element in self.driver.find_elements(*search_term)]


class Select(Element, SelectMixin):
    """The Select implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <select id="someClassId" class="someClass">
                <option value="1">Value 1</option>
                <option value="2">Value 2</option>
                <option value="3">Value 3</option>
                <option value="4">Value 4</option>
            </select>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <select data-qa-id="some.identifier" id="someClassId" class="someClass">
                <option value="1">Value 1</option>
                <option value="2">Value 2</option>
                <option value="3">Value 3</option>
                <option value="4">Value 4</option>
            </select>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//input[@data-qa-id="some.identifier"]")
            s = structures.Select(driver, *locator)

            # Example usage. Returns ['Value 1', 'Value 2', 'Value 3', 'Value 4']
            s.options()
    """

    pass


class Text(Element, TextMixin, ClickMixin):
    """The Text implementation

        **Example Use:**


        Let's take the following example:

        .. code-block:: html

            <p id="someClassId" class="someClass">
                ...
            </p>


        If the user wants to make the code above recognizable to the testing framework, they would add the attribute
        "data-qa-id" with a unique value.

        .. code-block:: html

            <p data-qa-id="some.identifier" id="someClassId" class="someClass">
                ...
            </p>


        An example on how to interact with the element:

        .. code-block:: python

            import selenium
            from selenium.webdriver.common.by import By
            from selenium_data_attributes import structures

            driver = webdriver.FireFox()
            driver.get('http://www.some-url.com')

            locator = (By.XPATH, "//p[@data-qa-id="some.identifier"]")
            d = structures.Text(driver, *locator)

            # Prints text inside text elements
            print d
    """

    pass


MEMBERS = inspect.getmembers(sys.modules[__name__], predicate=lambda o: inspect.isclass(o) and issubclass(o, Element))
TYPES = {_type[0].lower(): _type[1] for _type in MEMBERS}