
In the functional integration test, test class opened a firefox window as part of setUp() but in case of failure of setUp(), the window remained open, as the tearDown() method is not called on failure of setUp(). This problem can be solved by adding the close command for firefox through addCleanup method which is excecuted irrespective of success of the setUp method. Change-Id: Ic576fa05b62afa08356f11825cbf442baaf9a4cb Closes-bug: 1487446
420 lines
17 KiB
Python
420 lines
17 KiB
Python
# 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 json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import testtools
|
|
import time
|
|
import urlparse
|
|
|
|
from glanceclient import client as gclient
|
|
from keystoneclient.v2_0 import client as ksclient
|
|
from muranoclient import client as mclient
|
|
from oslo_log import handlers
|
|
from oslo_log import log
|
|
from selenium.common import exceptions as exc
|
|
from selenium import webdriver
|
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
import selenium.webdriver.common.by as by
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
from selenium.webdriver.support import ui
|
|
|
|
import config.config as cfg
|
|
from muranodashboard.tests.functional import consts
|
|
from muranodashboard.tests.functional import utils
|
|
|
|
logger = log.getLogger(__name__).logger
|
|
logger.level = logging.DEBUG
|
|
logger.addHandler(handlers.ColorHandler())
|
|
|
|
if sys.version_info >= (2, 7):
|
|
class BaseDeps(testtools.TestCase):
|
|
pass
|
|
else:
|
|
# Define asserts for python26
|
|
import unittest2
|
|
|
|
class BaseDeps(testtools.TestCase,
|
|
unittest2.TestCase):
|
|
pass
|
|
|
|
|
|
class UITestCase(BaseDeps):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.keystone_client = ksclient.Client(username=cfg.common.user,
|
|
password=cfg.common.password,
|
|
tenant_name=cfg.common.tenant,
|
|
auth_url=cfg.common.keystone_url)
|
|
cls.murano_client = mclient.Client(
|
|
'1', endpoint=cfg.common.murano_url,
|
|
token=cls.keystone_client.auth_token)
|
|
cls.url_prefix = urlparse.urlparse(cfg.common.horizon_url).path or ''
|
|
if cls.url_prefix.endswith('/'):
|
|
cls.url_prefix = cls.url_prefix[:-1]
|
|
|
|
def setUp(self):
|
|
super(UITestCase, self).setUp()
|
|
|
|
self.driver = webdriver.Firefox()
|
|
self.addCleanup(self.driver.quit)
|
|
self.driver.maximize_window()
|
|
self.driver.get(cfg.common.horizon_url + '/murano/environments')
|
|
self.driver.implicitly_wait(30)
|
|
self.addOnException(self.take_screenshot)
|
|
self.log_in()
|
|
|
|
def tearDown(self):
|
|
super(UITestCase, self).tearDown()
|
|
|
|
for env in self.murano_client.environments.list():
|
|
self.remove_environment(env.id)
|
|
|
|
def remove_environment(self, environment_id, timeout=180):
|
|
self.murano_client.environments.delete(environment_id)
|
|
|
|
start_time = time.time()
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
self.murano_client.environments.get(environment_id)
|
|
time.sleep(1)
|
|
except Exception:
|
|
# TODO(smurashov): bug/1378764 replace Exception to NotFound
|
|
return
|
|
raise Exception(
|
|
'Environment {0} was not deleted in {1} seconds'.format(
|
|
environment_id, timeout))
|
|
|
|
def take_screenshot(self, exception):
|
|
"""Taking screenshot on error
|
|
|
|
This decorators will take a screenshot of the browser
|
|
when the test failed or when exception raised on the test.
|
|
Screenshot will be saved as PNG inside screenshots folder.
|
|
|
|
"""
|
|
name = self._testMethodName
|
|
logger.error('{0} failed'.format(name))
|
|
screenshot_dir = './screenshots'
|
|
if not os.path.exists(screenshot_dir):
|
|
os.makedirs(screenshot_dir)
|
|
filename = os.path.join(screenshot_dir, name + '.png')
|
|
self.driver.get_screenshot_as_file(filename)
|
|
|
|
def log_in(self):
|
|
self.fill_field(by.By.ID, 'id_username', cfg.common.user)
|
|
self.fill_field(by.By.ID, 'id_password', cfg.common.password)
|
|
self.driver.find_element_by_xpath("//button[@type='submit']").click()
|
|
murano = self.driver.find_element_by_xpath(consts.Murano)
|
|
if 'collapsed' in murano.get_attribute('class'):
|
|
murano.click()
|
|
|
|
def fill_field(self, by_find, field, value):
|
|
self.driver.find_element(by=by_find, value=field).clear()
|
|
self.driver.find_element(by=by_find, value=field).send_keys(value)
|
|
|
|
def get_element_id(self, el_name):
|
|
el = ui.WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located(
|
|
(by.By.XPATH, consts.AppPackageDefinitions.format(el_name))))
|
|
path = el.get_attribute("id")
|
|
return path.split('__')[-1]
|
|
|
|
def select_and_click_action_for_app(self, action, app):
|
|
self.driver.find_element_by_xpath(
|
|
"//*[@href='{0}/murano/catalog/{1}/{2}']".format(self.url_prefix,
|
|
action,
|
|
app)).click()
|
|
|
|
def go_to_submenu(self, link):
|
|
element = self.wait_element_is_clickable(by.By.PARTIAL_LINK_TEXT, link)
|
|
element.click()
|
|
self.wait_for_sidebar_is_loaded()
|
|
|
|
def check_panel_is_present(self, panel_name):
|
|
self.assertIn(panel_name,
|
|
self.driver.find_element_by_xpath(
|
|
".//*[@class='page-header']").text)
|
|
|
|
def navigate_to(self, menu):
|
|
el = ui.WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located(
|
|
(by.By.XPATH, getattr(consts, menu))))
|
|
if 'collapsed' in el.get_attribute('class'):
|
|
el.click()
|
|
self.wait_for_sidebar_is_loaded()
|
|
|
|
def select_from_list(self, list_name, value):
|
|
locator = (by.By.XPATH,
|
|
"//select[contains(@name, '{0}')]"
|
|
"/option[@value='{1}']".format(list_name, value))
|
|
el = ui.WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located(locator))
|
|
el.click()
|
|
|
|
def check_element_on_page(self, method, value, sec=10):
|
|
try:
|
|
ui.WebDriverWait(self.driver, sec).until(
|
|
EC.presence_of_element_located((method, value)))
|
|
except exc.TimeoutException:
|
|
self.fail("Element {0} is not preset on the page".format(value))
|
|
|
|
def check_element_not_on_page(self, method, value):
|
|
self.driver.implicitly_wait(3)
|
|
present = True
|
|
try:
|
|
self.driver.find_element(method, value)
|
|
except (exc.NoSuchElementException, exc.ElementNotVisibleException):
|
|
present = False
|
|
self.assertFalse(present, "Element {0} is preset on the page"
|
|
" while it should't".format(value))
|
|
self.driver.implicitly_wait(30)
|
|
|
|
def create_environment(self, env_name, by_id=False):
|
|
if by_id:
|
|
self.driver.find_element_by_id(
|
|
'environments__action_CreateEnvironment').click()
|
|
else:
|
|
self.driver.find_element_by_css_selector(
|
|
consts.CreateEnvironment).click()
|
|
self.fill_field(by.By.ID, 'id_name', env_name)
|
|
self.driver.find_element_by_id(consts.ConfirmCreateEnvironment).click()
|
|
self.wait_for_alert_message()
|
|
|
|
def delete_environment(self, env_name):
|
|
self.select_action_for_environment(env_name, 'delete')
|
|
self.driver.find_element_by_xpath(consts.ConfirmDeletion).click()
|
|
self.wait_for_alert_message()
|
|
|
|
def edit_environment(self, old_name, new_name):
|
|
el_td = self.driver.find_element_by_css_selector(
|
|
'tr[data-display="{0}"] td:first-of-type'.format(old_name))
|
|
el_pencil = el_td.find_element_by_css_selector(
|
|
'button.ajax-inline-edit')
|
|
|
|
# hover to make pencil visible
|
|
hover = ActionChains(self.driver).move_to_element(el_td)
|
|
hover.perform()
|
|
el_pencil.click()
|
|
|
|
# fill in inline input
|
|
el_inline_input = self.driver.find_element_by_css_selector(
|
|
'tr[data-display="{0}"] '.format(old_name) +
|
|
'td:first-of-type .inline-edit-form input')
|
|
el_inline_input.clear()
|
|
el_inline_input.send_keys(new_name)
|
|
|
|
# click submit
|
|
el_submit = self.driver.find_element_by_css_selector(
|
|
'tr[data-display="{0}"] '.format(old_name) +
|
|
'td:first-of-type .inline-edit-actions button[type="submit"]')
|
|
el_submit.click()
|
|
# there is no alert message
|
|
|
|
def select_action_for_environment(self, env_name, action):
|
|
element_id = self.get_element_id(env_name)
|
|
more_button = consts.More.format('environments', element_id)
|
|
self.driver.find_element_by_xpath(more_button).click()
|
|
btn_id = "environments__row_{0}__action_{1}".format(element_id, action)
|
|
self.driver.find_element_by_id(btn_id).click()
|
|
|
|
def wait_for_alert_message(self):
|
|
locator = (by.By.CSS_SELECTOR, 'div.alert-success')
|
|
logger.debug("Waiting for a success message")
|
|
ui.WebDriverWait(self.driver, 2).until(
|
|
EC.presence_of_element_located(locator))
|
|
|
|
def wait_element_is_clickable(self, method, element):
|
|
return ui.WebDriverWait(self.driver, 10).until(
|
|
EC.element_to_be_clickable((method, element)))
|
|
|
|
def wait_for_sidebar_is_loaded(self):
|
|
ui.WebDriverWait(self.driver, 10).until(
|
|
EC.presence_of_element_located(
|
|
(by.By.CSS_SELECTOR, "div#sidebar li.active")))
|
|
time.sleep(0.5)
|
|
|
|
|
|
class PackageBase(UITestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(PackageBase, cls).setUpClass()
|
|
cls.mockapp_id = utils.upload_app_package(
|
|
cls.murano_client,
|
|
"MockApp",
|
|
{"categories": ["Web"], "tags": ["tag"]})
|
|
cls.postgre_id = utils.upload_app_package(
|
|
cls.murano_client,
|
|
"PostgreSQL",
|
|
{"categories": ["Databases"], "tags": ["tag"]})
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(PackageBase, cls).tearDownClass()
|
|
cls.murano_client.packages.delete(cls.mockapp_id)
|
|
cls.murano_client.packages.delete(cls.postgre_id)
|
|
|
|
|
|
class ImageTestCase(PackageBase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(ImageTestCase, cls).setUpClass()
|
|
glance_endpoint = cls.keystone_client.service_catalog.url_for(
|
|
service_type='image', endpoint_type='publicURL')
|
|
cls.glance = gclient.Client('1', endpoint=glance_endpoint,
|
|
token=cls.keystone_client.auth_token)
|
|
cls.image_title = 'New Image ' + str(time.time())
|
|
cls.image = cls.upload_image(cls.image_title)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(ImageTestCase, cls).tearDownClass()
|
|
cls.glance.images.delete(cls.image.id)
|
|
|
|
@classmethod
|
|
def upload_image(cls, title):
|
|
try:
|
|
property = {'murano_image_info': json.dumps({'title': title,
|
|
'type': 'linux'})}
|
|
|
|
image = cls.glance.images.create(name='TestImage',
|
|
disk_format='qcow2',
|
|
size=0,
|
|
is_public=True,
|
|
properties=property)
|
|
except Exception as e:
|
|
logger.error("Unable to create or update image in Glance")
|
|
raise e
|
|
return image
|
|
|
|
def select_and_click_element(self, element):
|
|
self.driver.find_element_by_xpath(
|
|
".//*[@value = '{0}']".format(element)).click()
|
|
|
|
def repair_image(self):
|
|
self.driver.find_element_by_id(
|
|
'marked_images__action_mark_image').click()
|
|
self.select_from_list('image', self.image.id)
|
|
self.fill_field(by.By.ID, 'id_title', self.image_title)
|
|
self.select_from_list('type', 'linux')
|
|
self.select_and_click_element('Mark Image')
|
|
self.check_element_on_page(by.By.XPATH,
|
|
consts.TestImage.format(self.image_title))
|
|
|
|
|
|
class FieldsTestCase(PackageBase):
|
|
def check_error_message_is_present(self, error_message):
|
|
self.driver.find_element_by_xpath(consts.ButtonSubmit).click()
|
|
self.driver.find_element_by_xpath(
|
|
consts.ErrorMessage.format(error_message))
|
|
|
|
def check_error_message_is_absent(self, error_message):
|
|
self.driver.find_element_by_xpath(consts.ButtonSubmit).click()
|
|
|
|
self.driver.implicitly_wait(2)
|
|
try:
|
|
self.driver.find_element_by_xpath(
|
|
consts.ErrorMessage.format(error_message))
|
|
except (exc.NoSuchElementException, exc.ElementNotVisibleException):
|
|
logger.info("Message {0} is not"
|
|
" present on the page".format(error_message))
|
|
|
|
self.driver.implicitly_wait(30)
|
|
|
|
|
|
class ApplicationTestCase(ImageTestCase):
|
|
def delete_component(self, component_name):
|
|
component_id = self.get_element_id(component_name)
|
|
self.driver.find_element_by_id(
|
|
'services__row_{0}__action_delete'.format(component_id)).click()
|
|
el = self.wait_element_is_clickable(by.By.LINK_TEXT,
|
|
'Delete Component')
|
|
el.click()
|
|
self.wait_for_alert_message()
|
|
|
|
def select_action_for_package(self, package_id, action):
|
|
if action == 'more':
|
|
el = self.wait_element_is_clickable(
|
|
by.By.XPATH, "//tr[@data-object-id='{0}']"
|
|
"//a[@data-toggle='dropdown']".format(package_id))
|
|
el.click()
|
|
ui.WebDriverWait(self.driver, 10).until(lambda s: s.find_element(
|
|
by.By.XPATH,
|
|
".//*[@id='packages__row_{0}__action_download_package']".
|
|
format(package_id)).is_displayed())
|
|
else:
|
|
self.driver.find_element_by_xpath(
|
|
".//*[@id='packages__row_{0}__action_{1}']".
|
|
format(package_id, action)).click()
|
|
|
|
def check_package_parameter(self, selector, column, value):
|
|
columns = {'Tenant Name': 3, 'Active': 4, 'Public': 5}
|
|
column_num = str(columns[column])
|
|
column_element = self.driver.find_element_by_xpath(
|
|
"//tr[{0}]/td[{1}]".format(selector, column_num))
|
|
self.assertTrue(column_element.text == value,
|
|
"'{0}' column doesn't contain '{1}'".format(column,
|
|
value))
|
|
|
|
def check_package_parameter_by_id(self, package_id, column, value):
|
|
selector = '@data-object-id="{0}"'.format(package_id)
|
|
self.check_package_parameter(selector, column, value)
|
|
|
|
def check_package_parameter_by_name(self, package_name, column, value):
|
|
selector = '@data-display="{0}"'.format(package_name)
|
|
self.check_package_parameter(selector, column, value)
|
|
|
|
def modify_package(self, param, value):
|
|
self.fill_field(by.By.ID, 'id_{0}'.format(param), value)
|
|
self.driver.find_element_by_xpath(consts.InputSubmit).click()
|
|
self.wait_for_alert_message()
|
|
|
|
def add_app_to_env(self, app_id, app_name='TestApp'):
|
|
self.go_to_submenu('Applications')
|
|
self.select_and_click_action_for_app('quick-add', app_id)
|
|
field_id = "{0}_0-name".format(app_id)
|
|
self.fill_field(by.By.ID, field_id, value=app_name)
|
|
self.driver.find_element_by_xpath(consts.ButtonSubmit).click()
|
|
self.driver.find_element_by_xpath(consts.InputSubmit).click()
|
|
self.select_from_list('osImage', self.image.id)
|
|
|
|
self.driver.find_element_by_xpath(consts.InputSubmit).click()
|
|
self.wait_for_alert_message()
|
|
|
|
|
|
class PackageTestCase(ApplicationTestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(ApplicationTestCase, cls).setUpClass()
|
|
cls.archive_name = "ToUpload"
|
|
cls.alt_archive_name = "ModifiedAfterUpload"
|
|
cls.archive = utils.compose_package(cls.archive_name,
|
|
consts.Manifest,
|
|
consts.PackageDir)
|
|
|
|
def tearDown(self):
|
|
super(PackageTestCase, self).tearDown()
|
|
|
|
for package in self.murano_client.packages.list(include_disabled=True):
|
|
if package.name in [self.archive_name, self.alt_archive_name]:
|
|
self.murano_client.packages.delete(package.id)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(ApplicationTestCase, cls).tearDownClass()
|
|
if os.path.exists(consts.Manifest):
|
|
os.remove(consts.Manifest)
|
|
if os.path.exists(cls.archive):
|
|
os.remove(cls.archive)
|