From 35da6c52e46cb3be2e4d41479cb15a8a88a790ba Mon Sep 17 00:00:00 2001 From: Jan Jasek Date: Mon, 11 Dec 2023 19:00:08 +0100 Subject: [PATCH] pytest-based selenium tests add volume actions tests test_extend_volume_demo test_volume_launch_as_instance_demo test_volume_upload_to_image_demo Change-Id: Ic577a94514881e56d32087a4fc4d6a06414ff568 --- .../test/selenium/integration/conftest.py | 89 ++++++++++ .../selenium/integration/test_floatingips.py | 5 - .../selenium/integration/test_instances.py | 83 +-------- .../integration/test_volume_snapshots.py | 5 - .../test/selenium/integration/test_volumes.py | 158 ++++++++++++------ openstack_dashboard/test/selenium/widgets.py | 17 ++ 6 files changed, 225 insertions(+), 132 deletions(-) diff --git a/openstack_dashboard/test/selenium/integration/conftest.py b/openstack_dashboard/test/selenium/integration/conftest.py index 988fc56037..490bb1c4ad 100644 --- a/openstack_dashboard/test/selenium/integration/conftest.py +++ b/openstack_dashboard/test/selenium/integration/conftest.py @@ -11,6 +11,7 @@ # under the License. import openstack as openstack_sdk +from oslo_utils import uuidutils import pytest from openstack_dashboard.test.selenium import widgets @@ -183,3 +184,91 @@ def complete_default_test_network(new_default_test_network, new_default_test_router, new_default_test_interface_for_router): yield new_default_test_network + + +# Instances fixtures + +@pytest.fixture +def instance_name(): + return 'horizon_instance_%s' % uuidutils.generate_uuid(dashed=False) + + +@pytest.fixture(params=[(1, False)]) +def new_instance_demo(complete_default_test_network, request, instance_name, + openstack_demo, config): + + count = request.param[0] + auto_ip_param = request.param[1] + instance = openstack_demo.create_server( + instance_name, + image=config.image.images_list[0], + flavor=config.launch_instances.flavor, + availability_zone=config.launch_instances.available_zone, + network=complete_default_test_network.name, + auto_ip=auto_ip_param, + wait=True, + max_count=count, + ) + yield instance + if count > 1: + for instance in range(0, count): + openstack_demo.delete_server(f"{instance_name}-{instance+1}") + else: + openstack_demo.delete_server(instance_name) + + +@pytest.fixture +def clear_instance_demo(instance_name, openstack_demo): + yield None + openstack_demo.delete_server( + instance_name, + wait=True, + ) + + +# Volumes fixtures + +@pytest.fixture(params=[1]) +def volume_name(request): + count = request.param + vol_name_list = ['horizon_vol_%s' % uuidutils.generate_uuid(dashed=False)] + if count > 1: + vol_name_list = [f"{vol_name_list[0]}-{item}" + for item in range(1, count + 1)] + return vol_name_list + + +@pytest.fixture +def new_volume_demo(volume_name, openstack_demo, config): + + for vol in volume_name: + volume = openstack_demo.create_volume( + name=vol, + image=config.image.images_list[0], + size=1, + wait=True, + ) + yield volume + for vol in volume_name: + openstack_demo.delete_volume( + name_or_id=vol, + wait=True, + ) + + +@pytest.fixture +def new_volume_admin(volume_name, openstack_admin, config): + + for vol in volume_name: + volume = openstack_admin.create_volume( + name=vol, + image=config.image.images_list[0], + size=1, + wait=True, + ) + yield volume + for vol in volume_name: + openstack_admin.delete_volume( + name_or_id=vol, + wait=True, + ) diff --git a/openstack_dashboard/test/selenium/integration/test_floatingips.py b/openstack_dashboard/test/selenium/integration/test_floatingips.py index c636933b27..93801c910a 100644 --- a/openstack_dashboard/test/selenium/integration/test_floatingips.py +++ b/openstack_dashboard/test/selenium/integration/test_floatingips.py @@ -14,14 +14,9 @@ import re from oslo_utils import uuidutils import pytest -import test_instances from openstack_dashboard.test.selenium import widgets -# Imported fixtures -instance_name = test_instances.instance_name -new_instance_demo = test_instances.new_instance_demo - @pytest.fixture def floatingip_description(): diff --git a/openstack_dashboard/test/selenium/integration/test_instances.py b/openstack_dashboard/test/selenium/integration/test_instances.py index e6135180f5..6348f1a3c2 100644 --- a/openstack_dashboard/test/selenium/integration/test_instances.py +++ b/openstack_dashboard/test/selenium/integration/test_instances.py @@ -10,49 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_utils import uuidutils import pytest -from selenium.common import exceptions from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -import test_volumes - from openstack_dashboard.test.selenium import widgets -# Imported fixtures -volume_name = test_volumes.volume_name -new_volume_demo = test_volumes.new_volume_demo - - -@pytest.fixture -def instance_name(): - return 'horizon_instance_%s' % uuidutils.generate_uuid(dashed=False) - - -@pytest.fixture(params=[(1, False)]) -def new_instance_demo(complete_default_test_network, request, instance_name, - openstack_demo, config): - - count = request.param[0] - auto_ip_param = request.param[1] - instance = openstack_demo.create_server( - instance_name, - image=config.image.images_list[0], - flavor=config.launch_instances.flavor, - availability_zone=config.launch_instances.available_zone, - network=complete_default_test_network.name, - auto_ip=auto_ip_param, - wait=True, - max_count=count, - ) - yield instance - if count > 1: - for instance in range(0, count): - openstack_demo.delete_server(f"{instance_name}-{instance+1}") - else: - openstack_demo.delete_server(instance_name) - @pytest.fixture(params=[(1, False)]) def new_instance_admin(complete_default_test_network, request, instance_name, @@ -78,15 +41,6 @@ def new_instance_admin(complete_default_test_network, request, instance_name, openstack_admin.delete_server(instance_name) -@pytest.fixture -def clear_instance_demo(instance_name, openstack_demo): - yield None - openstack_demo.delete_server( - instance_name, - wait=True, - ) - - @pytest.fixture def clear_instance_admin(instance_name, openstack_admin): yield None @@ -96,25 +50,6 @@ def clear_instance_admin(instance_name, openstack_admin): ) -def select_from_transfer_table(element, label): - """Choose row from available Images, Flavors, Networks, etc. - - in launch tab for example: m1.tiny for Flavor, cirros for image, etc. - """ - - try: - element.find_element_by_xpath( - f".//*[text()='{label}']//ancestor::tr/td//*" - f"[@class='btn btn-default fa fa-arrow-up']").click() - except exceptions.NoSuchElementException: - try: - element.find_element_by_xpath( - f".//*[text()='{label}']//ancestor::tr/td//*" - f"[@class='btn btn-default fa fa-arrow-down']") - except exceptions.NoSuchElementException: - raise - - def create_new_volume_during_create_instance(driver, required_state): create_new_volume_btn = widgets.find_already_visible_element_by_xpath( f".//*[@id='vol-create'][text()='{required_state}']", driver @@ -154,19 +89,19 @@ def test_create_instance_demo(complete_default_test_network, login, driver, network_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceNetworkForm]" ) - select_from_transfer_table(network_table, network) + widgets.select_from_transfer_table(network_table, network) navigation.find_element_by_link_text("Flavor").click() flavor_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceFlavorForm]" ) - select_from_transfer_table(flavor_table, flavor) + widgets.select_from_transfer_table(flavor_table, flavor) navigation.find_element_by_link_text("Source").click() source_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceSourceForm]" ) # create_new_volume_during_create_instance(source_table, "No") delete_volume_on_instance_delete(source_table, "Yes") - select_from_transfer_table(source_table, image) + widgets.select_from_transfer_table(source_table, image) wizard.find_element_by_css_selector( "button.btn-primary.finish").click() @@ -207,12 +142,12 @@ def test_create_instance_from_volume_demo(complete_default_test_network, login, network_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceNetworkForm]" ) - select_from_transfer_table(network_table, network) + widgets.select_from_transfer_table(network_table, network) navigation.find_element_by_link_text("Flavor").click() flavor_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceFlavorForm]" ) - select_from_transfer_table(flavor_table, flavor) + widgets.select_from_transfer_table(flavor_table, flavor) navigation.find_element_by_link_text("Source").click() source_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceSourceForm]" @@ -223,7 +158,7 @@ def test_create_instance_from_volume_demo(complete_default_test_network, login, select_boot_sources_type_tab.find_element_by_css_selector( "option[value='volume']").click() delete_volume_on_instance_delete(source_table, "No") - select_from_transfer_table(source_table, volume_name) + widgets.select_from_transfer_table(source_table, volume_name) wizard.find_element_by_css_selector("button.btn-primary.finish").click() WebDriverWait(driver, config.selenium.page_timeout).until( EC.invisibility_of_element_located(launch_instance_btn)) @@ -331,19 +266,19 @@ def test_create_instance_admin(complete_default_test_network, login, driver, network_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceNetworkForm]" ) - select_from_transfer_table(network_table, network) + widgets.select_from_transfer_table(network_table, network) navigation.find_element_by_link_text("Flavor").click() flavor_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceFlavorForm]" ) - select_from_transfer_table(flavor_table, flavor) + widgets.select_from_transfer_table(flavor_table, flavor) navigation.find_element_by_link_text("Source").click() source_table = wizard.find_element_by_css_selector( "ng-include[ng-form=launchInstanceSourceForm]" ) # create_new_volume_during_create_instance(source_table, "No") delete_volume_on_instance_delete(source_table, "Yes") - select_from_transfer_table(source_table, image) + widgets.select_from_transfer_table(source_table, image) wizard.find_element_by_css_selector( "button.btn-primary.finish").click() WebDriverWait(driver, config.selenium.page_timeout).until( diff --git a/openstack_dashboard/test/selenium/integration/test_volume_snapshots.py b/openstack_dashboard/test/selenium/integration/test_volume_snapshots.py index 8f86826a7f..1f8dbdb7fc 100644 --- a/openstack_dashboard/test/selenium/integration/test_volume_snapshots.py +++ b/openstack_dashboard/test/selenium/integration/test_volume_snapshots.py @@ -18,11 +18,6 @@ import test_volumes from openstack_dashboard.test.selenium import widgets -# Imported fixtures -volume_name = test_volumes.volume_name -new_volume_demo = test_volumes.new_volume_demo -new_volume_admin = test_volumes.new_volume_admin - @pytest.fixture(params=[1]) def volume_snapshot_names(request): diff --git a/openstack_dashboard/test/selenium/integration/test_volumes.py b/openstack_dashboard/test/selenium/integration/test_volumes.py index ab66316243..bf39e794a7 100644 --- a/openstack_dashboard/test/selenium/integration/test_volumes.py +++ b/openstack_dashboard/test/selenium/integration/test_volumes.py @@ -12,56 +12,16 @@ import time -from oslo_utils import uuidutils import pytest +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait +import test_images from openstack_dashboard.test.selenium import widgets - -@pytest.fixture(params=[1]) -def volume_name(request): - count = request.param - vol_name_list = ['horizon_vol_%s' % uuidutils.generate_uuid(dashed=False)] - if count > 1: - vol_name_list = [f"{vol_name_list[0]}-{item}" - for item in range(1, count + 1)] - return vol_name_list - - -@pytest.fixture -def new_volume_demo(volume_name, openstack_demo, config): - - for vol in volume_name: - volume = openstack_demo.create_volume( - name=vol, - image=config.image.images_list[0], - size=1, - wait=True, - ) - yield volume - for vol in volume_name: - openstack_demo.delete_volume( - name_or_id=vol, - wait=True, - ) - - -@pytest.fixture -def new_volume_admin(volume_name, openstack_admin, config): - - for vol in volume_name: - volume = openstack_admin.create_volume( - name=vol, - image=config.image.images_list[0], - size=1, - wait=True, - ) - yield volume - for vol in volume_name: - openstack_admin.delete_volume( - name_or_id=vol, - wait=True, - ) +# Import image fixtures +image_names = test_images.image_names +clear_image_demo = test_images.clear_image_demo @pytest.fixture @@ -87,8 +47,8 @@ def clear_volume_admin(volume_name, openstack_admin): def wait_for_steady_state_of_volume(openstack, volume_name): for attempt in range(10): if (openstack.block_storage.find_volume(volume_name).status in - ["available", "error", "error_restoring", "error_extending", - "error_managing"]): + ["available", "error", "in-use", "error_restoring", + "error_extending", "error_managing"]): break else: time.sleep(3) @@ -196,6 +156,108 @@ def test_edit_volume_description_demo(login, driver, volume_name, config, volume_name).description == f"EDITED_Description for: {volume_name}") +def test_extend_volume_demo(login, driver, openstack_demo, new_volume_demo, + config): + login('user') + url = '/'.join(( + config.dashboard.dashboard_url, + 'project', + 'volumes', + )) + driver.get(url) + rows = driver.find_elements_by_css_selector( + f"table#volumes tr[data-display='{new_volume_demo.name}']" + ) + assert len(rows) == 1 + assert(openstack_demo.block_storage.find_volume( + new_volume_demo.name).size == 1) + actions_column = rows[0].find_element_by_css_selector("td.actions_column") + widgets.select_from_dropdown(actions_column, "Extend Volume") + volume_form = driver.find_element_by_css_selector(".modal-dialog form") + volume_form.find_element_by_id("id_new_size").send_keys(2) + volume_form.find_element_by_css_selector( + ".btn-primary[value='Extend Volume']").click() + messages = widgets.get_and_dismiss_messages(driver) + assert f'Info: Extending volume: "{new_volume_demo.name}"' in messages + assert(openstack_demo.block_storage.find_volume( + new_volume_demo.name).size == 2) + + +def test_volume_launch_as_instance_demo(login, driver, openstack_demo, + new_volume_demo, instance_name, + clear_instance_demo, config, + complete_default_test_network): + flavor = config.launch_instances.flavor + network = complete_default_test_network.name + + login('user') + url = '/'.join(( + config.dashboard.dashboard_url, + 'project', + 'volumes', + )) + driver.get(url) + rows = driver.find_elements_by_css_selector( + f"table#volumes tr[data-display='{new_volume_demo.name}']" + ) + assert len(rows) == 1 + actions_column = rows[0].find_element_by_css_selector("td.actions_column") + widgets.select_from_dropdown(actions_column, "Launch as Instance") + wizard = driver.find_element_by_css_selector("wizard") + navigation = wizard.find_element_by_css_selector("div.wizard-nav") + widgets.find_already_visible_element_by_xpath( + ".//*[@id='name']", wizard).send_keys(instance_name) + navigation.find_element_by_link_text("Flavor").click() + flavor_table = wizard.find_element_by_css_selector( + "ng-include[ng-form=launchInstanceFlavorForm]") + widgets.select_from_transfer_table(flavor_table, flavor) + navigation.find_element_by_link_text("Networks").click() + network_table = wizard.find_element_by_css_selector( + "ng-include[ng-form=launchInstanceNetworkForm]") + widgets.select_from_transfer_table(network_table, network) + wizard.find_element_by_css_selector( + "button.btn-primary.finish").click() +# For create instance - message appears earlier than the page is refreshed. +# We are unable to ensure that the message will be captured. +# Checking of message is skipped, we wait for refresh page +# and then result is checked. +# JJ + WebDriverWait(driver, config.selenium.page_timeout).until( + EC.invisibility_of_element_located(actions_column)) + wait_for_steady_state_of_volume(openstack_demo, new_volume_demo.name) + assert(openstack_demo.block_storage.find_volume( + new_volume_demo.name).attachments[0]['server_id'] == + openstack_demo.compute.find_server(instance_name).id) + + +def test_volume_upload_to_image_demo(login, driver, openstack_demo, + new_volume_demo, image_names, + clear_image_demo, config): + login('user') + url = '/'.join(( + config.dashboard.dashboard_url, + 'project', + 'volumes', + )) + driver.get(url) + rows = driver.find_elements_by_css_selector( + f"table#volumes tr[data-display='{new_volume_demo.name}']" + ) + assert len(rows) == 1 + actions_column = rows[0].find_element_by_css_selector("td.actions_column") + widgets.select_from_dropdown(actions_column, "Upload to Image") + volume_form = driver.find_element_by_css_selector( + ".modal-dialog form") + volume_form.find_element_by_id("id_image_name").send_keys(image_names[0]) + volume_form.find_element_by_css_selector( + ".btn-primary[value='Upload']").click() + messages = widgets.get_and_dismiss_messages(driver) + assert(f'Info: Successfully sent the request to upload volume to image for ' + f'volume: "{new_volume_demo.name}"' in messages) + wait_for_steady_state_of_volume(openstack_demo, new_volume_demo.name) + assert openstack_demo.compute.find_image(image_names[0]) is not None + + @pytest.mark.parametrize('volume_name', [3], indirect=True) def test_volumes_pagination_demo(login, driver, volume_name, change_page_size_demo, diff --git a/openstack_dashboard/test/selenium/widgets.py b/openstack_dashboard/test/selenium/widgets.py index 76d25f64ae..c00e76e023 100644 --- a/openstack_dashboard/test/selenium/widgets.py +++ b/openstack_dashboard/test/selenium/widgets.py @@ -105,3 +105,20 @@ def get_image_table_definition(driver, sorting=False): prev=is_prev_link_available(driver), count=len(names), names=[names[0].text]) return actual_table + + +def select_from_transfer_table(element, label): + # Choose row from available Images, Flavors, Networks, etc. + # In launch tab for example: m1.tiny for Flavor, cirros for image, etc. + + try: + element.find_element_by_xpath( + f".//*[text()='{label}']//ancestor::tr/td//*" + f"[@class='btn btn-default fa fa-arrow-up']").click() + except exceptions.NoSuchElementException: + try: + element.find_element_by_xpath( + f".//*[text()='{label}']//ancestor::tr/td//*" + f"[@class='btn btn-default fa fa-arrow-down']") + except exceptions.NoSuchElementException: + raise