summaryrefslogtreecommitdiff
path: root/poky/bitbake/lib/toaster/tests
diff options
context:
space:
mode:
Diffstat (limited to 'poky/bitbake/lib/toaster/tests')
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py8
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py262
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py126
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_delete_project.py103
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_landing_page.py121
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py2
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py5
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py4
-rw-r--r--poky/bitbake/lib/toaster/tests/browser/test_sample.py9
-rw-r--r--poky/bitbake/lib/toaster/tests/builds/buildtest.py9
-rw-r--r--poky/bitbake/lib/toaster/tests/functional/functional_helpers.py6
-rw-r--r--poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py177
-rw-r--r--poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py37
-rw-r--r--poky/bitbake/lib/toaster/tests/functional/test_project_page.py247
-rw-r--r--poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py578
-rw-r--r--poky/bitbake/lib/toaster/tests/toaster-tests-requirements.txt6
-rw-r--r--poky/bitbake/lib/toaster/tests/views/test_views.py10
17 files changed, 1678 insertions, 32 deletions
diff --git a/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py b/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
index 9a4e27a3bc..e0ac43768e 100644
--- a/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
+++ b/poky/bitbake/lib/toaster/tests/browser/selenium_helpers_base.py
@@ -33,7 +33,13 @@ def create_selenium_driver(cls,browser='chrome'):
browser = env_browser
if browser == 'chrome':
- return webdriver.Chrome()
+ options = webdriver.ChromeOptions()
+ options.add_argument('headless')
+ options.add_argument('--disable-infobars')
+ options.add_argument('--disable-dev-shm-usage')
+ options.add_argument('--no-sandbox')
+ options.add_argument('--remote-debugging-port=9222')
+ return webdriver.Chrome(options=options)
elif browser == 'firefox':
return webdriver.Firefox()
elif browser == 'marionette':
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py b/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
index d4312bb35b..4e9b9fd760 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_all_builds_page.py
@@ -7,13 +7,16 @@
# SPDX-License-Identifier: GPL-2.0-only
#
-import re, time
+import re
+import time
from django.urls import reverse
+from selenium.webdriver.support.select import Select
from django.utils import timezone
+from bldcontrol.models import BuildRequest
from tests.browser.selenium_helpers import SeleniumTestCase
-from orm.models import BitbakeVersion, Release, Project, Build, Target
+from orm.models import BitbakeVersion, Layer, Layer_Version, Recipe, Release, Project, Build, Target, Task
from selenium.webdriver.common.by import By
@@ -102,6 +105,66 @@ class TestAllBuildsPage(SeleniumTestCase):
return found_row
+ def _get_create_builds(self, **kwargs):
+ """ Create a build and return the build object """
+ 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')
+
+ self._set_buildRequest_and_task_on_build(build)
+ 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')
+ self._set_buildRequest_and_task_on_build(build)
+ return build1, build2
+
+ def _create_recipe(self):
+ """ Add a recipe to the database and return it """
+ layer = Layer.objects.create()
+ layer_version = Layer_Version.objects.create(layer=layer)
+ return Recipe.objects.create(name='recipe_foo', layer_version=layer_version)
+
+ def _set_buildRequest_and_task_on_build(self, build):
+ """ Set buildRequest and task on build """
+ build.recipes_parsed = 1
+ build.save()
+ buildRequest = BuildRequest.objects.create(
+ build=build,
+ project=self.project1,
+ state=BuildRequest.REQ_COMPLETED)
+ build.build_request = buildRequest
+ recipe = self._create_recipe()
+ task = Task.objects.create(build=build,
+ recipe=recipe,
+ task_name='task',
+ outcome=Task.OUTCOME_SUCCESS)
+ task.save()
+ build.save()
+
def test_show_tasks_with_suffix(self):
""" Task should be shown as suffix on build name """
build = Build.objects.create(**self.project1_build_success)
@@ -128,7 +191,8 @@ class TestAllBuildsPage(SeleniumTestCase):
but should be shown for other builds
"""
build1 = Build.objects.create(**self.project1_build_success)
- default_build = Build.objects.create(**self.default_project_build_success)
+ default_build = Build.objects.create(
+ **self.default_project_build_success)
url = reverse('all-builds')
self.get(url)
@@ -146,7 +210,6 @@ class TestAllBuildsPage(SeleniumTestCase):
self.assertEqual(len(run_again_button), 0,
'should not see a rebuild button for cli builds')
-
def test_tooltips_on_project_name(self):
"""
Test tooltips shown next to project name in the main table
@@ -188,14 +251,7 @@ class TestAllBuildsPage(SeleniumTestCase):
recent builds area; failed builds should not have links on the time column,
or in the recent builds area
"""
- 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')
+ build1, build2 = self._get_create_builds()
url = reverse('all-builds')
self.get(url)
@@ -223,3 +279,185 @@ class TestAllBuildsPage(SeleniumTestCase):
links = build2_row.find_elements(By.CSS_SELECTOR, 'td.time a')
msg = 'should not be a link on the build time for a failed build'
self.assertEquals(len(links), 0, msg)
+
+ def test_builds_table_search_box(self):
+ """ Test the search box in the builds table on the all builds page """
+ self._get_create_builds()
+
+ url = reverse('all-builds')
+ self.get(url)
+
+ # Check search box is present and works
+ self.wait_until_present('#allbuildstable tbody tr')
+ search_box = self.find('#search-input-allbuildstable')
+ self.assertTrue(search_box.is_displayed())
+
+ # Check that we can search for a build by recipe name
+ search_box.send_keys('foo')
+ search_btn = self.find('#search-submit-allbuildstable')
+ search_btn.click()
+ self.wait_until_present('#allbuildstable tbody tr')
+ rows = self.find_all('#allbuildstable tbody tr')
+ self.assertTrue(len(rows) >= 1)
+
+ def test_filtering_on_failure_tasks_column(self):
+ """ Test the filtering on failure tasks column in the builds table on the all builds page """
+ self._get_create_builds(success=10, failure=10)
+
+ url = reverse('all-builds')
+ self.get(url)
+
+ # Check filtering on failure tasks column
+ self.wait_until_present('#allbuildstable tbody tr')
+ failed_tasks_filter = self.find('#failed_tasks_filter')
+ failed_tasks_filter.click()
+ # Check popup is visible
+ time.sleep(1)
+ self.wait_until_present('#filter-modal-allbuildstable')
+ self.assertTrue(
+ self.find('#filter-modal-allbuildstable').is_displayed())
+ # Check that we can filter by failure tasks
+ build_without_failure_tasks = self.find(
+ '#failed_tasks_filter\\:without_failed_tasks')
+ build_without_failure_tasks.click()
+ # click on apply button
+ self.find('#filter-modal-allbuildstable .btn-primary').click()
+ self.wait_until_present('#allbuildstable tbody tr')
+ # Check if filter is applied, by checking if failed_tasks_filter has btn-primary class
+ self.assertTrue(self.find('#failed_tasks_filter').get_attribute(
+ 'class').find('btn-primary') != -1)
+
+ def test_filtering_on_completedOn_column(self):
+ """ Test the filtering on completed_on column in the builds table on the all builds page """
+ self._get_create_builds(success=10, failure=10)
+
+ url = reverse('all-builds')
+ self.get(url)
+
+ # Check filtering on failure tasks column
+ self.wait_until_present('#allbuildstable tbody tr')
+ completed_on_filter = self.find('#completed_on_filter')
+ completed_on_filter.click()
+ # Check popup is visible
+ time.sleep(1)
+ self.wait_until_present('#filter-modal-allbuildstable')
+ self.assertTrue(
+ self.find('#filter-modal-allbuildstable').is_displayed())
+ # Check that we can filter by failure tasks
+ build_without_failure_tasks = self.find(
+ '#completed_on_filter\\:date_range')
+ build_without_failure_tasks.click()
+ # click on apply button
+ self.find('#filter-modal-allbuildstable .btn-primary').click()
+ self.wait_until_present('#allbuildstable tbody tr')
+ # Check if filter is applied, by checking if completed_on_filter has btn-primary class
+ self.assertTrue(self.find('#completed_on_filter').get_attribute(
+ 'class').find('btn-primary') != -1)
+
+ # Filter by date range
+ self.find('#completed_on_filter').click()
+ self.wait_until_present('#filter-modal-allbuildstable')
+ date_ranges = self.driver.find_elements(
+ By.XPATH, '//input[@class="form-control hasDatepicker"]')
+ today = timezone.now()
+ yestersday = today - timezone.timedelta(days=1)
+ time.sleep(1)
+ date_ranges[0].send_keys(yestersday.strftime('%Y-%m-%d'))
+ date_ranges[1].send_keys(today.strftime('%Y-%m-%d'))
+ self.find('#filter-modal-allbuildstable .btn-primary').click()
+ self.wait_until_present('#allbuildstable tbody tr')
+ self.assertTrue(self.find('#completed_on_filter').get_attribute(
+ 'class').find('btn-primary') != -1)
+ # Check if filter is applied, number of builds displayed should be 6
+ time.sleep(1)
+ self.assertTrue(len(self.find_all('#allbuildstable tbody tr')) == 6)
+
+ def test_builds_table_editColumn(self):
+ """ Test the edit column feature in the builds table on the all builds 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'#allbuildstable 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'#allbuildstable 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'#allbuildstable 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'#allbuildstable 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('all-builds')
+ self.get(url)
+ self.wait_until_present('#allbuildstable 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-errors_no')
+ test_edit_column('checkbox-failed_tasks')
+ test_edit_column('checkbox-image_files')
+ test_edit_column('checkbox-project')
+ test_edit_column('checkbox-started_on')
+ test_edit_column('checkbox-time')
+ test_edit_column('checkbox-warnings_no')
+
+ def test_builds_table_show_rows(self):
+ """ Test the show rows feature in the builds table on the all builds 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('#allbuildstable tbody tr')
+ time.sleep(1)
+ self.assertTrue(
+ len(self.find_all('#allbuildstable tbody tr')) == row_to_show
+ )
+
+ url = reverse('all-builds')
+ self.get(url)
+ self.wait_until_present('#allbuildstable tbody tr')
+
+ show_rows = self.driver.find_elements(
+ By.XPATH,
+ '//select[@class="form-control pagesize-allbuildstable"]'
+ )
+ # 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)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py b/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
index 3389d32366..a880dbcc68 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_all_projects_page.py
@@ -8,9 +8,11 @@
#
import re
+import time
from django.urls import reverse
from django.utils import timezone
+from selenium.webdriver.support.select import Select
from tests.browser.selenium_helpers import SeleniumTestCase
from orm.models import BitbakeVersion, Release, Project, Build
@@ -37,6 +39,17 @@ class TestAllProjectsPage(SeleniumTestCase):
self.release = None
+ def _create_projects(self, nb_project=10):
+ projects = []
+ for i in range(1, nb_project + 1):
+ projects.append(
+ Project(
+ name='test project {}'.format(i),
+ release=self.release,
+ )
+ )
+ Project.objects.bulk_create(projects)
+
def _add_build_to_default_project(self):
""" Add a build to the default project (not used in all tests) """
now = timezone.now()
@@ -205,3 +218,116 @@ class TestAllProjectsPage(SeleniumTestCase):
expected_url = reverse('project', args=(self.project.id,))
msg = 'link on project name should point to configuration but was %s' % link_url
self.assertTrue(link_url.endswith(expected_url), msg)
+
+ def test_allProject_table_search_box(self):
+ """ Test the search box in the all project table on the all projects page """
+ self._create_projects()
+
+ url = reverse('all-projects')
+ self.get(url)
+
+ # Chseck search box is present and works
+ self.wait_until_present('#projectstable tbody tr')
+ search_box = self.find('#search-input-projectstable')
+ self.assertTrue(search_box.is_displayed())
+
+ # Check that we can search for a project by project name
+ search_box.send_keys('test project 10')
+ search_btn = self.find('#search-submit-projectstable')
+ search_btn.click()
+ self.wait_until_present('#projectstable tbody tr')
+ time.sleep(1)
+ rows = self.find_all('#projectstable tbody tr')
+ self.assertTrue(len(rows) == 1)
+
+ def test_allProject_table_editColumn(self):
+ """ Test the edit column feature in the projects table on the all projects page """
+ self._create_projects()
+
+ 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'#projectstable 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'#projectstable 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'#projectstable 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'#projectstable 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('all-projects')
+ self.get(url)
+ self.wait_until_present('#projectstable 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-errors')
+ test_edit_column('checkbox-image_files')
+ test_edit_column('checkbox-last_build_outcome')
+ test_edit_column('checkbox-recipe_name')
+ test_edit_column('checkbox-warnings')
+
+ def test_allProject_table_show_rows(self):
+ """ Test the show rows feature in the projects table on the all projects page """
+ self._create_projects(nb_project=200)
+
+ 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('#projectstable tbody tr')
+ sleep_time = 1
+ if row_to_show == 150:
+ # wait more time for 150 rows
+ sleep_time = 2
+ time.sleep(sleep_time)
+ self.assertTrue(
+ len(self.find_all('#projectstable tbody tr')) == row_to_show
+ )
+
+ url = reverse('all-projects')
+ self.get(url)
+ self.wait_until_present('#projectstable tbody tr')
+
+ show_rows = self.driver.find_elements(
+ By.XPATH,
+ '//select[@class="form-control pagesize-projectstable"]'
+ )
+ # 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)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_delete_project.py b/poky/bitbake/lib/toaster/tests/browser/test_delete_project.py
new file mode 100644
index 0000000000..1941777ccc
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/browser/test_delete_project.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux Inc
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+import pytest
+from django.urls import reverse
+from selenium.webdriver.support.ui import Select
+from tests.browser.selenium_helpers import SeleniumTestCase
+from orm.models import BitbakeVersion, Project, Release
+from selenium.webdriver.common.by import By
+
+class TestDeleteProject(SeleniumTestCase):
+
+ def setUp(self):
+ bitbake, _ = BitbakeVersion.objects.get_or_create(
+ name="master",
+ giturl="git://master",
+ branch="master",
+ dirpath="master")
+
+ self.release, _ = Release.objects.get_or_create(
+ name="master",
+ description="Yocto Project master",
+ branch_name="master",
+ helptext="latest",
+ bitbake_version=bitbake)
+
+ Release.objects.get_or_create(
+ name="foo",
+ description="Yocto Project foo",
+ branch_name="foo",
+ helptext="latest",
+ bitbake_version=bitbake)
+
+ @pytest.mark.django_db
+ def test_delete_project(self):
+ """ Test delete a project
+ - Check delete modal is visible
+ - Check delete modal has right text
+ - Confirm delete
+ - Check project is deleted
+ """
+ project_name = "project_to_delete"
+ url = reverse('newproject')
+ self.get(url)
+ self.enter_text('#new-project-name', project_name)
+ select = Select(self.find('#projectversion'))
+ select.select_by_value(str(self.release.pk))
+ self.click("#create-project-button")
+ # We should get redirected to the new project's page with the
+ # notification at the top
+ element = self.wait_until_visible('#project-created-notification')
+ self.assertTrue(project_name in element.text,
+ "New project name not in new project notification")
+ self.assertTrue(Project.objects.filter(name=project_name).count(),
+ "New project not found in database")
+
+ # Delete project
+ delete_project_link = self.driver.find_element(
+ By.XPATH, '//a[@href="#delete-project-modal"]')
+ delete_project_link.click()
+
+ # Check delete modal is visible
+ self.wait_until_visible('#delete-project-modal')
+
+ # Check delete modal has right text
+ modal_header_text = self.find('#delete-project-modal .modal-header').text
+ self.assertTrue(
+ "Are you sure you want to delete this project?" in modal_header_text,
+ "Delete project modal header text is wrong")
+
+ modal_body_text = self.find('#delete-project-modal .modal-body').text
+ self.assertTrue(
+ "Cancel its builds currently in progress" in modal_body_text,
+ "Modal body doesn't contain: Cancel its builds currently in progress")
+ self.assertTrue(
+ "Remove its configuration information" in modal_body_text,
+ "Modal body doesn't contain: Remove its configuration information")
+ self.assertTrue(
+ "Remove its imported layers" in modal_body_text,
+ "Modal body doesn't contain: Remove its imported layers")
+ self.assertTrue(
+ "Remove its custom images" in modal_body_text,
+ "Modal body doesn't contain: Remove its custom images")
+ self.assertTrue(
+ "Remove all its build information" in modal_body_text,
+ "Modal body doesn't contain: Remove all its build information")
+
+ # Confirm delete
+ delete_btn = self.find('#delete-project-confirmed')
+ delete_btn.click()
+
+ # Check project is deleted
+ self.wait_until_visible('#change-notification')
+ delete_notification = self.find('#change-notification-msg')
+ self.assertTrue("You have deleted 1 project:" in delete_notification.text)
+ self.assertTrue(project_name in delete_notification.text)
+ self.assertFalse(Project.objects.filter(name=project_name).exists(),
+ "Project not deleted from database")
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py b/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
index 8bb64b9f3e..7ec52a4b40 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_landing_page.py
@@ -10,8 +10,9 @@
from django.urls import reverse
from django.utils import timezone
from tests.browser.selenium_helpers import SeleniumTestCase
+from selenium.webdriver.common.by import By
-from orm.models import Project, Build
+from orm.models import Layer, Layer_Version, Project, Build
class TestLandingPage(SeleniumTestCase):
""" Tests for redirects on the landing page """
@@ -29,6 +30,124 @@ class TestLandingPage(SeleniumTestCase):
self.project.is_default = True
self.project.save()
+ def test_icon_info_visible_and_clickable(self):
+ """ Test that the information icon is visible and clickable """
+ self.get(reverse('landing'))
+ info_sign = self.find('#toaster-version-info-sign')
+
+ # check that the info sign is visible
+ self.assertTrue(info_sign.is_displayed())
+
+ # check that the info sign is clickable
+ # and info modal is appearing when clicking on the info sign
+ info_sign.click() # click on the info sign make attribute 'aria-describedby' visible
+ info_model_id = info_sign.get_attribute('aria-describedby')
+ info_modal = self.find(f'#{info_model_id}')
+ self.assertTrue(info_modal.is_displayed())
+ self.assertTrue("Toaster version information" in info_modal.text)
+
+ def test_documentation_link_displayed(self):
+ """ Test that the documentation link is displayed """
+ self.get(reverse('landing'))
+ documentation_link = self.find('#navbar-docs > a')
+
+ # check that the documentation link is visible
+ self.assertTrue(documentation_link.is_displayed())
+
+ # check browser open new tab toaster manual when clicking on the documentation link
+ self.assertEqual(documentation_link.get_attribute('target') , '_blank')
+ self.assertEqual(
+ documentation_link.get_attribute('href'),
+ 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual')
+ self.assertTrue("Documentation" in documentation_link.text)
+
+ def test_openembedded_jumbotron_link_visible_and_clickable(self):
+ """ Test OpenEmbedded link jumbotron is visible and clickable: """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check OpenEmbedded
+ openembedded = jumbotron.find_element(By.LINK_TEXT, 'OpenEmbedded')
+ self.assertTrue(openembedded.is_displayed())
+ openembedded.click()
+ self.assertTrue("openembedded.org" in self.driver.current_url)
+
+ def test_bitbake_jumbotron_link_visible_and_clickable(self):
+ """ Test BitBake link jumbotron is visible and clickable: """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check BitBake
+ bitbake = jumbotron.find_element(By.LINK_TEXT, 'BitBake')
+ self.assertTrue(bitbake.is_displayed())
+ bitbake.click()
+ self.assertTrue("docs.yoctoproject.org/bitbake.html" in self.driver.current_url)
+
+ def test_yoctoproject_jumbotron_link_visible_and_clickable(self):
+ """ Test Yocto Project link jumbotron is visible and clickable: """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check Yocto Project
+ yoctoproject = jumbotron.find_element(By.LINK_TEXT, 'Yocto Project')
+ self.assertTrue(yoctoproject.is_displayed())
+ yoctoproject.click()
+ self.assertTrue("yoctoproject.org" in self.driver.current_url)
+
+ def test_link_setup_using_toaster_visible_and_clickable(self):
+ """ Test big magenta button setting up and using toaster link in jumbotron
+ if visible and clickable
+ """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check Big magenta button
+ big_magenta_button = jumbotron.find_element(By.LINK_TEXT,
+ 'Toaster is ready to capture your command line builds'
+ )
+ self.assertTrue(big_magenta_button.is_displayed())
+ big_magenta_button.click()
+ self.assertTrue("docs.yoctoproject.org/toaster-manual/setup-and-use.html#setting-up-and-using-toaster" in self.driver.current_url)
+
+ def test_link_create_new_project_in_jumbotron_visible_and_clickable(self):
+ """ Test big blue button create new project jumbotron if visible and clickable """
+ # Create a layer and a layer version to make visible the big blue button
+ layer = Layer.objects.create(name='bar')
+ Layer_Version.objects.create(layer=layer)
+
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check Big Blue button
+ big_blue_button = jumbotron.find_element(By.LINK_TEXT,
+ 'Create your first Toaster project to run manage builds'
+ )
+ self.assertTrue(big_blue_button.is_displayed())
+ big_blue_button.click()
+ self.assertTrue("toastergui/newproject/" in self.driver.current_url)
+
+ def test_toaster_manual_link_visible_and_clickable(self):
+ """ Test Read the Toaster manual link jumbotron is visible and clickable: """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check Read the Toaster manual
+ toaster_manual = jumbotron.find_element(By.LINK_TEXT, 'Read the Toaster manual')
+ self.assertTrue(toaster_manual.is_displayed())
+ toaster_manual.click()
+ self.assertTrue("https://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual" in self.driver.current_url)
+
+ def test_contrib_to_toaster_link_visible_and_clickable(self):
+ """ Test Contribute to Toaster link jumbotron is visible and clickable: """
+ self.get(reverse('landing'))
+ jumbotron = self.find('.jumbotron')
+
+ # check Contribute to Toaster
+ contribute_to_toaster = jumbotron.find_element(By.LINK_TEXT, 'Contribute to Toaster')
+ self.assertTrue(contribute_to_toaster.is_displayed())
+ contribute_to_toaster.click()
+ self.assertTrue("wiki.yoctoproject.org/wiki/contribute_to_toaster" in str(self.driver.current_url).lower())
+
def test_only_default_project(self):
"""
No projects except default
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py b/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
index 71bdd2aafd..cb7b915bf0 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_layerdetails_page.py
@@ -97,6 +97,8 @@ class TestLayerDetailsPage(SeleniumTestCase):
"Expecting any of \"%s\"but got \"%s\"" %
(self.initial_values, value))
+ # Make sure the input visible beofre sending keys
+ self.wait_until_visible("#layer-git input[type=text]")
inputs.send_keys("-edited")
# Save the new values
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py b/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
index a34a092884..949a94768a 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_most_recent_builds_states.py
@@ -54,6 +54,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
build.outcome = Build.IN_PROGRESS
build.recipes_to_parse = recipes_to_parse
build.recipes_parsed = 0
+ build.save()
build_request.state = BuildRequest.REQ_INPROGRESS
build_request.save()
@@ -100,7 +101,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
'Tasks starting', 'build should show "tasks starting" status')
# first task finished; check tasks progress bar
- task1.order = 1
+ task1.outcome = Task.OUTCOME_SUCCESS
task1.save()
self.get(url)
@@ -117,7 +118,7 @@ class TestMostRecentBuildsStates(SeleniumTestCase):
element = Wait(self.driver).until(task_bar_updated, msg)
# last task finished; check tasks progress bar updates
- task2.order = 2
+ task2.outcome = Task.OUTCOME_SUCCESS
task2.save()
self.get(url)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py b/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
index 6361f40347..34d1bd45c7 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_new_custom_image_page.py
@@ -48,8 +48,12 @@ class TestNewCustomImagePage(SeleniumTestCase):
self.recipe = Recipe.objects.create(
name='core-image-minimal',
layer_version=layer_version,
+ file_path='/tmp/core-image-minimal.bb',
is_image=True
)
+ # create a tmp file for the recipe
+ with open(self.recipe.file_path, 'w') as f:
+ f.write('foo')
# another project with a custom image already in it
project2 = Project.objects.create(name='whoop', release=release)
diff --git a/poky/bitbake/lib/toaster/tests/browser/test_sample.py b/poky/bitbake/lib/toaster/tests/browser/test_sample.py
index b0067c21cd..73973778e4 100644
--- a/poky/bitbake/lib/toaster/tests/browser/test_sample.py
+++ b/poky/bitbake/lib/toaster/tests/browser/test_sample.py
@@ -27,3 +27,12 @@ class TestSample(SeleniumTestCase):
self.get(url)
brand_link = self.find('.toaster-navbar-brand a.brand')
self.assertEqual(brand_link.text.strip(), 'Toaster')
+
+ def test_no_builds_message(self):
+ """ Test that a message is shown when there are no builds """
+ url = reverse('all-builds')
+ self.get(url)
+ div_msg = self.find('#empty-state-allbuildstable .alert-info')
+
+ msg = 'Sorry - no data found'
+ self.assertEqual(div_msg.text, msg)
diff --git a/poky/bitbake/lib/toaster/tests/builds/buildtest.py b/poky/bitbake/lib/toaster/tests/builds/buildtest.py
index 13b51fb0d8..53cd7a9ffa 100644
--- a/poky/bitbake/lib/toaster/tests/builds/buildtest.py
+++ b/poky/bitbake/lib/toaster/tests/builds/buildtest.py
@@ -116,6 +116,15 @@ class BuildTest(unittest.TestCase):
project = Project.objects.create_project(name=BuildTest.PROJECT_NAME,
release=release)
+ passthrough_variable_names = ["SSTATE_DIR", "DL_DIR"]
+ for variable_name in passthrough_variable_names:
+ current_variable = os.environ.get(variable_name)
+ if current_variable:
+ ProjectVariable.objects.get_or_create(
+ name=variable_name,
+ value=current_variable,
+ project=project)
+
if os.environ.get("TOASTER_TEST_USE_SSTATE_MIRROR"):
ProjectVariable.objects.get_or_create(
name="SSTATE_MIRRORS",
diff --git a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
index c3191f664a..b80d403bec 100644
--- a/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
+++ b/poky/bitbake/lib/toaster/tests/functional/functional_helpers.py
@@ -15,8 +15,6 @@ import time
import re
from tests.browser.selenium_helpers_base import SeleniumTestCaseBase
-from tests.builds.buildtest import load_build_environment
-from bldcontrol.models import BuildEnvironment
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
@@ -33,10 +31,6 @@ class SeleniumFunctionalTestCase(SeleniumTestCaseBase):
raise RuntimeError("Please initialise django with the tests settings: " \
"DJANGO_SETTINGS_MODULE='toastermain.settings_test'")
- if BuildEnvironment.objects.count() == 0:
- BuildEnvironment.objects.create(betype=BuildEnvironment.TYPE_LOCAL)
- load_build_environment()
-
# start toaster
cmd = "bash -c 'source toaster start'"
p = subprocess.Popen(
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
new file mode 100644
index 0000000000..dc7d1fc20b
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/test_create_new_project.py
@@ -0,0 +1,177 @@
+#! /usr/bin/env python3
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import re
+import pytest
+from django.urls import reverse
+from selenium.webdriver.support.select import Select
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from orm.models import Project
+from selenium.webdriver.common.by import By
+
+
+@pytest.mark.django_db
+class TestCreateNewProject(SeleniumFunctionalTestCase):
+
+ def _create_test_new_project(
+ self,
+ project_name,
+ release,
+ release_title,
+ merge_toaster_settings,
+ ):
+ """ 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)
+
+ # 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()
+
+ self.driver.find_element(By.ID, "create-project-button").click()
+
+ element = self.wait_until_visible('#project-created-notification')
+ self.assertTrue(
+ self.element_exists('#project-created-notification'),
+ f"Project:{project_name} creation notification not shown"
+ )
+ self.assertTrue(
+ project_name in element.text,
+ f"New project name:{project_name} not in new project notification"
+ )
+ self.assertTrue(
+ Project.objects.filter(name=project_name).count(),
+ f"New project:{project_name} not found in database"
+ )
+
+ # check release
+ self.assertTrue(re.search(
+ release_title,
+ self.driver.find_element(By.XPATH,
+ "//span[@id='project-release-title']"
+ ).text),
+ 'The project release is not defined')
+
+ def test_create_new_project_master(self):
+ """ Test create new project using:
+ - Project Name: Any string
+ - Release: Yocto Project master (option value: 3)
+ - Merge Toaster settings: False
+ """
+ release = '3'
+ release_title = 'Yocto Project master'
+ project_name = 'projectmaster'
+ self._create_test_new_project(
+ project_name,
+ release,
+ release_title,
+ False,
+ )
+
+ def test_create_new_project_kirkstone(self):
+ """ Test create new project using:
+ - Project Name: Any string
+ - Release: Yocto Project 4.0 "Kirkstone" (option value: 1)
+ - Merge Toaster settings: True
+ """
+ release = '1'
+ release_title = 'Yocto Project 4.0 "Kirkstone"'
+ project_name = 'projectkirkstone'
+ self._create_test_new_project(
+ project_name,
+ release,
+ release_title,
+ True,
+ )
+
+ def test_create_new_project_dunfell(self):
+ """ Test create new project using:
+ - Project Name: Any string
+ - Release: Yocto Project 3.1 "Dunfell" (option value: 5)
+ - Merge Toaster settings: False
+ """
+ release = '5'
+ release_title = 'Yocto Project 3.1 "Dunfell"'
+ project_name = 'projectdunfull'
+ self._create_test_new_project(
+ project_name,
+ release,
+ release_title,
+ False,
+ )
+
+ def test_create_new_project_local(self):
+ """ Test create new project using:
+ - Project Name: Any string
+ - Release: Local Yocto Project (option value: 2)
+ - Merge Toaster settings: True
+ """
+ release = '2'
+ release_title = 'Local Yocto Project'
+ project_name = 'projectlocal'
+ self._create_test_new_project(
+ project_name,
+ release,
+ release_title,
+ True,
+ )
+
+ def test_create_new_project_without_name(self):
+ """ Test create new project without project name """
+ self.get(reverse('newproject'))
+
+ select = Select(self.find('#projectversion'))
+ select.select_by_value(str(3))
+
+ # Check input name has required attribute
+ input_name = self.driver.find_element(By.ID, "new-project-name")
+ self.assertIsNotNone(input_name.get_attribute('required'),
+ 'Input name has not required attribute')
+
+ # Check create button is disabled
+ create_btn = self.driver.find_element(By.ID, "create-project-button")
+ self.assertIsNotNone(create_btn.get_attribute('disabled'),
+ 'Create button is not disabled')
+
+ def test_import_new_project(self):
+ """ Test import new project using:
+ - Project Name: Any string
+ - Project type: select (Import command line project)
+ - Import existing project directory: Wrong Path
+ """
+ project_name = 'projectimport'
+ self.get(reverse('newproject'))
+ self.driver.find_element(By.ID,
+ "new-project-name").send_keys(project_name)
+ # select import project
+ self.find('#type-import').click()
+
+ # set wrong path
+ wrong_path = '/wrongpath'
+ self.driver.find_element(By.ID,
+ "import-project-dir").send_keys(wrong_path)
+ self.driver.find_element(By.ID, "create-project-button").click()
+
+ # check error message
+ self.assertTrue(self.element_exists('.alert-danger'),
+ 'Allert message not shown')
+ self.assertTrue(wrong_path in self.find('.alert-danger').text,
+ "Wrong path not in alert message")
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
index 067ad99a9c..f558cce884 100644
--- a/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
+++ b/poky/bitbake/lib/toaster/tests/functional/test_functional_basic.py
@@ -7,21 +7,26 @@
# SPDX-License-Identifier: GPL-2.0-only
#
-import re
+import re, time
+from django.urls import reverse
+import pytest
from tests.functional.functional_helpers import SeleniumFunctionalTestCase
from orm.models import Project
from selenium.webdriver.common.by import By
+
+@pytest.mark.order("last")
class FuntionalTestBasic(SeleniumFunctionalTestCase):
# testcase (1514)
+ @pytest.mark.django_db
def test_create_slenium_project(self):
project_name = 'selenium-project'
- self.get('')
- self.driver.find_element(By.LINK_TEXT, "To start building, create your first Toaster project").click()
+ self.get(reverse('newproject'))
self.driver.find_element(By.ID, "new-project-name").send_keys(project_name)
self.driver.find_element(By.ID, 'projectversion').click()
self.driver.find_element(By.ID, "create-project-button").click()
+ time.sleep(2)
element = self.wait_until_visible('#project-created-notification')
self.assertTrue(self.element_exists('#project-created-notification'),'Project creation notification not shown')
self.assertTrue(project_name in element.text,
@@ -31,15 +36,18 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
# testcase (1515)
def test_verify_left_bar_menu(self):
- self.get('')
+ self.get(reverse('all-projects'))
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ time.sleep(2)
self.assertTrue(self.element_exists('#config-nav'),'Configuration Tab does not exist')
project_URL=self.get_URL()
self.driver.find_element(By.XPATH, '//a[@href="'+project_URL+'"]').click()
+ time.sleep(2)
try:
self.driver.find_element(By.XPATH, "//*[@id='config-nav']/ul/li/a[@href="+'"'+project_URL+'customimages/"'+"]").click()
+ time.sleep(2)
self.assertTrue(re.search("Custom images",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'Custom images information is not loading properly')
except:
self.fail(msg='No Custom images tab available')
@@ -78,14 +86,16 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
def test_review_configuration_information(self):
self.get('')
self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ time.sleep(2)
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
project_URL=self.get_URL()
-
+ time.sleep(2)
try:
self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
self.assertTrue(re.search("qemux86",self.driver.find_element(By.XPATH, "//span[@id='project-machine-name']").text),'The machine type is not assigned')
self.driver.find_element(By.XPATH, "//span[@id='change-machine-toggle']").click()
+ time.sleep(2)
self.wait_until_visible('#select-machine-form')
self.wait_until_visible('#cancel-machine-change')
self.driver.find_element(By.XPATH, "//form[@id='select-machine-form']/a[@id='cancel-machine-change']").click()
@@ -123,13 +133,16 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
def test_verify_machine_information(self):
self.get('')
self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ time.sleep(2)
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ time.sleep(2)
try:
self.assertTrue(self.element_exists('#machine-section'),'Machine section for the project configuration page does not exist')
self.assertTrue(re.search("qemux86",self.driver.find_element(By.ID, "project-machine-name").text),'The machine type is not assigned')
self.driver.find_element(By.ID, "change-machine-toggle").click()
+ time.sleep(2)
self.wait_until_visible('#select-machine-form')
self.wait_until_visible('#cancel-machine-change')
self.driver.find_element(By.ID, "cancel-machine-change").click()
@@ -140,14 +153,15 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
def test_verify_most_built_recipes_information(self):
self.get('')
self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
-
+ time.sleep(2)
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
project_URL=self.get_URL()
-
+ time.sleep(2)
try:
self.assertTrue(re.search("You haven't built any recipes yet",self.driver.find_element(By.ID, "no-most-built").text),'Default message of no builds is not present')
self.driver.find_element(By.XPATH, "//div[@id='no-most-built']/p/a[@href="+'"'+project_URL+'images/"'+"]").click()
+ time.sleep(2)
self.assertTrue(re.search("Compatible image recipes",self.driver.find_element(By.XPATH, "//div[@class='col-md-10']").text),'The Choose a recipe to build link is not working properly')
except:
self.fail(msg='No Most built information in project detail page')
@@ -156,8 +170,10 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
def test_verify_project_release_information(self):
self.get('')
self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ time.sleep(2)
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
+ time.sleep(2)
try:
self.assertTrue(re.search("Yocto Project master",self.driver.find_element(By.ID, "project-release-title").text),'The project release is not defined')
@@ -171,12 +187,12 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
project_URL=self.get_URL()
-
+ time.sleep(2)
try:
self.driver.find_element(By.XPATH, "//div[@id='layer-container']")
self.assertTrue(re.search("3",self.driver.find_element(By.ID, "project-layers-count").text),'There should be 3 layers listed in the layer count')
layer_list = self.driver.find_element(By.ID, "layers-in-project-list")
- layers = layer_list.find_element(By.TAG_NAME, "li")
+ layers = layer_list.find_elements(By.TAG_NAME, "li")
for layer in layers:
if re.match ("openembedded-core",layer.text):
@@ -199,10 +215,11 @@ class FuntionalTestBasic(SeleniumFunctionalTestCase):
def test_verify_project_detail_links(self):
self.get('')
self.driver.find_element(By.XPATH, "//div[@id='global-nav']/ul/li/a[@href="+'"'+'/toastergui/projects/'+'"'+"]").click()
+ time.sleep(2)
self.wait_until_visible('#projectstable')
self.find_element_by_link_text_in_table('projectstable', 'selenium-project').click()
project_URL=self.get_URL()
-
+ time.sleep(2)
self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").click()
self.assertTrue(re.search("Configuration",self.driver.find_element(By.XPATH, "//div[@id='project-topbar']/ul[@class='nav nav-tabs']/li[@id='topbar-configuration-tab']/a[@href="+'"'+project_URL+'"'+"]").text), 'Configuration tab in project topbar is misspelled')
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
new file mode 100644
index 0000000000..3edf967a2c
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page.py
@@ -0,0 +1,247 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+import pytest
+from django.urls import reverse
+from selenium.webdriver.support.select import Select
+from tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from selenium.webdriver.common.by import By
+
+
+@pytest.mark.django_db
+class TestProjectPage(SeleniumFunctionalTestCase):
+
+ def setUp(self):
+ 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,
+ ):
+ """ 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)
+
+ # 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()
+
+ self.driver.find_element(By.ID, "create-project-button").click()
+
+ def test_page_header_on_project_page(self):
+ """ Check page header in project page:
+ - AT LEFT -> Logo of Yocto project, displayed, clickable
+ - "Toaster"+" Information icon", displayed, clickable
+ - "Server Icon" + "All builds", displayed, clickable
+ - "Directory Icon" + "All projects", displayed, clickable
+ - "Book Icon" + "Documentation", displayed, clickable
+ - AT RIGHT -> button "New project", displayed, clickable
+ """
+ # navigate to the project page
+ url = reverse("project", args=(1,))
+ self.get(url)
+
+ # check page header
+ # AT LEFT -> Logo of Yocto project
+ logo = self.driver.find_element(
+ By.XPATH,
+ "//div[@class='toaster-navbar-brand']",
+ )
+ logo_img = logo.find_element(By.TAG_NAME, 'img')
+ self.assertTrue(logo_img.is_displayed(),
+ 'Logo of Yocto project not found')
+ self.assertTrue(
+ '/static/img/logo.png' in str(logo_img.get_attribute('src')),
+ 'Logo of Yocto project not found'
+ )
+ # "Toaster"+" Information icon", clickable
+ toaster = self.driver.find_element(
+ By.XPATH,
+ "//div[@class='toaster-navbar-brand']//a[@class='brand']",
+ )
+ self.assertTrue(toaster.is_displayed(), 'Toaster not found')
+ self.assertTrue(toaster.text == 'Toaster')
+ info_sign = self.find('.glyphicon-info-sign')
+ self.assertTrue(info_sign.is_displayed())
+
+ # "Server Icon" + "All builds"
+ all_builds = self.find('#navbar-all-builds')
+ all_builds_link = all_builds.find_element(By.TAG_NAME, 'a')
+ self.assertTrue("All builds" in all_builds_link.text)
+ self.assertTrue(
+ '/toastergui/builds/' in str(all_builds_link.get_attribute('href'))
+ )
+ server_icon = all_builds.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ server_icon.get_attribute('class') == 'glyphicon glyphicon-tasks'
+ )
+ self.assertTrue(server_icon.is_displayed())
+
+ # "Directory Icon" + "All projects"
+ all_projects = self.find('#navbar-all-projects')
+ all_projects_link = all_projects.find_element(By.TAG_NAME, 'a')
+ self.assertTrue("All projects" in all_projects_link.text)
+ self.assertTrue(
+ '/toastergui/projects/' in str(all_projects_link.get_attribute(
+ 'href'))
+ )
+ dir_icon = all_projects.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ dir_icon.get_attribute('class') == 'icon-folder-open'
+ )
+ self.assertTrue(dir_icon.is_displayed())
+
+ # "Book Icon" + "Documentation"
+ toaster_docs_link = self.find('#navbar-docs')
+ toaster_docs_link_link = toaster_docs_link.find_element(By.TAG_NAME,
+ 'a')
+ self.assertTrue("Documentation" in toaster_docs_link_link.text)
+ self.assertTrue(
+ toaster_docs_link_link.get_attribute('href') == 'http://docs.yoctoproject.org/toaster-manual/index.html#toaster-user-manual'
+ )
+ book_icon = toaster_docs_link.find_element(By.TAG_NAME, 'i')
+ self.assertTrue(
+ book_icon.get_attribute('class') == 'glyphicon glyphicon-book'
+ )
+ self.assertTrue(book_icon.is_displayed())
+
+ # AT RIGHT -> button "New project"
+ new_project_button = self.find('#new-project-button')
+ self.assertTrue(new_project_button.is_displayed())
+ self.assertTrue(new_project_button.text == 'New project')
+ new_project_button.click()
+ self.assertTrue(
+ '/toastergui/newproject/' in str(self.driver.current_url)
+ )
+
+ def test_edit_project_name(self):
+ """ Test edit project name:
+ - Click on "Edit" icon button
+ - Change project name
+ - Click on "Save" button
+ - Check project name is changed
+ """
+ # navigate to the project page
+ url = reverse("project", args=(1,))
+ self.get(url)
+
+ # click on "Edit" icon button
+ self.wait_until_visible('#project-name-container')
+ edit_button = self.find('#project-change-form-toggle')
+ edit_button.click()
+ project_name_input = self.find('#project-name-change-input')
+ self.assertTrue(project_name_input.is_displayed())
+ project_name_input.clear()
+ project_name_input.send_keys('New Name')
+ self.find('#project-name-change-btn').click()
+
+ # check project name is changed
+ self.wait_until_visible('#project-name-container')
+ self.assertTrue(
+ 'New Name' in str(self.find('#project-name-container').text)
+ )
+
+ def test_project_page_tabs(self):
+ """ Test project tabs:
+ - "configuration" tab
+ - "Builds" tab
+ - "Import layers" tab
+ - "New custom image" tab
+ Check search box used to build recipes
+ """
+ # navigate to the project page
+ url = reverse("project", args=(1,))
+ self.get(url)
+
+ # check "configuration" tab
+ self.wait_until_visible('#topbar-configuration-tab')
+ config_tab = self.find('#topbar-configuration-tab')
+ self.assertTrue(config_tab.get_attribute('class') == 'active')
+ self.assertTrue('Configuration' in config_tab.text)
+ config_tab_link = config_tab.find_element(By.TAG_NAME, 'a')
+ self.assertTrue(
+ f"/toastergui/project/1" in str(config_tab_link.get_attribute(
+ 'href'))
+ )
+
+ def get_tabs():
+ # tabs links list
+ return self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="project-topbar"]//li'
+ )
+
+ def check_tab_link(tab_index, tab_name, url):
+ tab = get_tabs()[tab_index]
+ tab_link = tab.find_element(By.TAG_NAME, 'a')
+ self.assertTrue(url in tab_link.get_attribute('href'))
+ self.assertTrue(tab_name in tab_link.text)
+ self.assertTrue(tab.get_attribute('class') == 'active')
+
+ # check "Builds" tab
+ builds_tab = get_tabs()[1]
+ builds_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 1,
+ 'Builds',
+ f"/toastergui/project/1/builds"
+ )
+
+ # check "Import layers" tab
+ import_layers_tab = get_tabs()[2]
+ import_layers_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 2,
+ 'Import layer',
+ f"/toastergui/project/1/importlayer"
+ )
+
+ # check "New custom image" tab
+ new_custom_image_tab = get_tabs()[3]
+ new_custom_image_tab.find_element(By.TAG_NAME, 'a').click()
+ check_tab_link(
+ 3,
+ 'New custom image',
+ f"/toastergui/project/1/newcustomimage"
+ )
+
+ # 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()
+ self.wait_until_visible('#latest-builds')
+ lastest_builds = self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="latest-builds"]',
+ )
+ last_build = lastest_builds[0]
+ self.assertTrue(
+ 'core-image-minimal' in str(last_build.text)
+ )
diff --git a/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
new file mode 100644
index 0000000000..23012d7865
--- /dev/null
+++ b/poky/bitbake/lib/toaster/tests/functional/test_project_page_tab_config.py
@@ -0,0 +1,578 @@
+#! /usr/bin/env python3 #
+# BitBake Toaster UI tests implementation
+#
+# Copyright (C) 2023 Savoir-faire Linux
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
+from time import sleep
+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 tests.functional.functional_helpers import SeleniumFunctionalTestCase
+from selenium.webdriver.common.by import By
+
+
+@pytest.mark.django_db
+class TestProjectConfigTab(SeleniumFunctionalTestCase):
+
+ 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,
+ ):
+ """ 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)
+
+ # 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()
+
+ 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)
+
+ 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')
+ lastest_builds = self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="latest-builds"]/div',
+ )
+ last_build = lastest_builds[0]
+ self.assertTrue(
+ 'core-image-minimal' in str(last_build.text)
+ )
+ cancel_button = last_build.find_element(
+ By.XPATH,
+ '//span[@class="cancel-build-btn pull-right alert-link"]',
+ )
+ cancel_button.click()
+ sleep(1)
+ self._wait_until_build('cancelled')
+
+ def _get_tabs(self):
+ # tabs links list
+ return self.driver.find_elements(
+ By.XPATH,
+ '//div[@id="project-topbar"]//li'
+ )
+
+ def _get_config_nav_item(self, index):
+ 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:
+ - Configuration
+ - COMPATIBLE METADATA
+ - Custom images
+ - Image recipes
+ - Software recipes
+ - Machines
+ - Layers
+ - Distro
+ - EXTRA CONFIGURATION
+ - Bitbake variables
+ - 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')
+
+ def _get_config_nav_item(index):
+ config_nav = self.find('#config-nav')
+ return config_nav.find_elements(By.TAG_NAME, 'li')[index]
+
+ def check_config_nav_item(index, item_name, url):
+ item = _get_config_nav_item(index)
+ self.assertTrue(item_name in item.text)
+ self.assertTrue(item.get_attribute('class') == 'active')
+ self.assertTrue(url in self.driver.current_url)
+
+ # check if the menu contains the right elements
+ # COMPATIBLE METADATA
+ compatible_metadata = _get_config_nav_item(1)
+ self.assertTrue(
+ "compatible metadata" in compatible_metadata.text.lower()
+ )
+ # EXTRA CONFIGURATION
+ extra_configuration = _get_config_nav_item(8)
+ self.assertTrue(
+ "extra configuration" in extra_configuration.text.lower()
+ )
+ # Actions
+ actions = _get_config_nav_item(10)
+ 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
+ ]
+ for index, item_name, url in conf_nav_list:
+ item = _get_config_nav_item(index)
+ if item.get_attribute('class') != 'active':
+ item.click()
+ check_config_nav_item(index, item_name, url)
+
+ def test_project_config_tab_right_section(self):
+ """ Test project config tab right section contains five blocks:
+ - Machine:
+ - check 'Machine' is displayed
+ - check can change Machine
+ - Distro:
+ - check 'Distro' is displayed
+ - check can change Distro
+ - Most built recipes:
+ - check 'Most built recipes' is displayed
+ - check can select a recipe and build it
+ - Project release:
+ - check 'Project release' is displayed
+ - check project has right release displayed
+ - Layers:
+ - check can add a layer if exists
+ - check at least three layers are displayed
+ - openembedded-core
+ - meta-poky
+ - meta-yocto-bsp
+ """
+ # navigate to the project page
+ url = reverse("project", args=(1,))
+ self.get(url)
+
+ # 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):
+ 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.click()
+ sleep(1)
+ name_input = block.find_element(By.ID, 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.click()
+ sleep(1)
+ project_name = block.find_element(By.ID, 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')
+ self.assertTrue(
+ f'You have changed the {item_name} to: {new_item_name}' in change_notification.text
+ )
+
+ # Machine
+ check_machine_distro(self, 'machine', 'qemux86-64', machine)
+ # Distro
+ check_machine_distro(self, 'distro', 'poky-altcfg', distro)
+
+ # Project release
+ title = project_release.find_element(By.TAG_NAME, 'h3')
+ self.assertTrue("Project release" in title.text)
+ 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)
+ # check at least three layers are displayed
+ # openembedded-core
+ # meta-poky
+ # 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)
+ # 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')
+ self.wait_until_visible('#layer-container > form > div > span > div')
+ dropdown_item = self.driver.find_element(
+ By.XPATH,
+ '//*[@id="layer-container"]/form/div/span/div'
+ )
+ dropdown_item.click()
+ add_layer_btn = layers.find_element(By.ID, 'add-layer-btn')
+ add_layer_btn.click()
+ sleep(1)
+ # check layer is added
+ layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
+ self.assertTrue(len(layers_list_items) == 4)
+
+ # 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()
+
+ # Refresh the page
+ self.get(url)
+
+ sleep(1) # wait for page to load
+ self.wait_until_visible('#project-page')
+ # 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)
+
+ 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)
+
+ # navigate to "Import layers" tab
+ import_layers_tab = self._get_tabs()[2]
+ import_layers_tab.find_element(By.TAG_NAME, 'a').click()
+ self.wait_until_visible('#layer-git-repo-url')
+
+ # Check git repo radio button
+ git_repo_radio = self.find('#git-repo-radio')
+ git_repo_radio.click()
+
+ # Set git repo url
+ input_repo_url = self.find('#layer-git-repo-url')
+ input_repo_url.send_keys('git://git.yoctoproject.org/meta-fake')
+ # Blur the input to trigger the validation
+ input_repo_url.send_keys(Keys.TAB)
+
+ # Check name is set
+ input_layer_name = self.find('#import-layer-name')
+ self.assertTrue(input_layer_name.get_attribute('value') == 'meta-fake')
+
+ # Set branch
+ input_branch = self.find('#layer-git-ref')
+ input_branch.send_keys('master')
+
+ # Import layer
+ self.find('#import-and-add-btn').click()
+
+ # Check layer is added
+ self.wait_until_visible('#layer-container')
+ block_l = self.driver.find_element(
+ By.XPATH, '//*[@id="project-page"]/div[2]')
+ layers = block_l.find_element(By.ID, 'layer-container')
+ layers_list = layers.find_element(By.ID, 'layers-in-project-list')
+ layers_list_items = layers_list.find_elements(By.TAG_NAME, 'li')
+ self.assertTrue(
+ 'meta-fake' in str(layers_list_items[-1].text)
+ )
+
+ 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)
+
+ # navigate to "Custom image" tab
+ custom_image_section = self._get_config_nav_item(2)
+ custom_image_section.click()
+ self.wait_until_visible('#empty-state-customimagestable')
+
+ # Check message when no custom image
+ self.assertTrue(
+ "You have not created any custom images yet." in str(
+ self.find('#empty-state-customimagestable').text
+ )
+ )
+ div_empty_msg = self.find('#empty-state-customimagestable')
+ link_create_custom_image = div_empty_msg.find_element(
+ By.TAG_NAME, 'a')
+ self.assertTrue(
+ f"/toastergui/project/1/newcustomimage" in str(
+ link_create_custom_image.get_attribute('href')
+ )
+ )
+ self.assertTrue(
+ "Create your first custom image" in str(
+ link_create_custom_image.text
+ )
+ )
+
+ def test_project_page_image_recipe(self):
+ """ Test project page section images
+ - Check image recipes are displayed
+ - Check search input
+ - 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')
+
+ # navigate to "Images section"
+ images_section = self._get_config_nav_item(3)
+ images_section.click()
+ self.wait_until_visible('#imagerecipestable')
+ rows = self.find_all('#imagerecipestable tbody tr')
+ self.assertTrue(len(rows) > 0)
+
+ # Test search input
+ self.wait_until_visible('#search-input-imagerecipestable')
+ recipe_input = self.find('#search-input-imagerecipestable')
+ recipe_input.send_keys('core-image-minimal')
+ self.find('#search-submit-imagerecipestable').click()
+ self.wait_until_visible('#imagerecipestable tbody tr')
+ rows = self.find_all('#imagerecipestable tbody tr')
+ self.assertTrue(len(rows) > 0)
+
+ # Test build button
+ image_to_build = rows[0]
+ build_btn = image_to_build.find_element(
+ By.XPATH,
+ '//td[@class="add-del-layers"]'
+ )
+ build_btn.click()
+ self._wait_until_build('parsing starting cloning')
+ 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(
+ 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)
diff --git a/poky/bitbake/lib/toaster/tests/toaster-tests-requirements.txt b/poky/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
index f30ac0706c..71cc083436 100644
--- a/poky/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
+++ b/poky/bitbake/lib/toaster/tests/toaster-tests-requirements.txt
@@ -1 +1,7 @@
selenium>=4.13.0
+pytest==7.4.2
+pytest-django==4.5.2
+pytest-env==1.1.0
+pytest-html==4.0.2
+pytest-metadata==3.0.0
+pytest-order==1.1.0
diff --git a/poky/bitbake/lib/toaster/tests/views/test_views.py b/poky/bitbake/lib/toaster/tests/views/test_views.py
index f962e76287..349881ebf6 100644
--- a/poky/bitbake/lib/toaster/tests/views/test_views.py
+++ b/poky/bitbake/lib/toaster/tests/views/test_views.py
@@ -9,6 +9,7 @@
"""Test cases for Toaster GUI and ReST."""
+import pytest
from django.test import TestCase
from django.test.client import RequestFactory
from django.urls import reverse
@@ -33,6 +34,7 @@ PROJECT_NAME2 = "test project 2"
CLI_BUILDS_PROJECT_NAME = 'Command line builds'
+@pytest.mark.order(1)
class ViewTests(TestCase):
"""Tests to verify view APIs."""
@@ -41,7 +43,15 @@ class ViewTests(TestCase):
def setUp(self):
self.project = Project.objects.first()
+
self.recipe1 = Recipe.objects.get(pk=2)
+ # create a file and to recipe1 file_path
+ file_path = f"/tmp/{self.recipe1.name.strip().replace(' ', '-')}.bb"
+ with open(file_path, 'w') as f:
+ f.write('foo')
+ self.recipe1.file_path = file_path
+ self.recipe1.save()
+
self.customr = CustomImageRecipe.objects.first()
self.cust_package = CustomImagePackage.objects.first()
self.package = Package.objects.first()