Add regions module to integration tests
Regions module should contains basic functionality that should be shared among multiple page objects: common tables, forms, notifications etc.. Basically every piece of code that can be reused by multiple classes in a form of a new object should be placed in here. * I would suggest that already created regions should be moved into this module. * I would consider region everything that can be reused by at least two page objects and aim for maximal code reusability in the future. * Every new region should inherit from class BaseRegion located in baseregion.py. Partially implements blueprint: selenium-integration-testing Change-Id: Ib8c20d8833e0830c85030633091663d33de6e3ab
This commit is contained in:
parent
eab348a7a3
commit
6107972e53
@ -22,14 +22,14 @@ class BaseWebObject(object):
|
|||||||
|
|
||||||
def _is_element_present(self, *locator):
|
def _is_element_present(self, *locator):
|
||||||
try:
|
try:
|
||||||
self.driver.find_element(*locator)
|
self._get_element(*locator)
|
||||||
return True
|
return True
|
||||||
except Exceptions.NoSuchElementException:
|
except Exceptions.NoSuchElementException:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_element_visible(self, *locator):
|
def _is_element_visible(self, *locator):
|
||||||
try:
|
try:
|
||||||
return self.driver.find_element(*locator).is_displayed()
|
return self._get_element(*locator).is_displayed()
|
||||||
except (Exceptions.NoSuchElementException,
|
except (Exceptions.NoSuchElementException,
|
||||||
Exceptions.ElementNotVisibleException):
|
Exceptions.ElementNotVisibleException):
|
||||||
return False
|
return False
|
||||||
@ -37,6 +37,9 @@ class BaseWebObject(object):
|
|||||||
def _get_element(self, *locator):
|
def _get_element(self, *locator):
|
||||||
return self.driver.find_element(*locator)
|
return self.driver.find_element(*locator)
|
||||||
|
|
||||||
|
def _get_elements(self, *locator):
|
||||||
|
return self.driver.find_elements(*locator)
|
||||||
|
|
||||||
def _fill_field_element(self, data, field_element):
|
def _fill_field_element(self, data, field_element):
|
||||||
field_element.clear()
|
field_element.clear()
|
||||||
field_element.send_keys(data)
|
field_element.send_keys(data)
|
||||||
|
@ -14,12 +14,15 @@ from selenium.webdriver.common import by
|
|||||||
|
|
||||||
from openstack_dashboard.test.integration_tests import basewebobject
|
from openstack_dashboard.test.integration_tests import basewebobject
|
||||||
from openstack_dashboard.test.integration_tests.pages import pageobject
|
from openstack_dashboard.test.integration_tests.pages import pageobject
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import bars
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import messages
|
||||||
|
|
||||||
|
|
||||||
class BasePage(pageobject.PageObject):
|
class BasePage(pageobject.PageObject):
|
||||||
"""Base class for all dashboard page objects."""
|
"""Base class for all dashboard page objects."""
|
||||||
|
|
||||||
_heading_locator = (by.By.CSS_SELECTOR, "div.page-header > h2")
|
_heading_locator = (by.By.CSS_SELECTOR, 'div.page-header > h2')
|
||||||
|
_error_msg_locator = (by.By.CSS_SELECTOR, 'div.alert-danger.alert')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def heading(self):
|
def heading(self):
|
||||||
@ -27,7 +30,7 @@ class BasePage(pageobject.PageObject):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def topbar(self):
|
def topbar(self):
|
||||||
return BasePage.TopBarRegion(self.driver, self.conf)
|
return bars.TopBarRegion(self.driver, self.conf)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_logged_in(self):
|
def is_logged_in(self):
|
||||||
@ -37,6 +40,13 @@ class BasePage(pageobject.PageObject):
|
|||||||
def navaccordion(self):
|
def navaccordion(self):
|
||||||
return BasePage.NavigationAccordionRegion(self.driver, self.conf)
|
return BasePage.NavigationAccordionRegion(self.driver, self.conf)
|
||||||
|
|
||||||
|
def error_message(self):
|
||||||
|
src_elem = self._get_element(*self._error_msg_locator)
|
||||||
|
return messages.ErrorMessageRegion(self.driver, self.conf, src_elem)
|
||||||
|
|
||||||
|
def is_error_message_present(self):
|
||||||
|
return self._is_element_present(*self._error_msg_locator)
|
||||||
|
|
||||||
def go_to_login_page(self):
|
def go_to_login_page(self):
|
||||||
self.driver.get(self.login_url)
|
self.driver.get(self.login_url)
|
||||||
|
|
||||||
@ -52,50 +62,6 @@ class BasePage(pageobject.PageObject):
|
|||||||
self.topbar.user_dropdown_menu.click()
|
self.topbar.user_dropdown_menu.click()
|
||||||
self.topbar.help_link.click()
|
self.topbar.help_link.click()
|
||||||
|
|
||||||
class TopBarRegion(basewebobject.BaseWebObject):
|
|
||||||
_user_dropdown_menu_locator = (by.By.CSS_SELECTOR,
|
|
||||||
'div#profile_editor_switcher'
|
|
||||||
' > button')
|
|
||||||
_settings_link_locator = (by.By.CSS_SELECTOR,
|
|
||||||
'a[href*="/settings/"]')
|
|
||||||
_help_link_locator = (by.By.CSS_SELECTOR,
|
|
||||||
'ul#editor_list li:nth-of-type(2) > a')
|
|
||||||
_logout_link_locator = (by.By.CSS_SELECTOR,
|
|
||||||
'a[href*="/auth/logout/"]')
|
|
||||||
_openstack_brand_locator = (by.By.CSS_SELECTOR, 'a[href*="/home/"]')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user(self):
|
|
||||||
return self._get_element(*self._user_dropdown_menu_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def brand(self):
|
|
||||||
return self._get_element(*self._openstack_brand_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def logout_link(self):
|
|
||||||
return self._get_element(*self._logout_link_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user_dropdown_menu(self):
|
|
||||||
return self._get_element(*self._user_dropdown_menu_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def settings_link(self):
|
|
||||||
return self._get_element(*self._settings_link_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def help_link(self):
|
|
||||||
return self._get_element(*self._help_link_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_logout_visible(self):
|
|
||||||
return self._is_element_visible(*self._logout_link_locator)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_logged_in(self):
|
|
||||||
return self._is_element_visible(*self._user_dropdown_menu_locator)
|
|
||||||
|
|
||||||
class NavigationAccordionRegion(basewebobject.BaseWebObject):
|
class NavigationAccordionRegion(basewebobject.BaseWebObject):
|
||||||
# TODO(sunlim): change Xpath to CSS
|
# TODO(sunlim): change Xpath to CSS
|
||||||
_project_bar_locator = (
|
_project_bar_locator = (
|
||||||
|
@ -9,13 +9,22 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
from selenium.webdriver.common import by
|
||||||
|
|
||||||
from openstack_dashboard.test.integration_tests.pages import accesssecuritypage
|
from openstack_dashboard.test.integration_tests.pages import accesssecuritypage
|
||||||
from openstack_dashboard.test.integration_tests.pages import basepage
|
from openstack_dashboard.test.integration_tests.pages import basepage
|
||||||
from openstack_dashboard.test.integration_tests.pages import settingspage
|
from openstack_dashboard.test.integration_tests.pages import settingspage
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import forms
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import tables
|
||||||
|
|
||||||
|
|
||||||
class ProjectPage(basepage.BasePage):
|
class ProjectPage(basepage.BasePage):
|
||||||
|
|
||||||
|
_usage_table_locator = (by.By.CSS_SELECTOR, 'table#project_usage')
|
||||||
|
_date_form_locator = (by.By.CSS_SELECTOR, 'form#date_form')
|
||||||
|
|
||||||
|
USAGE_TABLE_ACTIONS = ("download_csv",)
|
||||||
|
|
||||||
def __init__(self, driver, conf):
|
def __init__(self, driver, conf):
|
||||||
super(ProjectPage, self).__init__(driver, conf)
|
super(ProjectPage, self).__init__(driver, conf)
|
||||||
self._page_title = 'Instance Overview'
|
self._page_title = 'Instance Overview'
|
||||||
@ -33,3 +42,14 @@ class ProjectPage(basepage.BasePage):
|
|||||||
self.navaccordion.access_security.click()
|
self.navaccordion.access_security.click()
|
||||||
return accesssecuritypage.AccessSecurityPage(
|
return accesssecuritypage.AccessSecurityPage(
|
||||||
self.driver, self.conf)
|
self.driver, self.conf)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def usage_table(self):
|
||||||
|
src_elem = self._get_element(*self._usage_table_locator)
|
||||||
|
return tables.ActionsTableRegion(self.driver, self.conf, src_elem,
|
||||||
|
self.USAGE_TABLE_ACTIONS)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_form(self):
|
||||||
|
src_elem = self._get_element(*self._date_form_locator)
|
||||||
|
return forms.DateFormRegion(self.driver, self.conf, src_elem)
|
||||||
|
60
openstack_dashboard/test/integration_tests/regions/bars.py
Normal file
60
openstack_dashboard/test/integration_tests/regions/bars.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common import by
|
||||||
|
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import baseregion
|
||||||
|
|
||||||
|
|
||||||
|
class TopBarRegion(baseregion.BaseRegion):
|
||||||
|
_user_dropdown_menu_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'div#profile_editor_switcher'
|
||||||
|
' > button')
|
||||||
|
_settings_link_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'a[href*="/settings/"]')
|
||||||
|
_help_link_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'ul#editor_list li:nth-of-type(2) > a')
|
||||||
|
_logout_link_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'a[href*="/auth/logout/"]')
|
||||||
|
_openstack_brand_locator = (by.By.CSS_SELECTOR, 'a[href*="/home/"]')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self):
|
||||||
|
return self._get_element(*self._user_dropdown_menu_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brand(self):
|
||||||
|
return self._get_element(*self._openstack_brand_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def logout_link(self):
|
||||||
|
return self._get_element(*self._logout_link_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_dropdown_menu(self):
|
||||||
|
return self._get_element(*self._user_dropdown_menu_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def settings_link(self):
|
||||||
|
return self._get_element(*self._settings_link_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def help_link(self):
|
||||||
|
return self._get_element(*self._help_link_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_logout_visible(self):
|
||||||
|
return self._is_element_visible(*self._logout_link_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_logged_in(self):
|
||||||
|
return self._is_element_visible(*self._user_dropdown_menu_locator)
|
@ -0,0 +1,94 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
import types
|
||||||
|
|
||||||
|
from openstack_dashboard.test.integration_tests import basewebobject
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRegion(basewebobject.BaseWebObject):
|
||||||
|
"""Base class for region module
|
||||||
|
* there is necessity to override some basic methods for obtaining elements
|
||||||
|
as in content of regions it is required to do relative searches
|
||||||
|
|
||||||
|
* self.driver cannot be easily replaced with self.src_elem because that
|
||||||
|
would result in functionality loss, self.driver is WebDriver and
|
||||||
|
src_elem is WebElement its usage is different.
|
||||||
|
|
||||||
|
* this does not mean that self.src_elem cannot be self.driver
|
||||||
|
"""
|
||||||
|
|
||||||
|
# private methods
|
||||||
|
def __init__(self, driver, conf, src_elem=None):
|
||||||
|
super(BaseRegion, self).__init__(driver, conf)
|
||||||
|
self.src_elem = src_elem or driver
|
||||||
|
# variable for storing names of dynamic properties and
|
||||||
|
# associated 'getters' - meaning method that are supplying
|
||||||
|
# regions or web elements
|
||||||
|
self._dynamic_properties = {}
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
"""It is not possible to create property bounded just to object
|
||||||
|
and not class at runtime, therefore it is necessary to
|
||||||
|
override __getattr__ and make fake 'properties' by storing them in
|
||||||
|
the protected attribute _dynamic_attributes and returning result
|
||||||
|
of the method associated with the specified attribute.
|
||||||
|
|
||||||
|
This way the feeling of having regions accessed as 'properties'
|
||||||
|
is created, which is one of the requirement of page object pattern.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._dynamic_properties[name]()
|
||||||
|
except KeyError:
|
||||||
|
msg = "'{0}' object has no attribute '{1}'"
|
||||||
|
raise AttributeError(msg.format(type(self).__name__, name))
|
||||||
|
|
||||||
|
# protected methods and classes
|
||||||
|
class _DynamicProperty(object):
|
||||||
|
"""Serves as new property holder."""
|
||||||
|
|
||||||
|
def __init__(self, method, index=None):
|
||||||
|
"""In case object was created with index != None,
|
||||||
|
it is assumed that the result of self.method should be tuple()
|
||||||
|
and just certain index should be returned
|
||||||
|
"""
|
||||||
|
self.method = method
|
||||||
|
self.index = index
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
result = self.method()
|
||||||
|
return result if self.index is None else result[self.index]
|
||||||
|
|
||||||
|
def _init_dynamic_properties(self, new_attr_names, method):
|
||||||
|
"""Create new object's 'properties' at runtime."""
|
||||||
|
for index, new_attr_name in enumerate(new_attr_names):
|
||||||
|
self._init_dynamic_property(new_attr_name, method, index)
|
||||||
|
|
||||||
|
def _init_dynamic_property(self, new_attr_name, method, index=None):
|
||||||
|
"""Create new object's property at runtime. If index argument is
|
||||||
|
supplied it is assumed that method returns tuple() and only element
|
||||||
|
on ${index} position is returned.
|
||||||
|
"""
|
||||||
|
if (new_attr_name in dir(self) or
|
||||||
|
new_attr_name in self._dynamic_properties):
|
||||||
|
raise AttributeError("%s class has already attribute %s."
|
||||||
|
"The new property could not be "
|
||||||
|
"created." % (self.__class__.__name__,
|
||||||
|
new_attr_name))
|
||||||
|
new_method = self.__class__._DynamicProperty(method, index)
|
||||||
|
inst_method = types.MethodType(new_method, self)
|
||||||
|
self._dynamic_properties[new_attr_name] = inst_method
|
||||||
|
|
||||||
|
def _get_element(self, *locator):
|
||||||
|
return self.src_elem.find_element(*locator)
|
||||||
|
|
||||||
|
def _get_elements(self, *locator):
|
||||||
|
return self.src_elem.find_elements(*locator)
|
73
openstack_dashboard/test/integration_tests/regions/forms.py
Normal file
73
openstack_dashboard/test/integration_tests/regions/forms.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from selenium.webdriver.common import by
|
||||||
|
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import baseregion
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFormFieldRegion(baseregion.BaseRegion):
|
||||||
|
"""Base class for form fields classes."""
|
||||||
|
|
||||||
|
_label_locator = None
|
||||||
|
_element_locator = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label(self):
|
||||||
|
return self._get_element(*self._label_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def element(self):
|
||||||
|
return self._get_element(*self._element_locator)
|
||||||
|
|
||||||
|
def is_required(self):
|
||||||
|
classes = self.driver.get_attribute('class')
|
||||||
|
return 'required' in classes
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFormRegion(baseregion.BaseRegion):
|
||||||
|
"""Base class for forms."""
|
||||||
|
|
||||||
|
_submit_locator = (by.By.CSS_SELECTOR, 'button.btn.btn-primary,'
|
||||||
|
' a.btn.btn-primary')
|
||||||
|
|
||||||
|
def submit(self):
|
||||||
|
self._get_element(*self._submit_locator).click()
|
||||||
|
|
||||||
|
|
||||||
|
class DateFormRegion(BaseFormRegion):
|
||||||
|
"""Form that queries data to table that is regularly below the form,
|
||||||
|
typical example is located on Project/Compute/Overview page.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_from_field_locator = (by.By.CSS_SELECTOR, 'input#id_start')
|
||||||
|
_to_field_locator = (by.By.CSS_SELECTOR, 'input#id_end')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def from_date(self):
|
||||||
|
return self._get_element(*self._from_field_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def to_date(self):
|
||||||
|
return self._get_element(*self._to_field_locator)
|
||||||
|
|
||||||
|
def query(self, start, end):
|
||||||
|
self._set_from_field(start)
|
||||||
|
self._set_to_field(end)
|
||||||
|
self.submit()
|
||||||
|
|
||||||
|
def _set_from_field(self, value):
|
||||||
|
self._fill_field_element(value, self.from_date)
|
||||||
|
|
||||||
|
def _set_to_field(self, value):
|
||||||
|
self._fill_field_element(value, self.to_date)
|
@ -0,0 +1,23 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from selenium.webdriver.common import by
|
||||||
|
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import baseregion
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorMessageRegion(baseregion.BaseRegion):
|
||||||
|
|
||||||
|
_close_locator = (by.By.CSS_SELECTOR, 'a.close')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self._get_element(*self._close_locator).click()
|
221
openstack_dashboard/test/integration_tests/regions/tables.py
Normal file
221
openstack_dashboard/test/integration_tests/regions/tables.py
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
from selenium.webdriver.common import by
|
||||||
|
|
||||||
|
from openstack_dashboard.test.integration_tests.regions import baseregion
|
||||||
|
|
||||||
|
|
||||||
|
class RowRegion(baseregion.BaseRegion):
|
||||||
|
"""Classic table row."""
|
||||||
|
|
||||||
|
_cell_locator = (by.By.CSS_SELECTOR, 'td.normal_column')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cells(self):
|
||||||
|
return self._get_elements(*self._cell_locator)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseActionRowRegion(RowRegion):
|
||||||
|
"""Base class for creating ActionRow class derivative."""
|
||||||
|
|
||||||
|
_row_checkbox_locator = (by.By.CSS_SELECTOR, 'td > input')
|
||||||
|
|
||||||
|
def mark(self):
|
||||||
|
chck_box = self._get_element(*self._row_checkbox_locator)
|
||||||
|
chck_box.click()
|
||||||
|
|
||||||
|
|
||||||
|
class BtnActionRowRegion(BaseActionRowRegion):
|
||||||
|
"""Row with buttons in action column."""
|
||||||
|
|
||||||
|
_action_locator = (by.By.CSS_SELECTOR, 'td.actions_column > button')
|
||||||
|
|
||||||
|
def __init__(self, driver, conf, src_elem, action_name):
|
||||||
|
super(BtnActionRowRegion, self).__init__(driver, conf, src_elem)
|
||||||
|
self.action_name = action_name
|
||||||
|
self._init_action()
|
||||||
|
|
||||||
|
def _init_action(self):
|
||||||
|
self._init_dynamic_property(self.action_name, self._get_action)
|
||||||
|
|
||||||
|
def _get_action(self):
|
||||||
|
return self._get_element(*self._action_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action(self):
|
||||||
|
return self._get_action()
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexActionRowRegion(BaseActionRowRegion):
|
||||||
|
"""Row with button and select box in action column."""
|
||||||
|
|
||||||
|
_primary_action_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'td.actions_column > div.btn-group > *.btn')
|
||||||
|
_secondary_actions_dropdown_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'div.btn-group')
|
||||||
|
|
||||||
|
PRIMARY_ACTION = "primary_action"
|
||||||
|
SECONDARY_ACTIONS = "secondary_actions"
|
||||||
|
|
||||||
|
ACTIONS_ERROR_MSG = ("Actions must be supplied in dictionary:"
|
||||||
|
" {%s: 'action_name', '%s': ('action_name',...)}"
|
||||||
|
% (PRIMARY_ACTION, SECONDARY_ACTIONS))
|
||||||
|
|
||||||
|
def __init__(self, driver, conf, src_elem, action_names):
|
||||||
|
super(ComplexActionRowRegion, self).__init__(driver, conf, src_elem)
|
||||||
|
try:
|
||||||
|
self.primary_action_name = action_names[self.PRIMARY_ACTION]
|
||||||
|
self.secondary_action_names = action_names[self.SECONDARY_ACTIONS]
|
||||||
|
self._init_actions()
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
raise AttributeError(self.ACTIONS_ERROR_MSG)
|
||||||
|
|
||||||
|
def _init_actions(self):
|
||||||
|
self._init_dynamic_property(self.primary_action_name,
|
||||||
|
self._get_primary_action)
|
||||||
|
self._init_dynamic_properties(self.secondary_action_names,
|
||||||
|
self._get_secondary_actions)
|
||||||
|
|
||||||
|
def _get_primary_action(self):
|
||||||
|
return self._get_element(*self._primary_action_locator)
|
||||||
|
|
||||||
|
def _get_secondary_actions(self):
|
||||||
|
return self._get_elements(*self._secondary_actions_dropdown_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def primary_action(self):
|
||||||
|
return self._get_primary_action()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def secondary_actions(self):
|
||||||
|
return self._get_secondary_actions()
|
||||||
|
|
||||||
|
|
||||||
|
class BasicTableRegion(baseregion.BaseRegion):
|
||||||
|
"""Basic class representing table object."""
|
||||||
|
|
||||||
|
_heading_locator = (by.By.CSS_SELECTOR, 'h3.table_title')
|
||||||
|
_columns_names_locator = (by.By.CSS_SELECTOR, 'thead > tr > th')
|
||||||
|
_footer_locator = (by.By.CSS_SELECTOR, 'tfoot > tr > td > span')
|
||||||
|
_rows_locator = (by.By.CSS_SELECTOR, 'tbody > tr')
|
||||||
|
_empty_table_locator = (by.By.CSS_SELECTOR, 'tbody > tr.empty')
|
||||||
|
_search_field_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'div.table_search.client > input')
|
||||||
|
_search_button_locator = (by.By.CSS_SELECTOR,
|
||||||
|
'div.table_search.client > button')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def heading(self):
|
||||||
|
return self._get_element(*self._heading_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rows(self):
|
||||||
|
if self._is_element_present(*self._empty_table_locator):
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return self._get_rows()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def column_names(self):
|
||||||
|
return self._get_elements(*self._columns_names_locator)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def footer(self):
|
||||||
|
return self._get_element(*self._footer_locator)
|
||||||
|
|
||||||
|
def filter(self, value):
|
||||||
|
self._set_search_field(value)
|
||||||
|
self._click_search_btn()
|
||||||
|
|
||||||
|
def get_row(self, column_index, text):
|
||||||
|
"""Get row that contains in specified column specified text."""
|
||||||
|
for row in self.rows:
|
||||||
|
if text in row.cells[column_index].text:
|
||||||
|
return row
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _set_search_field(self, value):
|
||||||
|
srch_field = self._get_element(*self._search_field_locator)
|
||||||
|
srch_field.send_keys(value)
|
||||||
|
|
||||||
|
def _click_search_btn(self):
|
||||||
|
btn = self._get_element(*self._search_button_locator)
|
||||||
|
btn.click()
|
||||||
|
|
||||||
|
def _get_rows(self):
|
||||||
|
rows = []
|
||||||
|
for elem in self._get_elements(*self._rows_locator):
|
||||||
|
rows.append(RowRegion(self.driver, self.conf, elem))
|
||||||
|
|
||||||
|
|
||||||
|
class ActionsTableRegion(BasicTableRegion):
|
||||||
|
"""Base class for creating derivative of BasicTableRegion that
|
||||||
|
has some actions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_actions_locator = (by.By.CSS_SELECTOR, 'div.table_actions > button,'
|
||||||
|
' div.table_actions > a')
|
||||||
|
|
||||||
|
# private methods
|
||||||
|
def __init__(self, driver, conf, src_elm, action_names):
|
||||||
|
super(ActionsTableRegion, self).__init__(driver, conf, src_elm)
|
||||||
|
self.action_names = action_names
|
||||||
|
self._init_actions()
|
||||||
|
|
||||||
|
# protected methods
|
||||||
|
def _init_actions(self):
|
||||||
|
"""Create new methods that corresponds to picking table's
|
||||||
|
action buttons.
|
||||||
|
"""
|
||||||
|
self._init_dynamic_properties(self.action_names, self._get_actions)
|
||||||
|
|
||||||
|
def _get_actions(self):
|
||||||
|
return self._get_elements(*self._actions_locator)
|
||||||
|
|
||||||
|
# properties
|
||||||
|
@property
|
||||||
|
def actions(self):
|
||||||
|
return self._get_actions()
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleActionsTableRegion(ActionsTableRegion):
|
||||||
|
"""Table which rows has buttons in action column."""
|
||||||
|
|
||||||
|
def __init__(self, driver, conf, src_elm, action_names, row_action_name):
|
||||||
|
super(SimpleActionsTableRegion, self).__init__(driver, conf, src_elm,
|
||||||
|
action_names)
|
||||||
|
self.row_action_name = row_action_name
|
||||||
|
|
||||||
|
def _get_rows(self):
|
||||||
|
rows = []
|
||||||
|
for elem in self._get_elements(*self._rows_locator):
|
||||||
|
rows.append(BtnActionRowRegion(self.driver, self.conf, elem,
|
||||||
|
self.row_action_name))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexActionTableRegion(ActionsTableRegion):
|
||||||
|
"""Table which has button and selectbox in the action column."""
|
||||||
|
|
||||||
|
def __init__(self, driver, conf, src_elm, action_names, row_action_names):
|
||||||
|
super(ComplexActionTableRegion, self).__init__(driver, conf, src_elm,
|
||||||
|
action_names)
|
||||||
|
self.row_action_names = row_action_names
|
||||||
|
|
||||||
|
def _get_rows(self):
|
||||||
|
rows = []
|
||||||
|
for elem in self._get_elements(*self._rows_locator):
|
||||||
|
rows.append(ComplexActionRowRegion(self.driver, self.conf, elem,
|
||||||
|
self.row_action_names))
|
||||||
|
return rows
|
Loading…
Reference in New Issue
Block a user