diff mbox series

[4/4] toaster/test: Bug fixes, functional tests dependent on each other

Message ID 20231208015349.678997-4-alassane.yattara@savoirfairelinux.com
State New
Headers show
Series [1/4] toaster/test: Ensure to kill toaster process create for tests functional | expand

Commit Message

Alassane Yattara Dec. 8, 2023, 1:53 a.m. UTC
refactor test_create_project and test_project_page to remove their dependencies

Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
---
 .../functional/test_create_new_project.py     |   1 +
 .../tests/functional/test_project_page.py     | 164 +++++++++---------
 2 files changed, 79 insertions(+), 86 deletions(-)
diff mbox series

Patch

diff --git a/lib/toaster/tests/functional/test_create_new_project.py b/lib/toaster/tests/functional/test_create_new_project.py
index dc7d1fc2..bbda0cf4 100644
--- a/lib/toaster/tests/functional/test_create_new_project.py
+++ b/lib/toaster/tests/functional/test_create_new_project.py
@@ -16,6 +16,7 @@  from selenium.webdriver.common.by import By
 
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestCreateNewProject(SeleniumFunctionalTestCase):
 
     def _create_test_new_project(
diff --git a/lib/toaster/tests/functional/test_project_page.py b/lib/toaster/tests/functional/test_project_page.py
index 03f64f8f..077badb0 100644
--- a/lib/toaster/tests/functional/test_project_page.py
+++ b/lib/toaster/tests/functional/test_project_page.py
@@ -6,88 +6,89 @@ 
 # SPDX-License-Identifier: GPL-2.0-only
 #
 
+import os
 import random
 import string
+from unittest import skip
 import pytest
-from time import sleep
 from django.urls import reverse
 from django.utils import timezone
 from selenium.webdriver.common.keys import Keys
 from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException, TimeoutException
+from selenium.common.exceptions import TimeoutException
 from tests.functional.functional_helpers import SeleniumFunctionalTestCase
 from orm.models import Build, Project, Target
 from selenium.webdriver.common.by import By
 
+from .utils import get_projectId_from_url, wait_until_build, wait_until_build_cancelled
+
 
 @pytest.mark.django_db
+@pytest.mark.order("last")
 class TestProjectPage(SeleniumFunctionalTestCase):
+    project_id = None
+    PROJECT_NAME = 'TestProjectPage'
 
-    def setUp(self):
-        super().setUp()
-        release = '3'
-        project_name = 'project_' + self.generate_random_string()
-        self._create_test_new_project(
-            project_name,
-            release,
-            False,
-        )
-
-    def generate_random_string(self, length=10):
-        characters = string.ascii_letters + string.digits  # alphabetic and numerical characters
-        random_string = ''.join(random.choice(characters) for _ in range(length))
-        return random_string
-
-    def _create_test_new_project(
-        self,
-        project_name,
-        release,
-        merge_toaster_settings,
-    ):
+    def _create_project(self, project_name):
         """ Create/Test new project using:
           - Project Name: Any string
           - Release: Any string
           - Merge Toaster settings: True or False
         """
         self.get(reverse('newproject'))
-        self.driver.find_element(By.ID,
-                                 "new-project-name").send_keys(project_name)
-
-        select = Select(self.find('#projectversion'))
-        select.select_by_value(release)
+        self.wait_until_visible('#new-project-name')
+        self.find("#new-project-name").send_keys(project_name)
+        select = Select(self.find("#projectversion"))
+        select.select_by_value('3')
 
         # check merge toaster settings
         checkbox = self.find('.checkbox-mergeattr')
-        if merge_toaster_settings:
-            if not checkbox.is_selected():
-                checkbox.click()
+        if not checkbox.is_selected():
+            checkbox.click()
+
+        if self.PROJECT_NAME != 'TestProjectPage':
+            # Reset project name if it's not the default one
+            self.PROJECT_NAME = 'TestProjectPage'
+
+        self.find("#create-project-button").click()
+
+        try:
+            self.wait_until_visible('#hint-error-project-name')
+            url = reverse('project', args=(TestProjectPage.project_id, ))
+            self.get(url)
+            self.wait_until_visible('#config-nav', poll=3)
+        except TimeoutException:
+            self.wait_until_visible('#config-nav', poll=3)
+
+    def _random_string(self, length):
+        return ''.join(
+            random.choice(string.ascii_letters) for _ in range(length)
+        )
+
+    def _navigate_to_project_page(self):
+        # Navigate to project page
+        if TestProjectPage.project_id is None:
+            self._create_project(project_name=self._random_string(10))
+            current_url = self.driver.current_url
+            TestProjectPage.project_id = get_projectId_from_url(current_url)
         else:
-            if checkbox.is_selected():
-                checkbox.click()
-
-        self.driver.find_element(By.ID, "create-project-button").click()
+            url = reverse('project', args=(TestProjectPage.project_id,))
+            self.get(url)
+        self.wait_until_visible('#config-nav')
 
     def _get_create_builds(self, **kwargs):
         """ Create a build and return the build object """
         # parameters for builds to associate with the projects
         now = timezone.now()
-        release = '3'
-        project_name = 'projectmaster'
-        self._create_test_new_project(
-            project_name+"2",
-            release,
-            False,
-        )
-
         self.project1_build_success = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.SUCCEEDED
         }
 
         self.project1_build_failure = {
-            'project': Project.objects.get(id=1),
+            'project': Project.objects.get(id=TestProjectPage.project_id),
             'started_on': now,
             'completed_on': now,
             'outcome': Build.FAILED
@@ -180,9 +181,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
 
     def _navigate_to_config_nav(self, nav_id, nav_index):
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
-        self.wait_until_visible('#config-nav')
+        self._navigate_to_project_page()
         # click on "Software recipe" tab
         soft_recipe = self._get_config_nav_item(nav_index)
         soft_recipe.click()
@@ -211,29 +210,6 @@  class TestProjectPage(SeleniumFunctionalTestCase):
                 if row_to_show not in to_skip:
                     test_show_rows(row_to_show, show_row_link)
 
-    def _wait_until_build(self, state):
-        timeout = 10
-        start_time = 0
-        while True:
-            if start_time > timeout:
-                raise TimeoutException(
-                    f'Build did not reach {state} state within {timeout} seconds'
-                )
-            try:
-                last_build_state = self.driver.find_element(
-                    By.XPATH,
-                    '//*[@id="latest-builds"]/div[1]//div[@class="build-state"]',
-                )
-                build_state = last_build_state.get_attribute(
-                    'data-build-state')
-                state_text = state.lower().split()
-                if any(x in str(build_state).lower() for x in state_text):
-                    break
-            except NoSuchElementException:
-                continue
-            start_time += 1
-            sleep(1) # take a breath and try again
-
     def _mixin_test_table_search_input(self, **kwargs):
         input_selector, input_text, searchBtn_selector, table_selector, *_ = kwargs.values()
         # Test search input
@@ -245,11 +221,19 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         rows = self.find_all(f'#{table_selector} tbody tr')
         self.assertTrue(len(rows) > 0)
 
+    def test_create_project(self):
+        """ Create/Test new project using:
+          - Project Name: Any string
+          - Release: Any string
+          - Merge Toaster settings: True or False
+        """
+        self._create_project(project_name=self.PROJECT_NAME)
+
     def test_image_recipe_editColumn(self):
         """ Test the edit column feature in image recipe table on project page """
         self._get_create_builds(success=10, failure=10)
 
-        url = reverse('projectimagerecipes', args=(1,))
+        url = reverse('projectimagerecipes', args=(TestProjectPage.project_id,))
         self.get(url)
         self.wait_until_present('#imagerecipestable tbody tr')
 
@@ -276,8 +260,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
           - AT RIGHT -> button "New project", displayed, clickable
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check page header
         # AT LEFT -> Logo of Yocto project
@@ -360,8 +343,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
           - Check project name is changed
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # click on "Edit" icon button
         self.wait_until_visible('#project-name-container')
@@ -388,8 +370,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
           Check search box used to build recipes
         """
         # navigate to the project page
-        url = reverse("project", args=(1,))
-        self.get(url)
+        self._navigate_to_project_page()
 
         # check "configuration" tab
         self.wait_until_visible('#topbar-configuration-tab')
@@ -397,7 +378,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         self.assertTrue(config_tab.get_attribute('class') == 'active')
         self.assertTrue('Configuration' in str(config_tab.text))
         self.assertTrue(
-            f"/toastergui/project/1" in str(self.driver.current_url)
+            f"/toastergui/project/{TestProjectPage.project_id}" in str(self.driver.current_url)
         )
 
         def get_tabs():
@@ -420,7 +401,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         check_tab_link(
             1,
             'Builds',
-            f"/toastergui/project/1/builds"
+            f"/toastergui/project/{TestProjectPage.project_id}/builds"
         )
 
         # check "Import layers" tab
@@ -429,7 +410,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         check_tab_link(
             2,
             'Import layer',
-            f"/toastergui/project/1/importlayer"
+            f"/toastergui/project/{TestProjectPage.project_id}/importlayer"
         )
 
         # check "New custom image" tab
@@ -438,7 +419,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         check_tab_link(
             3,
             'New custom image',
-            f"/toastergui/project/1/newcustomimage"
+            f"/toastergui/project/{TestProjectPage.project_id}/newcustomimage"
         )
 
         # check search box can be use to build recipes
@@ -480,12 +461,20 @@  class TestProjectPage(SeleniumFunctionalTestCase):
             '//td[@class="add-del-layers"]//a[1]'
         )
         build_btn.click()
-        self._wait_until_build('parsing starting cloning queued')
+        build_state = wait_until_build(self, 'parsing starting cloning queued')
         lastest_builds = self.driver.find_elements(
             By.XPATH,
             '//div[@id="latest-builds"]/div'
         )
         self.assertTrue(len(lastest_builds) > 0)
+        last_build = lastest_builds[0]
+        cancel_button = last_build.find_element(
+            By.XPATH,
+            '//span[@class="cancel-build-btn pull-right alert-link"]',
+        )
+        cancel_button.click()
+        if 'starting' not in build_state:  # change build state when cancelled in starting state
+            wait_until_build_cancelled(self)
 
         # check software recipe table feature(show/hide column, pagination)
         self._navigate_to_config_nav('softwarerecipestable', 4)
@@ -547,6 +536,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
             searchBtn_selector='search-submit-machinestable',
             table_selector='machinestable'
         )
+        self.wait_until_visible('#machinestable tbody tr', poll=3)
         rows = self.find_all('#machinestable tbody tr')
         machine_to_add = rows[0]
         add_btn = machine_to_add.find_element(By.XPATH, '//td[@class="add-del-layers"]')
@@ -593,6 +583,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
             table_selector='layerstable'
         )
         # check "Add layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_add = rows[0]
         add_btn = layer_to_add.find_element(
@@ -601,7 +592,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
         )
         add_btn.click()
         # check modal is displayed
-        self.wait_until_visible('#dependencies-modal', poll=2)
+        self.wait_until_visible('#dependencies-modal', poll=3)
         list_dependencies = self.find_all('#dependencies-list li')
         # click on add-layers button
         add_layers_btn = self.driver.find_element(
@@ -615,6 +606,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
             f'You have added {len(list_dependencies)+1} layers to your project: {input_text} and its dependencies' in str(change_notification.text)
         )
         # check "Remove layer" button works
+        self.wait_until_visible('#layerstable tbody tr', poll=3)
         rows = self.find_all('#layerstable tbody tr')
         layer_to_remove = rows[0]
         remove_btn = layer_to_remove.find_element(
@@ -706,7 +698,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
                 - Check layer summary
                 - Check layer description
         """
-        url = reverse("layerdetails", args=(1, 8))
+        url = reverse("layerdetails", args=(TestProjectPage.project_id, 8))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed
@@ -765,7 +757,7 @@  class TestProjectPage(SeleniumFunctionalTestCase):
                 - Check recipe: name, summary, description, Version, Section,
                 License, Approx. packages included, Approx. size, Recipe file
         """
-        url = reverse("recipedetails", args=(1, 53428))
+        url = reverse("recipedetails", args=(TestProjectPage.project_id, 53428))
         self.get(url)
         self.wait_until_visible('.page-header')
         # check title is displayed