Added update image metadata test

Updated forms.py with MetadataFormRegion.
Updated tables.py with method to bind anchor row column.
Added test for update image metadata.

Implements blueprint: horizon-integration-tests-coverage
Change-Id: Idd3651955b8f0e1a0c08dd43abd657aafa5a3bc2
This commit is contained in:
Yury Tregubov 2016-01-22 14:09:14 +03:00
parent baee24dc21
commit d44eebecfa
5 changed files with 164 additions and 14 deletions

View File

@ -66,6 +66,15 @@ def gen_temporary_file(name='', suffix='.qcow2', size=10485760):
yield tmp_file.name
class AssertsMixin(object):
def assertSequenceTrue(self, actual):
return self.assertEqual(list(actual), [True] * len(actual))
def assertSequenceFalse(self, actual):
return self.assertEqual(list(actual), [False] * len(actual))
class BaseTestCase(testtools.TestCase):
CONFIG = config.get_config()
@ -197,7 +206,7 @@ class BaseTestCase(testtools.TestCase):
super(BaseTestCase, self).tearDown()
class TestCase(BaseTestCase):
class TestCase(BaseTestCase, AssertsMixin):
TEST_USER_NAME = BaseTestCase.CONFIG.identity.username
TEST_PASSWORD = BaseTestCase.CONFIG.identity.password
@ -225,7 +234,7 @@ class TestCase(BaseTestCase):
super(TestCase, self).tearDown()
class AdminTestCase(TestCase):
class AdminTestCase(TestCase, AssertsMixin):
TEST_USER_NAME = TestCase.CONFIG.identity.admin_username
TEST_PASSWORD = TestCase.CONFIG.identity.admin_password

View File

