Introduce field mappings for FormRegion descendants in i9n tests

This change is similar to the change that introduced decorators for
binding table-level and row-level actions to real table buttons - this
time for forms. Its aim is to provide means to reference a form field
in test using a name different from the one that exists in real
html.

Now it is possible to provide a dictionary for simple FormRegion,
where key is the name to be used in test and the value is used for
binding this name to a real html field widget. Also it is feasible to
provide values other than strings in that dictionary - in this case
they are mean to be a Python class. This Python class will be
initialized as any BaseRegion is usually initialized and then the
value's key will be used for referencing this object. This is useful
when dealing with non-standard widgets in forms (like Membership
widget in Create/Edit Project form or Networks widget in Launch
Instance form).

The old syntax for fields names in tests (the tuple of strings) is
still compatible with new FormRegion-s. It is treated as we had simply
used the same name both for referencing field and for binding it to a
real widget.

Changing TabbedForm region's tabs to be referenced by name instead of
index is out of the scope of this change, but should be done in future.
If not done, we're going to encounter problems with different tabs being
shown or not shown based on RBAC/ enabled settings/ etc.

Implements blueprint: integration-tests-improvements-part1
Change-Id: If3658119176d03dcd0742101ae2922f8e61ba757
This commit is contained in:
Timur Sufiev 2015-12-30 18:17:59 +03:00
parent 726ea9992f
commit c573b5b316
10 changed files with 47 additions and 18 deletions

View File

@ -27,7 +27,7 @@ class FlavorsTable(tables.TableRegion):
create_button.click()
return forms.TabbedFormRegion(
self.driver, self.conf,
form_field_names=self.CREATE_FLAVOR_FORM_FIELDS)
field_mappings=self.CREATE_FLAVOR_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_flavor(self, delete_button):

View File

@ -24,7 +24,7 @@ class ProjectsTable(tables.TableRegion):
create_button.click()
return forms.TabbedFormRegion(
self.driver, self.conf,
form_field_names=self.CREATE_PROJECT_FORM_FIELDS)
field_mappings=self.CREATE_PROJECT_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_project(self, delete_button):

View File

@ -24,7 +24,7 @@ class UsersTable(tables.TableRegion):
def create_user(self, create_button):
create_button.click()
return forms.FormRegion(self.driver, self.conf,
form_field_names=self.CREATE_USER_FORM_FIELDS)
field_mappings=self.CREATE_USER_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_user(self, delete_button):

View File

@ -27,7 +27,7 @@ class KeypairsTable(tables.TableRegion):
create_button.click()
return forms.FormRegion(
self.driver, self.conf,
form_field_names=self.CREATE_KEY_PAIR_FORM_FIELDS)
field_mappings=self.CREATE_KEY_PAIR_FORM_FIELDS)
@tables.bind_row_action('delete', primary=True)
def delete_keypair(self, delete_button, row):

View File

@ -24,7 +24,7 @@ class SecurityGroupsTable(tables.TableRegion):
create_button.click()
return forms.FormRegion(
self.driver, self.conf,
form_field_names=self.CREATE_SECURITYGROUP_FORM_FIELDS)
field_mappings=self.CREATE_SECURITYGROUP_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_group(self, delete_button):

View File

@ -30,7 +30,7 @@ class ImagesTable(tables.TableRegion):
def create_image(self, create_button):
create_button.click()
return forms.FormRegion(self.driver, self.conf,
form_field_names=self.CREATE_IMAGE_FORM_FIELDS)
field_mappings=self.CREATE_IMAGE_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_image(self, delete_button):

View File

@ -33,7 +33,7 @@ class InstancesTable(tables.TableRegion):
launch_button.click()
return forms.TabbedFormRegion(
self.driver, self.conf,
form_field_names=self.CREATE_INSTANCE_FORM_FIELDS)
field_mappings=self.CREATE_INSTANCE_FORM_FIELDS)
@tables.bind_table_action('delete')
def delete_instance(self, delete_button):

View File

