new file mode 100644
@@ -0,0 +1,335 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import string
+import random
+import pytest
+from django.urls import reverse
+from selenium.webdriver import Keys
+from selenium.webdriver.support.select import Select
+from selenium.common.exceptions import TimeoutException
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from selenium.webdriver.common.by import By
+
+from .utils import get_projectId_from_url
+
+
+@pytest.mark.django_db
+@pytest.mark.order("last")
+class TestProjectConfig(SeleniumFunctionalTestCase):
+ project_id = None
+ PROJECT_NAME = 'TestProjectConfig'
+ INVALID_PATH_START_TEXT = 'The directory path should either start with a /'
+ INVALID_PATH_CHAR_TEXT = 'The directory path cannot include spaces or ' \
+ 'any of these characters'
+
+ 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.wait_until_visible('#new-project-name', poll=2)
+ 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 not checkbox.is_selected():
+ checkbox.click()
+
+ if self.PROJECT_NAME != 'TestProjectConfig':
+ # Reset project name if it's not the default one
+ self.PROJECT_NAME = 'TestProjectConfig'
+
+ self.find("#create-project-button").click()
+
+ try:
+ self.wait_until_visible('#hint-error-project-name', poll=2)
+ url = reverse('project', args=(TestProjectConfig.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 _get_config_nav_item(self, index):
+ config_nav = self.find('#config-nav')
+ return config_nav.find_elements(By.TAG_NAME, 'li')[index]
+
+ def _navigate_bbv_page(self):
+ """ Navigate to project BitBake variables page """
+ # check if the menu is displayed
+ if TestProjectConfig.project_id is None:
+ self._create_project(project_name=self._random_string(10))
+ current_url = self.driver.current_url
+ TestProjectConfig.project_id = get_projectId_from_url(current_url)
+ else:
+ url = reverse('projectconf', args=(TestProjectConfig.project_id,))
+ self.get(url)
+ self.wait_until_visible('#config-nav', poll=3)
+ bbv_page_link = self._get_config_nav_item(9)
+ bbv_page_link.click()
+ self.wait_until_visible('#config-nav', poll=3)
+
+ def test_no_underscore_iamgefs_type(self):
+ """
+ Should not accept IMAGEFS_TYPE with an underscore
+ """
+ self._navigate_bbv_page()
+ imagefs_type = "foo_bar"
+
+ self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+ self.click('#change-image_fstypes-icon')
+
+ self.enter_text('#new-imagefs_types', imagefs_type)
+
+ element = self.wait_until_visible('#hintError-image-fs_type', poll=2)
+
+ self.assertTrue(("A valid image type cannot include underscores" in element.text),
+ "Did not find underscore error message")
+
+ def test_checkbox_verification(self):
+ """
+ Should automatically check the checkbox if user enters value
+ text box, if value is there in the checkbox.
+ """
+ self._navigate_bbv_page()
+
+ imagefs_type = "btrfs"
+
+ self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+ self.click('#change-image_fstypes-icon')
+
+ self.enter_text('#new-imagefs_types', imagefs_type)
+
+ checkboxes = self.driver.find_elements(By.XPATH, "//input[@class='fs-checkbox-fstypes']")
+
+ for checkbox in checkboxes:
+ if checkbox.get_attribute("value") == "btrfs":
+ self.assertEqual(checkbox.is_selected(), True)
+
+ def test_textbox_with_checkbox_verification(self):
+ """
+ Should automatically add or remove value in textbox, if user checks
+ or unchecks checkboxes.
+ """
+ self._navigate_bbv_page()
+
+ self.wait_until_visible('#change-image_fstypes-icon', poll=2)
+
+ self.click('#change-image_fstypes-icon')
+
+ checkboxes_selector = '.fs-checkbox-fstypes'
+
+ self.wait_until_visible(checkboxes_selector, poll=2)
+ checkboxes = self.find_all(checkboxes_selector)
+
+ for checkbox in checkboxes:
+ if checkbox.get_attribute("value") == "cpio":
+ checkbox.click()
+ element = self.driver.find_element(By.ID, 'new-imagefs_types')
+
+ self.wait_until_visible('#new-imagefs_types', poll=2)
+
+ self.assertTrue(("cpio" in element.get_attribute('value'),
+ "Imagefs not added into the textbox"))
+ checkbox.click()
+ self.assertTrue(("cpio" not in element.text),
+ "Image still present in the textbox")
+
+ def test_set_download_dir(self):
+ """
+ Validate the allowed and disallowed types in the directory field for
+ DL_DIR
+ """
+ self._navigate_bbv_page()
+
+ # activate the input to edit download dir
+ try:
+ change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+ except TimeoutException:
+ # If download dir is not displayed, test is skipped
+ return True
+ change_dl_dir_btn = self.wait_until_visible('#change-dl_dir-icon', poll=2)
+ change_dl_dir_btn.click()
+
+ # downloads dir path doesn't start with / or ${...}
+ input_field = self.wait_until_visible('#new-dl_dir', poll=2)
+ input_field.clear()
+ self.enter_text('#new-dl_dir', 'home/foo')
+ element = self.wait_until_visible('#hintError-initialChar-dl_dir', poll=2)
+
+ msg = 'downloads directory path starts with invalid character but ' \
+ 'treated as valid'
+ self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+ # downloads dir path has a space
+ self.driver.find_element(By.ID, 'new-dl_dir').clear()
+ self.enter_text('#new-dl_dir', '/foo/bar a')
+
+ element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+ msg = 'downloads directory path characters invalid but treated as valid'
+ self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+ # downloads dir path starts with ${...} but has a space
+ self.driver.find_element(By.ID,'new-dl_dir').clear()
+ self.enter_text('#new-dl_dir', '${TOPDIR}/down foo')
+
+ element = self.wait_until_visible('#hintError-dl_dir', poll=2)
+ msg = 'downloads directory path characters invalid but treated as valid'
+ self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+ # downloads dir path starts with /
+ self.driver.find_element(By.ID,'new-dl_dir').clear()
+ self.enter_text('#new-dl_dir', '/bar/foo')
+
+ hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+ self.assertEqual(hidden_element.is_displayed(), False,
+ 'downloads directory path valid but treated as invalid')
+
+ # downloads dir path starts with ${...}
+ self.driver.find_element(By.ID,'new-dl_dir').clear()
+ self.enter_text('#new-dl_dir', '${TOPDIR}/down')
+
+ hidden_element = self.driver.find_element(By.ID,'hintError-dl_dir')
+ self.assertEqual(hidden_element.is_displayed(), False,
+ 'downloads directory path valid but treated as invalid')
+
+ def test_set_sstate_dir(self):
+ """
+ Validate the allowed and disallowed types in the directory field for
+ SSTATE_DIR
+ """
+ self._navigate_bbv_page()
+
+ try:
+ self.wait_until_visible('#change-sstate_dir-icon', poll=2)
+ self.click('#change-sstate_dir-icon')
+ except TimeoutException:
+ # If sstate_dir is not displayed, test is skipped
+ return True
+
+ # path doesn't start with / or ${...}
+ input_field = self.wait_until_visible('#new-sstate_dir', poll=2)
+ input_field.clear()
+ self.enter_text('#new-sstate_dir', 'home/foo')
+ element = self.wait_until_visible('#hintError-initialChar-sstate_dir', poll=2)
+
+ msg = 'sstate directory path starts with invalid character but ' \
+ 'treated as valid'
+ self.assertTrue((self.INVALID_PATH_START_TEXT in element.text), msg)
+
+ # path has a space
+ self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+ self.enter_text('#new-sstate_dir', '/foo/bar a')
+
+ element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+ msg = 'sstate directory path characters invalid but treated as valid'
+ self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+ # path starts with ${...} but has a space
+ self.driver.find_element(By.ID,'new-sstate_dir').clear()
+ self.enter_text('#new-sstate_dir', '${TOPDIR}/down foo')
+
+ element = self.wait_until_visible('#hintError-sstate_dir', poll=2)
+ msg = 'sstate directory path characters invalid but treated as valid'
+ self.assertTrue((self.INVALID_PATH_CHAR_TEXT in element.text), msg)
+
+ # path starts with /
+ self.driver.find_element(By.ID,'new-sstate_dir').clear()
+ self.enter_text('#new-sstate_dir', '/bar/foo')
+
+ hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+ self.assertEqual(hidden_element.is_displayed(), False,
+ 'sstate directory path valid but treated as invalid')
+
+ # paths starts with ${...}
+ self.driver.find_element(By.ID, 'new-sstate_dir').clear()
+ self.enter_text('#new-sstate_dir', '${TOPDIR}/down')
+
+ hidden_element = self.driver.find_element(By.ID, 'hintError-sstate_dir')
+ self.assertEqual(hidden_element.is_displayed(), False,
+ 'sstate directory path valid but treated as invalid')
+
+ def _change_bbv_value(self, **kwargs):
+ var_name, field, btn_id, input_id, value, save_btn, *_ = kwargs.values()
+ """ Change bitbake variable value """
+ self._navigate_bbv_page()
+ self.wait_until_visible(f'#{btn_id}', poll=2)
+ if kwargs.get('new_variable'):
+ self.find(f"#{btn_id}").clear()
+ self.enter_text(f"#{btn_id}", f"{var_name}")
+ else:
+ self.click(f'#{btn_id}')
+ self.wait_until_visible(f'#{input_id}', poll=2)
+
+ if kwargs.get('is_select'):
+ select = Select(self.find(f'#{input_id}'))
+ select.select_by_visible_text(value)
+ else:
+ self.find(f"#{input_id}").clear()
+ self.enter_text(f'#{input_id}', f'{value}')
+ self.click(f'#{save_btn}')
+ value_displayed = str(self.wait_until_visible(f'#{field}').text).lower()
+ msg = f'{var_name} variable not changed'
+ self.assertTrue(str(value).lower() in value_displayed, msg)
+
+ def test_change_distro_var(self):
+ """ Test changing distro variable """
+ self._change_bbv_value(
+ var_name='DISTRO',
+ field='distro',
+ btn_id='change-distro-icon',
+ input_id='new-distro',
+ value='poky-changed',
+ save_btn="apply-change-distro",
+ )
+
+ def test_set_image_install_append_var(self):
+ """ Test setting IMAGE_INSTALL:append variable """
+ self._change_bbv_value(
+ var_name='IMAGE_INSTALL:append',
+ field='image_install',
+ btn_id='change-image_install-icon',
+ input_id='new-image_install',
+ value='bash, apt, busybox',
+ save_btn="apply-change-image_install",
+ )
+
+ def test_set_package_classes_var(self):
+ """ Test setting PACKAGE_CLASSES variable """
+ self._change_bbv_value(
+ var_name='PACKAGE_CLASSES',
+ field='package_classes',
+ btn_id='change-package_classes-icon',
+ input_id='package_classes-select',
+ value='package_deb',
+ save_btn="apply-change-package_classes",
+ is_select=True,
+ )
+
+ def test_create_new_bbv(self):
+ """ Test creating new bitbake variable """
+ self._change_bbv_value(
+ var_name='New_Custom_Variable',
+ field='configvar-list',
+ btn_id='variable',
+ input_id='value',
+ value='new variable value',
+ save_btn="add-configvar-button",
+ new_variable=True
+ )
@@ -6,87 +6,81 @@
# SPDX-License-Identifier: GPL-2.0-only
#
-from time import sleep
+import string
+import random
import pytest
-from django.utils import timezone
from django.urls import reverse
from selenium.webdriver import Keys
from selenium.webdriver.support.select import Select
-from selenium.common.exceptions import NoSuchElementException
-from orm.models import Build, Project, Target
+from selenium.common.exceptions import TimeoutException
+from orm.models import Project
from tests.functional.functional_helpers import SeleniumFunctionalTestCase
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 TestProjectConfigTab(SeleniumFunctionalTestCase):
+ PROJECT_NAME = 'TestProjectConfigTab'
+ project_id = None
- def setUp(self):
- self.recipe = None
- super().setUp()
- release = '3'
- project_name = 'projectmaster'
- self._create_test_new_project(
- project_name,
- release,
- False,
- )
-
- 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 != 'TestProjectConfigTab':
+ # Reset project name if it's not the default one
+ self.PROJECT_NAME = 'TestProjectConfigTab'
+
+ self.find("#create-project-button").click()
+
+ try:
+ self.wait_until_visible('#hint-error-project-name')
+ url = reverse('project', args=(TestProjectConfigTab.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 TestProjectConfigTab.project_id is None:
+ self._create_project(project_name=self._random_string(10))
+ current_url = self.driver.current_url
+ TestProjectConfigTab.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()
-
- @classmethod
- def _wait_until_build(cls, state):
- while True:
- try:
- last_build_state = cls.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
- sleep(1)
+ url = reverse('project', args=(TestProjectConfigTab.project_id,))
+ self.get(url)
+ self.wait_until_visible('#config-nav')
def _create_builds(self):
# check search box can be use to build recipes
search_box = self.find('#build-input')
search_box.send_keys('core-image-minimal')
self.find('#build-button').click()
- sleep(1)
self.wait_until_visible('#latest-builds')
# loop until reach the parsing state
- self._wait_until_build('parsing starting cloning')
+ build_state = wait_until_build(self, 'parsing starting cloning')
lastest_builds = self.driver.find_elements(
By.XPATH,
'//div[@id="latest-builds"]/div',
@@ -100,8 +94,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
'//span[@class="cancel-build-btn pull-right alert-link"]',
)
cancel_button.click()
- sleep(1)
- self._wait_until_build('cancelled')
+ if 'starting' not in build_state: # change build state when cancelled in starting state
+ wait_until_build_cancelled(self)
+ return build_state
def _get_tabs(self):
# tabs links list
@@ -114,64 +109,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
config_nav = self.find('#config-nav')
return config_nav.find_elements(By.TAG_NAME, 'li')[index]
- 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),
- 'started_on': now,
- 'completed_on': now,
- 'outcome': Build.SUCCEEDED
- }
-
- self.project1_build_failure = {
- 'project': Project.objects.get(id=1),
- 'started_on': now,
- 'completed_on': now,
- 'outcome': Build.FAILED
- }
- build1 = Build.objects.create(**self.project1_build_success)
- build2 = Build.objects.create(**self.project1_build_failure)
-
- # add some targets to these builds so they have recipe links
- # (and so we can find the row in the ToasterTable corresponding to
- # a particular build)
- Target.objects.create(build=build1, target='foo')
- Target.objects.create(build=build2, target='bar')
-
- if kwargs:
- # Create kwargs.get('success') builds with success status with target
- # and kwargs.get('failure') builds with failure status with target
- for i in range(kwargs.get('success', 0)):
- now = timezone.now()
- self.project1_build_success['started_on'] = now
- self.project1_build_success[
- 'completed_on'] = now - timezone.timedelta(days=i)
- build = Build.objects.create(**self.project1_build_success)
- Target.objects.create(build=build,
- target=f'{i}_success_recipe',
- task=f'{i}_success_task')
-
- for i in range(kwargs.get('failure', 0)):
- now = timezone.now()
- self.project1_build_failure['started_on'] = now
- self.project1_build_failure[
- 'completed_on'] = now - timezone.timedelta(days=i)
- build = Build.objects.create(**self.project1_build_failure)
- Target.objects.create(build=build,
- target=f'{i}_fail_recipe',
- task=f'{i}_fail_task')
- return build1, build2
-
def test_project_config_nav(self):
""" Test project config tab navigation:
- Check if the menu is displayed and contains the right elements:
@@ -188,13 +125,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
- Actions
- Delete project
"""
- # navigate to the project page
- url = reverse("project", args=(1,))
- self.get(url)
-
- # check if the menu is displayed
- self.wait_until_visible('#config-nav')
-
+ self._navigate_to_project_page()
def _get_config_nav_item(index):
config_nav = self.find('#config-nav')
return config_nav.find_elements(By.TAG_NAME, 'li')[index]
@@ -221,14 +152,14 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
self.assertTrue("actions" in str(actions.text).lower())
conf_nav_list = [
- [0, 'Configuration', f"/toastergui/project/1"], # config
- [2, 'Custom images', f"/toastergui/project/1/customimages"], # custom images
- [3, 'Image recipes', f"/toastergui/project/1/images"], # image recipes
- [4, 'Software recipes', f"/toastergui/project/1/softwarerecipes"], # software recipes
- [5, 'Machines', f"/toastergui/project/1/machines"], # machines
- [6, 'Layers', f"/toastergui/project/1/layers"], # layers
- [7, 'Distro', f"/toastergui/project/1/distro"], # distro
- [9, 'BitBake variables', f"/toastergui/project/1/configuration"], # bitbake variables
+ [0, 'Configuration', f"/toastergui/project/{TestProjectConfigTab.project_id}"], # config
+ [2, 'Custom images', f"/toastergui/project/{TestProjectConfigTab.project_id}/customimages"], # custom images
+ [3, 'Image recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/images"], # image recipes
+ [4, 'Software recipes', f"/toastergui/project/{TestProjectConfigTab.project_id}/softwarerecipes"], # software recipes
+ [5, 'Machines', f"/toastergui/project/{TestProjectConfigTab.project_id}/machines"], # machines
+ [6, 'Layers', f"/toastergui/project/{TestProjectConfigTab.project_id}/layers"], # layers
+ [7, 'Distros', f"/toastergui/project/{TestProjectConfigTab.project_id}/distros"], # distro
+ # [9, 'BitBake variables', f"/toastergui/project/{TestProjectConfigTab.project_id}/configuration"], # bitbake variables
]
for index, item_name, url in conf_nav_list:
item = _get_config_nav_item(index)
@@ -236,6 +167,96 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
item.click()
check_config_nav_item(index, item_name, url)
+ def test_image_recipe_editColumn(self):
+ """ Test the edit column feature in image recipe table on project page """
+ def test_edit_column(check_box_id):
+ # Check that we can hide/show table column
+ check_box = self.find(f'#{check_box_id}')
+ th_class = str(check_box_id).replace('checkbox-', '')
+ if check_box.is_selected():
+ # check if column is visible in table
+ self.assertTrue(
+ self.find(
+ f'#imagerecipestable thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+ )
+ check_box.click()
+ # check if column is hidden in table
+ self.assertFalse(
+ self.find(
+ f'#imagerecipestable thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+ )
+ else:
+ # check if column is hidden in table
+ self.assertFalse(
+ self.find(
+ f'#imagerecipestable thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
+ )
+ check_box.click()
+ # check if column is visible in table
+ self.assertTrue(
+ self.find(
+ f'#imagerecipestable thead th.{th_class}'
+ ).is_displayed(),
+ f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
+ )
+
+ self._navigate_to_project_page()
+ # navigate to project image recipe page
+ recipe_image_page_link = self._get_config_nav_item(3)
+ recipe_image_page_link.click()
+ self.wait_until_present('#imagerecipestable tbody tr')
+
+ # Check edit column
+ edit_column = self.find('#edit-columns-button')
+ self.assertTrue(edit_column.is_displayed())
+ edit_column.click()
+ # Check dropdown is visible
+ self.wait_until_visible('ul.dropdown-menu.editcol')
+
+ # Check that we can hide the edit column
+ test_edit_column('checkbox-get_description_or_summary')
+ test_edit_column('checkbox-layer_version__get_vcs_reference')
+ test_edit_column('checkbox-layer_version__layer__name')
+ test_edit_column('checkbox-license')
+ test_edit_column('checkbox-recipe-file')
+ test_edit_column('checkbox-section')
+ test_edit_column('checkbox-version')
+
+ def test_image_recipe_show_rows(self):
+ """ Test the show rows feature in image recipe table on project page """
+ def test_show_rows(row_to_show, show_row_link):
+ # Check that we can show rows == row_to_show
+ show_row_link.select_by_value(str(row_to_show))
+ self.wait_until_visible('#imagerecipestable tbody tr')
+ self.assertTrue(
+ len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
+ )
+
+ self._navigate_to_project_page()
+ # navigate to project image recipe page
+ recipe_image_page_link = self._get_config_nav_item(3)
+ recipe_image_page_link.click()
+ self.wait_until_present('#imagerecipestable tbody tr')
+
+ show_rows = self.driver.find_elements(
+ By.XPATH,
+ '//select[@class="form-control pagesize-imagerecipestable"]'
+ )
+ # Check show rows
+ for show_row_link in show_rows:
+ show_row_link = Select(show_row_link)
+ test_show_rows(10, show_row_link)
+ test_show_rows(25, show_row_link)
+ test_show_rows(50, show_row_link)
+ test_show_rows(100, show_row_link)
+ test_show_rows(150, show_row_link)
+
def test_project_config_tab_right_section(self):
""" Test project config tab right section contains five blocks:
- Machine:
@@ -257,35 +278,36 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
- meta-poky
- meta-yocto-bsp
"""
- # navigate to the project page
- url = reverse("project", args=(1,))
- self.get(url)
-
+ # Create a new project for this test
+ project_name = self._random_string(10)
+ self._create_project(project_name=project_name)
+ current_url = self.driver.current_url
+ TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
+ url = current_url.split('?')[0]
# check if the menu is displayed
self.wait_until_visible('#project-page')
block_l = self.driver.find_element(
By.XPATH, '//*[@id="project-page"]/div[2]')
- machine = self.find('#machine-section')
- distro = self.find('#distro-section')
most_built_recipes = self.driver.find_element(
By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
project_release = self.driver.find_element(
By.XPATH, '//*[@id="project-page"]/div[1]/div[4]')
layers = block_l.find_element(By.ID, 'layer-container')
- def check_machine_distro(self, item_name, new_item_name, block):
+ def check_machine_distro(self, item_name, new_item_name, block_id):
+ block = self.find(f'#{block_id}')
title = block.find_element(By.TAG_NAME, 'h3')
self.assertTrue(item_name.capitalize() in title.text)
- edit_btn = block.find_element(By.ID, f'change-{item_name}-toggle')
+ edit_btn = self.find(f'#change-{item_name}-toggle')
edit_btn.click()
- sleep(1)
- name_input = block.find_element(By.ID, f'{item_name}-change-input')
+ self.wait_until_visible(f'#{item_name}-change-input')
+ name_input = self.find(f'#{item_name}-change-input')
name_input.clear()
name_input.send_keys(new_item_name)
- change_btn = block.find_element(By.ID, f'{item_name}-change-btn')
+ change_btn = self.find(f'#{item_name}-change-btn')
change_btn.click()
- sleep(1)
- project_name = block.find_element(By.ID, f'project-{item_name}-name')
+ self.wait_until_visible(f'#project-{item_name}-name')
+ project_name = self.find(f'#project-{item_name}-name')
self.assertTrue(new_item_name in project_name.text)
# check change notificaiton is displayed
change_notification = self.find('#change-notification')
@@ -293,10 +315,30 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
f'You have changed the {item_name} to: {new_item_name}' in change_notification.text
)
+ def rebuild_from_most_build_recipes(recipe_list_items):
+ checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
+ checkbox.click()
+ build_btn = self.find('#freq-build-btn')
+ build_btn.click()
+ self.wait_until_visible('#latest-builds')
+ build_state = wait_until_build(self, 'parsing starting cloning queued')
+ lastest_builds = self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="latest-builds"]/div'
+ )
+ last_build = lastest_builds[0]
+ self.assertTrue(len(lastest_builds) >= 2)
+ 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)
# Machine
- check_machine_distro(self, 'machine', 'qemux86-64', machine)
+ check_machine_distro(self, 'machine', 'qemux86-64', 'machine-section')
# Distro
- check_machine_distro(self, 'distro', 'poky-altcfg', distro)
+ check_machine_distro(self, 'distro', 'poky-altcfg', 'distro-section')
# Project release
title = project_release.find_element(By.TAG_NAME, 'h3')
@@ -304,7 +346,6 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
self.assertTrue(
"Yocto Project master" in self.find('#project-release-title').text
)
-
# Layers
title = layers.find_element(By.TAG_NAME, 'h3')
self.assertTrue("Layers" in title.text)
@@ -314,7 +355,9 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
# meta-yocto-bsp
layers_list = layers.find_element(By.ID, 'layers-in-project-list')
layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
- self.assertTrue(len(layers_list_items) == 3)
+ # remove all layers except the first three layers
+ for i in range(3, len(layers_list_items)):
+ layers_list_items[i].find_element(By.TAG_NAME, 'span').click()
# check can add a layer if exists
add_layer_input = layers.find_element(By.ID, 'layer-add-input')
add_layer_input.send_keys('meta-oe')
@@ -326,7 +369,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
dropdown_item.click()
add_layer_btn = layers.find_element(By.ID, 'add-layer-btn')
add_layer_btn.click()
- sleep(1)
+ self.wait_until_visible('#layers-in-project-list')
# check layer is added
layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
self.assertTrue(len(layers_list_items) == 4)
@@ -334,48 +377,33 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
# Most built recipes
title = most_built_recipes.find_element(By.TAG_NAME, 'h3')
self.assertTrue("Most built recipes" in title.text)
- # Create a new builds 5
- self._create_builds()
+ # Create a new builds
+ build_state = self._create_builds()
# Refresh the page
- self.get(url)
+ self.driver.get(url)
- sleep(1) # wait for page to load
- self.wait_until_visible('#project-page')
+ self.wait_until_visible('#project-page', poll=3)
# check can select a recipe and build it
most_built_recipes = self.driver.find_element(
By.XPATH, '//*[@id="project-page"]/div[1]/div[3]')
recipe_list = most_built_recipes.find_element(By.ID, 'freq-build-list')
recipe_list_items = recipe_list.find_elements(By.TAG_NAME, 'li')
- self.assertTrue(
- len(recipe_list_items) > 0,
- msg="No recipes found in the most built recipes list",
- )
- checkbox = recipe_list_items[0].find_element(By.TAG_NAME, 'input')
- checkbox.click()
- build_btn = self.find('#freq-build-btn')
- build_btn.click()
- sleep(1) # wait for page to load
- self.wait_until_visible('#latest-builds')
- self._wait_until_build('parsing starting cloning queueing')
- lastest_builds = self.driver.find_elements(
- By.XPATH,
- '//div[@id="latest-builds"]/div'
- )
- 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()
- self.assertTrue(len(lastest_builds) == 2)
+ if 'starting' not in build_state: # Build will not appear in the list if canceled in starting state
+ self.assertTrue(
+ len(recipe_list_items) > 0,
+ msg="No recipes found in the most built recipes list",
+ )
+ rebuild_from_most_build_recipes(recipe_list_items)
+ else:
+ self.assertTrue(
+ len(recipe_list_items) == 0,
+ msg="Recipes found in the most built recipes list",
+ )
def test_project_page_tab_importlayer(self):
""" Test project page tab import layer """
- # navigate to the project page
- url = reverse("project", args=(1,))
- self.get(url)
-
+ self._navigate_to_project_page()
# navigate to "Import layers" tab
import_layers_tab = self._get_tabs()[2]
import_layers_tab.find_element(By.TAG_NAME, 'a').click()
@@ -415,10 +443,10 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
def test_project_page_custom_image_no_image(self):
""" Test project page tab "New custom image" when no custom image """
- # navigate to the project page
- url = reverse("project", args=(1,))
- self.get(url)
-
+ project_name = self._random_string(10)
+ self._create_project(project_name=project_name)
+ current_url = self.driver.current_url
+ TestProjectConfigTab.project_id = get_projectId_from_url(current_url)
# navigate to "Custom image" tab
custom_image_section = self._get_config_nav_item(2)
custom_image_section.click()
@@ -433,8 +461,10 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
div_empty_msg = self.find('#empty-state-customimagestable')
link_create_custom_image = div_empty_msg.find_element(
By.TAG_NAME, 'a')
+ last_project_id = Project.objects.get(name=project_name).id
+ self.assertTrue(last_project_id is not None)
self.assertTrue(
- f"/toastergui/project/1/newcustomimage" in str(
+ f"/toastergui/project/{last_project_id}/newcustomimage" in str(
link_create_custom_image.get_attribute('href')
)
)
@@ -451,11 +481,7 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
- Check image recipe build button works
- Check image recipe table features(show/hide column, pagination)
"""
- # navigate to the project page
- url = reverse("project", args=(1,))
- self.get(url)
- self.wait_until_visible('#config-nav')
-
+ self._navigate_to_project_page()
# navigate to "Images section"
images_section = self._get_config_nav_item(3)
images_section.click()
@@ -479,100 +505,17 @@ class TestProjectConfigTab(SeleniumFunctionalTestCase):
'//td[@class="add-del-layers"]'
)
build_btn.click()
- self._wait_until_build('parsing starting cloning')
+ 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)
-
- 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)
-
- def test_edit_column(check_box_id):
- # Check that we can hide/show table column
- check_box = self.find(f'#{check_box_id}')
- th_class = str(check_box_id).replace('checkbox-', '')
- if check_box.is_selected():
- # check if column is visible in table
- self.assertTrue(
- self.find(
- f'#imagerecipestable thead th.{th_class}'
- ).is_displayed(),
- f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
- )
- check_box.click()
- # check if column is hidden in table
- self.assertFalse(
- self.find(
- f'#imagerecipestable thead th.{th_class}'
- ).is_displayed(),
- f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
- )
- else:
- # check if column is hidden in table
- self.assertFalse(
- self.find(
- f'#imagerecipestable thead th.{th_class}'
- ).is_displayed(),
- f"The {th_class} column is unchecked in EditColumn dropdown, but it's visible in table"
- )
- check_box.click()
- # check if column is visible in table
- self.assertTrue(
- self.find(
- f'#imagerecipestable thead th.{th_class}'
- ).is_displayed(),
- f"The {th_class} column is checked in EditColumn dropdown, but it's not visible in table"
- )
-
- url = reverse('projectimagerecipes', args=(1,))
- self.get(url)
- self.wait_until_present('#imagerecipestable tbody tr')
-
- # Check edit column
- edit_column = self.find('#edit-columns-button')
- self.assertTrue(edit_column.is_displayed())
- edit_column.click()
- # Check dropdown is visible
- self.wait_until_visible('ul.dropdown-menu.editcol')
-
- # Check that we can hide the edit column
- test_edit_column('checkbox-get_description_or_summary')
- test_edit_column('checkbox-layer_version__get_vcs_reference')
- test_edit_column('checkbox-layer_version__layer__name')
- test_edit_column('checkbox-license')
- test_edit_column('checkbox-recipe-file')
- test_edit_column('checkbox-section')
- test_edit_column('checkbox-version')
-
- def test_image_recipe_show_rows(self):
- """ Test the show rows feature in image recipe table on project page """
- self._get_create_builds(success=100, failure=100)
-
- def test_show_rows(row_to_show, show_row_link):
- # Check that we can show rows == row_to_show
- show_row_link.select_by_value(str(row_to_show))
- self.wait_until_present('#imagerecipestable tbody tr')
- sleep(1)
- self.assertTrue(
- len(self.find_all('#imagerecipestable tbody tr')) == row_to_show
- )
-
- url = reverse('projectimagerecipes', args=(2,))
- self.get(url)
- self.wait_until_present('#imagerecipestable tbody tr')
-
- show_rows = self.driver.find_elements(
+ last_build = lastest_builds[0]
+ cancel_button = last_build.find_element(
By.XPATH,
- '//select[@class="form-control pagesize-imagerecipestable"]'
+ '//span[@class="cancel-build-btn pull-right alert-link"]',
)
- # Check show rows
- for show_row_link in show_rows:
- show_row_link = Select(show_row_link)
- test_show_rows(10, show_row_link)
- test_show_rows(25, show_row_link)
- test_show_rows(50, show_row_link)
- test_show_rows(100, show_row_link)
- test_show_rows(150, show_row_link)
+ cancel_button.click()
+ if 'starting' not in build_state: # change build state when cancelled in starting state
+ wait_until_build_cancelled(self)
- Split testcases from test_project_page_tab_config into tow files - Added new testcases in test_project_config - Test changing distro variable - Test setting IMAGE_INSTALL:append variable - Test setting PACKAGE_CLASSES variable - Test creating new bitbake variable Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com> --- .../tests/functional/test_project_config.py | 335 ++++++++++++ .../test_project_page_tab_config.py | 495 ++++++++---------- 2 files changed, 554 insertions(+), 276 deletions(-) create mode 100644 lib/toaster/tests/functional/test_project_config.py