@ -9,7 +9,6 @@
# 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 openstack_dashboard.test.integration_tests.pages import basepage
from openstack_dashboard.test.integration_tests.regions import forms
from openstack_dashboard.test.integration_tests.regions import tables
@ -20,6 +19,15 @@ from openstack_dashboard.test.integration_tests.pages.project.compute.\
volumes.volumespage import VolumesPage
DEFAULT_IMAGE_SOURCE = 'url'
DEFAULT_IMAGE_FORMAT = 'qcow2'
DEFAULT_ACCESSIBILITY = False
DEFAULT_PROTECTION = False
IMAGES_TABLE_NAME_COLUMN = 'name'
IMAGES_TABLE_STATUS_COLUMN = 'status'
IMAGES_TABLE_FORMAT_COLUMN = 'disk_format'
class ImagesTable(tables.TableRegion):
name = "images"
@ -69,23 +77,25 @@ class ImagesTable(tables.TableRegion):
self.driver, self.conf,
field_mappings=self.LAUNCH_INSTANCE_FROM_FIELDS)
@tables.bind_row_action('update_metadata')
def update_metadata(self, metadata_button, row):
metadata_button.click()
return forms.MetadataFormRegion(self.driver, self.conf)
@tables.bind_row_anchor_column(IMAGES_TABLE_NAME_COLUMN)
def go_to_image_description_page(self, row_link, row):
row_link.click()
return forms.ItemTextDescription(self.driver, self.conf)
class ImagesPage(basepage.BaseNavigationPage):
DEFAULT_IMAGE_SOURCE = 'url'
DEFAULT_IMAGE_FORMAT = 'qcow2'
DEFAULT_ACCESSIBILITY = False
DEFAULT_PROTECTION = False
IMAGES_TABLE_NAME_COLUMN = 'name'
IMAGES_TABLE_STATUS_COLUMN = 'status'
IMAGES_TABLE_FORMAT_COLUMN = 'disk_format'
def __init__(self, driver, conf):
super(ImagesPage, self).__init__(driver, conf)
self._page_title = "Images"
def _get_row_with_image_name(self, name):
return self.images_table.get_row(self.IMAGES_TABLE_NAME_COLUMN, name)
return self.images_table.get_row(IMAGES_TABLE_NAME_COLUMN, name)
@property
def images_table(self):
@ -123,20 +133,39 @@ class ImagesPage(basepage.BaseNavigationPage):
confirm_delete_images_form = self.images_table.delete_image()
confirm_delete_images_form.submit()
def add_custom_metadata(self, name, metadata):
row = self._get_row_with_image_name(name)
update_metadata_form = self.images_table.update_metadata(row)
for field_name, value in metadata.iteritems():
update_metadata_form.add_custom_field(field_name, value)
update_metadata_form.submit()
def check_image_details(self, name, dict_with_details):
row = self._get_row_with_image_name(name)
matches = []
description_page = self.images_table.go_to_image_description_page(row)
content = description_page.get_content()
for name, value in content.iteritems():
if name in dict_with_details:
if dict_with_details[name] in value:
matches.append(True)
return matches
def is_image_present(self, name):
return bool(self._get_row_with_image_name(name))
def is_image_active(self, name):
row = self._get_row_with_image_name(name)
return self.images_table.is_cell_status(
lambda: row.cells[self.IMAGES_TABLE_STATUS_COLUMN], 'Active')
lambda: row.cells[IMAGES_TABLE_STATUS_COLUMN], 'Active')
def wait_until_image_active(self, name):
self._wait_until(lambda x: self.is_image_active(name))
def get_image_format(self, name):
row = self._get_row_with_image_name(name)
return row.cells[self.IMAGES_TABLE_FORMAT_COLUMN].text
return row.cells[IMAGES_TABLE_FORMAT_COLUMN].text
def create_volume_from_image(self, name, volume_name=None,
description=None,

View File

@ -11,6 +11,7 @@
# under the License.
import six
from selenium.common import exceptions
from selenium.webdriver.common import by
import selenium.webdriver.support.ui as Support
@ -403,3 +404,70 @@ class DateFormRegion(BaseFormRegion):
def _set_to_field(self, value):
self._fill_field_element(value, self.to_date)
class MetadataFormRegion(BaseFormRegion):
_input_fields = (by.By.CSS_SELECTOR, 'div.input-group')
_custom_input_field = (by.By.XPATH, "//input[@name='customItem']")
_custom_input_button = (by.By.CSS_SELECTOR, 'span.input-group-btn > .btn')
_submit_locator = (by.By.CSS_SELECTOR, '.modal-footer > .btn.btn-primary')
_cancel_locator = (by.By.CSS_SELECTOR, '.modal-footer > .btn.btn-default')
def _form_getter(self):
return self.driver.find_element(*self._default_form_locator)
@property
def custom_field_value(self):
return self._get_element(*self._custom_input_field)
@property
def add_button(self):
return self._get_element(*self._custom_input_button)
def add_custom_field(self, field_name, field_value):
self.custom_field_value.send_keys(field_name)
self.add_button.click()
for div in self._get_elements(*self._input_fields):
if div.text in field_name:
field = div.find_element(by.By.CSS_SELECTOR, 'input')
if not hasattr(self, field_name):
self._dynamic_properties[field_name] = field
self.set_field_value(field_name, field_value)
def set_field_value(self, field_name, field_value):
if hasattr(self, field_name):
field = getattr(self, field_name)
field.send_keys(field_value)
else:
raise AttributeError("Unknown form field '{}'.".format(field_name))
def wait_till_spinner_disappears(self):
# No spinner is invoked after the 'Save' button click
# Will wait till the form itself disappears
try:
self.wait_till_element_disappears(self._form_getter)
except exceptions.StaleElementReferenceException:
# The form might be absent already by the time the first check
# occurs. So just suppress the exception here.
pass
class ItemTextDescription(baseregion.BaseRegion):
_separator_locator = (by.By.CSS_SELECTOR, 'dl.dl-horizontal')
_key_locator = (by.By.CSS_SELECTOR, 'dt')
_value_locator = (by.By.CSS_SELECTOR, 'dd')
def __init__(self, driver, conf, src=None):
super(ItemTextDescription, self).__init__(driver, conf, src)
def get_content(self):
keys = []
values = []
for section in self._get_elements(*self._separator_locator):
keys.extend([x.text for x in
section.find_elements(*self._key_locator)])
values.extend([x.text for x in
section.find_elements(*self._value_locator)])
return dict(zip(keys, values))

View File

@ -301,3 +301,23 @@ def bind_row_action(action_name):
return method(table, action_element, row)
return wrapper
return decorator
def bind_row_anchor_column(column_name):
"""A decorator to bind table region method to a anchor in a column.
Typical examples of such tables are Project -> Compute -> Images, Admin
-> System -> Flavors, Project -> Compute -> Instancies.
The method can be used to follow the link in the anchor by the click.
"""
def decorator(method):
@functools.wraps(method)
def wrapper(table, row):
cell = row.cells[column_name]
action_element = cell.find_element(
by.By.CSS_SELECTOR, 'td.%s > a' % NORMAL_COLUMN_CLASS)
return method(table, action_element, row)
return wrapper
return decorator

View File

@ -117,6 +117,30 @@ class TestImagesBasic(helpers.TestCase):
settings_page.change_pagesize()
settings_page.find_message_and_dismiss(messages.SUCCESS)
def test_update_image_metadata(self):
"""Test update image metadata
* logs in as admin user
* creates image from locally downloaded file
* verifies the image appears in the images table as active
* invokes action 'Update Metadata' for the image
* adds custom filed 'metadata'
* adds value 'image' for the custom filed 'metadata'
* gets the actual description of the image
* verifies that custom filed is present in the image description
* deletes the image
* verifies the image does not appear in the table after deletion
"""
new_metadata = {'metadata1': helpers.gen_random_resource_name("value"),
'metadata2': helpers.gen_random_resource_name("value")}
with helpers.gen_temporary_file() as file_name:
images_page = self.image_create(local_file=file_name)
images_page.add_custom_metadata(self.IMAGE_NAME, new_metadata)
results = images_page.check_image_details(self.IMAGE_NAME,
new_metadata)
self.image_delete()
self.assertSequenceTrue(results) # custom matcher
class TestImagesAdvanced(helpers.TestCase):
"""Login as demo user"""