@ -26,8 +26,9 @@ class ChangepasswordPage(basepage.BaseNavigationPage):
@property
def password_form(self):
src_elem = self._get_element(*self._password_form_locator)
return forms.FormRegion(self.driver, self.conf, src_elem,
self.CHANGE_PASSWORD_FORM_FIELDS)
return forms.FormRegion(
self.driver, self.conf, src_elem=src_elem,
field_mappings=self.CHANGE_PASSWORD_FORM_FIELDS)
def change_password(self, current, new):
self.password_form.current_password.text = current

View File

@ -44,8 +44,9 @@ class UsersettingsPage(basepage.BaseNavigationPage):
@property
def settings_form(self):
src_elem = self._get_element(*self._settings_form_locator)
return forms.FormRegion(self.driver, self.conf, src_elem,
self.SETTINGS_FORM_FIELDS)
return forms.FormRegion(
self.driver, self.conf, src_elem=src_elem,
field_mappings=self.SETTINGS_FORM_FIELDS)
@property
def changepassword(self):

View File

@ -265,16 +265,28 @@ class FormRegion(BaseFormRegion):
_fields_locator = (by.By.CSS_SELECTOR, 'fieldset')
# private methods
def __init__(self, driver, conf, src_elem=None, form_field_names=None):
def __init__(self, driver, conf, src_elem=None, field_mappings=None):
super(FormRegion, self).__init__(driver, conf, src_elem)
self.form_field_names = form_field_names
self.field_mappings = self._prepare_mappings(field_mappings)
self.wait_till_spinner_disappears()
self._init_form_fields()
def _prepare_mappings(self, field_mappings):
if isinstance(field_mappings, tuple):
return {item: item for item in field_mappings}
else:
return field_mappings
# protected methods
def _init_form_fields(self):
self.fields_src_elem = self._get_element(*self._fields_locator)
self._dynamic_properties.update(self._get_form_fields())
fields = self._get_form_fields()
for accessor_name, accessor_expr in self.field_mappings.items():
if isinstance(accessor_expr, six.string_types):
self._dynamic_properties[accessor_name] = fields[accessor_expr]
else: # it is a class
self._dynamic_properties[accessor_name] = accessor_expr(
self.driver, self.conf)
def _get_form_fields(self):
factory = FieldFactory(self.driver, self.conf, self.fields_src_elem)
@ -346,18 +358,33 @@ class TabbedFormRegion(FormRegion):
_submit_locator = (by.By.CSS_SELECTOR, '*.btn.btn-primary[type=submit]')
_side_info_locator = (by.By.CSS_SELECTOR, "td.help_text")
def __init__(self, driver, conf, form_field_names=None, default_tab=0):
def __init__(self, driver, conf, field_mappings=None, default_tab=0):
self.current_tab = default_tab
super(TabbedFormRegion, self).__init__(
driver, conf, form_field_names=form_field_names)
driver, conf, field_mappings=field_mappings)
def _prepare_mappings(self, field_mappings):
return [super(TabbedFormRegion, self)._prepare_mappings(tab_mappings)
for tab_mappings in field_mappings]
def _init_form_fields(self):
self._init_tab_fields(self.current_tab)
self.switch_to(self.current_tab)
def _init_tab_fields(self, tab_index):
fieldsets = self._get_elements(*self._fields_locator)
self.fields_src_elem = fieldsets[tab_index]
self._dynamic_properties.update(self._get_form_fields())
fields = self._get_form_fields()
current_tab_mappings = self.field_mappings[tab_index]
for accessor_name, accessor_expr in current_tab_mappings.items():
if isinstance(accessor_expr, six.string_types):
self._dynamic_properties[accessor_name] = fields[accessor_expr]
else: # it is a class
self._dynamic_properties[accessor_name] = accessor_expr(
self.driver, self.conf)
def switch_to(self, tab_index=0):
self.tabs.switch_to(index=tab_index)
self._init_tab_fields(tab_index)
@property
def tabs(self):