bitbake: toaster/tests: Bug fixes, functional tests dependent on each other

refactor test_create_project and test_project_page to remove their dependencies

(Bitbake rev: 54f7c0bb6ff435c4936c3422532aa071bd5b66e8)

Signed-off-by: Alassane Yattara <alassane.yattara@savoirfairelinux.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
This commit is contained in:
Alassane Yattara
2023-12-08 02:53:19 +01:00
committed by Richard Purdie
parent d5a6e3b546
commit 23d3e2c718
2 changed files with 79 additions and 86 deletions

View File

@@ -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(

View File

@@ -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()
else:
if checkbox.is_selected():
checkbox.click()
if not checkbox.is_selected():
checkbox.click()
self.driver.find_element(By.ID, "create-project-button").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:
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