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:
Timur Sufiev 2015-08-28 17:35:43 +03:00
parent f6436bbefd
commit d25d4d2b0d
10 changed files with 88 additions and 51 deletions

View File

@ -23,7 +23,8 @@ class FlavorsPage(basepage.BaseNavigationPage):
_flavors_table_locator = (by.By.ID, 'flavors') _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 = { FLAVORS_TABLE_ROW_ACTIONS = {
tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_flavor", tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_flavor",
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ( tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
@ -47,6 +48,7 @@ class FlavorsPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._flavors_table_locator) src_elem = self._get_element(*self._flavors_table_locator)
return tables.ComplexActionTableRegion(self.driver, return tables.ComplexActionTableRegion(self.driver,
self.conf, src_elem, self.conf, src_elem,
self.FLAVORS_TABLE_NAME,
self.FLAVORS_TABLE_ACTIONS, self.FLAVORS_TABLE_ACTIONS,
self.FLAVORS_TABLE_ROW_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, def create_flavor(self, name, id_=DEFAULT_ID, vcpus=None, ram=None,
root_disk=None, ephemeral_disk=None, swap_disk=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 self.create_flavor_form.name.text = name
if id_ is not None: if id_ is not None:
self.create_flavor_form.flavor_id.text = id_ self.create_flavor_form.flavor_id.text = id_
@ -76,7 +78,7 @@ class FlavorsPage(basepage.BaseNavigationPage):
def delete_flavor(self, name): def delete_flavor(self, name):
row = self._get_row_with_flavor_name(name) row = self._get_row_with_flavor_name(name)
row.mark() row.mark()
self.flavors_table.delete_flavors.click() self.flavors_table.delete.click()
self.confirm_delete_flavors_form.submit.click() self.confirm_delete_flavors_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -35,7 +35,8 @@ class ProjectsPage(basepage.BaseNavigationPage):
DEFAULT_ENABLED = True DEFAULT_ENABLED = True
PROJECTS_TABLE_NAME_COLUMN_INDEX = 0 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 = { PROJECTS_TABLE_ROW_ACTIONS = {
tables.ComplexActionRowRegion.PRIMARY_ACTION: "manage_members", tables.ComplexActionRowRegion.PRIMARY_ACTION: "manage_members",
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ( tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
@ -58,6 +59,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._projects_table_locator) src_elem = self._get_element(*self._projects_table_locator)
return tables.ComplexActionTableRegion(self.driver, return tables.ComplexActionTableRegion(self.driver,
self.conf, src_elem, self.conf, src_elem,
self.PROJECTS_TABLE_NAME,
self.PROJECTS_TABLE_ACTIONS, self.PROJECTS_TABLE_ACTIONS,
self.PROJECTS_TABLE_ROW_ACTIONS self.PROJECTS_TABLE_ROW_ACTIONS
) )
@ -94,7 +96,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
def create_project(self, project_name, description=None, def create_project(self, project_name, description=None,
is_enabled=DEFAULT_ENABLED): is_enabled=DEFAULT_ENABLED):
self.projects_table.create_project.click() self.projects_table.create.click()
self.create_project_form.name.text = project_name self.create_project_form.name.text = project_name
if description is not None: if description is not None:
self.create_project_form.description.text = description self.create_project_form.description.text = description
@ -106,7 +108,7 @@ class ProjectsPage(basepage.BaseNavigationPage):
def delete_project(self, project_name): def delete_project(self, project_name):
row = self._get_row_with_project_name(project_name) row = self._get_row_with_project_name(project_name)
row.mark() row.mark()
self.projects_table.delete_projects.click() self.projects_table.delete.click()
self.delete_project_submit_button.click() self.delete_project_submit_button.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -23,7 +23,8 @@ class UsersPage(basepage.BaseNavigationPage):
USERS_TABLE_NAME_COLUMN_INDEX = 0 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 = { USERS_TABLE_ROW_ACTIONS = {
tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_user", tables.ComplexActionRowRegion.PRIMARY_ACTION: "edit_user",
@ -47,6 +48,7 @@ class UsersPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._users_table_locator) src_elem = self._get_element(*self._users_table_locator)
return tables.ComplexActionTableRegion(self.driver, return tables.ComplexActionTableRegion(self.driver,
self.conf, src_elem, self.conf, src_elem,
self.USERS_TABLE_NAME,
self.USERS_TABLE_ACTIONS, self.USERS_TABLE_ACTIONS,
self.USERS_TABLE_ROW_ACTIONS self.USERS_TABLE_ROW_ACTIONS
) )
@ -62,7 +64,7 @@ class UsersPage(basepage.BaseNavigationPage):
def create_user(self, name, password, def create_user(self, name, password,
project, role, email=None): project, role, email=None):
self.users_table.create_user.click() self.users_table.create.click()
self.create_user_form.name.text = name self.create_user_form.name.text = name
if email is not None: if email is not None:
self.create_user_form.email.text = email self.create_user_form.email.text = email
@ -76,7 +78,7 @@ class UsersPage(basepage.BaseNavigationPage):
def delete_user(self, name): def delete_user(self, name):
row = self._get_row_with_user_name(name) row = self._get_row_with_user_name(name)
row.mark() row.mark()
self.users_table.delete_users.click() self.users_table.delete.click()
self.confirm_delete_users_form.submit.click() self.confirm_delete_users_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -30,8 +30,8 @@ class FloatingipsPage(basepage.BaseNavigationPage):
_floatingips_fadein_popup_locator = ( _floatingips_fadein_popup_locator = (
by.By.CSS_SELECTOR, '.alert.alert-success.alert-dismissable.fade.in>p') by.By.CSS_SELECTOR, '.alert.alert-success.alert-dismissable.fade.in>p')
FLOATING_IPS_TABLE_ACTIONS = ("allocate_ip_to_project", FLOATING_IPS_TABLE_NAME = 'floating_ips'
"release_floating_ips") FLOATING_IPS_TABLE_ACTIONS = ("allocate", "release")
FLOATING_IPS_TABLE_ROW_ACTION = { FLOATING_IPS_TABLE_ROW_ACTION = {
tables.ComplexActionRowRegion.PRIMARY_ACTION: "associate", tables.ComplexActionRowRegion.PRIMARY_ACTION: "associate",
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ( tables.ComplexActionRowRegion.SECONDARY_ACTIONS: (
@ -51,6 +51,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._floating_ips_table_locator) src_elem = self._get_element(*self._floating_ips_table_locator)
return tables.ComplexActionTableRegion( return tables.ComplexActionTableRegion(
self.driver, self.conf, src_elem, self.driver, self.conf, src_elem,
self.FLOATING_IPS_TABLE_NAME,
self.FLOATING_IPS_TABLE_ACTIONS, self.FLOATING_IPS_TABLE_ACTIONS,
self.FLOATING_IPS_TABLE_ROW_ACTION) self.FLOATING_IPS_TABLE_ROW_ACTION)
@ -59,7 +60,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
return forms.BaseFormRegion(self.driver, self.conf, None) return forms.BaseFormRegion(self.driver, self.conf, None)
def allocate_floatingip(self): def allocate_floatingip(self):
self.floatingips_table.allocate_ip_to_project.click() self.floatingips_table.allocate.click()
self.floatingip_form.submit.click() self.floatingip_form.submit.click()
ip = re.compile('(([2][5][0-5]\.)|([2][0-4][0-9]\.)' ip = re.compile('(([2][5][0-5]\.)|([2][0-4][0-9]\.)'
+ '|([0-1]?[0-9]?[0-9]\.)){3}(([2][5][0-5])|' + '|([0-1]?[0-9]?[0-9]\.)){3}(([2][5][0-5])|'
@ -73,7 +74,7 @@ class FloatingipsPage(basepage.BaseNavigationPage):
def release_floatingip(self, floatingip): def release_floatingip(self, floatingip):
row = self._get_row_with_floatingip(floatingip) row = self._get_row_with_floatingip(floatingip)
row.mark() row.mark()
self.floatingips_table.release_floating_ips.click() self.floatingips_table.release.click()
self.floatingip_form.submit.click() self.floatingip_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -24,9 +24,9 @@ class KeypairsPage(basepage.BaseNavigationPage):
_key_pairs_table_locator = (by.By.ID, 'keypairs') _key_pairs_table_locator = (by.By.ID, 'keypairs')
KEY_PAIRS_TABLE_ACTIONS = ("create_key_pair", "import_key_pair", KEY_PAIRS_TABLE_NAME = "keypairs"
"delete_key_pair") KEY_PAIRS_TABLE_ACTIONS = ("create", "import", "delete")
KEY_PAIRS_TABLE_ROW_ACTION = "delete_key_pair" KEY_PAIRS_TABLE_ROW_ACTION = "delete"
KEY_PAIRS_TABLE_NAME_COLUMN_INDEX = 0 KEY_PAIRS_TABLE_NAME_COLUMN_INDEX = 0
CREATE_KEY_PAIR_FORM_FIELDS = ('name',) CREATE_KEY_PAIR_FORM_FIELDS = ('name',)
@ -44,6 +44,7 @@ class KeypairsPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._key_pairs_table_locator) src_elem = self._get_element(*self._key_pairs_table_locator)
return tables.SimpleActionsTableRegion(self.driver, self.conf, return tables.SimpleActionsTableRegion(self.driver, self.conf,
src_elem, src_elem,
self.KEY_PAIRS_TABLE_NAME,
self.KEY_PAIRS_TABLE_ACTIONS, self.KEY_PAIRS_TABLE_ACTIONS,
self.KEY_PAIRS_TABLE_ROW_ACTION) self.KEY_PAIRS_TABLE_ROW_ACTION)
@ -60,12 +61,12 @@ class KeypairsPage(basepage.BaseNavigationPage):
return bool(self._get_row_with_keypair_name(name)) return bool(self._get_row_with_keypair_name(name))
def create_keypair(self, keypair_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.name.text = keypair_name
self.create_keypair_form.submit.click() self.create_keypair_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()
def delete_keypair(self, name): 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.delete_keypair_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -29,7 +29,8 @@ class ImagesPage(basepage.BaseNavigationPage):
_images_table_locator = (by.By.ID, 'images') _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 = { IMAGES_TABLE_ROW_ACTIONS = {
tables.ComplexActionRowRegion.PRIMARY_ACTION: "launch", tables.ComplexActionRowRegion.PRIMARY_ACTION: "launch",
tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ("create_volume",) tables.ComplexActionRowRegion.SECONDARY_ACTIONS: ("create_volume",)
@ -55,6 +56,7 @@ class ImagesPage(basepage.BaseNavigationPage):
src_elem = self._get_element(*self._images_table_locator) src_elem = self._get_element(*self._images_table_locator)
return tables.ComplexActionTableRegion(self.driver, return tables.ComplexActionTableRegion(self.driver,
self.conf, src_elem, self.conf, src_elem,
self.IMAGES_TABLE_NAME,
self.IMAGES_TABLE_ACTIONS, self.IMAGES_TABLE_ACTIONS,
self.IMAGES_TABLE_ROW_ACTIONS self.IMAGES_TABLE_ROW_ACTIONS
) )
@ -74,7 +76,7 @@ class ImagesPage(basepage.BaseNavigationPage):
image_format=DEFAULT_IMAGE_FORMAT, image_format=DEFAULT_IMAGE_FORMAT,
is_public=DEFAULT_ACCESSIBILITY, is_public=DEFAULT_ACCESSIBILITY,
is_protected=DEFAULT_PROTECTION): is_protected=DEFAULT_PROTECTION):
self.images_table.create_image.click() self.images_table.create.click()
self.create_image_form.name.text = name self.create_image_form.name.text = name
if description is not None: if description is not None:
self.create_image_form.description.text = description self.create_image_form.description.text = description
@ -98,7 +100,7 @@ class ImagesPage(basepage.BaseNavigationPage):
def delete_image(self, name): def delete_image(self, name):
row = self._get_row_with_image_name(name) row = self._get_row_with_image_name(name)
row.mark() row.mark()
self.images_table.delete_images.click() self.images_table.delete.click()
self.confirm_delete_images_form.submit.click() self.confirm_delete_images_form.submit.click()
self.wait_till_popups_disappear() self.wait_till_popups_disappear()

View File

@ -20,7 +20,7 @@ class OverviewPage(basepage.BaseNavigationPage):
_usage_table_locator = (by.By.ID, 'project_usage') _usage_table_locator = (by.By.ID, 'project_usage')
_date_form_locator = (by.By.ID, 'date_form') _date_form_locator = (by.By.ID, 'date_form')
USAGE_TABLE_ACTIONS = ("download_csv",) USAGE_TABLE_ACTIONS = ("csv_summary",)
def __init__(self, driver, conf): def __init__(self, driver, conf):
super(OverviewPage, self).__init__(driver, conf) super(OverviewPage, self).__init__(driver, conf)

View File

@ -65,31 +65,48 @@ class BaseRegion(basewebobject.BaseWebObject):
class _DynamicProperty(object): class _DynamicProperty(object):
"""Serves as new property holder.""" """Serves as new property holder."""
def __init__(self, method, index=None, name=None): def __init__(self, method, name=None, id_pattern=None):
"""In case object was created with index != None, """Invocation of `method` should return either single property, or
it is assumed that the result of self.method should be tuple() a dictionary of properties, or a list of them.
and just certain index should be returned
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.method = method
self.index = index
self.name = name self.name = name
self.id_pattern = id_pattern
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
result = self.method() result = self.method()
if isinstance(result, dict): if self.name is None:
return result if self.name is None else result[self.name] return result
else: 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.""" """Create new object's 'properties' at runtime."""
for index, new_attr_name in enumerate(new_attr_names): for new_attr_name in new_attr_names:
self._init_dynamic_property(new_attr_name, method, index) self._init_dynamic_property(new_attr_name, method, id_pattern)
def _init_dynamic_property(self, new_attr_name, method, index=None): def _init_dynamic_property(self, new_attr_name, method, id_pattern=None):
"""Create new object's property at runtime. If index argument is """Create new object's property at runtime. See _DynamicProperty's
supplied it is assumed that method returns tuple() and only element __init__ docstring for a description of arguments.
on ${index} position is returned.
""" """
if (new_attr_name in dir(self) or if (new_attr_name in dir(self) or
new_attr_name in self._dynamic_properties): new_attr_name in self._dynamic_properties):
@ -97,8 +114,8 @@ class BaseRegion(basewebobject.BaseWebObject):
"The new property could not be " "The new property could not be "
"created." % (self.__class__.__name__, "created." % (self.__class__.__name__,
new_attr_name)) new_attr_name))
new_method = self.__class__._DynamicProperty(method, index, new_method = self.__class__._DynamicProperty(
new_attr_name) method, new_attr_name, id_pattern)
inst_method = types.MethodType(new_method, self) inst_method = types.MethodType(new_method, self)
self._dynamic_properties[new_attr_name] = inst_method self._dynamic_properties[new_attr_name] = inst_method

View File

@ -253,7 +253,6 @@ class FormRegion(BaseFormRegion):
_header_locator = (by.By.CSS_SELECTOR, 'div.modal-header > h3') _header_locator = (by.By.CSS_SELECTOR, 'div.modal-header > h3')
_side_info_locator = (by.By.CSS_SELECTOR, 'div.right') _side_info_locator = (by.By.CSS_SELECTOR, 'div.right')
_fields_locator = (by.By.CSS_SELECTOR, 'fieldset > div.form-group') _fields_locator = (by.By.CSS_SELECTOR, 'fieldset > div.form-group')
_input_locator = (by.By.CSS_SELECTOR, 'input,select,textarea')
# private methods # private methods
def __init__(self, driver, conf, src_elem, form_field_names): def __init__(self, driver, conf, src_elem, form_field_names):

View File

@ -45,10 +45,13 @@ class BtnActionRowRegion(BaseActionRowRegion):
def __init__(self, driver, conf, src_elem, action_name): def __init__(self, driver, conf, src_elem, action_name):
super(BtnActionRowRegion, self).__init__(driver, conf, src_elem) super(BtnActionRowRegion, self).__init__(driver, conf, src_elem)
self.action_name = action_name self.action_name = action_name
self._action_id_pattern = ("%s__action_%%s" %
src_elem.get_attribute('id'))
self._init_action() self._init_action()
def _init_action(self): 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): def _get_action(self):
return self._get_element(*self._action_locator) return self._get_element(*self._action_locator)
@ -78,15 +81,19 @@ class ComplexActionRowRegion(BaseActionRowRegion):
try: try:
self.primary_action_name = action_names[self.PRIMARY_ACTION] self.primary_action_name = action_names[self.PRIMARY_ACTION]
self.secondary_action_names = action_names[self.SECONDARY_ACTIONS] self.secondary_action_names = action_names[self.SECONDARY_ACTIONS]
self._action_id_pattern = ("%s__action_%%s" %
src_elem.get_attribute('id'))
self._init_actions() self._init_actions()
except (TypeError, KeyError): except (TypeError, KeyError):
raise AttributeError(self.ACTIONS_ERROR_MSG) raise AttributeError(self.ACTIONS_ERROR_MSG)
def _init_actions(self): def _init_actions(self):
self._init_dynamic_property(self.primary_action_name, 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._init_dynamic_properties(self.secondary_action_names,
self._get_secondary_actions) self._get_secondary_actions,
self._action_id_pattern)
def _get_primary_action(self): def _get_primary_action(self):
return self._get_element(*self._primary_action_locator) return self._get_element(*self._primary_action_locator)
@ -182,8 +189,9 @@ class ActionsTableRegion(BasicTableRegion):
' div.table_actions > a') ' div.table_actions > a')
# private methods # 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) super(ActionsTableRegion, self).__init__(driver, conf, src_elm)
self._action_id_pattern = "%s__action_%%s" % table_name
self.action_names = action_names self.action_names = action_names
self._init_actions() self._init_actions()
@ -192,7 +200,8 @@ class ActionsTableRegion(BasicTableRegion):
"""Create new methods that corresponds to picking table's """Create new methods that corresponds to picking table's
action buttons. 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): def _get_actions(self):
return self._get_elements(*self._actions_locator) return self._get_elements(*self._actions_locator)
@ -206,9 +215,10 @@ class ActionsTableRegion(BasicTableRegion):
class SimpleActionsTableRegion(ActionsTableRegion): class SimpleActionsTableRegion(ActionsTableRegion):
"""Table which rows has buttons in action column.""" """Table which rows has buttons in action column."""
def __init__(self, driver, conf, src_elm, action_names, row_action_name): def __init__(self, driver, conf, src_elm, table_name, action_names,
super(SimpleActionsTableRegion, self).__init__(driver, conf, src_elm, row_action_name):
action_names) super(SimpleActionsTableRegion, self).__init__(
driver, conf, src_elm, table_name, action_names)
self.row_action_name = row_action_name self.row_action_name = row_action_name
def _get_rows(self): def _get_rows(self):
@ -222,9 +232,10 @@ class SimpleActionsTableRegion(ActionsTableRegion):
class ComplexActionTableRegion(ActionsTableRegion): class ComplexActionTableRegion(ActionsTableRegion):
"""Table which has button and selectbox in the action column.""" """Table which has button and selectbox in the action column."""
def __init__(self, driver, conf, src_elm, action_names, row_action_names): def __init__(self, driver, conf, src_elm, table_name,
super(ComplexActionTableRegion, self).__init__(driver, conf, src_elm, action_names, row_action_names):
action_names) super(ComplexActionTableRegion, self).__init__(
driver, conf, src_elm, table_name, action_names)
self.row_action_names = row_action_names self.row_action_names = row_action_names
def _get_rows(self): def _get_rows(self):