Do not match table actions by ordering in integration tests
Instead of this match them by action <a> tag's id, which is composed: * from table name, fixed part and action name for table-level actions and * from row id, fixed part and action name for row-level actions. Doing so may make action names in page objects less readable as they have to be the same as real object name (which are rather terse). Implements blueprint: integration-tests-hardening Change-Id: I3f92ef4cfd098d080199350cbf5e6061aa050907
This commit is contained in:
parent
f6436bbefd
commit
d25d4d2b0d
openstack_dashboard/test/integration_tests
pages
admin/system
identity
project/compute
regions
@ -23,7 +23,8 @@ class FlavorsPage(basepage.BaseNavigationPage):
|
||||
|
||||
_flavors_table_locator = (by.By.ID, 'flavors')
|
||||
|
||||
FLAVORS_TABLE_ACTIONS = ("create_flavor", "delete_flavors")
|
||||
FLAVORS_TABLE_NAME = "flavors"
|
||||
FLAVORS_TABLE_ACTIONS = ("create", "delete")
|
||||
FLAVORS_TABLE_ROW_ACTIONS = {
|
||||
tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_flavor",
|
||||
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
|
||||
@ -47,6 +48,7 @@ class FlavorsPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._flavors_table_locator)
|
||||
return tables.ComplexActionTableRegion(self.driver,
|
||||
self.conf, src_elem,
|
||||
self.FLAVORS_TABLE_NAME,
|
||||
self.FLAVORS_TABLE_ACTIONS,
|
||||
self.FLAVORS_TABLE_ROW_ACTIONS)
|
||||
|
||||
@ -61,7 +63,7 @@ class FlavorsPage(basepage.BaseNavigationPage):
|
||||
|
||||
def create_flavor(self, name, id_=DEFAULT_ID, vcpus=None, ram=None,
|
||||
root_disk=None, ephemeral_disk=None, swap_disk=None):
|
||||
self.flavors_table.create_flavor.click()
|
||||
self.flavors_table.create.click()
|
||||
self.create_flavor_form.name.text = name
|
||||
if id_ is not None:
|
||||
self.create_flavor_form.flavor_id.text = id_
|
||||
@ -76,7 +78,7 @@ class FlavorsPage(basepage.BaseNavigationPage):
|
||||
def delete_flavor(self, name):
|
||||
row = self._get_row_with_flavor_name(name)
|
||||
row.mark()
|
||||
self.flavors_table.delete_flavors.click()
|
||||
self.flavors_table.delete.click()
|
||||
self.confirm_delete_flavors_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
|
@ -35,7 +35,8 @@ class ProjectsPage(basepage.BaseNavigationPage):
|
||||
|
||||
DEFAULT_ENABLED = True
|
||||
PROJECTS_TABLE_NAME_COLUMN_INDEX = 0
|
||||
PROJECTS_TABLE_ACTIONS = ("create_project", "delete_projects")
|
||||
PROJECTS_TABLE_NAME = "tenants"
|
||||
PROJECTS_TABLE_ACTIONS = ("create", "delete")
|
||||
PROJECTS_TABLE_ROW_ACTIONS = {
|
||||
tables.ComplexActionRowRegion.PRIMARY_ACTION: "manage_members",
|
||||
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
|
||||
@ -58,6 +59,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._projects_table_locator)
|
||||
return tables.ComplexActionTableRegion(self.driver,
|
||||
self.conf, src_elem,
|
||||
self.PROJECTS_TABLE_NAME,
|
||||
self.PROJECTS_TABLE_ACTIONS,
|
||||
self.PROJECTS_TABLE_ROW_ACTIONS
|
||||
)
|
||||
@ -94,7 +96,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
|
||||
|
||||
def create_project(self, project_name, description=None,
|
||||
is_enabled=DEFAULT_ENABLED):
|
||||
self.projects_table.create_project.click()
|
||||
self.projects_table.create.click()
|
||||
self.create_project_form.name.text = project_name
|
||||
if description is not None:
|
||||
self.create_project_form.description.text = description
|
||||
@ -106,7 +108,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
|
||||
def delete_project(self, project_name):
|
||||
row = self._get_row_with_project_name(project_name)
|
||||
row.mark()
|
||||
self.projects_table.delete_projects.click()
|
||||
self.projects_table.delete.click()
|
||||
self.delete_project_submit_button.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
|
@ -23,7 +23,8 @@ class UsersPage(basepage.BaseNavigationPage):
|
||||
|
||||
USERS_TABLE_NAME_COLUMN_INDEX = 0
|
||||
|
||||
USERS_TABLE_ACTIONS = ("create_user", "delete_users")
|
||||
USERS_TABLE_NAME = "users"
|
||||
USERS_TABLE_ACTIONS = ("create", "delete")
|
||||
|
||||
USERS_TABLE_ROW_ACTIONS = {
|
||||
tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_user",
|
||||
@ -47,6 +48,7 @@ class UsersPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._users_table_locator)
|
||||
return tables.ComplexActionTableRegion(self.driver,
|
||||
self.conf, src_elem,
|
||||
self.USERS_TABLE_NAME,
|
||||
self.USERS_TABLE_ACTIONS,
|
||||
self.USERS_TABLE_ROW_ACTIONS
|
||||
)
|
||||
@ -62,7 +64,7 @@ class UsersPage(basepage.BaseNavigationPage):
|
||||
|
||||
def create_user(self, name, password,
|
||||
project, role, email=None):
|
||||
self.users_table.create_user.click()
|
||||
self.users_table.create.click()
|
||||
self.create_user_form.name.text = name
|
||||
if email is not None:
|
||||
self.create_user_form.email.text = email
|
||||
@ -76,7 +78,7 @@ class UsersPage(basepage.BaseNavigationPage):
|
||||
def delete_user(self, name):
|
||||
row = self._get_row_with_user_name(name)
|
||||
row.mark()
|
||||
self.users_table.delete_users.click()
|
||||
self.users_table.delete.click()
|
||||
self.confirm_delete_users_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
|
@ -30,8 +30,8 @@ class FloatingipsPage(basepage.BaseNavigationPage):
|
||||
_floatingips_fadein_popup_locator = (
|
||||
by.By.CSS_SELECTOR, '.alert.alert-success.alert-dismissable.fade.in>p')
|
||||
|
||||
FLOATING_IPS_TABLE_ACTIONS = ("allocate_ip_to_project",
|
||||
"release_floating_ips")
|
||||
FLOATING_IPS_TABLE_NAME = 'floating_ips'
|
||||
FLOATING_IPS_TABLE_ACTIONS = ("allocate", "release")
|
||||
FLOATING_IPS_TABLE_ROW_ACTION = {
|
||||
tables.ComplexActionRowRegion.PRIMARY_ACTION: "associate",
|
||||
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
|
||||
@ -51,6 +51,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._floating_ips_table_locator)
|
||||
return tables.ComplexActionTableRegion(
|
||||
self.driver, self.conf, src_elem,
|
||||
self.FLOATING_IPS_TABLE_NAME,
|
||||
self.FLOATING_IPS_TABLE_ACTIONS,
|
||||
self.FLOATING_IPS_TABLE_ROW_ACTION)
|
||||
|
||||
@ -59,7 +60,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
|
||||
return forms.BaseFormRegion(self.driver, self.conf, None)
|
||||
|
||||
def allocate_floatingip(self):
|
||||
self.floatingips_table.allocate_ip_to_project.click()
|
||||
self.floatingips_table.allocate.click()
|
||||
self.floatingip_form.submit.click()
|
||||
ip = re.compile('(([2][5][0-5]\.)|([2][0-4][0-9]\.)'
|
||||
+ '|([0-1]?[0-9]?[0-9]\.)){3}(([2][5][0-5])|'
|
||||
@ -73,7 +74,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
|
||||
def release_floatingip(self, floatingip):
|
||||
row = self._get_row_with_floatingip(floatingip)
|
||||
row.mark()
|
||||
self.floatingips_table.release_floating_ips.click()
|
||||
self.floatingips_table.release.click()
|
||||
self.floatingip_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
|
11
openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py
11
openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py
@ -24,9 +24,9 @@ class KeypairsPage(basepage.BaseNavigationPage):
|
||||
|
||||
_key_pairs_table_locator = (by.By.ID, 'keypairs')
|
||||
|
||||
KEY_PAIRS_TABLE_ACTIONS = ("create_key_pair", "import_key_pair",
|
||||
"delete_key_pair")
|
||||
KEY_PAIRS_TABLE_ROW_ACTION = "delete_key_pair"
|
||||
KEY_PAIRS_TABLE_NAME = "keypairs"
|
||||
KEY_PAIRS_TABLE_ACTIONS = ("create", "import", "delete")
|
||||
KEY_PAIRS_TABLE_ROW_ACTION = "delete"
|
||||
KEY_PAIRS_TABLE_NAME_COLUMN_INDEX = 0
|
||||
|
||||
CREATE_KEY_PAIR_FORM_FIELDS = ('name',)
|
||||
@ -44,6 +44,7 @@ class KeypairsPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._key_pairs_table_locator)
|
||||
return tables.SimpleActionsTableRegion(self.driver, self.conf,
|
||||
src_elem,
|
||||
self.KEY_PAIRS_TABLE_NAME,
|
||||
self.KEY_PAIRS_TABLE_ACTIONS,
|
||||
self.KEY_PAIRS_TABLE_ROW_ACTION)
|
||||
|
||||
@ -60,12 +61,12 @@ class KeypairsPage(basepage.BaseNavigationPage):
|
||||
return bool(self._get_row_with_keypair_name(name))
|
||||
|
||||
def create_keypair(self, keypair_name):
|
||||
self.keypairs_table.create_key_pair.click()
|
||||
self.keypairs_table.create.click()
|
||||
self.create_keypair_form.name.text = keypair_name
|
||||
self.create_keypair_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
def delete_keypair(self, name):
|
||||
self._get_row_with_keypair_name(name).delete_key_pair.click()
|
||||
self._get_row_with_keypair_name(name).delete.click()
|
||||
self.delete_keypair_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
@ -29,7 +29,8 @@ class ImagesPage(basepage.BaseNavigationPage):
|
||||
|
||||
_images_table_locator = (by.By.ID, 'images')
|
||||
|
||||
IMAGES_TABLE_ACTIONS = ("create_image", "delete_images")
|
||||
IMAGES_TABLE_NAME = "images"
|
||||
IMAGES_TABLE_ACTIONS = ("create", "delete")
|
||||
IMAGES_TABLE_ROW_ACTIONS = {
|
||||
tables.ComplexActionRowRegion.PRIMARY_ACTION: "launch",
|
||||
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ("create_volume",)
|
||||
@ -55,6 +56,7 @@ class ImagesPage(basepage.BaseNavigationPage):
|
||||
src_elem = self._get_element(*self._images_table_locator)
|
||||
return tables.ComplexActionTableRegion(self.driver,
|
||||
self.conf, src_elem,
|
||||
self.IMAGES_TABLE_NAME,
|
||||
self.IMAGES_TABLE_ACTIONS,
|
||||
self.IMAGES_TABLE_ROW_ACTIONS
|
||||
)
|
||||
@ -74,7 +76,7 @@ class ImagesPage(basepage.BaseNavigationPage):
|
||||
image_format=DEFAULT_IMAGE_FORMAT,
|
||||
is_public=DEFAULT_ACCESSIBILITY,
|
||||
is_protected=DEFAULT_PROTECTION):
|
||||
self.images_table.create_image.click()
|
||||
self.images_table.create.click()
|
||||
self.create_image_form.name.text = name
|
||||
if description is not None:
|
||||
self.create_image_form.description.text = description
|
||||
@ -98,7 +100,7 @@ class ImagesPage(basepage.BaseNavigationPage):
|
||||
def delete_image(self, name):
|
||||
row = self._get_row_with_image_name(name)
|
||||
row.mark()
|
||||
self.images_table.delete_images.click()
|
||||
self.images_table.delete.click()
|
||||
self.confirm_delete_images_form.submit.click()
|
||||
self.wait_till_popups_disappear()
|
||||
|
||||
|
@ -20,7 +20,7 @@ class OverviewPage(basepage.BaseNavigationPage):
|
||||
_usage_table_locator = (by.By.ID, 'project_usage')
|
||||
_date_form_locator = (by.By.ID, 'date_form')
|
||||
|
||||
USAGE_TABLE_ACTIONS = ("download_csv",)
|
||||
USAGE_TABLE_ACTIONS = ("csv_summary",)
|
||||
|
||||
def __init__(self, driver, conf):
|
||||
super(OverviewPage, self).__init__(driver, conf)
|
||||
|
@ -65,31 +65,48 @@ class BaseRegion(basewebobject.BaseWebObject):
|
||||
class _DynamicProperty(object):
|
||||
"""Serves as new property holder."""
|
||||
|
||||
def __init__(self, method, index=None, name=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
|
||||
def __init__(self, method, name=None, id_pattern=None):
|
||||
"""Invocation of `method` should return either single property, or
|
||||
a dictionary of properties, or a list of them.
|
||||
|
||||
In case it's single, neither name, nor id_pattern is required.
|
||||
|
||||
In case it's a dictionary, it's expected that it has a value for
|
||||
the key equal to `name` argument. That's a standard way of
|
||||
fetching a form field).
|
||||
|
||||
In case it's a list, the element with an id equal to the result of
|
||||
`id_pattern % name` is supposed to be there. That's a standard way
|
||||
of fetching a table action (either table-wise or row-wise).
|
||||
"""
|
||||
self.method = method
|
||||
self.index = index
|
||||
self.name = name
|
||||
self.id_pattern = id_pattern
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
result = self.method()
|
||||
if isinstance(result, dict):
|
||||
return result if self.name is None else result[self.name]
|
||||
if self.name is None:
|
||||
return result
|
||||
else:
|
||||
return result if self.index is None else result[self.index]
|
||||
if isinstance(result, list) and self.id_pattern is not None:
|
||||
# NOTE(tsufiev): map table actions to action names using
|
||||
# action tag's ids
|
||||
actual_id = self.id_pattern % self.name
|
||||
result = {self.name: entry for entry in result
|
||||
if entry.get_attribute('id') == actual_id}
|
||||
if isinstance(result, dict):
|
||||
return result[self.name]
|
||||
return result
|
||||
|
||||
def _init_dynamic_properties(self, new_attr_names, method):
|
||||
def _init_dynamic_properties(self, new_attr_names, method,
|
||||
id_pattern=None):
|
||||
"""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)
|
||||
for new_attr_name in new_attr_names:
|
||||
self._init_dynamic_property(new_attr_name, method, id_pattern)
|
||||
|
||||
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.
|
||||
def _init_dynamic_property(self, new_attr_name, method, id_pattern=None):
|
||||
"""Create new object's property at runtime. See _DynamicProperty's
|
||||
__init__ docstring for a description of arguments.
|
||||
"""
|
||||
if (new_attr_name in dir(self) or
|
||||
new_attr_name in self._dynamic_properties):
|
||||
@ -97,8 +114,8 @@ class BaseRegion(basewebobject.BaseWebObject):
|
||||
"The new property could not be "
|
||||
"created." % (self.__class__.__name__,
|
||||
new_attr_name))
|
||||
new_method = self.__class__._DynamicProperty(method, index,
|
||||
new_attr_name)
|
||||
new_method = self.__class__._DynamicProperty(
|
||||
method, new_attr_name, id_pattern)
|
||||
inst_method = types.MethodType(new_method, self)
|
||||
self._dynamic_properties[new_attr_name] = inst_method
|
||||
|
||||
|
@ -253,7 +253,6 @@ class FormRegion(BaseFormRegion):
|
||||
_header_locator = (by.By.CSS_SELECTOR, 'div.modal-header > h3')
|
||||
_side_info_locator = (by.By.CSS_SELECTOR, 'div.right')
|
||||
_fields_locator = (by.By.CSS_SELECTOR, 'fieldset > div.form-group')
|
||||
_input_locator = (by.By.CSS_SELECTOR, 'input,select,textarea')
|
||||
|
||||
# private methods
|
||||
def __init__(self, driver, conf, src_elem, form_field_names):
|
||||
|
@ -45,10 +45,13 @@ class BtnActionRowRegion(BaseActionRowRegion):
|
||||
def __init__(self, driver, conf, src_elem, action_name):
|
||||
super(BtnActionRowRegion, self).__init__(driver, conf, src_elem)
|
||||
self.action_name = action_name
|
||||
self._action_id_pattern = ("%s__action_%%s" %
|
||||
src_elem.get_attribute('id'))
|
||||
self._init_action()
|
||||
|
||||
def _init_action(self):
|
||||
self._init_dynamic_property(self.action_name, self._get_action)
|
||||
self._init_dynamic_property(self.action_name, self._get_action,
|
||||
self._action_id_pattern)
|
||||
|
||||
def _get_action(self):
|
||||
return self._get_element(*self._action_locator)
|
||||
@ -78,15 +81,19 @@ class ComplexActionRowRegion(BaseActionRowRegion):
|
||||
try:
|
||||
self.primary_action_name = action_names[self.PRIMARY_ACTION]
|
||||
self.secondary_action_names = action_names[self.SECONDARY_ACTIONS]
|
||||
self._action_id_pattern = ("%s__action_%%s" %
|
||||
src_elem.get_attribute('id'))
|
||||
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._get_primary_action,
|
||||
self._action_id_pattern)
|
||||
self._init_dynamic_properties(self.secondary_action_names,
|
||||
self._get_secondary_actions)
|
||||
self._get_secondary_actions,
|
||||
self._action_id_pattern)
|
||||
|
||||
def _get_primary_action(self):
|
||||
return self._get_element(*self._primary_action_locator)
|
||||
@ -182,8 +189,9 @@ class ActionsTableRegion(BasicTableRegion):
|
||||
' div.table_actions > a')
|
||||
|
||||
# private methods
|
||||
def __init__(self, driver, conf, src_elm, action_names):
|
||||
def __init__(self, driver, conf, src_elm, table_name, action_names):
|
||||
super(ActionsTableRegion, self).__init__(driver, conf, src_elm)
|
||||
self._action_id_pattern = "%s__action_%%s" % table_name
|
||||
self.action_names = action_names
|
||||
self._init_actions()
|
||||
|
||||
@ -192,7 +200,8 @@ class ActionsTableRegion(BasicTableRegion):
|
||||
"""Create new methods that corresponds to picking table's
|
||||
action buttons.
|
||||
"""
|
||||
self._init_dynamic_properties(self.action_names, self._get_actions)
|
||||
self._init_dynamic_properties(self.action_names, self._get_actions,
|
||||
self._action_id_pattern)
|
||||
|
||||
def _get_actions(self):
|
||||
return self._get_elements(*self._actions_locator)
|
||||
@ -206,9 +215,10 @@ class ActionsTableRegion(BasicTableRegion):
|
||||
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)
|
||||
def __init__(self, driver, conf, src_elm, table_name, action_names,
|
||||
row_action_name):
|
||||
super(SimpleActionsTableRegion, self).__init__(
|
||||
driver, conf, src_elm, table_name, action_names)
|
||||
self.row_action_name = row_action_name
|
||||
|
||||
def _get_rows(self):
|
||||
@ -222,9 +232,10 @@ class SimpleActionsTableRegion(ActionsTableRegion):
|
||||
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)
|
||||
def __init__(self, driver, conf, src_elm, table_name,
|
||||
action_names, row_action_names):
|
||||
super(ComplexActionTableRegion, self).__init__(
|
||||
driver, conf, src_elm, table_name, action_names)
|
||||
self.row_action_names = row_action_names
|
||||
|
||||
def _get_rows(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user