mirror of
https://git.yoctoproject.org/poky
synced 2026-05-08 17:19:20 +00:00
bitbake: toaster/tests: Refactorize tests/functional
- 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
(Bitbake rev: 649218c648b79a89b0e91aa80d8c9bf8fa2de645)
Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
committed by
Richard Purdie
parent
49128ef8ba
commit
d5a6e3b546
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user