From 8ff7426095b03aa02a459ad33f185591fca85c08 Mon Sep 17 00:00:00 2001 From: Csók Tamás <godhak@gmail.com> Date: Fri, 13 Mar 2015 18:14:47 +0100 Subject: [PATCH] selenium: hierachy changes, teardown clearing, method changes --- circle/dashboard/tests/selenium/basic_tests.py | 620 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- circle/dashboard/tests/selenium/config.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ circle/dashboard/tests/selenium/util.py | 548 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 668 insertions(+), 555 deletions(-) create mode 100644 circle/dashboard/tests/selenium/config.py create mode 100644 circle/dashboard/tests/selenium/util.py diff --git a/circle/dashboard/tests/selenium/basic_tests.py b/circle/dashboard/tests/selenium/basic_tests.py index f122950..66be58c 100644 --- a/circle/dashboard/tests/selenium/basic_tests.py +++ b/circle/dashboard/tests/selenium/basic_tests.py @@ -16,550 +16,58 @@ # # You should have received a copy of the GNU General Public License along # with CIRCLE. If not, see <http://www.gnu.org/licenses/>. -from selenose.cases import SeleniumTestCase -from django.contrib.auth.models import User +import logging +from sys import stdout import random -import urlparse import re -import time -import logging -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.select import Select -from selenium.webdriver.common.by import By -from datetime import datetime -from selenium.common.exceptions import NoSuchElementException -logging.getLogger(__name__) -random_pass = "".join([random.choice( - '0123456789abcdefghijklmnopqrstvwxyz') for n in xrange(10)]) -random_accents = random_pass + "".join([random.choice( - u"áéíöóúűÁÉÍÖÓÜÚŰ") for n in xrange(5)]) -wait_max_sec = 10 -host = 'https:127.0.0.1' -client_name = 'test_%s' % random_accents - - -class UtilityMixin(object): - def login(self, username, password='password', location=None): - driver = self.driver - if location is None: - location = '/dashboard/' - driver.get('%s%s' % (host, location)) - # Only if we aren't logged in already - if location not in urlparse.urlparse(self.driver.current_url).path: - try: - name_input = driver.find_element_by_id("id_username") - password_input = driver.find_element_by_id("id_password") - submit_input = driver.find_element_by_id("submit-id-submit") - except: - inputs = driver.find_elements_by_tag_name("input") - for current_input in inputs: - input_type = current_input.get_attribute("type") - if input_type == "text": - name_input = current_input - if input_type == "password": - password_input = current_input - if input_type == "submit": - submit_input = current_input - try: - name_input.clear() - name_input.send_keys(username) - password_input.clear() - password_input.send_keys(password) - submit_input.click() - try: - # If selenium runs only in a small (virtual) screen - driver.find_element_by_class_name('navbar-toggle').click() - WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CSS_SELECTOR, - "a[href*='/dashboard/profile/']"))) - except: - time.sleep(0.5) - except: - logging.exception('Selenium cannot find the form controls') - - def list_options(self, select): - try: - option_dic = {} - select = Select(select) - for option in select.options: - key = option.get_attribute('value') - if key is not None and key: - option_dic[key] = [option.text] - return option_dic - except: - logging.exception( - 'Selenium cannot list the select possibilities') - - def select_option(self, select, what=None): - """ - From an HTML select imput type try to choose the specified one. - Select is a selenium web element type. What represent both the - text of the option and it's ID. - """ - try: - my_choice = None - options = self.list_options(select) - select = Select(select) - if what is not None: - for key, value in options.iteritems(): - if what in key: - my_choice = key - else: - if isinstance(value, list): - for single_value in value: - if what in single_value: - my_choice = key - else: - if what in value: - my_choice = key - if my_choice is None: - my_choose_list = options.keys() - my_choice = my_choose_list[random.randint( - 0, len(my_choose_list) - 1)] - select.select_by_value(my_choice) - except: - logging.exception( - 'Selenium cannot select the chosen one') - - def get_link_by_href(self, target_href, attributes=None): - try: - links = self.driver.find_elements_by_tag_name('a') - for link in links: - href = link.get_attribute('href') - if href is not None and href: - if target_href in href: - perfect_fit = True - if isinstance(attributes, dict): - for key, target_value in attributes.iteritems(): - attr_check = link.get_attribute(key) - if attr_check is not None and attr_check: - if target_value not in attr_check: - perfect_fit = False - if perfect_fit: - return link - except: - logging.exception( - 'Selenium cannot find the href=%s link' % target_href) - - def click_on_link(self, link): - """ - There are situations when selenium built in click() function - doesn't work as intended, that's when this function is used. - Fires a click event via javascript injection. - """ - try: - # Javascript function to simulate a click on a link - javascript = ( - "var link = arguments[0];" - "var cancelled = false;" - "if(document.createEvent) {" - " var event = document.createEvent(\"MouseEvents\");" - " event.initMouseEvent(" - " \"click\", true, true, window, 0, 0, 0, 0, 0," - " false,false,false,false,0,null);" - " cancelled = !link.dispatchEvent(event);" - "} else if(link.fireEvent) {" - " cancelled = !link.fireEvent(\"onclick\");" - "} if (!cancelled) {" - " window.location = link.href;" - "}") - self.driver.execute_script(javascript, link) - except: - logging.exception( - 'Selenium cannot inject javascript to the page') - - def wait_and_accept_operation(self, argument=None): - """ - Accepts the operation confirmation pop up window. - Fills out the text inputs before accepting if argument is given. - """ - try: - accept = WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CLASS_NAME, "modal-accept"))) - if argument is not None: - possible = self.driver.find_elements_by_css_selector( - "div.controls > input[type='text']") - if isinstance(argument, list): - for x in range(0, len(possible)): - possible[x].clear() - possible[x].send_keys(argument[x % len(argument)]) - else: - for form in possible: - form.clear() - form.send_keys(argument) - accept.click() - except: - logging.exception( - 'Selenium cannot accept the operation confirmation') - - def save_template_from_vm(self, name): - try: - WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CSS_SELECTOR, - "a[href$='/op/deploy/']"))) - self.click_on_link(self.get_link_by_href("/op/deploy/")) - self.wait_and_accept_operation() - recent_deploy = self.recently(self.get_timeline_elements( - "vm.Instance.deploy")) - if not self.check_operation_result(recent_deploy): - print ("Selenium cannot deploy the " - "chosen template virtual machine") - logging.exception('Cannot deploy the virtual machine') - self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CSS_SELECTOR, - "a[href$='/op/shut_off/']")))) - self.wait_and_accept_operation() - recent_shut_off = self.recently(self.get_timeline_elements( - "vm.Instance.shut_off")) - if not self.check_operation_result(recent_shut_off): - print ("Selenium cannot shut off the " - "chosen template virtual machine") - logging.exception('Cannot shut off the virtual machine') - self.click_on_link(WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CSS_SELECTOR, - "a[href$='/op/save_as_template/']")))) - self.wait_and_accept_operation(name) - return name - except: - logging.exception( - 'Selenium cannot save a vm as a template') - - def create_base_template(self, name=None, architecture="x86-64", - method=None, op_system=None, lease=None, - network="vm"): - if name is None: - name = "template_new_%s" % client_name - if op_system is None: - op_system = "!os %s" % client_name - try: - self.driver.get('%s/dashboard/template/choose/' % host) - self.driver.find_element_by_css_selector( - "input[type='radio'][value='base_vm']").click() - self.driver.find_element_by_id( - "template-choose-next-button").click() - template_name = WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, 'id_name'))) - template_name.clear() - template_name.send_keys(name) - self.select_option(self.driver.find_element_by_id( - "id_arch"), architecture) - self.select_option(self.driver.find_element_by_id( - "id_access_method"), method) - system_name = self.driver.find_element_by_id("id_system") - system_name.clear() - system_name.send_keys(op_system) - self.select_option(self.driver.find_element_by_id( - "id_lease"), lease) - self.select_option(self.driver.find_element_by_id( - "id_networks"), network) - self.driver.find_element_by_css_selector( - "input.btn[type='submit']").click() - return self.save_template_from_vm(name) - except: - logging.exception( - 'Selenium cannot create a base template virtual machine') - - def get_template_id(self, name=None, from_all=False): - """ - In default settings find all templates ID in the template list. - If name is specified searches that specific template's ID - from_all sets whether to use owned templates or all of them - Returns list of the templates ID - """ - try: - self.driver.get('%s/dashboard/template/list/' % host) - css_selector_of_a_template = ("a[data-original-title]" - "[href*='/dashboard/template/']") - if from_all: - self.select_option(self.driver.find_element_by_id( - 'id_stype'), "all") - self.driver.find_element_by_css_selector( - "button[type='submit']").click() - try: - WebDriverWait(self.driver, wait_max_sec).until( - ec.presence_of_element_located(( - By.CSS_SELECTOR, css_selector_of_a_template))) - except: - print "Selenium could not locate any templates" - template_table = self.driver.find_element_by_css_selector( - "table[class*='template-list-table']") - templates = template_table.find_elements_by_css_selector("td.name") - found_template_ids = [] - for template in templates: - if name is None or name in template.text: - try: - template_link = template.find_element_by_css_selector( - css_selector_of_a_template) - template_id = re.search( - r'\d+', - template_link.get_attribute("outerHTML")).group() - found_template_ids.append(template_id) - print ("Found '%(name)s' template's ID as %(id)s" % { - 'name': template.text, - 'id': template_id}) - except NoSuchElementException: - pass - except: - raise - if not found_template_ids and name is not None: - print ("Selenium could not find the specified " - "%(name)s template in the list" % { - 'name': name}) - return found_template_ids - except: - logging.exception( - 'Selenium cannot found the template\'s id') - - def check_operation_result(self, operation_id, restore=True): - """ - Returns wheter the operation_id result is success (returns: boolean) - """ - try: - if restore: - url_base = urlparse.urlparse(self.driver.current_url) - url_save = ("%(host)s%(url)s" % { - 'host': host, - 'url': urlparse.urljoin(url_base.path, url_base.query)}) - if url_base.fragment: - url_save = ("%(url)s#%(fragment)s" % { - 'url': url_save, - 'fragment': url_base.fragment}) - self.driver.get('%(host)s/dashboard/vm/activity/%(id)s/' % { - 'host': host, - 'id': operation_id}) - result = WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, "activity_status"))) - print ("%(id)s result text is '%(result)s'" % { - 'id': operation_id, - 'result': result.text}) - if (result.text == "success"): - out = True - elif (result.text == "wait"): - time.sleep(2) - out = self.check_operation_result(operation_id, False) - else: - out = False - if restore: - print "Restoring to %s url" % url_save - self.driver.get(url_save) - return out - except: - logging.exception( - 'Selenium cannot check the result of an operation') - - def recently(self, timeline_dict, second=90): - try: - if isinstance(timeline_dict, dict): - for key, value in timeline_dict.iteritems(): - time = datetime.strptime(key, '%Y-%m-%d %H:%M') - delta = datetime.now() - time - if delta.total_seconds() <= second: - return value - except: - logging.exception( - 'Selenium cannot filter timeline activities to recent') - - def get_timeline_elements(self, code=None): - try: - if code is None: - css_activity_selector = "div[data-activity-code]" - code = "all activity" - else: - css_activity_selector = ("div[data-activity-code=" - "'%(code)s']" % { - 'code': code}) - WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CSS_SELECTOR, "a[href*='#activity']"))).click() - activity_dict = {} - timeline = WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, "activity-timeline"))) - searched_activity = timeline.find_elements_by_css_selector( - css_activity_selector) - print "Found activity list for %s:" % code - for activity in searched_activity: - activity_id = activity.get_attribute('data-activity-id') - activity_text = activity.text - key = re.search( - r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1] - print ("%(id)s @ %(activity)s" % { - 'id': activity_id, - 'activity': key}) - activity_dict[key] = activity_id - return activity_dict - except: - logging.exception('Selenium cannot find the searched activity') - - def create_template_from_base(self, delete_disk=True, name=None): - try: - if name is None: - name = "template_from_base_%s" % client_name - self.driver.get('%s/dashboard/template/choose/' % host) - choice_list = [] - choices = self.driver.find_elements_by_css_selector( - "input[type='radio']") - choice_list = [item for item in choices if ( - 'test' not in item.get_attribute('value') - and item.get_attribute('value') != 'base_vm')] - chosen = random.randint(0, len(choice_list) - 1) - choice_list[chosen].click() - self.driver.find_element_by_id( - "template-choose-next-button").click() - if delete_disk: - self.click_on_link( - self.get_link_by_href("#resources")) - disks = WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, 'vm-details-resources-disk'))) - disk_list = disks.find_elements_by_css_selector( - "h4[class*='list-group-item-heading']") - if len(disk_list) > 0: - self.click_on_link( - self.get_link_by_href("/op/remove_disk/")) - self.wait_and_accept_operation() - WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, "_activity"))) - recent_remove_disk = self.recently( - self.get_timeline_elements( - "vm.Instance.remove_disk")) - if not self.check_operation_result(recent_remove_disk): - print ("Selenium cannot delete disk " - "of the chosen template") - logging.exception('Cannot delete disk') - return self.save_template_from_vm(name) - except: - logging.exception( - 'Selenium cannot start a template from a base one') +import urlparse - def delete_template(self, template_id): - try: - self.driver.get('%s/dashboard/template/%s/' % (host, template_id)) - url = urlparse.urlparse(self.driver.current_url) - self.click_on_link( - self.get_link_by_href( - "/dashboard/template/delete/%s/" % template_id)) - self.wait_and_accept_operation() - WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.CLASS_NAME, 'alert-success'))) - url = urlparse.urlparse(self.driver.current_url) - if "/template/list/" not in url.path: - logging.exception( - 'System does not redirect to template listing') - except: - logging.exception('Selenium cannot delete the desired template') +from django.contrib.auth.models import User +from django.db.models import Q - def create_random_vm(self): - try: - self.driver.get('%s/dashboard/vm/create/' % host) - vm_list = [] - pk = None - vm_list = self.driver.find_elements_by_class_name( - 'vm-create-template-summary') - choice = random.randint(0, len(vm_list) - 1) - vm_list[choice].click() - WebDriverWait(self.driver, wait_max_sec).until( - ec.element_to_be_clickable(( - By.CLASS_NAME, 'vm-create-start'))).click() - WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.CLASS_NAME, 'alert-success'))) - url = urlparse.urlparse(self.driver.current_url) - pk = re.search(r'\d+', url.path).group() - return pk - except: - logging.exception('Selenium cannot start a VM') +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.support.ui import WebDriverWait +from selenose.cases import SeleniumTestCase - def view_change(self, target_box): - driver = self.driver - driver.get('%s/dashboard/' % host) - list_view = driver.find_element_by_id('%s-list-view' % target_box) - graph_view = driver.find_element_by_id('%s-graph-view' % target_box) - js_script = 'return arguments[0].style.display;' - required_attributes = {'data-index-box': target_box} - graph_view_link = self.get_link_by_href( - '#index-graph-view', - required_attributes).find_element_by_tag_name('i') - list_view_link = self.get_link_by_href( - '#index-list-view', - required_attributes).find_element_by_tag_name('i') - self.click_on_link(list_view_link) - states = [driver.execute_script(js_script, list_view), - driver.execute_script(js_script, graph_view)] - self.click_on_link(graph_view_link) - states.extend([driver.execute_script(js_script, list_view), - driver.execute_script(js_script, graph_view)]) - self.click_on_link(list_view_link) - states.extend([driver.execute_script(js_script, list_view), - driver.execute_script(js_script, graph_view)]) - return states +from vm.models import Instance +from .config import SeleniumConfig +from .util import CircleSeleniumMixin, SeleniumMixin - def delete_vm(self, pk): - try: - # For relability reasons instead of using the JS operatation - self.driver.get("%(host)s/dashboard/vm/%(id)s/op/destroy/" % { - 'host': host, - 'id': pk}) - self.wait_and_accept_operation() - try: - status_span = WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.ID, 'vm-details-state'))) - WebDriverWait(status_span, wait_max_sec).until( - ec.visibility_of_element_located(( - By.CLASS_NAME, 'fa-trash-o'))) - except: - # Selenium can time-out by not realising the JS refresh - recent_destroy_vm = self.recently( - self.get_timeline_elements("vm.Instance.destroy")) - if not self.check_operation_result(recent_destroy_vm): - print ("Selenium cannot destroy " - "the chosen %(id)s vm" % { - 'id': pk}) - logging.exception('Cannot destroy the specified vm') - self.driver.get('%s/dashboard/vm/%s/' % (host, pk)) - try: - WebDriverWait(self.driver, wait_max_sec).until( - ec.visibility_of_element_located(( - By.CSS_SELECTOR, - "span[data-status*='DESTROYED']"))) - return True - except: - return False - except: - logging.exception("Selenium can not destroy a VM") +logger = logging.getLogger(__name__) +consoleHandler = logging.StreamHandler(stdout) +logger.addHandler(consoleHandler) +conf = SeleniumConfig() -class VmDetailTest(UtilityMixin, SeleniumTestCase): - template_ids = [] - vm_ids = [] +class BasicSeleniumTests(SeleniumTestCase, SeleniumMixin, CircleSeleniumMixin): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.conf = conf + self.template_ids = [] + self.vm_ids = [] @classmethod def setup_class(cls): - cls._user = User.objects.create(username=client_name, - is_superuser=True) - cls._user.set_password(random_accents) - cls._user.save() + if conf.create_user: + cls._user = User.objects.create(username=conf.client_name, + is_superuser=True) + cls._user.set_password(conf.random_pass) + cls._user.save() @classmethod def teardown_class(cls): - cls._user.delete() + if conf.create_user: + for instance in Instance.objects.all().filter( + ~Q(status=u'DESTROYED'), owner=cls._user): + instance.destroy(system=True) + cls._user.delete() def test_01_login(self): title = 'Dashboard | CIRCLE' location = '/dashboard/' - self.login(client_name, random_accents) - self.driver.get('%s/dashboard/' % host) + self.login() + self.driver.get('%s/dashboard/' % conf.host) url = urlparse.urlparse(self.driver.current_url) (self.assertIn('%s' % title, self.driver.title, '%s is not found in the title' % title) or @@ -567,17 +75,17 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): 'URL path is not equal with %s' % location)) def test_02_add_template_rights(self): - self.login(client_name, random_accents) + self.login() template_pool = self.get_template_id(from_all=True) if len(template_pool) > 1: chosen = template_pool[random.randint(0, len(template_pool) - 1)] elif len(template_pool) == 1: chosen = template_pool[0] else: - print "Selenium did not found any templates" + logging.warning("Selenium did not found any templates") logging.exception( "System did not meet required conditions to continue") - self.driver.get('%s/dashboard/template/%s/' % (host, chosen)) + self.driver.get('%s/dashboard/template/%s/' % (conf.host, chosen)) acces_form = self.driver.find_element_by_css_selector( "form[action*='/dashboard/template/%(template_id)s/acl/']" "[method='post']" % { @@ -587,8 +95,8 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): user_status = acces_form.find_element_by_css_selector( "select[name='level']") user_name.clear() - user_name.send_keys(client_name) - self.select_option(user_status) + user_name.send_keys(conf.client_name) + self.select_option(user_status, 'user') # For strange reasons clicking on submit button doesn't work anymore acces_form.submit() found_users = [] @@ -598,31 +106,33 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): user_text = re.split(r':[ ]?', user.text) if len(user_text) == 2: found_name = re.search(r'[\w\W]+(?=\))', user_text[1]).group() - print ("'%(user)s' found in ACL list for template %(id)s" % { - 'user': found_name, - 'id': chosen}) + logging.info("'%(user)s' found in ACL " + "list for template %(id)s" % { + 'user': found_name, + 'id': chosen}) found_users.append(found_name) - self.assertIn(client_name, found_users, + self.assertIn(conf.client_name, found_users, "Could not add user to template's ACL") def test_03_able_to_create_template(self): - self.login(client_name, random_accents) + self.login() template_list = None create_template = self.get_link_by_href('/dashboard/template/choose/') self.click_on_link(create_template) - WebDriverWait(self.driver, wait_max_sec).until( + WebDriverWait(self.driver, conf.wait_max_sec).until( ec.visibility_of_element_located(( By.ID, 'confirmation-modal'))) template_list = self.driver.find_elements_by_class_name( 'template-choose-list-element') - print 'Selenium found %s template possibilities' % len(template_list) + logging.info('Selenium found %(count)s template possibilities' % { + 'count': len(template_list)}) (self.assertIsNotNone( template_list, "Selenium can not find the create template list") or self.assertGreater(len(template_list), 0, "The create template list is empty")) def test_04_create_base_template(self): - self.login(client_name, random_accents) + self.login() created_template_id = self.get_template_id( self.create_base_template()) found = created_template_id is not None @@ -633,7 +143,7 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): "Could not found the created template in the template list") def test_05_create_template_from_base(self): - self.login(client_name, random_accents) + self.login() created_template_id = self.get_template_id( self.create_template_from_base()) found = created_template_id is not None @@ -645,9 +155,9 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): def test_06_delete_templates(self): success = False - self.login(client_name, random_accents) + self.login() for template_id in self.template_ids: - print "Deleting template %s" % template_id + logging.info("Deleting template %s" % template_id) self.delete_template(template_id) existing_templates = self.get_template_id() if len(existing_templates) == 0: @@ -662,52 +172,52 @@ class VmDetailTest(UtilityMixin, SeleniumTestCase): success, "Could not delete (all) the test template(s)") def test_07_able_to_create_vm(self): - self.login(client_name, random_accents) + self.login() vm_list = None create_vm_link = self.get_link_by_href('/dashboard/vm/create/') create_vm_link.click() - WebDriverWait(self.driver, wait_max_sec).until( + WebDriverWait(self.driver, conf.wait_max_sec).until( ec.visibility_of_element_located(( By.ID, 'confirmation-modal'))) vm_list = self.driver.find_elements_by_class_name( 'vm-create-template-summary') - print ("Selenium found %(vm_number)s virtual machine template " - " possibilities" % { - 'vm_number': len(vm_list)}) + logging.info("Selenium found %(vm_number)s virtual machine template " + " possibilities" % { + 'vm_number': len(vm_list)}) (self.assertIsNotNone( vm_list, "Selenium can not find the VM list") or self.assertGreater(len(vm_list), 0, "The create VM list is empty")) def test_08_create_vm(self): - self.login(client_name, random_accents) + self.login() pk = self.create_random_vm() self.vm_ids.append(pk) self.assertIsNotNone(pk, "Can not create a VM") def test_09_vm_view_change(self): - self.login(client_name, random_accents) + self.login() expected_states = ["", "none", "none", "", "block", "none"] states = self.view_change("vm") - print 'states: [%s]' % ', '.join(map(str, states)) - print 'expected: [%s]' % ', '.join(map(str, expected_states)) + logging.info('states: [%s]' % ', '.join(map(str, states))) + logging.info('expected: [%s]' % ', '.join(map(str, expected_states))) self.assertListEqual(states, expected_states, "The view mode does not change for VM listing") def test_10_node_view_change(self): - self.login(client_name, random_accents) + self.login() expected_states = ["", "none", "none", "", "block", "none"] states = self.view_change("node") - print 'states: [%s]' % ', '.join(map(str, states)) - print 'expected: [%s]' % ', '.join(map(str, expected_states)) + logging.info('states: [%s]' % ', '.join(map(str, states))) + logging.info('expected: [%s]' % ', '.join(map(str, expected_states))) self.assertListEqual(states, expected_states, "The view mode does not change for NODE listing") def test_11_delete_vm(self): - self.login(client_name, random_accents) + self.login() succes = True for vm in self.vm_ids: if not self.delete_vm(vm): diff --git a/circle/dashboard/tests/selenium/config.py b/circle/dashboard/tests/selenium/config.py new file mode 100644 index 0000000..37bcdb8 --- /dev/null +++ b/circle/dashboard/tests/selenium/config.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2014 Budapest University of Technology and Economics (BME IK) +# +# This file is part of CIRCLE Cloud. +# +# CIRCLE is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. +import random + + +class SeleniumConfig: + # How many sec can selenium wait till certain parts of a page appears + wait_max_sec = 10 + # How much sec can pass before the activity is no longer happened recently + recently_sec = 90 + + # Accented letters from which selenium can choose to name stuff + accents = u"áéíöóúűÁÉÍÖÓÜÚŰ" + # Non accented letters from which selenium can choose to name stuff + valid_chars = "0123456789abcdefghijklmnopqrstvwxyz" + + # First we choose 10 random normal letters + random_pass = "".join([random.choice( + valid_chars) for n in xrange(10)]) + # Then we append it with 5 random accented one + random_pass += "".join([random.choice( + accents) for n in xrange(5)]) + # Then we name our client as test_%(password)s + client_name = 'test_%s' % random_pass + + # Which webpage should selenium use (localhost is recommended) + host = 'https:127.0.0.1' + # In default the tests create a new user then delete it afteword + # Disable this if selenium cannot acces the database + create_user = True + + """ + Note: It's possible to setup that selenium uses a distant web server + for testing. If you choose this method you must provide a distant superuser + account info for that server by overriding random_pass and client_name by + uncommenting the lines below. + """ + # client_name = "user name here" + # random_pass = "password here" diff --git a/circle/dashboard/tests/selenium/util.py b/circle/dashboard/tests/selenium/util.py new file mode 100644 index 0000000..cdd8bee --- /dev/null +++ b/circle/dashboard/tests/selenium/util.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright 2014 Budapest University of Technology and Economics (BME IK) +# +# This file is part of CIRCLE Cloud. +# +# CIRCLE is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# CIRCLE is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along +# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. +from datetime import datetime +import logging +from sys import stdout +import random +import re +import time +import urlparse + +from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.support.select import Select +from selenium.webdriver.support.ui import WebDriverWait + +logger = logging.getLogger(__name__) +consoleHandler = logging.StreamHandler(stdout) +logger.addHandler(consoleHandler) + + +class CircleSeleniumMixin(object): + def login(self, location=None): + driver = self.driver + if location is None: + location = '/dashboard/' + driver.get('%s%s' % (self.conf.host, location)) + # Only if we aren't logged in already + if location not in urlparse.urlparse(self.driver.current_url).path: + try: + name_input = driver.find_element_by_id("id_username") + password_input = driver.find_element_by_id("id_password") + submit_input = driver.find_element_by_id("submit-id-submit") + except: + inputs = driver.find_elements_by_tag_name("input") + for current_input in inputs: + input_type = current_input.get_attribute("type") + if input_type == "text": + name_input = current_input + if input_type == "password": + password_input = current_input + if input_type == "submit": + submit_input = current_input + try: + name_input.clear() + name_input.send_keys(self.conf.client_name) + password_input.clear() + password_input.send_keys(self.conf.random_pass) + submit_input.click() + try: + # If selenium runs only in a small (virtual) screen + driver.find_element_by_class_name('navbar-toggle').click() + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CSS_SELECTOR, + "a[href*='/dashboard/profile/']"))) + except: + time.sleep(0.5) + except: + logging.exception('Selenium cannot find the form controls') + + def wait_and_accept_operation(self, argument=None): + """ + Accepts the operation confirmation pop up window. + Fills out the text inputs before accepting if argument is given. + """ + try: + accept = WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CLASS_NAME, "modal-accept"))) + if argument is not None: + possible = self.driver.find_elements_by_css_selector( + "div.controls > input[type='text']") + if isinstance(argument, list): + for x in range(0, len(possible)): + possible[x].clear() + possible[x].send_keys(argument[x % len(argument)]) + else: + for form in possible: + form.clear() + form.send_keys(argument) + accept.click() + except: + logging.exception( + 'Selenium cannot accept the operation confirmation') + + def save_template_from_vm(self, name): + try: + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CSS_SELECTOR, + "a[href$='/op/deploy/']"))) + self.click_on_link(self.get_link_by_href("/op/deploy/")) + self.wait_and_accept_operation() + recent_deploy = self.recently(self.get_timeline_elements( + "vm.Instance.deploy")) + if not self.check_operation_result(recent_deploy): + logging.warning("Selenium cannot deploy the " + "chosen template virtual machine") + logging.exception('Cannot deploy the virtual machine') + self.click_on_link(WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CSS_SELECTOR, + "a[href$='/op/shut_off/']")))) + self.wait_and_accept_operation() + recent_shut_off = self.recently(self.get_timeline_elements( + "vm.Instance.shut_off")) + if not self.check_operation_result(recent_shut_off): + logging.warning("Selenium cannot shut off the " + "chosen template virtual machine") + logging.exception('Cannot shut off the virtual machine') + self.click_on_link(WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CSS_SELECTOR, + "a[href$='/op/save_as_template/']")))) + self.wait_and_accept_operation(name) + return name + except: + logging.exception( + 'Selenium cannot save a vm as a template') + + def create_base_template(self, name=None, architecture="x86-64", + method=None, op_system=None, lease=None, + network="vm"): + if name is None: + name = "template_new_%s" % self.conf.client_name + if op_system is None: + op_system = "!os %s" % self.conf.client_name + try: + self.driver.get('%s/dashboard/template/choose/' % self.conf.host) + self.driver.find_element_by_css_selector( + "input[type='radio'][value='base_vm']").click() + self.driver.find_element_by_id( + "template-choose-next-button").click() + template_name = WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, 'id_name'))) + template_name.clear() + template_name.send_keys(name) + self.select_option(self.driver.find_element_by_id( + "id_arch"), architecture) + self.select_option(self.driver.find_element_by_id( + "id_access_method"), method) + system_name = self.driver.find_element_by_id("id_system") + system_name.clear() + system_name.send_keys(op_system) + self.select_option(self.driver.find_element_by_id( + "id_lease"), lease) + self.select_option(self.driver.find_element_by_id( + "id_networks"), network) + self.driver.find_element_by_css_selector( + "input.btn[type='submit']").click() + return self.save_template_from_vm(name) + except: + logging.exception( + 'Selenium cannot create a base template virtual machine') + + def get_template_id(self, name=None, from_all=False): + """ + In default settings find all templates ID in the template list. + If name is specified searches that specific template's ID + from_all sets whether to use owned templates or all of them + Returns list of the templates ID + """ + try: + self.driver.get('%s/dashboard/template/list/' % self.conf.host) + css_selector_of_a_template = ("a[data-original-title]" + "[href*='/dashboard/template/']") + if from_all: + self.select_option(self.driver.find_element_by_id( + 'id_stype'), "all") + self.driver.find_element_by_css_selector( + "button[type='submit']").click() + try: + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.presence_of_element_located(( + By.CSS_SELECTOR, css_selector_of_a_template))) + except: + logging.warning("Selenium could not locate any templates") + template_table = self.driver.find_element_by_css_selector( + "table[class*='template-list-table']") + templates = template_table.find_elements_by_css_selector("td.name") + found_template_ids = [] + for template in templates: + if name is None or name in template.text: + try: + template_link = template.find_element_by_css_selector( + css_selector_of_a_template) + template_id = re.search( + r'\d+', + template_link.get_attribute("outerHTML")).group() + found_template_ids.append(template_id) + logging.info("Found '%(name)s' " + "template's ID as %(id)s" % { + 'name': template.text, + 'id': template_id}) + except NoSuchElementException: + pass + except: + raise + if not found_template_ids and name is not None: + logging.warning("Selenium could not find the specified " + "%(name)s template in the list" % { + 'name': name}) + return found_template_ids + except: + logging.exception( + 'Selenium cannot found the template\'s id') + + def check_operation_result(self, operation_id, restore=True): + """ + Returns wheter the operation_id result is success (returns: boolean) + """ + try: + if restore: + url_base = urlparse.urlparse(self.driver.current_url) + url_save = ("%(host)s%(url)s" % { + 'host': self.conf.host, + 'url': urlparse.urljoin(url_base.path, url_base.query)}) + if url_base.fragment: + url_save = ("%(url)s#%(fragment)s" % { + 'url': url_save, + 'fragment': url_base.fragment}) + self.driver.get('%(host)s/dashboard/vm/activity/%(id)s/' % { + 'host': self.conf.host, + 'id': operation_id}) + result = WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, "activity_status"))) + logging.info("%(id)s result text is '%(result)s'" % { + 'id': operation_id, + 'result': result.text}) + if (result.text == "success"): + out = True + elif (result.text == "wait"): + time.sleep(2) + out = self.check_operation_result(operation_id, False) + else: + out = False + if restore: + logging.info("Restoring to %s url" % url_save) + self.driver.get(url_save) + return out + except: + logging.exception( + 'Selenium cannot check the result of an operation') + + def recently(self, timeline_dict, second=None): + if second is None: + second = self.conf.recently_sec + try: + if isinstance(timeline_dict, dict): + for key, value in timeline_dict.iteritems(): + time = datetime.strptime(key, '%Y-%m-%d %H:%M') + delta = datetime.now() - time + if delta.total_seconds() <= second: + return value + except: + logging.exception( + 'Selenium cannot filter timeline activities to recent') + + def get_timeline_elements(self, code=None): + try: + if code is None: + css_activity_selector = "div[data-activity-code]" + code = "all activity" + else: + css_activity_selector = ("div[data-activity-code=" + "'%(code)s']" % { + 'code': code}) + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CSS_SELECTOR, "a[href*='#activity']"))).click() + activity_dict = {} + timeline = WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, "activity-timeline"))) + searched_activity = timeline.find_elements_by_css_selector( + css_activity_selector) + logging.info("Found activity list for %s:" % code) + for activity in searched_activity: + activity_id = activity.get_attribute('data-activity-id') + activity_text = activity.text + key = re.search( + r'\d+-\d+-\d+ \d+:\d+,', activity_text).group()[:-1] + logging.info("%(id)s @ %(activity)s" % { + 'id': activity_id, + 'activity': key}) + activity_dict[key] = activity_id + return activity_dict + except: + logging.exception('Selenium cannot find the searched activity') + + def create_template_from_base(self, delete_disk=True, name=None): + try: + if name is None: + name = "template_from_base_%s" % self.conf.client_name + self.driver.get('%s/dashboard/template/choose/' % self.conf.host) + choice_list = [] + choices = self.driver.find_elements_by_css_selector( + "input[type='radio']") + choice_list = [item for item in choices if ( + 'test' not in item.get_attribute('value') + and item.get_attribute('value') != 'base_vm')] + chosen = random.randint(0, len(choice_list) - 1) + choice_list[chosen].click() + self.driver.find_element_by_id( + "template-choose-next-button").click() + if delete_disk: + self.click_on_link( + self.get_link_by_href("#resources")) + disks = WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, 'vm-details-resources-disk'))) + disk_list = disks.find_elements_by_css_selector( + "h4[class*='list-group-item-heading']") + if len(disk_list) > 0: + self.click_on_link( + self.get_link_by_href("/op/remove_disk/")) + self.wait_and_accept_operation() + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, "_activity"))) + recent_remove_disk = self.recently( + self.get_timeline_elements( + "vm.Instance.remove_disk")) + if not self.check_operation_result(recent_remove_disk): + logging.warning("Selenium cannot delete disk " + "of the chosen template") + logging.exception('Cannot delete disk') + return self.save_template_from_vm(name) + except: + logging.exception( + 'Selenium cannot start a template from a base one') + + def delete_template(self, template_id): + try: + self.driver.get( + '%s/dashboard/template/%s/' % (self.conf.host, template_id)) + url = urlparse.urlparse(self.driver.current_url) + self.click_on_link( + self.get_link_by_href( + "/dashboard/template/delete/%s/" % template_id)) + self.wait_and_accept_operation() + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.CLASS_NAME, 'alert-success'))) + url = urlparse.urlparse(self.driver.current_url) + if "/template/list/" not in url.path: + logging.exception( + 'System does not redirect to template listing') + except: + logging.exception('Selenium cannot delete the desired template') + + def create_random_vm(self): + try: + self.driver.get('%s/dashboard/vm/create/' % self.conf.host) + vm_list = [] + pk = None + vm_list = self.driver.find_elements_by_class_name( + 'vm-create-template-summary') + choice = random.randint(0, len(vm_list) - 1) + vm_list[choice].click() + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.element_to_be_clickable(( + By.CLASS_NAME, 'vm-create-start'))).click() + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.CLASS_NAME, 'alert-success'))) + url = urlparse.urlparse(self.driver.current_url) + pk = re.search(r'\d+', url.path).group() + return pk + except: + logging.exception('Selenium cannot start a VM') + + def view_change(self, target_box): + driver = self.driver + driver.get('%s/dashboard/' % self.conf.host) + list_view = driver.find_element_by_id('%s-list-view' % target_box) + graph_view = driver.find_element_by_id('%s-graph-view' % target_box) + js_script = 'return arguments[0].style.display;' + required_attributes = {'data-index-box': target_box} + graph_view_link = self.get_link_by_href( + '#index-graph-view', + required_attributes).find_element_by_tag_name('i') + list_view_link = self.get_link_by_href( + '#index-list-view', + required_attributes).find_element_by_tag_name('i') + self.click_on_link(list_view_link) + states = [driver.execute_script(js_script, list_view), + driver.execute_script(js_script, graph_view)] + self.click_on_link(graph_view_link) + states.extend([driver.execute_script(js_script, list_view), + driver.execute_script(js_script, graph_view)]) + self.click_on_link(list_view_link) + states.extend([driver.execute_script(js_script, list_view), + driver.execute_script(js_script, graph_view)]) + return states + + def delete_vm(self, pk): + try: + # For relability reasons instead of using the JS operatation + self.driver.get("%(host)s/dashboard/vm/%(id)s/op/destroy/" % { + 'host': self.conf.host, + 'id': pk}) + self.wait_and_accept_operation() + try: + status_span = WebDriverWait( + self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.ID, 'vm-details-state'))) + WebDriverWait(status_span, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.CLASS_NAME, 'fa-trash-o'))) + except: + # Selenium can time-out by not realising the JS refresh + recent_destroy_vm = self.recently( + self.get_timeline_elements("vm.Instance.destroy")) + if not self.check_operation_result(recent_destroy_vm): + logging.warning("Selenium cannot destroy " + "the chosen %(id)s vm" % { + 'id': pk}) + logging.exception('Cannot destroy the specified vm') + self.driver.get('%s/dashboard/vm/%s/' % (self.conf.host, pk)) + try: + WebDriverWait(self.driver, self.conf.wait_max_sec).until( + ec.visibility_of_element_located(( + By.CSS_SELECTOR, + "span[data-status*='DESTROYED']"))) + return True + except: + return False + except: + logging.exception("Selenium can not destroy a VM") + + +class SeleniumMixin(object): + def list_options(self, select): + try: + option_dic = {} + select = Select(select) + for option in select.options: + key = option.get_attribute('value') + if key is not None and key: + option_dic[key] = [option.text] + return option_dic + except: + logging.exception( + 'Selenium cannot list the select possibilities') + + def select_option(self, select, what=None): + """ + From an HTML select imput type try to choose the specified one. + Select is a selenium web element type. What represent both the + text of the option and it's ID. + """ + try: + my_choice = None + options = self.list_options(select) + select = Select(select) + if what is not None: + for key, value in options.iteritems(): + if what in key: + my_choice = key + else: + if isinstance(value, list): + for single_value in value: + if what in single_value: + my_choice = key + else: + if what in value: + my_choice = key + if my_choice is None: + my_choose_list = options.keys() + my_choice = my_choose_list[random.randint( + 0, len(my_choose_list) - 1)] + select.select_by_value(my_choice) + except: + logging.exception( + 'Selenium cannot select the chosen one') + + def get_link_by_href(self, target_href, attributes=None): + try: + links = self.driver.find_elements_by_tag_name('a') + for link in links: + href = link.get_attribute('href') + if href is not None and href: + if target_href in href: + perfect_fit = True + if isinstance(attributes, dict): + for key, target_value in attributes.iteritems(): + attr_check = link.get_attribute(key) + if attr_check is not None and attr_check: + if target_value not in attr_check: + perfect_fit = False + if perfect_fit: + return link + except: + logging.exception( + 'Selenium cannot find the href=%s link' % target_href) + + def click_on_link(self, link): + """ + There are situations when selenium built in click() function + doesn't work as intended, that's when this function is used. + Fires a click event via javascript injection. + """ + try: + # Javascript function to simulate a click on a link + javascript = ( + "var link = arguments[0];" + "var cancelled = false;" + "if(document.createEvent) {" + " var event = document.createEvent(\"MouseEvents\");" + " event.initMouseEvent(" + " \"click\", true, true, window, 0, 0, 0, 0, 0," + " false,false,false,false,0,null);" + " cancelled = !link.dispatchEvent(event);" + "} else if(link.fireEvent) {" + " cancelled = !link.fireEvent(\"onclick\");" + "} if (!cancelled) {" + " window.location = link.href;" + "}") + self.driver.execute_script(javascript, link) + except: + logging.exception( + 'Selenium cannot inject javascript to the page') -- libgit2 0.26.0